can-route-pushstate
Advanced tools
Comparing version 3.2.0 to 4.0.0-pre.1
/* jshint asi:true,scripturl:true */ | ||
var QUnit = require('steal-qunit'); | ||
var extend = require('can-util/js/assign/assign'); | ||
var canEvent = require('can-event'); | ||
var domEvents = require('can-util/dom/events/events'); | ||
require("can-util/dom/events/delegate/delegate"); | ||
var route = require('./can-route-pushstate'); | ||
@@ -513,2 +514,3 @@ var domDispatch = require('can-util/dom/dispatch/'); | ||
test("clicked hashes work (#259)", function () { | ||
@@ -518,3 +520,3 @@ | ||
window.routeTestReady = function (iCanRoute, loc, hist, win) { | ||
//win.queues.log("flush"); | ||
iCanRoute(win.location.pathname, { | ||
@@ -533,7 +535,5 @@ page: "index" | ||
win.document.body.appendChild(link); | ||
domDispatch.call(link, "click"); | ||
setTimeout(function () { | ||
deepEqual(extend({}, iCanRoute.attr()), { | ||
@@ -688,3 +688,3 @@ type: "articles", | ||
// kill the click b/c phantom doesn't like it. | ||
canEvent.on.call(info.window.document, "click", clickKiller); | ||
domEvents.addEventListener.call(info.window.document, "click", clickKiller); | ||
@@ -691,0 +691,0 @@ info.history.pushState = function () { |
@@ -16,8 +16,7 @@ // # can/route/pushstate/pushstate.js | ||
var each = require('can-util/js/each/each'); | ||
var makeArray = require('can-util/js/make-array/make-array'); | ||
var diffObject = require('can-util/js/diff-object/diff-object'); | ||
var namespace = require('can-util/namespace'); | ||
var LOCATION = require('can-globals/location/location'); | ||
var canEvent = require('can-event'); | ||
var domEvents = require('can-util/dom/events/events'); | ||
require("can-util/dom/events/delegate/delegate"); | ||
var route = require('can-route'); | ||
@@ -29,147 +28,115 @@ | ||
var usePushStateRouting = hasPushstate && loc && validProtocols[loc.protocol]; | ||
var canReflect = require("can-reflect"); | ||
var KeyTree = require("can-key-tree"); | ||
var bindingProxy = require("can-route/src/binding-proxy"); | ||
var queues = require("can-queues"); | ||
var SimpleObservable = require("can-simple-observable"); | ||
// Initialize plugin only if browser supports pushstate. | ||
if (usePushStateRouting) { | ||
// Registers itself within `route.bindings`. | ||
route.bindings.pushstate = { | ||
/** | ||
* @property {String} can-route-pushstate.root root | ||
* @parent can-route-pushstate.static | ||
* | ||
* @description Configure the base url that will not be modified. | ||
* | ||
* @option {String} Represents the base url that pushstate will prepend to all | ||
* routes. `root` defaults to: `"/"`. | ||
* | ||
* @body | ||
* | ||
* ## Use | ||
* | ||
* By default, a route like: | ||
* | ||
* route(":type/:id") | ||
* | ||
* Matches urls like: | ||
* | ||
* http://domain.com/contact/5 | ||
* | ||
* But sometimes, you only want to match pages within a certain directory. For | ||
* example, an application that is a filemanager. You might want to | ||
* specify root and routes like: | ||
* | ||
* route.pushstate.root = "/filemanager/" | ||
* route("file-:fileId"); | ||
* route("folder-:fileId") | ||
* | ||
* Which matches urls like: | ||
* | ||
* http://domain.com/filemanager/file-34234 | ||
* | ||
*/ | ||
// Start of `location.pathname` is the root. | ||
// (Can be configured via `route.bindings.pushstate.root`) | ||
root: "/", | ||
// don't greedily match slashes in routing rules | ||
matchSlashes: false, | ||
paramsMatcher: /^\?(?:[^=]+=[^&]*&)*[^=]+=[^&]*/, | ||
querySeparator: '?', | ||
// Original methods on `history` that will be overwritten | ||
var methodsToOverwrite = ['pushState', 'replaceState']; | ||
// ## bind | ||
// Always returns clean root, without domain. | ||
var cleanRoot = function () { | ||
var domain = location.protocol + "//" + location.host, | ||
root = bindingProxy.call("root"), | ||
index = root.indexOf(domain); | ||
if (index === 0) { | ||
return root.substr(domain.length); | ||
} | ||
return root; | ||
}; | ||
// Intercepts clicks on `<a>` elements and rewrites original `history` methods. | ||
bind: function () { | ||
if(isNode()) { | ||
return; | ||
} | ||
// Handler function for `click` events. | ||
// Checks if a route is matched, if one is, calls `.pushState` | ||
// Intercept routable links. | ||
canEvent.on.call(document.documentElement, 'click', 'a', anchorClickHandler); | ||
// gets the current url after the root | ||
function getCurrentUrl(){ | ||
var root = cleanRoot(), | ||
location = LOCATION(), | ||
loc = (location.pathname + location.search), | ||
index = loc.indexOf(root); | ||
// Rewrites original `pushState`/`replaceState` methods on `history` and keeps pointer to original methods | ||
each(methodsToOverwrite, function (method) { | ||
originalMethods[method] = window.history[method]; | ||
window.history[method] = function (state, title, url) { | ||
// Avoid doubled history states (with pushState). | ||
var absolute = url.indexOf("http") === 0; | ||
var loc = LOCATION(); | ||
var searchHash = loc.search + loc.hash; | ||
// If url differs from current call original histoy method and update `route` state. | ||
if ((!absolute && url !== loc.pathname + searchHash) || | ||
(absolute && url !== loc.href + searchHash)) { | ||
originalMethods[method].apply(window.history, arguments); | ||
route.setState(); | ||
} | ||
}; | ||
}); | ||
return loc.substr(index + root.length); | ||
} | ||
// Bind to `popstate` event, fires on back/forward. | ||
canEvent.on.call(window, 'popstate', route.setState); | ||
}, | ||
// ## unbind | ||
// Unbinds and restores original `history` methods | ||
unbind: function () { | ||
canEvent.off.call(document.documentElement, 'click', 'a', anchorClickHandler); | ||
each(methodsToOverwrite, function (method) { | ||
window.history[method] = originalMethods[method]; | ||
}); | ||
canEvent.off.call(window, 'popstate', route.setState); | ||
}, | ||
// ## matchingPartOfURL | ||
// Returns matching part of url without root. | ||
matchingPartOfURL: function () { | ||
var root = cleanRoot(), | ||
location = LOCATION(), | ||
loc = (location.pathname + location.search), | ||
index = loc.indexOf(root); | ||
return loc.substr(index + root.length); | ||
}, | ||
// ## setURL | ||
// Updates URL by calling `pushState`. | ||
setURL: function (path, newProps, oldProps) { | ||
var method = "pushState"; | ||
var changed; | ||
// Keeps hash if not in path. | ||
if (includeHash && path.indexOf("#") === -1 && window.location.hash) { | ||
path += window.location.hash; | ||
} | ||
changed = diffObject(oldProps, newProps) | ||
.map(function (d) { | ||
return d.property; | ||
}); | ||
if(replaceStateAttrs.length > 0) { | ||
var toRemove = []; | ||
for(var i = 0, l = changed.length; i < l; i++) { | ||
if(replaceStateAttrs.indexOf(changed[i]) !== -1) { | ||
method = "replaceState"; | ||
} | ||
if(replaceStateAttrs.once && (replaceStateAttrs.once.indexOf(changed[i]) !== -1)) { | ||
toRemove.push(changed[i]); | ||
} | ||
} | ||
if(toRemove.length > 0) { | ||
removeAttrs(replaceStateAttrs, toRemove); | ||
removeAttrs(replaceStateAttrs.once, toRemove); | ||
} | ||
} | ||
window.history[method](null, null, route._call("root") + path); | ||
} | ||
function PushstateObservable(options) { | ||
/* | ||
* - replaceStateKeys | ||
* - replaceStateOnceKeys | ||
*/ | ||
this.options = options; | ||
this.dispatchHandlers = this.dispatchHandlers.bind(this); | ||
var self = this; | ||
this.anchorClickHandler = function(event){ | ||
PushstateObservable.prototype.anchorClickHandler.call(self, this, event); | ||
}; | ||
this.handlers = new KeyTree([Object,Array],{ | ||
onFirst: this.setup.bind(this), | ||
onEmpty: this.teardown.bind(this) | ||
}); | ||
} | ||
PushstateObservable.prototype = Object.create(SimpleObservable.prototype); | ||
PushstateObservable.constructor = PushstateObservable; | ||
canReflect.assign(PushstateObservable.prototype,{ | ||
/** | ||
* @property {String} can-route-pushstate.root root | ||
* @parent can-route-pushstate.static | ||
* | ||
* @description Configure the base url that will not be modified. | ||
* | ||
* @option {String} Represents the base url that pushstate will prepend to all | ||
* routes. `root` defaults to: `"/"`. | ||
* | ||
* @body | ||
* | ||
* ## Use | ||
* | ||
* By default, a route like: | ||
* | ||
* route(":type/:id") | ||
* | ||
* Matches urls like: | ||
* | ||
* http://domain.com/contact/5 | ||
* | ||
* But sometimes, you only want to match pages within a certain directory. For | ||
* example, an application that is a filemanager. You might want to | ||
* specify root and routes like: | ||
* | ||
* route.pushstate.root = "/filemanager/" | ||
* route("file-:fileId"); | ||
* route("folder-:fileId") | ||
* | ||
* Which matches urls like: | ||
* | ||
* http://domain.com/filemanager/file-34234 | ||
* | ||
*/ | ||
// ## anchorClickHandler | ||
// Start of `location.pathname` is the root. | ||
// (Can be configured via `route.bindings.pushstate.root`) | ||
root: "/", | ||
// don't greedily match slashes in routing rules | ||
matchSlashes: false, | ||
paramsMatcher: /^\?(?:[^=]+=[^&]*&)*[^=]+=[^&]*/, | ||
querySeparator: '?', | ||
dispatchHandlers: function() { | ||
var old = this.value; | ||
this.value = getCurrentUrl(); | ||
if(old !== this.value) { | ||
queues.enqueueByQueue(this.handlers.getNode([]), this, [this.value, old] | ||
//!steal-remove-start | ||
/* jshint laxcomma: true */ | ||
, null | ||
, [ canReflect.getName(this), "changed to", this.value, "from", old ] | ||
/* jshint laxcomma: false */ | ||
//!steal-remove-end | ||
); | ||
} | ||
}, | ||
anchorClickHandler: function (node, e) { | ||
// Handler function for `click` events. | ||
var anchorClickHandler = function (e) { | ||
if (!(e.isDefaultPrevented ? e.isDefaultPrevented() : e.defaultPrevented === true)) { | ||
// YUI calls back events triggered with this as a wrapped object. | ||
var node = this._node || this; | ||
// Fix for IE showing blank host, but blank host means current host. | ||
@@ -185,2 +152,3 @@ var linksHost = node.host || window.location.host; | ||
var root = cleanRoot(); | ||
// And the link is within the `root` | ||
if (node.pathname.indexOf(root) === 0) { | ||
@@ -192,5 +160,12 @@ | ||
var curParams = route.deparam(url); | ||
// If we've matched a route | ||
if (curParams.hasOwnProperty('route')) { | ||
// Makes it possible to have a link with a hash. | ||
includeHash = true; | ||
// Calling .pushState will dispatch events, causing | ||
// `can-route` to update its data, and then try to set back | ||
// the url without the hash. We need to retain that. | ||
if(node.href.indexOf("#") >= 0 ) { | ||
this.keepHash = true; | ||
} | ||
window.history.pushState(null, null, node.href); | ||
@@ -209,32 +184,109 @@ | ||
}, | ||
setup: function(){ | ||
if(isNode()) { | ||
return; | ||
} | ||
this.value = getCurrentUrl(); | ||
// Intercept routable links. | ||
domEvents.addDelegateListener.call(document.documentElement, 'click', 'a', this.anchorClickHandler); | ||
var originalMethods = this.originalMethods = {}; | ||
var dispatchHandlers = this.dispatchHandlers; | ||
// Rewrites original `pushState`/`replaceState` methods on `history` and keeps pointer to original methods | ||
each(methodsToOverwrite, function (method) { | ||
this.originalMethods[method] = window.history[method]; | ||
window.history[method] = function (state, title, url) { | ||
// Avoid doubled history states (with pushState). | ||
var absolute = url.indexOf("http") === 0; | ||
var loc = LOCATION(); | ||
var searchHash = loc.search + loc.hash; | ||
// If url differs from current call original histoy method and update `route` state. | ||
if ((!absolute && url !== loc.pathname + searchHash) || | ||
(absolute && url !== loc.href + searchHash)) { | ||
originalMethods[method].apply(window.history, arguments); | ||
dispatchHandlers(); | ||
} | ||
}; | ||
}, this); | ||
// ## cleanRoot | ||
// Bind to `popstate` event, fires on back/forward. | ||
domEvents.addEventListener.call(window, 'popstate', this.dispatchHandlers); | ||
}, | ||
teardown: function(){ | ||
domEvents.removeEventListener.call(document.documentElement, 'click', 'a', this.anchorClickHandler); | ||
// Always returns clean root, without domain. | ||
cleanRoot = function () { | ||
var domain = location.protocol + "//" + location.host, | ||
root = route._call("root"), | ||
index = root.indexOf(domain); | ||
if (index === 0) { | ||
return root.substr(domain.length); | ||
} | ||
return root; | ||
}, | ||
removeAttrs = function(arr, attrs) { | ||
var index; | ||
for(var i = attrs.length - 1; i >= 0; i--) { | ||
if( (index = arr.indexOf(attrs[i])) !== -1) { | ||
arr.splice(index, 1); | ||
each(methodsToOverwrite, function (method) { | ||
window.history[method] = this.originalMethods[method]; | ||
}, this); | ||
domEvents.removeEventListener.call(window, 'popstate', this.dispatchHandlers); | ||
}, | ||
get: getCurrentUrl, | ||
set: function(path){ | ||
var newProps = route.deparam(path); | ||
var oldProps = route.deparam(getCurrentUrl()); | ||
var method = "pushState"; | ||
var changed; | ||
// Keeps hash if not in path. | ||
if (this.keepHash && path.indexOf("#") === -1 && window.location.hash) { | ||
path += window.location.hash; | ||
} | ||
changed = {}; | ||
diffObject(oldProps, newProps) | ||
.forEach(function (patch) { | ||
return changed[patch.property] = true; | ||
}); | ||
// check if we should call replaceState or pushState | ||
if( this.options.replaceStateKeys.length ) { | ||
this.options.replaceStateKeys.forEach(function(replaceKey){ | ||
if(changed[replaceKey]) { | ||
method = "replaceState"; | ||
} | ||
}); | ||
} | ||
if( this.options.replaceStateOnceKeys.length ) { | ||
for(var i = this.options.replaceStateOnceKeys.length - 1; i >= 0; i--) { | ||
var replaceOnceKey = this.options.replaceStateOnceKeys[i]; | ||
if(changed[replaceOnceKey]) { | ||
method = "replaceState"; | ||
// remove so we don't do this again | ||
this.options.replaceStateOnceKeys.splice(i,1); | ||
} | ||
} | ||
}, | ||
// Original methods on `history` that will be overwritten | ||
methodsToOverwrite = ['pushState', 'replaceState'], | ||
// A place to store pointers to original `history` methods. | ||
originalMethods = {}, | ||
// Used to tell setURL to include the hash because we clicked on a link. | ||
includeHash = false, | ||
// Attributes that will cause replaceState to be called | ||
replaceStateAttrs = []; | ||
} | ||
window.history[method](null, null, bindingProxy.call("root") + path); | ||
} | ||
}); | ||
canReflect.assignSymbols(PushstateObservable.prototype,{ | ||
"can.getValue": PushstateObservable.prototype.get, | ||
"can.setValue": PushstateObservable.prototype.set, | ||
"can.onValue": PushstateObservable.prototype.on, | ||
"can.offValue": PushstateObservable.prototype.off, | ||
"can.isMapLike": false, | ||
"can.valueHasDependencies": function(){ | ||
return true; | ||
}, | ||
//!steal-remove-start | ||
"can.getName": function() { | ||
return "PushstateObservable<" + this.value + ">"; | ||
}, | ||
//!steal-remove-end | ||
}); | ||
// Initialize plugin only if browser supports pushstate. | ||
if (usePushStateRouting) { | ||
var options = { | ||
replaceStateOnceKeys: [], | ||
replaceStateKeys: [] | ||
}; | ||
var pushstateBinding = new PushstateObservable(options); | ||
// Registers itself within `route.bindings`. | ||
route.bindings.pushstate = pushstateBinding; | ||
// Enables plugin, by default `hashchange` binding is used. | ||
@@ -245,15 +297,10 @@ route.defaultBinding = "pushstate"; | ||
replaceStateOn: function() { | ||
var attrs = makeArray(arguments); | ||
Array.prototype.push.apply(replaceStateAttrs, attrs); | ||
canReflect.addValues( options.replaceStateKeys, canReflect.toArray(arguments) ); | ||
}, | ||
replaceStateOnce: function() { | ||
var attrs = makeArray(arguments); | ||
replaceStateAttrs.once = makeArray(replaceStateAttrs.once); | ||
Array.prototype.push.apply(replaceStateAttrs.once, attrs); | ||
route.replaceStateOn.apply(this, arguments); | ||
canReflect.addValues( options.replaceStateOnceKeys, canReflect.toArray(arguments) ); | ||
}, | ||
replaceStateOff: function() { | ||
var attrs = makeArray(arguments); | ||
removeAttrs(replaceStateAttrs, attrs); | ||
canReflect.removeValues( options.replaceStateKeys, canReflect.toArray(arguments) ); | ||
canReflect.removeValues( options.replaceStateOnceKeys, canReflect.toArray(arguments) ); | ||
} | ||
@@ -263,2 +310,2 @@ }); | ||
module.exports = namespace.route = route; | ||
module.exports = route; |
{ | ||
"name": "can-route-pushstate", | ||
"version": "3.2.0", | ||
"version": "4.0.0-pre.1", | ||
"description": "Pushstate for can-route", | ||
@@ -16,5 +16,4 @@ "homepage": "https://canjs.com", | ||
"scripts": { | ||
"preversion": "npm test && npm run build", | ||
"version": "git commit -am \"Update dist for release\" && git checkout -b release && git add -f dist/", | ||
"postversion": "git push --tags && git checkout master && git branch -D release && git push", | ||
"preversion": "npm test", | ||
"postpublish": "git push --tags && git push", | ||
"testee": "testee test/test.html --browsers firefox", | ||
@@ -37,11 +36,14 @@ "test": "npm run detect-cycle && npm run jshint && npm run testee", | ||
"dependencies": { | ||
"can-event": "^3.5.0", | ||
"can-globals": "^0.2.3", | ||
"can-route": "^3.1.0", | ||
"can-dom-events": "^1.0.0", | ||
"can-globals": "<2.0.0", | ||
"can-key-tree": "<2.0.0", | ||
"can-queues": "<2.0.0", | ||
"can-reflect": "^1.8.0", | ||
"can-route": "^4.0.0-pre.5", | ||
"can-simple-observable": "^2.0.0-pre.21", | ||
"can-util": "^3.9.0" | ||
}, | ||
"devDependencies": { | ||
"bit-docs": "0.0.8-0", | ||
"can-define": "^1.2.0", | ||
"can-map": "^3.1.0", | ||
"can-define": "^2.0.0-pre.0", | ||
"can-map": "^4.0.0-pre.0", | ||
"detect-cyclic-packages": "^1.1.0", | ||
@@ -48,0 +50,0 @@ "jshint": "^2.9.1", |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
8
1055
44490
8
11
1
+ Addedcan-dom-events@^1.0.0
+ Addedcan-key-tree@<2.0.0
+ Addedcan-queues@<2.0.0
+ Addedcan-reflect@^1.8.0
+ Addedcan-bind@1.5.1(transitive)
+ Addedcan-data-types@1.2.1(transitive)
+ Addedcan-define@2.8.1(transitive)
+ Addedcan-define-lazy-value@1.1.1(transitive)
+ Addedcan-diff@1.5.1(transitive)
+ Addedcan-event-queue@1.1.8(transitive)
+ Addedcan-key@1.2.1(transitive)
+ Addedcan-observation@4.2.0(transitive)
+ Addedcan-observation-recorder@1.3.1(transitive)
+ Addedcan-queues@1.3.2(transitive)
+ Addedcan-reflect-dependencies@1.1.2(transitive)
+ Addedcan-route@4.4.9(transitive)
+ Addedcan-route-hash@1.0.2(transitive)
+ Addedcan-simple-observable@2.5.0(transitive)
+ Addedcan-single-reference@1.3.0(transitive)
+ Addedcan-string-to-any@1.2.1(transitive)
- Removedcan-event@^3.5.0
- Removedcan-compute@3.3.10(transitive)
- Removedcan-event@3.7.7(transitive)
- Removedcan-globals@0.2.6(transitive)
- Removedcan-observation@3.3.6(transitive)
- Removedcan-reflect-promise@1.1.5(transitive)
- Removedcan-route@3.3.4(transitive)
- Removedcan-simple-map@3.3.2(transitive)
- Removedcan-stache-key@0.1.4(transitive)
Updatedcan-globals@<2.0.0
Updatedcan-route@^4.0.0-pre.5