diff --git a/dist/susanin.js b/dist/susanin.js index 8277446..0dc811c 100644 --- a/dist/susanin.js +++ b/dist/susanin.js @@ -300,6 +300,10 @@ function Route(options) { GROUP_CLOSED_CHAR; this._conditions[QUERY_STRING_PARAM_NAME] = '.*'; + this._paramsMap = []; + this._mainParamsMap = {}; + this._requiredParams = []; + /** * @type {Array} * @private @@ -312,10 +316,11 @@ function Route(options) { /** * @param {String} pattern + * @param {Boolean} [isOptional=false] * @returns {Array} * @private */ -Route.prototype._parsePattern = function(pattern) { +Route.prototype._parsePattern = function(pattern, isOptional) { var parts = [], part = '', character, @@ -332,7 +337,7 @@ Route.prototype._parsePattern = function(pattern) { ++countOpened; part += character; } else { - this._parseParams(part, parts); + this._parseParams(part, parts, isOptional); part = ''; countOpened = 0; isFindingClosed = true; @@ -343,7 +348,7 @@ Route.prototype._parsePattern = function(pattern) { part = { what : 'optional', dependOnParams : [], - parts : this._parsePattern(part) + parts : this._parsePattern(part, true) }; parts.push(part); @@ -368,7 +373,7 @@ Route.prototype._parsePattern = function(pattern) { } } - this._parseParams(part, parts); + this._parseParams(part, parts, isOptional); return parts; }; @@ -376,21 +381,28 @@ Route.prototype._parsePattern = function(pattern) { /** * @param {String} pattern * @param {Array} parts + * @param {Boolean} isOptional * @private */ -Route.prototype._parseParams = function(pattern, parts) { +Route.prototype._parseParams = function(pattern, parts, isOptional) { var matches = pattern.match(PARSE_PARAMS_REGEXP), i, size, - part; + part, + paramName; if (matches) { for (i = 0, size = matches.length; i < size; ++i) { part = matches[i]; if (part.charAt(0) === PARAM_OPENED_CHAR && part.charAt(part.length - 1) === PARAM_CLOSED_CHAR) { + paramName = part.substr(1, part.length - 2); + this._paramsMap.push(paramName); + this._mainParamsMap[paramName] = true; + isOptional || this._requiredParams.push(paramName); + parts.push({ what : 'param', - name : part.substr(1, part.length - 2) + name : paramName }); } else { parts.push(part); @@ -403,7 +415,6 @@ Route.prototype._parseParams = function(pattern, parts) { * @private */ Route.prototype._buildParseRegExp = function() { - this._reqExpParamsMap = []; this._parseRegExpSource = '^' + this._buildParseRegExpParts(this._parts) + '$'; this._parseRegExp = new RegExp(this._parseRegExpSource); }; @@ -424,7 +435,6 @@ Route.prototype._buildParseRegExpParts = function(parts) { if (typeof part === 'string') { ret += escape(part); } else if (part.what === 'param') { - this._reqExpParamsMap.push(part.name); ret += '(' + (this._getParamValueRegExpSource(part.name) || PARAM_VALUE_REGEXP_SOURCE) + ')'; } else { ret += '(?:' + this._buildParseRegExpParts(part.parts) + ')?'; @@ -494,7 +504,6 @@ Route.prototype._checkParamValue = function(paramName, paramValue) { * @private */ Route.prototype._buildBuildFn = function() { - this._mainParamsMap = {}; this._buildFnSource = 'var h=({}).hasOwnProperty;return ' + this._buildBuildFnParts(this._parts) + ';'; /*jshint evil:true */ this._buildFn = new Function('p', this._buildFnSource); @@ -598,7 +607,7 @@ Route.prototype.match = function(path, data) { for (i = 1, size = matches.length; i < size; ++i) { if (typeof matches[i] !== 'undefined' && /* for IE lt 9*/ matches[i] !== '') { - paramName = this._reqExpParamsMap[i - 1]; + paramName = this._paramsMap[i - 1]; if (paramName === QUERY_STRING_PARAM_NAME) { queryString = matches[i]; } else if (paramName === TRAILING_SLASH_PARAM_NAME) { @@ -654,31 +663,44 @@ Route.prototype.match = function(path, data) { /** * Build path from params * @param {Object} params - * @returns {String} + * @param {Boolean} [isStrict=false] + * @returns {?String} */ -Route.prototype.build = function(params) { +Route.prototype.build = function(params, isStrict) { var options = this._options, newParams = {}, useQueryString = options.useQueryString !== false, queryParams = {}, queryString, - key, - filter = options.preBuild; + paramName, + paramValue, + filter = options.preBuild, + i, size; if (typeof filter === 'function') { params = filter(params); } - for (key in params) { + for (paramName in params) { if ( - has(params, key) && - params[key] !== null && - typeof params[key] !== 'undefined' + has(params, paramName) && + params[paramName] !== null && + typeof params[paramName] !== 'undefined' && + (this._mainParamsMap[paramName] || useQueryString) ) { - if (this._mainParamsMap[key]) { - newParams[key] = params[key]; - } else if (useQueryString) { - queryParams[key] = params[key]; + paramValue = params[paramName]; + if (isStrict && ! this._checkParamValue(paramName, paramValue)) { + return null; + } + + (this._mainParamsMap[paramName] ? newParams : queryParams)[paramName] = paramValue; + } + } + + if (isStrict) { + for (i = 0, size = this._requiredParams.length; i < size; ++i) { + if ( ! has(newParams, this._requiredParams[i])) { + return null; } } } diff --git a/dist/susanin.min.js b/dist/susanin.min.js index 55fa469..c9ecd20 100644 --- a/dist/susanin.min.js +++ b/dist/susanin.min.js @@ -1 +1 @@ -!function(t){function e(t){return{"./querystring":function(){var t={},e=Object.prototype.hasOwnProperty,r=Object.prototype.toString,n=function(t){return"[object Array]"===r.call(t)},o={decode:function(t){var e;try{e=decodeURIComponent(t.replace(/\+/g,"%20"))}catch(r){e=t}return e},parse:function(t,r,i){var a,s,p,u,f,h,c={};if("string"!=typeof t||""===t)return c;for(r||(r="&"),i||(i="="),a=t.split(r),f=0,h=a.length;h>f;++f)s=a[f].split(i),p="undefined"!=typeof s[1]?o.decode(s[1]):"",u=o.decode(s[0]),e.call(c,u)?n(c[u])?c[u].push(p):c[u]=[c[u],p]:c[u]=p;return c},stringify:function(t,r,n){var o,i,a,s,p,u,f="";if(!t)return f;r||(r="&"),n||(n="=");for(u in t)if(e.call(t,u))for(a=[].concat(t[u]),s=0,p=a.length;p>s;++s)i=typeof a[s],o="object"===i||"undefined"===i?"":encodeURIComponent(a[s]),f+=r+encodeURIComponent(u)+n+o;return f.substr(r.length)}};return t.exports=o,t.exports},"./route":function(){function t(e){if(!(this instanceof t))return new t(e);if("string"==typeof e&&(e={pattern:e}),!e||"object"!=typeof e)throw new Error("You must specify options");if("string"!=typeof e.pattern)throw new Error("You must specify the pattern of the route");this._options=e,this._conditions=e.conditions&&"object"==typeof e.conditions?e.conditions:{},e.isTrailingSlashOptional!==!1&&(e.pattern+=c+f+m+h+l,this._conditions[m]=P),e.pattern+=c+"?"+f+x+h+l,this._conditions[x]=".*",this._parts=this._parsePattern(e.pattern),this._buildParseRegExp(),this._buildBuildFn()}var r={},n=Object.prototype.hasOwnProperty,o=function(t,e){return n.call(t,e)},i=Object.prototype.toString,a=function(t){return"[object Array]"===i.call(t)},s=e("./querystring"),p=function(){var t=["/",".","*","+","?","|","(",")","[","]","{","}","\\"],e=new RegExp("(\\"+t.join("|\\")+")","g");return function(t){return t.replace(e,"\\$1")}}(),u=String(Math.random()).substr(2,5),f="<",h=">",c="(",l=")",d="[a-zA-Z_][\\w\\-]*",_="[\\w\\-\\.~]+",g=new RegExp("("+p(f)+d+p(h)+"|"+"[^"+p(f)+p(h)+"]+"+"|"+p(f)+"|"+p(h)+")","g"),m="ts_"+u,y="/",P=p("/"),x="qs_"+u;return t.prototype._parsePattern=function(t){for(var e,r,n,o=[],i="",a=0,s=0,p=!1,u=t.length;u>a;)if(e=t.charAt(a++),e===c)p?(++s,i+=e):(this._parseParams(i,o),i="",s=0,p=!0);else if(e===l)if(p)if(0===s){for(i={what:"optional",dependOnParams:[],parts:this._parsePattern(i)},o.push(i),r=0,n=i.parts.length;n>r;++r)i.parts[r]&&"param"===i.parts[r].what&&i.dependOnParams.push(i.parts[r].name);i="",p=!1}else--s,i+=e;else i+=e;else i+=e;return this._parseParams(i,o),o},t.prototype._parseParams=function(t,e){var r,n,o,i=t.match(g);if(i)for(r=0,n=i.length;n>r;++r)o=i[r],o.charAt(0)===f&&o.charAt(o.length-1)===h?e.push({what:"param",name:o.substr(1,o.length-2)}):e.push(o)},t.prototype._buildParseRegExp=function(){this._reqExpParamsMap=[],this._parseRegExpSource="^"+this._buildParseRegExpParts(this._parts)+"$",this._parseRegExp=new RegExp(this._parseRegExpSource)},t.prototype._buildParseRegExpParts=function(t){var e,r,n,o="";for(e=0,r=t.length;r>e;++e)n=t[e],"string"==typeof n?o+=p(n):"param"===n.what?(this._reqExpParamsMap.push(n.name),o+="("+(this._getParamValueRegExpSource(n.name)||_)+")"):o+="(?:"+this._buildParseRegExpParts(n.parts)+")?";return o},t.prototype._getParamValueRegExpSource=function(t){var e,r,n=this._conditionRegExpSources||(this._conditionRegExpSources={}),i=this._conditions;return o(n,t)||(o(i,t)?(r=i[t],e=a(r)?"(?:"+r.join("|")+")":r+""):e=null,n[t]=e),n[t]},t.prototype._getParamValueRegExp=function(t){var e,r=this._conditionRegExps||(this._conditionRegExps={});return o(r,t)||(e=this._getParamValueRegExpSource(t),r[t]=e?new RegExp("^"+e+"$"):null),r[t]},t.prototype._checkParamValue=function(t,e){var r=this._getParamValueRegExp(t);return r?r.test(e):!0},t.prototype._buildBuildFn=function(){this._mainParamsMap={},this._buildFnSource="var h=({}).hasOwnProperty;return "+this._buildBuildFnParts(this._parts)+";",this._buildFn=new Function("p",this._buildFnSource)},t.prototype._buildBuildFnParts=function(t){var e,r,n,i,a,s,u='""',f=this._options.defaults;for(e=0,r=t.length;r>e;++e)if(a=t[e],"string"==typeof a)u+='+"'+p(a)+'"';else if("param"===a.what)this._mainParamsMap[a.name]=!0,u+='+(h.call(p,"'+p(a.name)+'")?'+'p["'+p(a.name)+'"]:'+(f&&o(f,a.name)?'"'+p(f[a.name])+'"':'""')+")";else{for(u+="+((false",n=0,i=a.dependOnParams.length;i>n;++n)s=a.dependOnParams[n],u+='||(h.call(p,"'+p(s)+'")'+(f&&o(f,s)?'&&p["'+p(s)+'"]!=="'+p(f[s])+'"':"")+")";u+=")?("+this._buildBuildFnParts(a.parts)+'):"")'}return u},t.prototype._isDataMatched=function(t){var e,r=this._options.data;if("function"==typeof t)return Boolean(t(r));if(t&&"object"==typeof t)for(e in t)if(o(t,e)&&(!r||"object"!=typeof r||r[e]!==t[e]))return!1;return!0},t.prototype.match=function(t,e){var r,n,i,p,u,f,h,c=null,l=this._options,d=l.postMatch,_=l.defaults;if("string"!=typeof t||e&&!this._isDataMatched(e))return c;if(r=t.match(this._parseRegExp)){for(c={},n=1,i=r.length;i>n;++n)if("undefined"!=typeof r[n]&&""!==r[n])if(p=this._reqExpParamsMap[n-1],p===x)h=r[n];else if(p===m){if(t.charAt(t.length-2)===y)return null}else c[p]=r[n];if(h&&l.useQueryString!==!1){f=s.parse(h);for(p in f)if(o(f,p)&&!o(c,p))if(u=f[p],this._mainParamsMap[p]&&a(u)&&(u=u[0]),a(u))for(c[p]=[],n=0,i=u.length;i>n;++n)this._checkParamValue(p,u[n])&&c[p].push(u[n]);else this._checkParamValue(p,u)&&(c[p]=u)}for(p in _)o(_,p)&&!o(c,p)&&(c[p]=_[p])}return c&&"function"==typeof d&&(c=d(c),c&&"object"==typeof c||(c=null)),c},t.prototype.build=function(t){var e,r,n=this._options,i={},a=n.useQueryString!==!1,p={},u=n.preBuild;"function"==typeof u&&(t=u(t));for(r in t)o(t,r)&&null!==t[r]&&"undefined"!=typeof t[r]&&(this._mainParamsMap[r]?i[r]=t[r]:a&&(p[r]=t[r]));return a&&(e=s.stringify(p),e&&(i[x]=e)),this._buildFn(i)},t.prototype.getData=function(){return this._options.data},t.prototype.getName=function(){return this._options.name},r.exports=t,r.exports},"./router":function(){function t(){return this instanceof t?(this._routes=[],this._routesByName={},void 0):new t}var r={},n=e("./route");return t.prototype.addRoute=function(t){var e,r;return e=new n(t),this._routes.push(e),r=e.getName(),r&&(this._routesByName[r]=e),e},t.prototype.find=function(){var t,e,r,n=[],o=this._routes;for(e=0,r=o.length;r>e;++e)t=o[e].match.apply(o[e],arguments),null!==t&&n.push([o[e],t]);return n},t.prototype.findFirst=function(){var t,e,r,n=this._routes;for(e=0,r=n.length;r>e;++e)if(t=n[e].match.apply(n[e],arguments),null!==t)return[n[e],t];return null},t.prototype.getRouteByName=function(t){return this._routesByName[t]||null},t.Route=n,r.exports=t,r.exports}}[t]()}var r=e("./router"),n=!0;t.module&&"object"==typeof module.exports&&(module.exports=r,n=!1),t.modules&&modules.define&&modules.require&&(modules.define("susanin",function(t){t(r)}),n=!1),"function"==typeof t.define&&define.amd&&(define(function(){return r}),n=!1),n&&(t.Susanin=r)}(this); \ No newline at end of file +!function(t){function e(t){return{"./querystring":function(){var t={},e=Object.prototype.hasOwnProperty,r=Object.prototype.toString,n=function(t){return"[object Array]"===r.call(t)},i={decode:function(t){var e;try{e=decodeURIComponent(t.replace(/\+/g,"%20"))}catch(r){e=t}return e},parse:function(t,r,o){var a,s,u,p,f,h,c={};if("string"!=typeof t||""===t)return c;for(r||(r="&"),o||(o="="),a=t.split(r),f=0,h=a.length;h>f;++f)s=a[f].split(o),u="undefined"!=typeof s[1]?i.decode(s[1]):"",p=i.decode(s[0]),e.call(c,p)?n(c[p])?c[p].push(u):c[p]=[c[p],u]:c[p]=u;return c},stringify:function(t,r,n){var i,o,a,s,u,p,f="";if(!t)return f;r||(r="&"),n||(n="=");for(p in t)if(e.call(t,p))for(a=[].concat(t[p]),s=0,u=a.length;u>s;++s)o=typeof a[s],i="object"===o||"undefined"===o?"":encodeURIComponent(a[s]),f+=r+encodeURIComponent(p)+n+i;return f.substr(r.length)}};return t.exports=i,t.exports},"./route":function(){function t(e){if(!(this instanceof t))return new t(e);if("string"==typeof e&&(e={pattern:e}),!e||"object"!=typeof e)throw new Error("You must specify options");if("string"!=typeof e.pattern)throw new Error("You must specify the pattern of the route");this._options=e,this._conditions=e.conditions&&"object"==typeof e.conditions?e.conditions:{},e.isTrailingSlashOptional!==!1&&(e.pattern+=c+f+g+h+l,this._conditions[g]=P),e.pattern+=c+"?"+f+b+h+l,this._conditions[b]=".*",this._paramsMap=[],this._mainParamsMap={},this._requiredParams=[],this._parts=this._parsePattern(e.pattern),this._buildParseRegExp(),this._buildBuildFn()}var r={},n=Object.prototype.hasOwnProperty,i=function(t,e){return n.call(t,e)},o=Object.prototype.toString,a=function(t){return"[object Array]"===o.call(t)},s=e("./querystring"),u=function(){var t=["/",".","*","+","?","|","(",")","[","]","{","}","\\"],e=new RegExp("(\\"+t.join("|\\")+")","g");return function(t){return t.replace(e,"\\$1")}}(),p=String(Math.random()).substr(2,5),f="<",h=">",c="(",l=")",d="[a-zA-Z_][\\w\\-]*",_="[\\w\\-\\.~]+",m=new RegExp("("+u(f)+d+u(h)+"|"+"[^"+u(f)+u(h)+"]+"+"|"+u(f)+"|"+u(h)+")","g"),g="ts_"+p,y="/",P=u("/"),b="qs_"+p;return t.prototype._parsePattern=function(t,e){for(var r,n,i,o=[],a="",s=0,u=0,p=!1,f=t.length;f>s;)if(r=t.charAt(s++),r===c)p?(++u,a+=r):(this._parseParams(a,o,e),a="",u=0,p=!0);else if(r===l)if(p)if(0===u){for(a={what:"optional",dependOnParams:[],parts:this._parsePattern(a,!0)},o.push(a),n=0,i=a.parts.length;i>n;++n)a.parts[n]&&"param"===a.parts[n].what&&a.dependOnParams.push(a.parts[n].name);a="",p=!1}else--u,a+=r;else a+=r;else a+=r;return this._parseParams(a,o,e),o},t.prototype._parseParams=function(t,e,r){var n,i,o,a,s=t.match(m);if(s)for(n=0,i=s.length;i>n;++n)o=s[n],o.charAt(0)===f&&o.charAt(o.length-1)===h?(a=o.substr(1,o.length-2),this._paramsMap.push(a),this._mainParamsMap[a]=!0,r||this._requiredParams.push(a),e.push({what:"param",name:a})):e.push(o)},t.prototype._buildParseRegExp=function(){this._parseRegExpSource="^"+this._buildParseRegExpParts(this._parts)+"$",this._parseRegExp=new RegExp(this._parseRegExpSource)},t.prototype._buildParseRegExpParts=function(t){var e,r,n,i="";for(e=0,r=t.length;r>e;++e)n=t[e],i+="string"==typeof n?u(n):"param"===n.what?"("+(this._getParamValueRegExpSource(n.name)||_)+")":"(?:"+this._buildParseRegExpParts(n.parts)+")?";return i},t.prototype._getParamValueRegExpSource=function(t){var e,r,n=this._conditionRegExpSources||(this._conditionRegExpSources={}),o=this._conditions;return i(n,t)||(i(o,t)?(r=o[t],e=a(r)?"(?:"+r.join("|")+")":r+""):e=null,n[t]=e),n[t]},t.prototype._getParamValueRegExp=function(t){var e,r=this._conditionRegExps||(this._conditionRegExps={});return i(r,t)||(e=this._getParamValueRegExpSource(t),r[t]=e?new RegExp("^"+e+"$"):null),r[t]},t.prototype._checkParamValue=function(t,e){var r=this._getParamValueRegExp(t);return r?r.test(e):!0},t.prototype._buildBuildFn=function(){this._buildFnSource="var h=({}).hasOwnProperty;return "+this._buildBuildFnParts(this._parts)+";",this._buildFn=new Function("p",this._buildFnSource)},t.prototype._buildBuildFnParts=function(t){var e,r,n,o,a,s,p='""',f=this._options.defaults;for(e=0,r=t.length;r>e;++e)if(a=t[e],"string"==typeof a)p+='+"'+u(a)+'"';else if("param"===a.what)this._mainParamsMap[a.name]=!0,p+='+(h.call(p,"'+u(a.name)+'")?'+'p["'+u(a.name)+'"]:'+(f&&i(f,a.name)?'"'+u(f[a.name])+'"':'""')+")";else{for(p+="+((false",n=0,o=a.dependOnParams.length;o>n;++n)s=a.dependOnParams[n],p+='||(h.call(p,"'+u(s)+'")'+(f&&i(f,s)?'&&p["'+u(s)+'"]!=="'+u(f[s])+'"':"")+")";p+=")?("+this._buildBuildFnParts(a.parts)+'):"")'}return p},t.prototype._isDataMatched=function(t){var e,r=this._options.data;if("function"==typeof t)return Boolean(t(r));if(t&&"object"==typeof t)for(e in t)if(i(t,e)&&(!r||"object"!=typeof r||r[e]!==t[e]))return!1;return!0},t.prototype.match=function(t,e){var r,n,o,u,p,f,h,c=null,l=this._options,d=l.postMatch,_=l.defaults;if("string"!=typeof t||e&&!this._isDataMatched(e))return c;if(r=t.match(this._parseRegExp)){for(c={},n=1,o=r.length;o>n;++n)if("undefined"!=typeof r[n]&&""!==r[n])if(u=this._paramsMap[n-1],u===b)h=r[n];else if(u===g){if(t.charAt(t.length-2)===y)return null}else c[u]=r[n];if(h&&l.useQueryString!==!1){f=s.parse(h);for(u in f)if(i(f,u)&&!i(c,u))if(p=f[u],this._mainParamsMap[u]&&a(p)&&(p=p[0]),a(p))for(c[u]=[],n=0,o=p.length;o>n;++n)this._checkParamValue(u,p[n])&&c[u].push(p[n]);else this._checkParamValue(u,p)&&(c[u]=p)}for(u in _)i(_,u)&&!i(c,u)&&(c[u]=_[u])}return c&&"function"==typeof d&&(c=d(c),c&&"object"==typeof c||(c=null)),c},t.prototype.build=function(t,e){var r,n,o,a,u,p=this._options,f={},h=p.useQueryString!==!1,c={},l=p.preBuild;"function"==typeof l&&(t=l(t));for(n in t)if(i(t,n)&&null!==t[n]&&"undefined"!=typeof t[n]&&(this._mainParamsMap[n]||h)){if(o=t[n],e&&!this._checkParamValue(n,o))return null;(this._mainParamsMap[n]?f:c)[n]=o}if(e)for(a=0,u=this._requiredParams.length;u>a;++a)if(!i(f,this._requiredParams[a]))return null;return h&&(r=s.stringify(c),r&&(f[b]=r)),this._buildFn(f)},t.prototype.getData=function(){return this._options.data},t.prototype.getName=function(){return this._options.name},r.exports=t,r.exports},"./router":function(){function t(){return this instanceof t?(this._routes=[],this._routesByName={},void 0):new t}var r={},n=e("./route");return t.prototype.addRoute=function(t){var e,r;return e=new n(t),this._routes.push(e),r=e.getName(),r&&(this._routesByName[r]=e),e},t.prototype.find=function(){var t,e,r,n=[],i=this._routes;for(e=0,r=i.length;r>e;++e)t=i[e].match.apply(i[e],arguments),null!==t&&n.push([i[e],t]);return n},t.prototype.findFirst=function(){var t,e,r,n=this._routes;for(e=0,r=n.length;r>e;++e)if(t=n[e].match.apply(n[e],arguments),null!==t)return[n[e],t];return null},t.prototype.getRouteByName=function(t){return this._routesByName[t]||null},t.Route=n,r.exports=t,r.exports}}[t]()}var r=e("./router"),n=!0;t.module&&"object"==typeof module.exports&&(module.exports=r,n=!1),t.modules&&modules.define&&modules.require&&(modules.define("susanin",function(t){t(r)}),n=!1),"function"==typeof t.define&&define.amd&&(define(function(){return r}),n=!1),n&&(t.Susanin=r)}(this); \ No newline at end of file diff --git a/lib/route.js b/lib/route.js index 41f8a60..8eef506 100644 --- a/lib/route.js +++ b/lib/route.js @@ -166,6 +166,10 @@ function Route(options) { GROUP_CLOSED_CHAR; this._conditions[QUERY_STRING_PARAM_NAME] = '.*'; + this._paramsMap = []; + this._mainParamsMap = {}; + this._requiredParams = []; + /** * @type {Array} * @private @@ -178,10 +182,11 @@ function Route(options) { /** * @param {String} pattern + * @param {Boolean} [isOptional=false] * @returns {Array} * @private */ -Route.prototype._parsePattern = function(pattern) { +Route.prototype._parsePattern = function(pattern, isOptional) { var parts = [], part = '', character, @@ -198,7 +203,7 @@ Route.prototype._parsePattern = function(pattern) { ++countOpened; part += character; } else { - this._parseParams(part, parts); + this._parseParams(part, parts, isOptional); part = ''; countOpened = 0; isFindingClosed = true; @@ -209,7 +214,7 @@ Route.prototype._parsePattern = function(pattern) { part = { what : 'optional', dependOnParams : [], - parts : this._parsePattern(part) + parts : this._parsePattern(part, true) }; parts.push(part); @@ -234,7 +239,7 @@ Route.prototype._parsePattern = function(pattern) { } } - this._parseParams(part, parts); + this._parseParams(part, parts, isOptional); return parts; }; @@ -242,21 +247,28 @@ Route.prototype._parsePattern = function(pattern) { /** * @param {String} pattern * @param {Array} parts + * @param {Boolean} isOptional * @private */ -Route.prototype._parseParams = function(pattern, parts) { +Route.prototype._parseParams = function(pattern, parts, isOptional) { var matches = pattern.match(PARSE_PARAMS_REGEXP), i, size, - part; + part, + paramName; if (matches) { for (i = 0, size = matches.length; i < size; ++i) { part = matches[i]; if (part.charAt(0) === PARAM_OPENED_CHAR && part.charAt(part.length - 1) === PARAM_CLOSED_CHAR) { + paramName = part.substr(1, part.length - 2); + this._paramsMap.push(paramName); + this._mainParamsMap[paramName] = true; + isOptional || this._requiredParams.push(paramName); + parts.push({ what : 'param', - name : part.substr(1, part.length - 2) + name : paramName }); } else { parts.push(part); @@ -269,7 +281,6 @@ Route.prototype._parseParams = function(pattern, parts) { * @private */ Route.prototype._buildParseRegExp = function() { - this._reqExpParamsMap = []; this._parseRegExpSource = '^' + this._buildParseRegExpParts(this._parts) + '$'; this._parseRegExp = new RegExp(this._parseRegExpSource); }; @@ -290,7 +301,6 @@ Route.prototype._buildParseRegExpParts = function(parts) { if (typeof part === 'string') { ret += escape(part); } else if (part.what === 'param') { - this._reqExpParamsMap.push(part.name); ret += '(' + (this._getParamValueRegExpSource(part.name) || PARAM_VALUE_REGEXP_SOURCE) + ')'; } else { ret += '(?:' + this._buildParseRegExpParts(part.parts) + ')?'; @@ -360,7 +370,6 @@ Route.prototype._checkParamValue = function(paramName, paramValue) { * @private */ Route.prototype._buildBuildFn = function() { - this._mainParamsMap = {}; this._buildFnSource = 'var h=({}).hasOwnProperty;return ' + this._buildBuildFnParts(this._parts) + ';'; /*jshint evil:true */ this._buildFn = new Function('p', this._buildFnSource); @@ -464,7 +473,7 @@ Route.prototype.match = function(path, data) { for (i = 1, size = matches.length; i < size; ++i) { if (typeof matches[i] !== 'undefined' && /* for IE lt 9*/ matches[i] !== '') { - paramName = this._reqExpParamsMap[i - 1]; + paramName = this._paramsMap[i - 1]; if (paramName === QUERY_STRING_PARAM_NAME) { queryString = matches[i]; } else if (paramName === TRAILING_SLASH_PARAM_NAME) { @@ -520,31 +529,44 @@ Route.prototype.match = function(path, data) { /** * Build path from params * @param {Object} params - * @returns {String} + * @param {Boolean} [isStrict=false] + * @returns {?String} */ -Route.prototype.build = function(params) { +Route.prototype.build = function(params, isStrict) { var options = this._options, newParams = {}, useQueryString = options.useQueryString !== false, queryParams = {}, queryString, - key, - filter = options.preBuild; + paramName, + paramValue, + filter = options.preBuild, + i, size; if (typeof filter === 'function') { params = filter(params); } - for (key in params) { + for (paramName in params) { if ( - has(params, key) && - params[key] !== null && - typeof params[key] !== 'undefined' + has(params, paramName) && + params[paramName] !== null && + typeof params[paramName] !== 'undefined' && + (this._mainParamsMap[paramName] || useQueryString) ) { - if (this._mainParamsMap[key]) { - newParams[key] = params[key]; - } else if (useQueryString) { - queryParams[key] = params[key]; + paramValue = params[paramName]; + if (isStrict && ! this._checkParamValue(paramName, paramValue)) { + return null; + } + + (this._mainParamsMap[paramName] ? newParams : queryParams)[paramName] = paramValue; + } + } + + if (isStrict) { + for (i = 0, size = this._requiredParams.length; i < size; ++i) { + if ( ! has(newParams, this._requiredParams[i])) { + return null; } } } diff --git a/test/isStrict_option.js b/test/isStrict_option.js new file mode 100644 index 0000000..79cadde --- /dev/null +++ b/test/isStrict_option.js @@ -0,0 +1,48 @@ +/* global describe, it, Router, assert */ + +describe('isStrict option', function() { + var Route = Router.Route, + route1 = Route({ + pattern : '/opa/(/)', + conditions : { + param : '\\d', + param1 : '\\d\\d', + param2 : '\\d\\d\\d' + } + }), + route2 = Route({ + pattern : '/opa/(/)', + useQueryString : false, + conditions : { + param : '\\d', + param1 : '\\d\\d', + param2 : '\\d\\d\\d' + } + }); + + it('isStrict is false by default => validation params is not applicable', function(done) { + assert.deepEqual(route1.build({}), '/opa/'); + assert.deepEqual(route1.build({ param : 'value' }), '/opa/?param=value'); + assert.deepEqual(route1.build({ param1 : 'value1' }), '/opa/value1'); + assert.deepEqual(route1.build({ param1 : 'value1', param2 : 'value2' }), '/opa/value1/value2'); + + done(); + }); + + it('isStrict === true', function(done) { + assert.deepEqual(route1.build({}, true), null); + assert.deepEqual(route1.build({ param1 : 'value1' }, true), null); + assert.deepEqual(route1.build({ param1 : '12' }, true), '/opa/12'); + assert.deepEqual(route1.build({ param2 : 'value2' }, true), null); + assert.deepEqual(route1.build({ param2 : '123' }, true), null); + assert.deepEqual(route1.build({ param1 : '12', param2 : 'value2' }, true), null); + assert.deepEqual(route1.build({ param1 : '12', param2 : '123' }, true), '/opa/12/123'); + assert.deepEqual(route1.build({ param1 : '12', param : '12' }, true), null); + assert.deepEqual(route1.build({ param1 : '12', param : '1' }, true), '/opa/12?param=1'); + assert.deepEqual(route2.build({ param1 : '12', param : '12' }, true), '/opa/12'); + assert.deepEqual(route2.build({ param1 : '12', param : '1' }, true), '/opa/12'); + + done(); + }); + +});