UNPKG

koco

Version:

knockout components with routing

410 lines (371 loc) 14.2 kB
(function (global, factory) { if (typeof define === "function" && define.amd) { define(['exports', './pattern-lexer'], factory); } else if (typeof exports !== "undefined") { factory(exports, require('./pattern-lexer')); } else { var mod = { exports: {} }; factory(mod.exports, global.patternLexer); global.byroads = mod.exports; } })(this, function (exports, _patternLexer) { // Copyright (c) CBC/Radio-Canada. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. /** * Most of the code comes from Crossroads.js */ /** @license * crossroads <http://millermedeiros.github.com/crossroads.js/> * Author: Miller Medeiros | MIT License * v0.12.0 (2013/01/21 13:47) */ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _patternLexer2 = _interopRequireDefault(_patternLexer); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); //todo:export... var NORM_AS_ARRAY = function NORM_AS_ARRAY(req, vals) { return [vals.vals_]; }; var NORM_AS_OBJECT = function NORM_AS_OBJECT(req, vals) { return [vals]; }; var UNDEF; // Helpers ----------- //==================== // IE 7-8 capture optional groups as empty strings while other browsers // capture as `undefined` var _hasOptionalGroupBug = /t(.+)?/.exec('t')[1] === ''; function arrayIndexOf(arr, val) { if (arr.indexOf) { return arr.indexOf(val); } else { //Array.indexOf doesn't work on IE 6-7 var n = arr.length; while (n--) { if (arr[n] === val) { return n; } } return -1; } } function arrayRemove(arr, item) { var i = arrayIndexOf(arr, item); if (i !== -1) { arr.splice(i, 1); } } function isKind(val, kind) { return '[object ' + kind + ']' === Object.prototype.toString.call(val); } function isRegExp(val) { return isKind(val, 'RegExp'); } function isArray(val) { return isKind(val, 'Array'); } function isFunction(val) { return typeof val === 'function'; } //borrowed from AMD-utils function typecastValue(val) { var r; if (val === null || val === 'null') { r = null; } else if (val === 'true') { r = true; } else if (val === 'false') { r = false; } else if (val === UNDEF || val === 'undefined') { r = UNDEF; } else if (val === '' || isNaN(val)) { //isNaN('') returns false r = val; } else { //parseFloat(null || '') returns NaN r = parseFloat(val); } return r; } function typecastArrayValues(values) { var n = values.length, result = []; while (n--) { result[n] = typecastValue(values[n]); } return result; } //borrowed from AMD-Utils function decodeQueryString(str, shouldTypecast) { var queryArr = (str || '').replace('?', '').split('&'), n = queryArr.length, obj = {}, item, val; while (n--) { item = queryArr[n].split('='); val = shouldTypecast ? typecastValue(item[1]) : item[1]; obj[item[0]] = typeof val === 'string' ? decodeURIComponent(val) : val; } return obj; } var Route = function () { function Route(pattern, priority, router) { _classCallCheck(this, Route); var isRegexPattern = isRegExp(pattern), patternLexer = router.patternLexer; this._router = router; this._pattern = pattern; this._paramsIds = isRegexPattern ? null : patternLexer.getParamIds(pattern); this._optionalParamsIds = isRegexPattern ? null : patternLexer.getOptionalParamsIds(pattern); this._matchRegexp = isRegexPattern ? pattern : patternLexer.compilePattern(pattern, router.ignoreCase); this._priority = priority || 0; this.rules = void 0; } _createClass(Route, [{ key: 'match', value: function match(request) { request = request || ''; return this._matchRegexp.test(request) && this._validateParams(request); //validate params even if regexp because of `request_` rule. } }, { key: '_validateParams', value: function _validateParams(request) { var rules = this.rules, values = this._getParamsObject(request), key; for (key in rules) { // normalize_ isn't a validation rule... (#39) if (key !== 'normalize_' && rules.hasOwnProperty(key) && !this._isValidParam(request, key, values)) { return false; } } return true; } }, { key: '_isValidParam', value: function _isValidParam(request, prop, values) { var validationRule = this.rules[prop], val = values[prop], isValid = false, isQuery = prop.indexOf('?') === 0; if (val == null && this._optionalParamsIds && arrayIndexOf(this._optionalParamsIds, prop) !== -1) { isValid = true; } else if (isRegExp(validationRule)) { if (isQuery) { val = values[prop + '_']; //use raw string } isValid = validationRule.test(val); } else if (isArray(validationRule)) { if (isQuery) { val = values[prop + '_']; //use raw string } isValid = this._isValidArrayRule(validationRule, val); } else if (isFunction(validationRule)) { isValid = validationRule(val, request, values); } return isValid; //fail silently if validationRule is from an unsupported type } }, { key: '_isValidArrayRule', value: function _isValidArrayRule(arr, val) { if (!this._router.ignoreCase) { return arrayIndexOf(arr, val) !== -1; } if (typeof val === 'string') { val = val.toLowerCase(); } var n = arr.length, item, compareVal; while (n--) { item = arr[n]; compareVal = typeof item === 'string' ? item.toLowerCase() : item; if (compareVal === val) { return true; } } return false; } }, { key: '_getParamsObject', value: function _getParamsObject(request) { var shouldTypecast = this._router.shouldTypecast, values = this._router.patternLexer.getParamValues(request, this._matchRegexp, shouldTypecast), o = {}, n = values.length, param, val; while (n--) { val = values[n]; if (this._paramsIds) { param = this._paramsIds[n]; if (param.indexOf('?') === 0 && val) { //make a copy of the original string so array and //RegExp validation can be applied properly o[param + '_'] = val; //update vals_ array as well since it will be used //during dispatch val = decodeQueryString(val, shouldTypecast); values[n] = val; } // IE will capture optional groups as empty strings while other // browsers will capture `undefined` so normalize behavior. // see: #gh-58, #gh-59, #gh-60 if (_hasOptionalGroupBug && val === '' && arrayIndexOf(this._optionalParamsIds, param) !== -1) { val = void 0; values[n] = val; } o[param] = val; } //alias to paths and for RegExp pattern o[n] = val; } o.request_ = shouldTypecast ? typecastValue(request) : request; o.vals_ = values; return o; } }, { key: '_getParamsArray', value: function _getParamsArray(request) { var norm = this.rules ? this.rules.normalize_ : null, params; norm = norm || this._router.normalizeFn; // default normalize if (norm && isFunction(norm)) { params = norm(request, this._getParamsObject(request)); } else { params = this._getParamsObject(request).vals_; } return params; } }, { key: 'interpolate', value: function interpolate(replacements) { var str = this._router.patternLexer.interpolate(this._pattern, replacements); if (!this._validateParams(str)) { throw new Error('Generated string doesn\'t validate against `Route.rules`.'); } return str; } }, { key: 'dispose', value: function dispose() { this._router.removeRoute(this); } }, { key: 'toString', value: function toString() { return '[Route pattern:"' + this._pattern + '"]'; } }]); return Route; }(); var Byroads = function () { function Byroads() { _classCallCheck(this, Byroads); this._routes = []; this.ignoreCase = true; this.shouldTypecast = false; this.normalizeFn = NORM_AS_OBJECT; this.patternLexer = new _patternLexer2.default(); } _createClass(Byroads, [{ key: 'create', value: function create() { return new Byroads(); } }, { key: 'addRoute', value: function addRoute(pattern, priority) { var route = new Route(pattern, priority, this); this._sortedInsert(route); return route; } }, { key: 'removeRoute', value: function removeRoute(route) { arrayRemove(this._routes, route); } }, { key: 'removeAllRoutes', value: function removeAllRoutes() { this._routes.length = 0; } }, { key: 'getNumRoutes', value: function getNumRoutes() { return this._routes.length; } }, { key: '_sortedInsert', value: function _sortedInsert(route) { //simplified insertion sort var routes = this._routes, n = routes.length; do { --n; } while (routes[n] && route._priority <= routes[n]._priority); routes.splice(n + 1, 0, route); } }, { key: 'getMatchedRoutes', value: function getMatchedRoutes(request, returnAllMatchedRoutes) { request = request || ''; var res = [], routes = this._routes, n = routes.length, route; //should be decrement loop since higher priorities are added at the end of array while (route = routes[--n]) { if ((!res.length || returnAllMatchedRoutes) && route.match(request)) { res.push({ route: route, params: route._getParamsArray(request) }); } if (!returnAllMatchedRoutes && res.length) { break; } } return res; } }, { key: 'toString', value: function toString() { return '[byroads numRoutes:' + this.getNumRoutes() + ']'; } }]); return Byroads; }(); exports.default = Byroads; });