can-route
Advanced tools
Comparing version 4.0.0-pre.2 to 4.0.0-pre.3
258
can-route.js
/*jshint -W079 */ | ||
var queues = require("can-queues"); | ||
var Observation = require('can-observation'); | ||
var SimpleObservable = require("can-simple-observable"); | ||
@@ -20,3 +19,2 @@ var namespace = require('can-namespace'); | ||
var registerRoute = require("./src/register"); | ||
var urlDispatcher = require("./src/-url-dispatcher"); | ||
var urlHelpers = require("./src/url-helpers"); | ||
@@ -41,67 +39,18 @@ var routeParam = require("./src/param"); | ||
// | ||
// Helper methods used for matching routes. | ||
var attrHelper = function (prop, value) { | ||
if("attr" in this) { | ||
return this.attr.apply(this, arguments); | ||
} else { | ||
if(arguments.length > 1) { | ||
canReflect.setKeyValue(this, prop, value); | ||
return this; | ||
} else if(typeof prop === 'object') { | ||
canReflect.assignDeep(this,prop); | ||
return this; | ||
} else if(arguments.length === 1){ | ||
return canReflect.getKeyValue(this, prop); | ||
} else { | ||
return canReflect.unwrap(this); | ||
} | ||
} | ||
}; | ||
// Helper for convert any object (or value) to stringified object (or value) | ||
var stringify = function (obj) { | ||
// Object is array, plain object, Map or List | ||
if (obj && typeof obj === "object") { | ||
if (obj && typeof obj === "object" && ("serialize" in obj)) { | ||
obj = obj.serialize(); | ||
} else { | ||
// Get array from array-like or shallow-copy object | ||
obj = isFunction(obj.slice) ? obj.slice() : assign({}, obj); | ||
} | ||
// Convert each object property or array item into stringified new | ||
each(obj, function (val, prop) { | ||
obj[prop] = stringify(val); | ||
}); | ||
// Object supports toString function | ||
} else if (obj !== undefined && obj !== null && isFunction(obj.toString)) { | ||
obj = obj.toString(); | ||
} | ||
return obj; | ||
}; | ||
// A ~~throttled~~ debounced function called multiple times will only fire once the | ||
// timer runs down. Each call resets the timer. | ||
var timer; | ||
// Intermediate storage for `canRoute.data`. | ||
var curParams; | ||
// The last hash caused by a data change | ||
var lastHash; | ||
// Are data changes pending that haven't yet updated the hash | ||
var changingData; | ||
// List of attributes that have changed since last update | ||
var changedAttrs = []; | ||
// A dummy events object used to dispatch url change events on. | ||
var matchedObservable = new Observation(function canRoute_matchedRoute(){ | ||
var url = bindingProxy.call("can.getValue"); | ||
return canRoute.deparam(url).route; | ||
}); | ||
// If the `route.data` changes, update the hash. | ||
@@ -111,14 +60,9 @@ // Using `.serialize()` retrieves the raw data contained in the `observable`. | ||
// This might be able to use batchNum and avoid this. | ||
var oldProperties = null; | ||
var onRouteDataChange = function (ev, newProps, oldProps) { | ||
function updateUrl(serializedData) { | ||
// indicate that data is changing | ||
changingData = 1; | ||
// collect attributes that are changing | ||
if(!oldProperties) { | ||
oldProperties = oldProps; | ||
} | ||
clearTimeout(timer); | ||
timer = setTimeout(function () { | ||
var old = oldProperties; | ||
oldProperties = null; | ||
// indicate that the hash is set to look like the data | ||
@@ -130,97 +74,44 @@ changingData = 0; | ||
if(route) { | ||
// if we are paraming for setting the hash | ||
// we also want to make sure the route value is updated | ||
// TODO: matched can almost certainly be an observation around the route derived from the serialize data | ||
canRoute.matched(route.route); | ||
} | ||
bindingProxy.call("can.setValue", path, newProps, old); | ||
//canRoute._call("setURL", path, newProps, old); | ||
// trigger a url change so its possible to live-bind on url-based changes | ||
urlDispatcher.dispatch("__url",[path, lastHash]); | ||
lastHash = path; | ||
changedAttrs = []; | ||
bindingProxy.call("can.setValue", path); | ||
}, 10); | ||
}; | ||
} | ||
// everything in the backing Map is a string | ||
// add type coercion during Map setter to coerce all values to strings | ||
var stringCoercingMapDecorator = function(map) { | ||
var sym = canSymbol.for("can.route.stringCoercingMapDecorator"); | ||
if(!map.attr[sym]) { | ||
var attrSuper = map.attr; | ||
//!steal-remove-start | ||
Object.defineProperty(updateUrl, "name", { | ||
value: "can-route.updateUrl" | ||
}); | ||
//!steal-remove-end | ||
map.attr = function(prop, val) { | ||
var serializable = this.define === undefined || this.define[prop] === undefined || !!this.define[prop].serialize, | ||
args; | ||
if (serializable) { // if setting non-str non-num attr | ||
args = stringify(Array.apply(null, arguments)); | ||
} else { | ||
args = arguments; | ||
} | ||
return attrSuper.apply(this, args); | ||
}; | ||
canReflect.setKeyValue(map.attr, sym, true); | ||
} | ||
return map; | ||
}; | ||
var recursiveClean = function(old, cur, data){ | ||
for(var attr in old){ | ||
if(cur[attr] === undefined){ | ||
if("removeAttr" in data) { | ||
data.removeAttr(attr); | ||
} else { | ||
cur[attr] = undefined; | ||
} | ||
} | ||
else if(Object.prototype.toString.call(old[attr]) === "[object Object]") { | ||
recursiveClean( old[attr], cur[attr], attrHelper.call(data,attr) ); | ||
} | ||
} | ||
}; | ||
var // Deparameterizes the portion of the hash of interest and assign the | ||
// Deparameterizes the portion of the hash of interest and assign the | ||
// values to the `route.data` removing existing values no longer in the hash. | ||
// setState is called typically by hashchange which fires asynchronously | ||
// updateRouteData is called typically by hashchange which fires asynchronously | ||
// So it's possible that someone started changing the data before the | ||
// hashchange event fired. For this reason, it will not set the route data | ||
// if the data is changing or the hash already matches the hash that was set. | ||
setState =canRoute.setState = function () { | ||
var hash =bindingProxy.call("can.getValue"); | ||
var oldParams = curParams; | ||
curParams =canRoute.deparam(hash); | ||
var matched; | ||
function updateRouteData() { | ||
var hash = bindingProxy.call("can.getValue"); | ||
// if the hash data is currently changing, or | ||
// the hash is what we set it to anyway, do NOT change the hash | ||
if (!changingData || hash !== lastHash) { | ||
if ( !changingData ) { | ||
queues.batch.start(); | ||
recursiveClean(oldParams, curParams,canRoute.data); | ||
matched = curParams.route; | ||
delete curParams.route; | ||
canRoute.matched(matched); | ||
canRoute.attr(curParams); | ||
curParams.route = matched; | ||
// trigger a url change so its possible to live-bind on url-based changes | ||
urlDispatcher.dispatch("__url",[hash, lastHash]); | ||
var state = canRoute.deparam(hash); | ||
delete state.route; | ||
canReflect.update(canRoute.data,state); | ||
queues.batch.stop(); | ||
} | ||
}; | ||
} | ||
//!steal-remove-start | ||
Object.defineProperty(updateRouteData, "name", { | ||
value: "can-route.updateRouteData" | ||
}); | ||
//!steal-remove-end | ||
var matchedObservable = new SimpleObservable(); | ||
/** | ||
* @static | ||
*/ | ||
Object.defineProperty(canRoute,"routes",{ | ||
@@ -266,3 +157,3 @@ /** | ||
set: function(newVal){ | ||
bindingProxy.currentBinding = newVal; | ||
bindingProxy.currentBinding = newVal; | ||
} | ||
@@ -317,3 +208,10 @@ }); | ||
if(isBrowserWindow() || isWebWorker()) { | ||
canRoute.setState(); | ||
// We can't use updateRouteData because we want to merge the route data | ||
// into .data | ||
var hash = bindingProxy.call("can.getValue"); | ||
queues.batch.start(); | ||
var state = canRoute.deparam(hash); | ||
delete state.route; | ||
canReflect.assign(canRoute.data,state); | ||
queues.batch.stop(); | ||
} | ||
@@ -334,3 +232,3 @@ } | ||
// we need to be able to | ||
// easily kick off calling setState | ||
// easily kick off calling updateRouteData | ||
// teardown whatever is there | ||
@@ -342,4 +240,4 @@ // turn on a particular binding | ||
if (!canRoute.currentBinding) { | ||
bindingProxy.call("can.onValue", setState); | ||
canReflect.onValue( canRoute.serializedObservation, onRouteDataChange, "notify"); | ||
bindingProxy.call("can.onValue", updateRouteData); | ||
canReflect.onValue( canRoute.serializedObservation, updateUrl, "notify"); | ||
canRoute.currentBinding =canRoute.defaultBinding; | ||
@@ -350,4 +248,4 @@ } | ||
if (canRoute.currentBinding) { | ||
bindingProxy.call("can.offValue", setState); | ||
canReflect.offValue( canRoute.serializedObservation, onRouteDataChange, "notify"); | ||
bindingProxy.call("can.offValue", updateRouteData); | ||
canReflect.offValue( canRoute.serializedObservation, updateUrl, "notify"); | ||
canRoute.currentBinding = null; | ||
@@ -394,5 +292,5 @@ } | ||
// exposing all internal eventQueue evt's to canRoute | ||
canRoute[name] = function(eventName) { | ||
canRoute[name] = function(eventName, handler) { | ||
if (eventName === '__url') { | ||
return urlDispatcher[name].apply(urlDispatcher, arguments); | ||
return bindingProxy.call("can.onValue", handler ); | ||
} | ||
@@ -423,3 +321,3 @@ return bindToCanRouteData(name, arguments); | ||
if(!serializedObservation) { | ||
serializedObservation = new Observation(function canRouteSerialized(){ | ||
serializedObservation = new Observation(function canRoute_data_serialized(){ | ||
return canReflect.serialize( canRoute.data ); | ||
@@ -439,2 +337,49 @@ }); | ||
}); | ||
// Helper for convert any object (or value) to stringified object (or value) | ||
var stringify = function (obj) { | ||
// Object is array, plain object, Map or List | ||
if (obj && typeof obj === "object") { | ||
if (obj && typeof obj === "object" && ("serialize" in obj)) { | ||
obj = obj.serialize(); | ||
} else { | ||
// Get array from array-like or shallow-copy object | ||
obj = isFunction(obj.slice) ? obj.slice() : assign({}, obj); | ||
} | ||
// Convert each object property or array item into stringified new | ||
each(obj, function (val, prop) { | ||
obj[prop] = stringify(val); | ||
}); | ||
// Object supports toString function | ||
} else if (obj !== undefined && obj !== null && isFunction(obj.toString)) { | ||
obj = obj.toString(); | ||
} | ||
return obj; | ||
}; | ||
// everything in the backing Map is a string | ||
// add type coercion during Map setter to coerce all values to strings so unexpected conflicts don't happen. | ||
// https://github.com/canjs/canjs/issues/2206 | ||
var stringCoercingMapDecorator = function(map) { | ||
var sym = canSymbol.for("can.route.stringCoercingMapDecorator"); | ||
if(!map.attr[sym]) { | ||
var attrSuper = map.attr; | ||
map.attr = function(prop, val) { | ||
var serializable = this.define === undefined || this.define[prop] === undefined || !!this.define[prop].serialize, | ||
args; | ||
if (serializable) { // if setting non-str non-num attr | ||
args = stringify(Array.apply(null, arguments)); | ||
} else { | ||
args = arguments; | ||
} | ||
return attrSuper.apply(this, args); | ||
}; | ||
canReflect.setKeyValue(map.attr, sym, true); | ||
} | ||
return map; | ||
}; | ||
Object.defineProperty(canRoute,"data", { | ||
@@ -461,4 +406,19 @@ get: function(){ | ||
canRoute.attr = function(){ | ||
return attrHelper.apply(canRoute.data,arguments); | ||
canRoute.attr = function(prop, value){ | ||
console.warn("can-route: can-route.attr is deprecated. Use methods on can-route.data instead."); | ||
if("attr" in canRoute.data) { | ||
return canRoute.data.attr.apply(canRoute.data, arguments); | ||
} else { | ||
if(arguments.length > 1) { | ||
canReflect.setKeyValue(canRoute.data, prop, value); | ||
return canRoute.data; | ||
} else if(typeof prop === 'object') { | ||
canReflect.assignDeep(canRoute.data,prop); | ||
return canRoute.data; | ||
} else if(arguments.length === 1){ | ||
return canReflect.getKeyValue(canRoute.data, prop); | ||
} else { | ||
return canReflect.unwrap(canRoute.data); | ||
} | ||
} | ||
}; | ||
@@ -465,0 +425,0 @@ |
{ | ||
"name": "can-route", | ||
"version": "4.0.0-pre.2", | ||
"version": "4.0.0-pre.3", | ||
"description": "Observable front-end application routing for CanJS.", | ||
@@ -52,6 +52,8 @@ "homepage": "https://canjs.com/doc/can-route.html", | ||
"can-symbol": "^1.0.0", | ||
"can-util": "^3.9.0" | ||
"can-util": "^3.9.0", | ||
"can-key-tree": "<2.0.0" | ||
}, | ||
"devDependencies": { | ||
"can-define": "^2.0.0-pre.0", | ||
"can-map": "^4.0.0-pre.8", | ||
"can-stache-key": "^1.0.0-pre.0", | ||
@@ -58,0 +60,0 @@ "detect-cyclic-packages": "^1.1.0", |
var makeArray = require('can-util/js/make-array/make-array'); | ||
var canSymbol = require("can-symbol"); | ||
var SimpleObservable = require("can-simple-observable"); | ||
var defaultBinding = new SimpleObservable("hashchange") | ||
var bindingProxy = { | ||
defaultBinding: "hashchange", | ||
get defaultBinding(){ | ||
return defaultBinding.get(); | ||
}, | ||
set defaultBinding(newVal){ | ||
defaultBinding.set(newVal); | ||
}, | ||
currentBinding: null, | ||
@@ -7,0 +15,0 @@ bindings: {}, |
@@ -6,5 +6,31 @@ // Regular expression for identifying &key=value lists. | ||
var canReflect = require("can-reflect"); | ||
var eventQueue = require("can-event-queue"); | ||
var domEvents = require("can-util/dom/events/events"); | ||
var ObservationRecorder = require("can-observation-recorder"); | ||
var queues = require("can-queues"); | ||
var KeyTree = require("can-key-tree"); | ||
var SimpleObservable = require("can-simple-observable"); | ||
module.exports = canReflect.assignSymbols({ | ||
function getHash(){ | ||
var loc = LOCATION(); | ||
return loc.href.split(/#!?/)[1] || ""; | ||
} | ||
function HashchangeObservable() { | ||
var dispatchHandlers = this.dispatchHandlers.bind(this); | ||
var self = this; | ||
this.handlers = new KeyTree([Object,Array],{ | ||
onFirst: function(){ | ||
self.value = getHash(); | ||
domEvents.addEventListener.call(window, 'hashchange', dispatchHandlers); | ||
}, | ||
onEmpty: function(){ | ||
domEvents.removeEventListener.call(window, 'hashchange', dispatchHandlers); | ||
} | ||
}); | ||
} | ||
HashchangeObservable.prototype = Object.create(SimpleObservable.prototype); | ||
HashchangeObservable.constructor = HashchangeObservable; | ||
canReflect.assign(HashchangeObservable.prototype,{ | ||
// STUFF NEEDED FOR can-route integration | ||
paramsMatcher: paramsMatcher, | ||
@@ -14,21 +40,23 @@ querySeparator: "&", | ||
matchSlashes: false, | ||
root: "#!" | ||
},{ | ||
"can.onValue": function(handler){ | ||
eventQueue.on.call(window, 'hashchange', handler); | ||
root: "#!", | ||
dispatchHandlers: function() { | ||
var old = this.value; | ||
this.value = getHash(); | ||
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 | ||
); | ||
} | ||
}, | ||
"can.offValue": function(handler) { | ||
eventQueue.on.call(window, 'hashchange', handler); | ||
get: function(){ | ||
ObservationRecorder.add(this); | ||
return getHash(); | ||
}, | ||
// Gets the part of the url we are determinging the route from. | ||
// For hashbased routing, it's everything after the #, for | ||
// pushState it's configurable | ||
"can.getValue": function() { | ||
set: function(path){ | ||
var loc = LOCATION(); | ||
return loc.href.split(/#!?/)[1] || ""; | ||
}, | ||
// gets called with the serializedcanRoute data after a route has changed | ||
// returns what the url has been updated to (for matching purposes) | ||
"can.setValue": function(path){ | ||
var loc = LOCATION(); | ||
if(loc.hash !== "#" + path) { | ||
@@ -40,1 +68,19 @@ loc.hash = "!" + path; | ||
}); | ||
canReflect.assignSymbols(HashchangeObservable.prototype,{ | ||
"can.getValue": HashchangeObservable.prototype.get, | ||
"can.setValue": HashchangeObservable.prototype.set, | ||
"can.onValue": HashchangeObservable.prototype.on, | ||
"can.offValue": HashchangeObservable.prototype.off, | ||
"can.isMapLike": false, | ||
"can.valueHasDependencies": function(){ | ||
return true; | ||
}, | ||
//!steal-remove-start | ||
"can.getName": function() { | ||
return "HashchangeObservable<" + this.value + ">"; | ||
}, | ||
//!steal-remove-end | ||
}); | ||
module.exports = new HashchangeObservable(); |
var bindingProxy = require("./binding-proxy"); | ||
var ObservationRecorder = require('can-observation-recorder'); | ||
var routeDeparam = require("./deparam"); | ||
@@ -10,6 +9,3 @@ var routeParam = require("./param"); | ||
var urlDispatcher = require("./-url-dispatcher"); | ||
var makeProps = function (props) { | ||
@@ -41,3 +37,2 @@ var tags = []; | ||
if (merge) { | ||
ObservationRecorder.add(urlDispatcher,"__url"); | ||
var baseOptions = routeDeparam(bindingProxy.call("can.getValue")); | ||
@@ -191,7 +186,5 @@ options = assign(assign({}, baseOptions), options); | ||
current: function canRoute_current(options, subsetMatch) { | ||
// "reads" the url so the url is live-bindable. | ||
ObservationRecorder.add(urlDispatcher,"__url"); | ||
if(subsetMatch) { | ||
// everything in options shouhld be in baseOptions | ||
var baseOptions = routeDeparam( bindingProxy.call("matchingPartOfURL") ); | ||
var baseOptions = routeDeparam( bindingProxy.call("getValue") ); | ||
return matchCheck(options, baseOptions); | ||
@@ -198,0 +191,0 @@ } else { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
1
56552
13
10
17
1011
+ Addedcan-key-tree@<2.0.0