Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 3d686a9

Browse files
Wei Wanggkalpak
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 #14959 for more details on a possible usecase. Closes #14976
1 parent 8df4367 commit 3d686a9

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
@@ -87,13 +87,13 @@ function serverBase(url) {
8787

8888

8989
/**
90-
* LocationHtml5Url represents an url
90+
* LocationHtml5Url represents a URL
9191
* This object is exposed as $location service when HTML5 mode is enabled and supported
9292
*
9393
* @constructor
9494
* @param {string} appBase application base URL
9595
* @param {string} appBaseNoFile application base URL stripped of any filename
96-
* @param {string} basePrefix url path prefix
96+
* @param {string} basePrefix URL path prefix
9797
*/
9898
function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
9999
this.$$html5 = true;
@@ -102,8 +102,8 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
102102

103103

104104
/**
105-
* Parse given html5 (regular) url string into properties
106-
* @param {string} url HTML5 url
105+
* Parse given HTML5 (regular) URL string into properties
106+
* @param {string} url HTML5 URL
107107
* @private
108108
*/
109109
this.$$parse = function(url) {
@@ -165,7 +165,7 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
165165

166166

167167
/**
168-
* LocationHashbangUrl represents url
168+
* LocationHashbangUrl represents URL
169169
* This object is exposed as $location service when developer doesn't opt into html5 mode.
170170
* It also serves as the base class for html5 mode fallback on legacy browsers.
171171
*
@@ -180,8 +180,8 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
180180

181181

182182
/**
183-
* Parse given hashbang url into properties
184-
* @param {string} url Hashbang url
183+
* Parse given hashbang URL into properties
184+
* @param {string} url Hashbang URL
185185
* @private
186186
*/
187187
this.$$parse = function(url) {
@@ -190,7 +190,7 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
190190

191191
if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
192192

193-
// The rest of the url starts with a hash so we have
193+
// The rest of the URL starts with a hash so we have
194194
// got either a hashbang path or a plain hash fragment
195195
withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl);
196196
if (isUndefined(withoutHashUrl)) {
@@ -255,7 +255,7 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
255255
};
256256

257257
/**
258-
* Compose hashbang url and update `absUrl` property
258+
* Compose hashbang URL and update `absUrl` property
259259
* @private
260260
*/
261261
this.$$compose = function() {
@@ -277,7 +277,7 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
277277

278278

279279
/**
280-
* LocationHashbangUrl represents url
280+
* LocationHashbangUrl represents URL
281281
* This object is exposed as $location service when html5 history api is enabled but the browser
282282
* does not support it.
283283
*
@@ -329,7 +329,7 @@ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
329329
var locationPrototype = {
330330

331331
/**
332-
* Ensure absolute url is initialized.
332+
* Ensure absolute URL is initialized.
333333
* @private
334334
*/
335335
$$absUrl:'',
@@ -353,17 +353,17 @@ var locationPrototype = {
353353
* @description
354354
* This method is getter only.
355355
*
356-
* Return full url representation with all segments encoded according to rules specified in
356+
* Return full URL representation with all segments encoded according to rules specified in
357357
* [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
358358
*
359359
*
360360
* ```js
361-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
361+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
362362
* var absUrl = $location.absUrl();
363363
* // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
364364
* ```
365365
*
366-
* @return {string} full url
366+
* @return {string} full URL
367367
*/
368368
absUrl: locationGetter('$$absUrl'),
369369

@@ -374,18 +374,18 @@ var locationPrototype = {
374374
* @description
375375
* This method is getter / setter.
376376
*
377-
* Return url (e.g. `/path?a=b#hash`) when called without any parameter.
377+
* Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
378378
*
379379
* Change path, search and hash, when called with parameter and return `$location`.
380380
*
381381
*
382382
* ```js
383-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
383+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
384384
* var url = $location.url();
385385
* // => "/some/path?foo=bar&baz=xoxo"
386386
* ```
387387
*
388-
* @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
388+
* @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`)
389389
* @return {string} url
390390
*/
391391
url: function(url) {
@@ -408,16 +408,16 @@ var locationPrototype = {
408408
* @description
409409
* This method is getter only.
410410
*
411-
* Return protocol of current url.
411+
* Return protocol of current URL.
412412
*
413413
*
414414
* ```js
415-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
415+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
416416
* var protocol = $location.protocol();
417417
* // => "http"
418418
* ```
419419
*
420-
* @return {string} protocol of current url
420+
* @return {string} protocol of current URL
421421
*/
422422
protocol: locationGetter('$$protocol'),
423423

@@ -428,24 +428,24 @@ var locationPrototype = {
428428
* @description
429429
* This method is getter only.
430430
*
431-
* Return host of current url.
431+
* Return host of current URL.
432432
*
433433
* Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
434434
*
435435
*
436436
* ```js
437-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
437+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
438438
* var host = $location.host();
439439
* // => "example.com"
440440
*
441-
* // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
441+
* // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
442442
* host = $location.host();
443443
* // => "example.com"
444444
* host = location.host;
445445
* // => "example.com:8080"
446446
* ```
447447
*
448-
* @return {string} host of current url.
448+
* @return {string} host of current URL.
449449
*/
450450
host: locationGetter('$$host'),
451451

@@ -456,11 +456,11 @@ var locationPrototype = {
456456
* @description
457457
* This method is getter only.
458458
*
459-
* Return port of current url.
459+
* Return port of current URL.
460460
*
461461
*
462462
* ```js
463-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
463+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
464464
* var port = $location.port();
465465
* // => 80
466466
* ```
@@ -476,7 +476,7 @@ var locationPrototype = {
476476
* @description
477477
* This method is getter / setter.
478478
*
479-
* Return path of current url when called without any parameter.
479+
* Return path of current URL when called without any parameter.
480480
*
481481
* Change path when called with parameter and return `$location`.
482482
*
@@ -485,7 +485,7 @@ var locationPrototype = {
485485
*
486486
*
487487
* ```js
488-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
488+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
489489
* var path = $location.path();
490490
* // => "/some/path"
491491
* ```
@@ -505,13 +505,13 @@ var locationPrototype = {
505505
* @description
506506
* This method is getter / setter.
507507
*
508-
* Return search part (as object) of current url when called without any parameter.
508+
* Return search part (as object) of current URL when called without any parameter.
509509
*
510510
* Change search part when called with parameter and return `$location`.
511511
*
512512
*
513513
* ```js
514-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
514+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
515515
* var searchObject = $location.search();
516516
* // => {foo: 'bar', baz: 'xoxo'}
517517
*
@@ -527,7 +527,7 @@ var locationPrototype = {
527527
* of `$location` to the specified value.
528528
*
529529
* If the argument is a hash object containing an array of values, these values will be encoded
530-
* as duplicate search parameters in the url.
530+
* as duplicate search parameters in the URL.
531531
*
532532
* @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
533533
* will override only a single search property.
@@ -589,7 +589,7 @@ var locationPrototype = {
589589
*
590590
*
591591
* ```js
592-
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
592+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
593593
* var hash = $location.hash();
594594
* // => "hashValue"
595595
* ```
@@ -750,8 +750,12 @@ function $LocationProvider() {
750750
* whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
751751
* true, and a base tag is not present, an error will be thrown when `$location` is injected.
752752
* See the {@link guide/$location $location guide for more information}
753-
* - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
754-
* enables/disables url rewriting for relative links.
753+
* - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled,
754+
* enables/disables URL rewriting for relative links. If set to a string, URL rewriting will
755+
* only happen on links with an attribute that matches the given string. For example, if set
756+
* to `'internal-link'`, then the URL will only be rewritten for `<a internal-link>` links.
757+
* Note that [attribute name normalization](guide/directive#normalization) does not apply
758+
* here, so `'internalLink'` will **not** match `'internal-link'`.
755759
*
756760
* @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
757761
*/
@@ -769,7 +773,7 @@ function $LocationProvider() {
769773
html5Mode.requireBase = mode.requireBase;
770774
}
771775

772-
if (isBoolean(mode.rewriteLinks)) {
776+
if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) {
773777
html5Mode.rewriteLinks = mode.rewriteLinks;
774778
}
775779

@@ -866,10 +870,11 @@ function $LocationProvider() {
866870
}
867871

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

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

874879
var elm = jqLite(event.target);
875880

@@ -879,6 +884,8 @@ function $LocationProvider() {
879884
if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
880885
}
881886

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

0 commit comments

Comments
 (0)