Skip to content

Commit 2bd8e09

Browse files
Wei Wangpetebacondarwin
Wei Wang
authored andcommitted
feat($location): add support for selectively rewriting links based on attribute
In HTML5 mode, links can now be selectively rewritten, by setting `mode.rewriteLinks` to a string (denoting an attribute name). Anchor elements that have the specified attribute will be rewritten, while other links will remain untouched. This can be useful in situations where it is desirable to use HTML5 mode without a `<base>` tag, but still support rewriting specific links only. See angular#14959 for more details on a possible usecase. Closes angular#14976
1 parent a4c791c commit 2bd8e09

File tree

3 files changed

+123
-54
lines changed

3 files changed

+123
-54
lines changed

docs/content/guide/$location.ngdoc

+34-13
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,27 @@ To configure the `$location` service, retrieve the
9191
{@link ng.$locationProvider $locationProvider} and set the parameters as follows:
9292

9393

94-
- **html5Mode(mode)**: {boolean|Object}<br />
95-
`true` or `enabled:true` - see HTML5 mode<br />
96-
`false` or `enabled:false` - see Hashbang mode<br />
97-
`requireBase:true` - see Relative links<br />
98-
default: `enabled:false`
99-
100-
- **hashPrefix(prefix)**: {string}<br />
101-
prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)<br />
102-
default: `""`
94+
- **html5Mode(mode)**: `{boolean|Object}`<br />
95+
`false` or `{enabled: false}` (default) -
96+
see [Hashbang mode](guide/$location#hashbang-mode-default-mode-)<br />
97+
`true` or `{enabled: true}` -
98+
see [HTML5 mode](guide/$location#html5-mode)<br />
99+
`{..., requireBase: true/false}` (only affects HTML5 mode) -
100+
see [Relative links](guide/$location#relative-links)<br />
101+
`{..., rewriteLinks: true/false/'string'}` (only affects HTML5 mode) -
102+
see [HTML link rewriting](guide/$location#html-link-rewriting)<br />
103+
Default:
104+
```j
105+
{
106+
enabled: false,
107+
requireBase: true,
108+
rewriteLinks: true
109+
}
110+
```
111+
112+
- **hashPrefix(prefix)**: `{string}`<br />
113+
Prefix used for Hashbang URLs (used in Hashbang mode or in legacy browsers in HTML5 mode).<br />
114+
Default: `''`
103115

104116
### Example configuration
105117
```js
@@ -305,7 +317,7 @@ path and search. If the history API is not supported by a browser, `$location` s
305317
URL. This frees you from having to worry about whether the browser viewing your app supports the
306318
history API or not; the `$location` service makes this transparent to you.
307319

308-
### Html link rewriting
320+
### HTML link rewriting
309321

310322
When you use HTML5 history API mode, you will not need special hashbang links. All you have to do
311323
is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
@@ -326,6 +338,18 @@ reload to the original link.
326338
- Links starting with '/' that lead to a different base path<br>
327339
Example: `<a href="/not-my-base/link">link</a>`
328340

341+
If `mode.rewriteLinks` is set to `false` in the `mode` configuration object passed to
342+
`$locationProvider.html5Mode()`, the browser will perform a full page reload for every link.
343+
`mode.rewriteLinks` can also be set to a string, which will enable link rewriting only on anchor
344+
elements that have the given attribute.
345+
346+
For example, if `mode.rewriteLinks` is set to `'internal-link'`:
347+
- `<a href="/some/path" internal-link>link</a>` will be rewritten
348+
- `<a href="/some/path">link</a>` will perform a full page reload
349+
350+
Note that [attribute name normalization](guide/directive#normalization) does not apply here, so
351+
`'internalLink'` will **not** match `'internal-link'`.
352+
329353

330354
### Relative links
331355

@@ -853,6 +877,3 @@ angular.module('locationExample', [])
853877
# Related API
854878

855879
* {@link ng.$location `$location` API}
856-
857-
858-

src/ng/location.js

+43-36
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,13 @@ function serverBase(url) {
9292

9393

9494
/**
95-
* LocationHtml5Url represents an url
95+
* LocationHtml5Url represents a URL
9696
* This object is exposed as $location service when HTML5 mode is enabled and supported
9797
*
9898
* @constructor
9999
* @param {string} appBase application base URL
100100
* @param {string} appBaseNoFile application base URL stripped of any filename
101-
* @param {string} basePrefix url path prefix
101+
* @param {string} basePrefix URL path prefix
102102
*/
103103
function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
104104
this.$$html5 = true;
@@ -107,8 +107,8 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
107107

108108

109109
/**
110-
* Parse given html5 (regular) url string into properties
111-
* @param {string} url HTML5 url
110+
* Parse given HTML5 (regular) URL string into properties
111+
* @param {string} url HTML5 URL
112112
* @private
113113
*/
114114
this.$$parse = function(url) {
@@ -171,7 +171,7 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
171171

172172

173173
/**
174-
* LocationHashbangUrl represents url
174+
* LocationHashbangUrl represents URL
175175
* This object is exposed as $location service when developer doesn't opt into html5 mode.
176176
* It also serves as the base class for html5 mode fallback on legacy browsers.
177177
*
@@ -186,8 +186,8 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
186186

187187

188188
/**
189-
* Parse given hashbang url into properties
190-
* @param {string} url Hashbang url
189+
* Parse given hashbang URL into properties
190+
* @param {string} url Hashbang URL
191191
* @private
192192
*/
193193
this.$$parse = function(url) {
@@ -196,7 +196,7 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
196196

197197
if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
198198

199-
// The rest of the url starts with a hash so we have
199+
// The rest of the URL starts with a hash so we have
200200
// got either a hashbang path or a plain hash fragment
201201
withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl);
202202
if (isUndefined(withoutHashUrl)) {
@@ -261,7 +261,7 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
261261
};
262262

263263
/**
264-
* Compose hashbang url and update `absUrl` property
264+
* Compose hashbang URL and update `absUrl` property
265265
* @private
266266
*/
267267
this.$$compose = function() {
@@ -283,7 +283,7 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
283283

284284

285285
/**
286-
* LocationHashbangUrl represents url
286+
* LocationHashbangUrl represents URL
287287
* This object is exposed as $location service when html5 history api is enabled but the browser
288288
* does not support it.
289289
*
@@ -335,7 +335,7 @@ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
335335
var locationPrototype = {
336336

337337
/**
338-
* Ensure absolute url is initialized.
338+
* Ensure absolute URL is initialized.
339339
* @private
340340
*/
341341
$$absUrl:'',
@@ -359,17 +359,17 @@ var locationPrototype = {
359359
* @description
360360
* This method is getter only.
361361
*
362-
* Return full url representation with all segments encoded according to rules specified in
362+
* Return full URL representation with all segments encoded according to rules specified in
363363
* [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
364364
*
365365
*
366366
* ```js
367-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
367+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
368368
* var absUrl = $location.absUrl();
369369
* // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
370370
* ```
371371
*
372-
* @return {string} full url
372+
* @return {string} full URL
373373
*/
374374
absUrl: locationGetter('$$absUrl'),
375375

@@ -380,18 +380,18 @@ var locationPrototype = {
380380
* @description
381381
* This method is getter / setter.
382382
*
383-
* Return url (e.g. `/path?a=b#hash`) when called without any parameter.
383+
* Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
384384
*
385385
* Change path, search and hash, when called with parameter and return `$location`.
386386
*
387387
*
388388
* ```js
389-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
389+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
390390
* var url = $location.url();
391391
* // => "/some/path?foo=bar&baz=xoxo"
392392
* ```
393393
*
394-
* @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
394+
* @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`)
395395
* @return {string} url
396396
*/
397397
url: function(url) {
@@ -414,16 +414,16 @@ var locationPrototype = {
414414
* @description
415415
* This method is getter only.
416416
*
417-
* Return protocol of current url.
417+
* Return protocol of current URL.
418418
*
419419
*
420420
* ```js
421-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
421+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
422422
* var protocol = $location.protocol();
423423
* // => "http"
424424
* ```
425425
*
426-
* @return {string} protocol of current url
426+
* @return {string} protocol of current URL
427427
*/
428428
protocol: locationGetter('$$protocol'),
429429

@@ -434,24 +434,24 @@ var locationPrototype = {
434434
* @description
435435
* This method is getter only.
436436
*
437-
* Return host of current url.
437+
* Return host of current URL.
438438
*
439439
* Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
440440
*
441441
*
442442
* ```js
443-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
443+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
444444
* var host = $location.host();
445445
* // => "example.com"
446446
*
447-
* // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
447+
* // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
448448
* host = $location.host();
449449
* // => "example.com"
450450
* host = location.host;
451451
* // => "example.com:8080"
452452
* ```
453453
*
454-
* @return {string} host of current url.
454+
* @return {string} host of current URL.
455455
*/
456456
host: locationGetter('$$host'),
457457

@@ -462,11 +462,11 @@ var locationPrototype = {
462462
* @description
463463
* This method is getter only.
464464
*
465-
* Return port of current url.
465+
* Return port of current URL.
466466
*
467467
*
468468
* ```js
469-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
469+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
470470
* var port = $location.port();
471471
* // => 80
472472
* ```
@@ -482,7 +482,7 @@ var locationPrototype = {
482482
* @description
483483
* This method is getter / setter.
484484
*
485-
* Return path of current url when called without any parameter.
485+
* Return path of current URL when called without any parameter.
486486
*
487487
* Change path when called with parameter and return `$location`.
488488
*
@@ -491,7 +491,7 @@ var locationPrototype = {
491491
*
492492
*
493493
* ```js
494-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
494+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
495495
* var path = $location.path();
496496
* // => "/some/path"
497497
* ```
@@ -511,13 +511,13 @@ var locationPrototype = {
511511
* @description
512512
* This method is getter / setter.
513513
*
514-
* Return search part (as object) of current url when called without any parameter.
514+
* Return search part (as object) of current URL when called without any parameter.
515515
*
516516
* Change search part when called with parameter and return `$location`.
517517
*
518518
*
519519
* ```js
520-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
520+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
521521
* var searchObject = $location.search();
522522
* // => {foo: 'bar', baz: 'xoxo'}
523523
*
@@ -533,7 +533,7 @@ var locationPrototype = {
533533
* of `$location` to the specified value.
534534
*
535535
* If the argument is a hash object containing an array of values, these values will be encoded
536-
* as duplicate search parameters in the url.
536+
* as duplicate search parameters in the URL.
537537
*
538538
* @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
539539
* will override only a single search property.
@@ -595,7 +595,7 @@ var locationPrototype = {
595595
*
596596
*
597597
* ```js
598-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
598+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
599599
* var hash = $location.hash();
600600
* // => "hashValue"
601601
* ```
@@ -755,8 +755,12 @@ function $LocationProvider() {
755755
* whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
756756
* true, and a base tag is not present, an error will be thrown when `$location` is injected.
757757
* See the {@link guide/$location $location guide for more information}
758-
* - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
759-
* enables/disables url rewriting for relative links.
758+
* - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled,
759+
* enables/disables URL rewriting for relative links. If set to a string, URL rewriting will
760+
* only happen on links with an attribute that matches the given string. For example, if set
761+
* to `'internal-link'`, then the URL will only be rewritten for `<a internal-link>` links.
762+
* Note that [attribute name normalization](guide/directive#normalization) does not apply
763+
* here, so `'internalLink'` will **not** match `'internal-link'`.
760764
*
761765
* @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
762766
*/
@@ -774,7 +778,7 @@ function $LocationProvider() {
774778
html5Mode.requireBase = mode.requireBase;
775779
}
776780

777-
if (isBoolean(mode.rewriteLinks)) {
781+
if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) {
778782
html5Mode.rewriteLinks = mode.rewriteLinks;
779783
}
780784

@@ -871,10 +875,11 @@ function $LocationProvider() {
871875
}
872876

873877
$rootElement.on('click', function(event) {
878+
var rewriteLinks = html5Mode.rewriteLinks;
874879
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
875880
// currently we open nice url link and redirect then
876881

877-
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return;
882+
if (!rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return;
878883

879884
var elm = jqLite(event.target);
880885

@@ -884,6 +889,8 @@ function $LocationProvider() {
884889
if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
885890
}
886891

892+
if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks))) return;
893+
887894
var absHref = elm.prop('href');
888895
// get the actual href attribute - see
889896
// http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx

0 commit comments

Comments
 (0)