can-route
Advanced tools
Comparing version 3.2.4 to 4.0.0-pre.1
724
can-route.js
/*jshint -W079 */ | ||
var canBatch = require('can-event/batch/batch'); | ||
var canEvent = require('can-event'); | ||
var queues = require("can-queues"); | ||
var Observation = require('can-observation'); | ||
var compute = require('can-compute'); | ||
var SimpleObservable = require("can-simple-observable"); | ||
var namespace = require('can-namespace'); | ||
var param = require('can-param'); | ||
var deparam = require('can-deparam'); | ||
var each = require('can-util/js/each/each'); | ||
var string = require('can-util/js/string/string'); | ||
var isFunction = require('can-util/js/is-function/is-function'); | ||
var isEmptyObject = require('can-util/js/is-empty-object/is-empty-object'); | ||
var deepAssign = require('can-util/js/deep-assign/deep-assign'); | ||
var isWebWorker = require('can-util/js/is-web-worker/is-web-worker'); | ||
var isBrowserWindow = require('can-util/js/is-browser-window/is-browser-window'); | ||
var makeArray = require('can-util/js/make-array/make-array'); | ||
var assign = require("can-util/js/assign/assign"); | ||
var types = require('can-types'); | ||
var dev = require('can-util/js/dev/dev'); | ||
var diff = require('can-util/js/diff/diff'); | ||
var diffObject = require('can-util/js/diff-object/diff-object'); | ||
var canReflect = require('can-reflect'); | ||
var canSymbol = require('can-symbol'); | ||
var makeCompute = require("can-simple-observable/make-compute/make-compute"); | ||
var SimpleMap = require("can-simple-map"); | ||
var registerRoute = require("./src/register"); | ||
var urlDispatcher = require("./src/-url-dispatcher"); | ||
var urlHelpers = require("./src/url-helpers"); | ||
var routeParam = require("./src/param"); | ||
var routeDeparam = require("./src/deparam"); | ||
var bindingProxy = require("./src/binding-proxy"); | ||
var hashchange = require("./src/hashchange"); | ||
bindingProxy.bindings.hashchange = hashchange; | ||
bindingProxy.defaultBinding = "hashchange"; | ||
// ## route.js | ||
@@ -30,53 +34,14 @@ // `can-route` | ||
// `window.location.hash` with a `Map`._ | ||
function canRoute(url, defaults){ | ||
registerRoute.register(url, defaults); | ||
return canRoute; | ||
} | ||
// | ||
// Helper methods used for matching routes. | ||
// `RegExp` used to match route variables of the type '{name}'. | ||
// Any word character or a period is matched. | ||
var curliesMatcher = /\{\s*([\w.]+)\s*\}/g; | ||
var colonMatcher = /\:([\w.]+)/g; | ||
// Regular expression for identifying &key=value lists. | ||
var paramsMatcher = /^(?:&[^=]+=[^&]*)+/; | ||
// Converts a JS Object into a list of parameters that can be | ||
// inserted into an html element tag. | ||
var makeProps = function (props) { | ||
var tags = []; | ||
each(props, function (val, name) { | ||
tags.push((name === 'className' ? 'class' : name) + '="' + | ||
(name === "href" ? val : string.esc(val)) + '"'); | ||
}); | ||
return tags.join(" "); | ||
}; | ||
// Checks if a route matches the data provided. If any route variable | ||
// is not present in the data, the route does not match. If all route | ||
// variables are present in the data, the number of matches is returned | ||
// to allow discerning between general and more specific routes. | ||
var matchesData = function (route, data) { | ||
var count = 0, | ||
i = 0, | ||
defaults = {}; | ||
// look at default values, if they match ... | ||
for (var name in route.defaults) { | ||
if (route.defaults[name] === data[name]) { | ||
// mark as matched | ||
defaults[name] = 1; | ||
count++; | ||
} | ||
} | ||
for (; i < route.names.length; i++) { | ||
if (!data.hasOwnProperty(route.names[i])) { | ||
return -1; | ||
} | ||
if (!defaults[route.names[i]]) { | ||
count++; | ||
} | ||
} | ||
return count; | ||
}; | ||
var location = typeof window !== 'undefined' ? window.location : {}; | ||
var wrapQuote = function (str) { | ||
return (str + '') | ||
.replace(/([.?*+\^$\[\]\\(){}|\-])/g, "\\$1"); | ||
}; | ||
var attrHelper = function (prop, value) { | ||
@@ -123,6 +88,4 @@ if("attr" in this) { | ||
var removeBackslash = function (str) { | ||
return str.replace(/\\/g, ""); | ||
}; | ||
// A ~~throttled~~ debounced function called multiple times will only fire once the | ||
@@ -140,92 +103,6 @@ // timer runs down. Each call resets the timer. | ||
// A dummy events object used to dispatch url change events on. | ||
var eventsObject = assign({}, canEvent); | ||
var canRoute = function (url, defaults) { | ||
// if route ends with a / and url starts with a /, remove the leading / of the url | ||
var root = canRoute._call("root"); | ||
if (root.lastIndexOf("/") === root.length - 1 && | ||
url.indexOf("/") === 0) { | ||
url = url.substr(1); | ||
} | ||
defaults = defaults || {}; | ||
// Extract the variable names and replace with `RegExp` that will match | ||
// an atual URL with values. | ||
var names = [], | ||
res, | ||
test = "", | ||
matcher, | ||
lastIndex, | ||
next, | ||
querySeparator = canRoute._call("querySeparator"), | ||
matchSlashes = canRoute._call("matchSlashes"); | ||
// fall back to legacy `:foo` RegExp if necessary | ||
if (colonMatcher.test(url)) { | ||
matcher = colonMatcher; | ||
//!steal-remove-start | ||
dev.warn('update route "' + url + '" to "' + url.replace(colonMatcher, function(name, key) { | ||
return '{' + key + '}'; | ||
}) + '"'); | ||
//!steal-remove-end | ||
} else { | ||
matcher = curliesMatcher; | ||
} | ||
lastIndex = matcher.lastIndex = 0; | ||
// res will be something like ["{foo}","foo"] | ||
while (res = matcher.exec(url)) { | ||
names.push(res[1]); | ||
test += removeBackslash(url.substring(lastIndex, matcher.lastIndex - res[0].length)); | ||
// if matchSlashes is false (the default) don't greedily match any slash in the string, assume its part of the URL | ||
next = "\\" + (removeBackslash(url.substr(matcher.lastIndex, 1)) || querySeparator+(matchSlashes? "": "|/")); | ||
// a name without a default value HAS to have a value | ||
// a name that has a default value can be empty | ||
// The `\\` is for string-escaping giving single `\` for `RegExp` escaping. | ||
test += "([^" + next + "]" + (defaults[res[1]] ? "*" : "+") + ")"; | ||
lastIndex = matcher.lastIndex; | ||
} | ||
test += url.substr(lastIndex) | ||
.replace("\\", ""); | ||
//!steal-remove-start | ||
// warn if new route uses same map properties as an existing route | ||
each(canRoute.routes, function(r) { | ||
var existingKeys = r.names.concat(Object.keys(r.defaults)).sort(); | ||
var keys = names.concat(Object.keys(defaults)).sort(); | ||
var sameMapKeys = !diff(existingKeys, keys).length; | ||
var sameDefaultValues = !diffObject(r.defaults, defaults).length; | ||
//the regex removes the trailing slash | ||
var matchingRoutesWithoutTrailingSlash = r.route.replace(/\/$/, "") === url.replace(/\/$/, ""); | ||
if (sameMapKeys && sameDefaultValues && !matchingRoutesWithoutTrailingSlash) { | ||
dev.warn('two routes were registered with matching keys:\n' + | ||
'\t(1) route("' + r.route + '", ' + JSON.stringify(r.defaults) + ')\n' + | ||
'\t(2) route("' + url + '", ' + JSON.stringify(defaults) + ')\n' + | ||
'(1) will always be chosen since it was registered first'); | ||
} | ||
}); | ||
//!steal-remove-end | ||
// Add route in a form that can be easily figured out. | ||
canRoute.routes[url] = { | ||
// A regular expression that will match the route when variable values | ||
// are present; i.e. for (`{page}/{type}`) the `RegExp` is `/([\w\.]*)/([\w\.]*)/` which | ||
// will match for any value of `{page}` and `{type}` (word chars or period). | ||
test: new RegExp("^" + test + "($|" + wrapQuote(querySeparator) + ")"), | ||
// The original URL, same as the index for this entry in routes. | ||
route: url, | ||
// An `array` of all the variable names in this route. | ||
names: names, | ||
// Default values provided for the variables. | ||
defaults: defaults, | ||
// The number of parts in the URL separated by `/`. | ||
length: url.split('/') | ||
.length | ||
}; | ||
return canRoute; | ||
}; | ||
// If the `route.data` changes, update the hash. | ||
@@ -250,6 +127,15 @@ // Using `.serialize()` retrieves the raw data contained in the `observable`. | ||
var serialized =canRoute.data.serialize(), | ||
path =canRoute.param(serialized, true); | ||
canRoute._call("setURL", path, newProps, old); | ||
route = routeParam.getMatchedRoute(serialized), | ||
path = routeParam.paramFromRoute(route, serialized); | ||
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 | ||
canEvent.dispatch.call(eventsObject,"__url",[path, lastHash]); | ||
urlDispatcher.dispatch("__url",[path, lastHash]); | ||
lastHash = path; | ||
@@ -301,18 +187,4 @@ changedAttrs = []; | ||
// makes sure source has every value in matcher | ||
var matchCheck = function(source, matcher){ | ||
/*jshint eqeqeq:false*/ | ||
for(var prop in source) { | ||
var s = source[prop], | ||
m = matcher[prop]; | ||
if(s && m && typeof s === "object" && typeof matcher === "object") { | ||
return matchCheck(s, m); | ||
} | ||
if(s != m) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
}; | ||
var // Deparameterizes the portion of the hash of interest and assign the | ||
@@ -325,3 +197,3 @@ // values to the `route.data` removing existing values no longer in the hash. | ||
setState =canRoute.setState = function () { | ||
var hash =canRoute._call("matchingPartOfURL"); | ||
var hash =bindingProxy.call("can.getValue"); | ||
var oldParams = curParams; | ||
@@ -334,3 +206,3 @@ curParams =canRoute.deparam(hash); | ||
if (!changingData || hash !== lastHash) { | ||
canRoute.batch.start(); | ||
queues.batch.start(); | ||
recursiveClean(oldParams, curParams,canRoute.data); | ||
@@ -344,228 +216,17 @@ | ||
// trigger a url change so its possible to live-bind on url-based changes | ||
canEvent.dispatch.call(eventsObject,"__url",[hash, lastHash]); | ||
canRoute.batch.stop(); | ||
urlDispatcher.dispatch("__url",[hash, lastHash]); | ||
queues.batch.stop(); | ||
} | ||
}; | ||
var decode = function(str){ | ||
try { | ||
return decodeURIComponent(str); | ||
} catch(ex) { | ||
return unescape(str); | ||
} | ||
}; | ||
var matchedObservable = new SimpleObservable(); | ||
/** | ||
* @static | ||
*/ | ||
assign(canRoute, { | ||
Object.defineProperty(canRoute,"routes",{ | ||
/** | ||
* @function can-route.param param | ||
* @parent can-route.static | ||
* @description Get a route path from given data. | ||
* @signature `route.param(data)` | ||
* @param {data} object The data to populate the route with. | ||
* @return {String} The route, with the data populated in it. | ||
* | ||
* @body | ||
* Parameterizes the raw JS object representation provided in data. | ||
* | ||
* ```js | ||
* route.param({ type: "video", id: 5 }); | ||
* // -> "type=video&id=5" | ||
* ``` | ||
* | ||
* If a route matching the provided data is found, that URL is built | ||
* from the data. Any remaining data is added at the end of the | ||
* URL as & separated key/value parameters. | ||
* | ||
* ```js | ||
* route("{type}/{id}"); | ||
* | ||
* route.param({ type: "video", id: 5 }) // -> "video/5" | ||
* route.param({ type: "video", id: 5, isNew: false }) | ||
* // -> "video/5&isNew=false" | ||
* ``` | ||
*/ | ||
param: function (data, _setRoute) { | ||
// Check if the provided data keys match the names in any routes; | ||
// Get the one with the most matches. | ||
var route, | ||
// Need to have at least 1 match. | ||
matches = 0, | ||
matchCount, | ||
routeName = data.route, | ||
propCount = 0, | ||
cpy, | ||
res, | ||
after, | ||
matcher; | ||
delete data.route; | ||
each(data, function () { | ||
propCount++; | ||
}); | ||
// Otherwise find route. | ||
each(canRoute.routes, function (temp, name) { | ||
// best route is the first with all defaults matching | ||
matchCount = matchesData(temp, data); | ||
if (matchCount > matches) { | ||
route = temp; | ||
matches = matchCount; | ||
} | ||
if (matchCount >= propCount) { | ||
return false; | ||
} | ||
}); | ||
// If we have a route name in our `canRoute` data, and it's | ||
// just as good as what currently matches, use that | ||
if (canRoute.routes[routeName] && matchesData(canRoute.routes[routeName], data) === matches) { | ||
route = canRoute.routes[routeName]; | ||
} | ||
// If this is match... | ||
if (route) { | ||
cpy = assign({}, data); | ||
// fall back to legacy :foo RegExp if necessary | ||
matcher = colonMatcher.test(route.route) ? colonMatcher : curliesMatcher; | ||
// Create the url by replacing the var names with the provided data. | ||
// If the default value is found an empty string is inserted. | ||
res = route.route.replace(matcher, function (whole, name) { | ||
delete cpy[name]; | ||
return data[name] === route.defaults[name] ? "" : encodeURIComponent(data[name]); | ||
}) | ||
.replace("\\", ""); | ||
// Remove matching default values | ||
each(route.defaults, function (val, name) { | ||
if (cpy[name] === val) { | ||
delete cpy[name]; | ||
} | ||
}); | ||
// The remaining elements of data are added as | ||
// `&` separated parameters to the url. | ||
after = param(cpy); | ||
// if we are paraming for setting the hash | ||
// we also want to make sure the route value is updated | ||
if (_setRoute) { | ||
canRoute.matched(route.route); | ||
} | ||
return res + (after ? canRoute._call("querySeparator") + after : ""); | ||
} | ||
// If no route was found, there is no hash URL, only paramters. | ||
return isEmptyObject(data) ? "" :canRoute._call("querySeparator") + param(data); | ||
}, | ||
/** | ||
* @function can-route.deparam deparam | ||
* @parent can-route.static | ||
* @description Extract data from a route path. | ||
* @signature `route.deparam(url)` | ||
* | ||
* Extract data from a url, creating an object representing its values. | ||
* | ||
* ```js | ||
* route("{page}"); | ||
* | ||
* var result = route.deparam("page=home"); | ||
* console.log(result.page); // -> "home" | ||
* ``` | ||
* | ||
* @param {String} url A route fragment to extract data from. | ||
* @return {Object} An object containing the extracted data. | ||
* | ||
* @body | ||
* | ||
* Creates a data object based on the query string passed into it. This is | ||
* useful to create an object based on the `location.hash`. | ||
* | ||
* ```js | ||
* route.deparam("id=5&type=videos"); | ||
* // -> { id: 5, type: "videos" } | ||
* ``` | ||
* | ||
* | ||
* It's important to make sure the hash or exclamation point is not passed | ||
* to `route.deparam` otherwise it will be included in the first property's | ||
* name. | ||
* | ||
* ```js | ||
* route.data.id = 5 // location.hash -> #!id=5 | ||
* route.data.type = "videos" | ||
* // location.hash -> #!id=5&type=videos | ||
* route.deparam(location.hash); | ||
* // -> { #!id: 5, type: "videos" } | ||
* ``` | ||
* | ||
* `route.deparam` will try and find a matching route and, if it does, | ||
* will deconstruct the URL and parse out the key/value parameters into the | ||
* data object. | ||
* | ||
* ```js | ||
* route("{type}/{id}"); | ||
* | ||
* route.deparam("videos/5"); | ||
* // -> { id: 5, route: "{type}/{id}", type: "videos" } | ||
* ``` | ||
*/ | ||
deparam: function (url) { | ||
// remove the url | ||
var root =canRoute._call("root"); | ||
if (root.lastIndexOf("/") === root.length - 1 && | ||
url.indexOf("/") === 0) { | ||
url = url.substr(1); | ||
} | ||
// See if the url matches any routes by testing it against the `route.test` `RegExp`. | ||
// By comparing the URL length the most specialized route that matches is used. | ||
var route = { | ||
length: -1 | ||
}, | ||
querySeparator =canRoute._call("querySeparator"), | ||
paramsMatcher =canRoute._call("paramsMatcher"); | ||
each(canRoute.routes, function (temp, name) { | ||
if (temp.test.test(url) && temp.length > route.length) { | ||
route = temp; | ||
} | ||
}); | ||
// If a route was matched. | ||
if (route.length > -1) { | ||
var // Since `RegExp` backreferences are used in `route.test` (parens) | ||
// the parts will contain the full matched string and each variable (back-referenced) value. | ||
parts = url.match(route.test), | ||
// Start will contain the full matched string; parts contain the variable values. | ||
start = parts.shift(), | ||
// The remainder will be the `&key=value` list at the end of the URL. | ||
remainder = url.substr(start.length - (parts[parts.length - 1] === querySeparator ? 1 : 0)), | ||
// If there is a remainder and it contains a `&key=value` list deparam it. | ||
obj = (remainder && paramsMatcher.test(remainder)) ? deparam(remainder.slice(1)) : {}; | ||
// Add the default values for this route. | ||
obj = deepAssign(true, {}, route.defaults, obj); | ||
// Overwrite each of the default values in `obj` with those in | ||
// parts if that part is not empty. | ||
each(parts, function (part, i) { | ||
if (part && part !== querySeparator) { | ||
obj[route.names[i]] = decode(part); | ||
} | ||
}); | ||
obj.route = route.route; | ||
return obj; | ||
} | ||
// If no route was matched, it is parsed as a `&key=value` list. | ||
if (url.charAt(0) !== querySeparator) { | ||
url = querySeparator + url; | ||
} | ||
return paramsMatcher.test(url) ? deparam(url.slice(1)) : {}; | ||
}, | ||
map: function(data){ | ||
//!steal-remove-start | ||
dev.warn('Set route.data directly instead of calling route.map'); | ||
//!steal-remove-end | ||
canRoute.data = data; | ||
}, | ||
/** | ||
* @property {Object} routes | ||
@@ -589,3 +250,36 @@ * @hide | ||
*/ | ||
routes: {}, | ||
get: function() { | ||
return registerRoute.routes; | ||
}, | ||
set: function(newVal) { | ||
return registerRoute.routes = newVal; | ||
} | ||
}); | ||
Object.defineProperty(canRoute,"defaultBinding",{ | ||
get: function(){ | ||
return bindingProxy.defaultBinding; | ||
}, | ||
set: function(newVal){ | ||
bindingProxy.defaultBinding = newVal; | ||
} | ||
}); | ||
Object.defineProperty(canRoute,"currentBinding",{ | ||
get: function(){ | ||
return bindingProxy.currentBinding; | ||
}, | ||
set: function(newVal){ | ||
bindingProxy.currentBinding = newVal; | ||
} | ||
}); | ||
assign(canRoute, { | ||
param: routeParam, | ||
deparam: routeDeparam, | ||
map: function(data){ | ||
//!steal-remove-start | ||
dev.warn('Set route.data directly instead of calling route.map'); | ||
//!steal-remove-end | ||
canRoute.data = data; | ||
}, | ||
/** | ||
@@ -632,194 +326,7 @@ * @function can-route.ready ready | ||
}, | ||
/** | ||
* @function can-route.url url | ||
* @parent can-route.static | ||
* @description Creates a URL fragment based on registered routes given a set of data. | ||
* @signature `route.url(data [, merge])` | ||
* | ||
* Make a URL fragment that when set to window.location.hash will update can-route's properties | ||
* to match those in `data`. | ||
* | ||
* ```js | ||
* route.url({ page: "home" }); | ||
* // -> "#!page=home" | ||
* ``` | ||
* | ||
* @param {Object} data The data to populate the route with. | ||
* @param {Boolean} [merge] Whether the given options should be merged into | ||
* the current state of the route. | ||
* @return {String} The route URL and query string. | ||
* | ||
* @body | ||
* Similar to [can-route.link], but instead of creating an anchor tag, | ||
* `route.url` creates only the URL based on the route options passed into it. | ||
* | ||
* ```js | ||
* route.url( { type: "videos", id: 5 } ); | ||
* // -> "#!type=videos&id=5" | ||
* ``` | ||
* | ||
* If a route matching the provided data is found the URL is built from the | ||
* data. Any remaining data is added at the end of the URL as & separated | ||
* key/value parameters. | ||
* | ||
* ```js | ||
* route("{type}/{id}"); | ||
* | ||
* route.url( { type: "videos", id: 5 } ) // -> "#!videos/5" | ||
* route.url( { type: "video", id: 5, isNew: false } ) | ||
* // -> "#!video/5&isNew=false" | ||
* ``` | ||
*/ | ||
url: function (options, merge) { | ||
url: urlHelpers.url, | ||
link: urlHelpers.link, | ||
current: urlHelpers.current, | ||
bindings: bindingProxy.bindings, | ||
if (merge) { | ||
Observation.add(eventsObject,"__url"); | ||
var baseOptions = canRoute.deparam(canRoute._call("matchingPartOfURL")); | ||
options = assign(assign({}, baseOptions), options); | ||
} | ||
return canRoute._call("root") +canRoute.param(options); | ||
}, | ||
/** | ||
* @function can-route.link link | ||
* @parent can-route.static | ||
* @description Creates a string representation of an anchor link using | ||
* data and the registered routes. | ||
* @signature `route.link(innerText, data, props [, merge])` | ||
* | ||
* Make an anchor tag (`<A>`) that when clicked on will update can-route's | ||
* properties to match those in `data`. | ||
* | ||
* @param {Object} innerText The text inside the link. | ||
* @param {Object} data The data to populate the route with. | ||
* @param {Object} props Properties for the anchor other than `href`. | ||
* @param {Boolean} [merge] Whether the given options should be merged into the current state of the route. | ||
* @return {String} A string with an anchor tag that points to the populated route. | ||
* | ||
* @body | ||
* Creates and returns an anchor tag with an href of the route | ||
* attributes passed into it, as well as any properties desired | ||
* for the tag. | ||
* | ||
* ```js | ||
* route.link( "My videos", { type: "videos" }, {}, false ) | ||
* // -> <a href="#!type=videos">My videos</a> | ||
* ``` | ||
* | ||
* Other attributes besides href can be added to the anchor tag | ||
* by passing in a data object with the attributes desired. | ||
* | ||
* ```js | ||
* route.link( "My videos", { type: "videos" }, | ||
* { className: "new" }, false ) | ||
* // -> <a href="#!type=videos" class="new">My Videos</a> | ||
* ``` | ||
* | ||
* It is possible to utilize the current route options when making anchor | ||
* tags in order to make your code more reusable. If merge is set to true, | ||
* the route options passed into `canRoute.link` will be passed into the | ||
* current ones. | ||
* | ||
* ```js | ||
* location.hash = "#!type=videos" | ||
* route.link( "The zoo", { id: 5 }, true ) | ||
* // -> <a href="#!type=videos&id=5">The zoo</true> | ||
* | ||
* location.hash = "#!type=pictures" | ||
* route.link( "The zoo", { id: 5 }, true ) | ||
* // -> <a href="#!type=pictures&id=5">The zoo</true> | ||
* ``` | ||
*/ | ||
link: function (name, options, props, merge) { | ||
return "<a " + makeProps( | ||
assign({ | ||
href:canRoute.url(options, merge) | ||
}, props)) + ">" + name + "</a>"; | ||
}, | ||
/** | ||
* @function can-route.current current | ||
* @parent can-route.static | ||
* | ||
* Check if data represents the current route. | ||
* | ||
* @signature `route.current(data [,subsetMatch] )` | ||
* | ||
* Compares `data` to the current route. Used to verify if an object is | ||
* representative of the current route. | ||
* | ||
* ``` | ||
* route.data.set({page: "recipes", id: '5'}); | ||
* | ||
* route.current({page: "recipes"}); //-> false | ||
* route.current({page: "recipes"}, true); //-> true | ||
* ``` | ||
* | ||
* @param {Object} data Data to check agains the current route. | ||
* @param {Boolean} [subsetMatch] If true, `route.current` will return true | ||
* if every value in `data` matches the current route data, even if | ||
* the route data has additional properties that are not matched. Defaults to `false` | ||
* where every property needs to be present. | ||
* @return {Boolean} Whether the data matches the current URL. | ||
* | ||
* @body | ||
* | ||
* ## Use | ||
* | ||
* Checks the page's current URL to see if the route represents the options | ||
* passed into the function. | ||
* | ||
* Returns true if the options represent the current URL. | ||
* | ||
* ```js | ||
* route.data.id = 5; // location.hash -> "#!id=5" | ||
* route.current({ id: 5 }); // -> true | ||
* route.current({ id: 5, type: 'videos' }); // -> false | ||
* | ||
* route.data.type = 'videos'; | ||
* // location.hash -> #!id=5&type=videos | ||
* route.current({ id: 5, type: 'videos' }); // -> true | ||
* ``` | ||
*/ | ||
current: function (options, subsetMatch) { | ||
// "reads" the url so the url is live-bindable. | ||
Observation.add(eventsObject,"__url"); | ||
if(subsetMatch) { | ||
// everything in options shouhld be in baseOptions | ||
var baseOptions = canRoute.deparam(canRoute._call("matchingPartOfURL")); | ||
return matchCheck(options, baseOptions); | ||
} else { | ||
return this._call("matchingPartOfURL") === canRoute.param(options); | ||
} | ||
}, | ||
bindings: { | ||
hashchange: { | ||
paramsMatcher: paramsMatcher, | ||
querySeparator: "&", | ||
// don't greedily match slashes in routing rules | ||
matchSlashes: false, | ||
bind: function () { | ||
canEvent.on.call(window, 'hashchange', setState); | ||
}, | ||
unbind: function () { | ||
canEvent.on.call(window, 'hashchange', setState); | ||
}, | ||
// 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 | ||
matchingPartOfURL: function () { | ||
var loc =canRoute.location || 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) | ||
setURL: function (path) { | ||
if(location.hash !== "#" + path) { | ||
location.hash = "!" + path; | ||
} | ||
return path; | ||
}, | ||
root: "#!" | ||
} | ||
}, | ||
defaultBinding: "hashchange", | ||
currentBinding: null, | ||
// ready calls setup | ||
@@ -838,4 +345,4 @@ // setup binds and listens to data changes | ||
if (!canRoute.currentBinding) { | ||
canRoute._call("bind"); | ||
canRoute.serializedCompute.addEventListener("change", onRouteDataChange); | ||
bindingProxy.call("can.onValue", setState); | ||
canReflect.onValue( canRoute.serializedObservation, onRouteDataChange, "notify"); | ||
canRoute.currentBinding =canRoute.defaultBinding; | ||
@@ -846,4 +353,4 @@ } | ||
if (canRoute.currentBinding) { | ||
canRoute._call("unbind"); | ||
canRoute.serializedCompute.removeEventListener("change", onRouteDataChange); | ||
bindingProxy.call("can.offValue", setState); | ||
canReflect.offValue( canRoute.serializedObservation, onRouteDataChange, "notify"); | ||
canRoute.currentBinding = null; | ||
@@ -854,14 +361,2 @@ } | ||
}, | ||
// a helper to get stuff from the current or default bindings | ||
_call: function () { | ||
var args = makeArray(arguments), | ||
prop = args.shift(), | ||
binding =canRoute.bindings[canRoute.currentBinding ||canRoute.defaultBinding], | ||
method = binding[prop]; | ||
if (method.apply) { | ||
return method.apply(binding, args); | ||
} else { | ||
return method; | ||
} | ||
}, | ||
/** | ||
@@ -888,3 +383,3 @@ * @function can-route.matched matched | ||
*/ | ||
matched: compute() | ||
matched: makeCompute( matchedObservable ) | ||
}); | ||
@@ -897,3 +392,3 @@ | ||
if (!canRoute.data[name]) { | ||
return; | ||
return canRoute.data.addEventListener.apply(canRoute.data, args); | ||
} | ||
@@ -904,6 +399,6 @@ return canRoute.data[name].apply(canRoute.data, args); | ||
each(['addEventListener','removeEventListener','bind', 'unbind', 'on', 'off'], function(name) { | ||
// exposing all internal canEvent evt's to canRoute | ||
// exposing all internal eventQueue evt's to canRoute | ||
canRoute[name] = function(eventName) { | ||
if (eventName === '__url') { | ||
return canEvent[name].apply(eventsObject, arguments); | ||
return urlDispatcher[name].apply(urlDispatcher, arguments); | ||
} | ||
@@ -928,9 +423,19 @@ return bindToCanRouteData(name, arguments); | ||
}; | ||
var serializedObservation; | ||
var serializedCompute; | ||
Object.defineProperty(canRoute,"serializedObservation", { | ||
get: function(){ | ||
if(!serializedObservation) { | ||
serializedObservation = new Observation(function canRouteSerialized(){ | ||
return canReflect.serialize( canRoute.data ); | ||
}); | ||
} | ||
return serializedObservation; | ||
} | ||
}); | ||
Object.defineProperty(canRoute,"serializedCompute", { | ||
get: function(){ | ||
if(!serializedCompute) { | ||
serializedCompute = compute(function(){ | ||
return canRoute.data.serialize(); | ||
}); | ||
serializedCompute = makeCompute(canRoute.serializedObservation); | ||
} | ||
@@ -944,17 +449,4 @@ return serializedCompute; | ||
return routeData; | ||
} else if( types.DefaultMap ) { | ||
if( types.DefaultMap.prototype.toObject ) { | ||
var DefaultRouteMap = types.DefaultMap.extend({ | ||
seal: false | ||
},{ | ||
"*": "stringOrObservable" | ||
}); | ||
return setRouteData(new DefaultRouteMap()); | ||
} else { | ||
return setRouteData( stringCoercingMapDecorator( new types.DefaultMap() ) ); | ||
} | ||
} else { | ||
throw new Error("can.route.data accessed without being set"); | ||
return setRouteData( stringCoercingMapDecorator( new SimpleMap() ) ); | ||
} | ||
@@ -975,4 +467,2 @@ }, | ||
canRoute.attr = function(){ | ||
@@ -982,4 +472,2 @@ return attrHelper.apply(canRoute.data,arguments); | ||
//Allow for overriding of route batching by can.transaction | ||
canRoute.batch = canBatch; | ||
@@ -986,0 +474,0 @@ canReflect.setKeyValue(canRoute, canSymbol.for("can.isFunctionLike"), false); |
@@ -5,4 +5,3 @@ @function can-route can-route | ||
@test can-route/test.html | ||
@parent can-routing | ||
@collection can-core | ||
@parent can-core | ||
@link ../docco/route/route.html docco | ||
@@ -9,0 +8,0 @@ @package ./package.json |
{ | ||
"name": "can-route", | ||
"version": "3.2.4", | ||
"version": "4.0.0-pre.1", | ||
"description": "", | ||
@@ -17,5 +17,4 @@ "homepage": "", | ||
"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", | ||
@@ -28,3 +27,2 @@ "test": "npm run detect-cycle && npm run jshint && npm run testee", | ||
"release:major": "npm version major && npm publish", | ||
"build": "node build.js", | ||
"develop": "done-serve --static --develop --port 8080", | ||
@@ -45,19 +43,18 @@ "detect-cycle": "detect-cyclic-packages --ignore done-serve" | ||
"dependencies": { | ||
"can-compute": "^3.3.1", | ||
"can-deparam": "^1.0.1", | ||
"can-event": "^3.6.0", | ||
"can-event-queue": "<2.0.0", | ||
"can-namespace": "1.0.0", | ||
"can-observation": "^3.3.1", | ||
"can-observation": "^4.0.0-pre.19", | ||
"can-observation-recorder": "<2.0.0", | ||
"can-param": "^1.0.1", | ||
"can-reflect": "^1.2.1", | ||
"can-simple-map": "^3.3.0", | ||
"can-queues": "<2.0.0", | ||
"can-reflect": "^1.6.0", | ||
"can-simple-map": "^4.0.0-pre.9", | ||
"can-simple-observable": "^2.0.0-pre.21", | ||
"can-symbol": "^1.0.0", | ||
"can-types": "^1.1.0", | ||
"can-util": "^3.9.0" | ||
}, | ||
"devDependencies": { | ||
"can-define": "^1.3.3", | ||
"can-list": "^3.2.0", | ||
"can-map": "^3.3.1", | ||
"can-stache-key": "^0.1.0", | ||
"can-define": "^2.0.0-pre.0", | ||
"can-stache-key": "^1.0.0-pre.0", | ||
"detect-cyclic-packages": "^1.1.0", | ||
@@ -64,0 +61,0 @@ "done-serve": "^0.2.0", |
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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
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
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
9
55920
12
1276
18
998
2
+ Addedcan-event-queue@<2.0.0
+ Addedcan-queues@<2.0.0
+ Addedcan-define-lazy-value@1.1.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-simple-map@4.3.3(transitive)
+ Addedcan-simple-observable@2.5.0(transitive)
- Removedcan-compute@^3.3.1
- Removedcan-event@^3.6.0
- Removedcan-types@^1.1.0
- Removedcan-compute@3.3.10(transitive)
- Removedcan-event@3.7.7(transitive)
- Removedcan-observation@3.3.6(transitive)
- Removedcan-reflect-promise@1.1.5(transitive)
- Removedcan-simple-map@3.3.2(transitive)
- Removedcan-stache-key@0.1.4(transitive)
Updatedcan-reflect@^1.6.0
Updatedcan-simple-map@^4.0.0-pre.9