diff --git a/.travis.yml b/.travis.yml index 80be6389..4251d8a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,8 +28,8 @@ before_install: install: - composer update $COMPOSER_FLAGS --prefer-dist - - npm install google-closure-library + - cd Resources && npm install && cd ../ script: - ./phpunit - - phantomjs Resources/js/run_jsunit.js Resources/js/router_test.html + - cd Resources && npm run test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 78fad5bc..1576229f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,19 +46,31 @@ $ ./phpunit ### JavaScript Test Suite -First, install [PhantomJS](http://phantomjs.org/) and [Google Closure -Library](https://github.com/google/closure-library): +First, install [PhantomJS](http://phantomjs.org/) (see the website for further +details or simply use your favourite package manager) and the development dependencies using: ```bash -$ npm install google-closure-library +$ cd Resources +$ npm install ``` -Run the JS test suite with: +then run the JS test suite with: ```bash -$ phantomjs Resources/js/run_jsunit.js Resources/js/router_test.html +$ npm run test ``` +Because the current test suite runs against the built javascript a build is automatically +run first (see 'Compiling the JavaScript files' below for further details). You can +explicitly run only the test suite with: + +```bash +$ phantomjs js/run_jsunit.js js/router_test.html +``` + +Alternatively you can open `Resources/js/router_test.html` in your browser which +runs the same test suite with a graphical output. + Compiling the JavaScript files ------------------------------ @@ -67,19 +79,23 @@ Compiling the JavaScript files > We already provide a compiled version of the JavaScript; this section is only > relevant if you want to make changes to this script. -In order to re-compile the JavaScript source files that we ship with this -bundle, you need the Google Closure Tools. You need the -[plovr](http://plovr.com/download.html) tool, which is a Java ARchive, so you -also need a working Java environment. You can re-compile the JavaScript with the -following command: +This project is using [Gulp](https://gulpjs.com/) to compile JavaScript files. +In order to use Gulp you must install both [node](https://nodejs.org/en/) and +[npm](https://www.npmjs.com/). + +If you are not familiar with using Gulp, it is recommended that you review this +[An Introduction to Gulp.js](https://www.sitepoint.com/introduction-gulp-js/) +tutorial which will guide you through the process of getting node and npm installed. + +Once you have node and npm installed: ```bash -$ java -jar plovr.jar build Resources/config/plovr/compile.js +$ cd Resources +$ npm install ``` -Alternatively, you can use the JMSGoogleClosureBundle. If you install this -bundle, you can re-compile the JavaScript with the following command: +Then to perform a build ```bash -$ php app/console plovr:build @FOSJsRoutingBundle/compile.js +$ npm run build ``` diff --git a/Resources/doc/usage.rst b/Resources/doc/usage.rst index 3b94784d..492bce20 100644 --- a/Resources/doc/usage.rst +++ b/Resources/doc/usage.rst @@ -1,13 +1,13 @@ Usage ===== -Add these two lines in your layout: +In applications not using webpack add these two lines in your layout: .. configuration-block:: .. code-block:: html+twig - + .. code-block:: html+php @@ -17,9 +17,28 @@ Add these two lines in your layout: .. note:: - If you are not using Twig, then it is no problem. What you need is to add + If you are not using Twig, then it is no problem. What you need is the two JavaScript files above loaded at some point in your web page. + +If you are using webpack and Encore to package your assets you will need to use the dump command +and export your routes to json: + +.. code-block:: bash + + bin/console fos:js-routing:dump --format=json + +Then within your JavaScript development you can use: + +.. code-block:: javascript + + const routes = require('../../web/js/fos_js_routes.json'); + import Routing from '../../vendor/friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js'; + + Routing.setRoutingData(routes); + Routing.generate('rep_log_list'); + + Generating URIs --------------- diff --git a/Resources/gulpfile.js b/Resources/gulpfile.js new file mode 100755 index 00000000..22e47e62 --- /dev/null +++ b/Resources/gulpfile.js @@ -0,0 +1,22 @@ +const gulp = require('gulp'); +const babel = require('gulp-babel'); +const rename = require('gulp-rename'); +const uglify = require('gulp-uglify'); +const wrap = require('gulp-wrap'); + +gulp.task('js', function() { + return gulp.src('js/router.js') + .pipe(babel({ + presets: ["es2015"], + plugins: ["transform-object-assign"] + })) + .pipe(wrap({ src: 'js/router.template.js' })) + .pipe(gulp.dest('public/js')) + .pipe(rename({ extname: '.min.js' })) + .pipe(uglify()) + .pipe(gulp.dest('public/js')); +}); + +gulp.task('default', function() { + return gulp.start(['js']); +}); diff --git a/Resources/js/export.js b/Resources/js/export.js deleted file mode 100644 index 66eb01cc..00000000 --- a/Resources/js/export.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @fileoverview This file is the entry point for the compiler. - * - * You can compile this script by running (assuming you have JMSGoogleClosureBundle installed): - * - * php app/console plovr:build @FOSJsRoutingBundle/compile.js - */ - -goog.require('fos.Router'); - -goog.exportSymbol('fos.Router', fos.Router); -goog.exportSymbol('fos.Router.setData', function(data) { - var router = fos.Router.getInstance(); - router.setBaseUrl(/** @type {string} */ (data['base_url'])); - router.setRoutes(/** @type {Object.} */ (data['routes'])); - if ('prefix' in data) { - router.setPrefix(/** @type {string} */ (data['prefix'])); - } - router.setHost(/** @type {string} */ (data['host'])); - router.setScheme(/** @type {string} */ (data['scheme'])); -}); -goog.exportProperty(fos.Router, 'getInstance', fos.Router.getInstance); -goog.exportProperty(fos.Router.prototype, 'setRoutes', fos.Router.prototype.setRoutes); -goog.exportProperty(fos.Router.prototype, 'getRoutes', fos.Router.prototype.getRoutes); -goog.exportProperty(fos.Router.prototype, 'setBaseUrl', fos.Router.prototype.setBaseUrl); -goog.exportProperty(fos.Router.prototype, 'getBaseUrl', fos.Router.prototype.getBaseUrl); -goog.exportProperty(fos.Router.prototype, 'generate', fos.Router.prototype.generate); -goog.exportProperty(fos.Router.prototype, 'setPrefix', fos.Router.prototype.setPrefix); -goog.exportProperty(fos.Router.prototype, 'getRoute', fos.Router.prototype.getRoute); - -window['Routing'] = fos.Router.getInstance(); diff --git a/Resources/js/externs.js b/Resources/js/externs.js deleted file mode 100644 index ffa0f650..00000000 --- a/Resources/js/externs.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @fileoverview This file contains some properties which we don't - * want the compiler to rename. - */ -var externs = { - tokens: '', - defaults: '', - requirements: '', - hosttokens: '', - schemes: '', - methods: '' -}; diff --git a/Resources/js/router.js b/Resources/js/router.js index 6ad1f82a..4e594bb8 100644 --- a/Resources/js/router.js +++ b/Resources/js/router.js @@ -1,187 +1,207 @@ -goog.provide('fos.Router'); - -goog.require('goog.structs.Map'); -goog.require('goog.array'); -goog.require('goog.object'); -goog.require('goog.uri.utils'); +'use strict'; /** - * @constructor - * @param {fos.Router.Context=} opt_context - * @param {Object.=} opt_routes + * @fileoverview This file defines the Router class. + * + * You can compile this file by running the following command from the Resources folder: + * + * npm install && npm run build */ -fos.Router = function(opt_context, opt_routes) { - this.context_ = opt_context || {base_url: '', prefix: '', host: '', scheme: ''}; - this.setRoutes(opt_routes || {}); -}; -goog.addSingletonGetter(fos.Router); /** - * @typedef {{ - * tokens: (Array.>), - * defaults: (Object.), - * requirements: Object, - * hosttokens: (Array.), - * schemes: (Array.), - * methods: (Array.) - * }} + * Class Router */ -fos.Router.Route; +class Router { + + /** + * @constructor + * @param {Router.Context=} context + * @param {Object.=} routes + */ + constructor(context, routes) { + this.context_ = context || {base_url: '', prefix: '', host: '', scheme: ''}; + this.setRoutes(routes || {}); + } -/** - * @typedef {{ - * base_url: (string) - * }} - */ -fos.Router.Context; + /** + * Returns the current instance. + * @returns {Router} + */ + static getInstance() { + return Routing; + } -/** - * @param {Object.} routes - */ -fos.Router.prototype.setRoutes = function(routes) { - this.routes_ = new goog.structs.Map(routes); -}; + /** + * Configures the current Router instance with the provided data. + * @param {Object} data + */ + static setData(data) { + let router = Router.getInstance(); -/** - * @return {Object.} routes - */ -fos.Router.prototype.getRoutes = function() { - return this.routes_; -}; + router.setRoutingData(data); + } -/** - * @param {string} baseUrl - */ -fos.Router.prototype.setBaseUrl = function(baseUrl) { - this.context_.base_url = baseUrl; -}; + /** + * Sets data for the current instance + * @param {Object} data + */ + setRoutingData(data) { + this.setBaseUrl(data['base_url']); + this.setRoutes(data['routes']); -/** - * @return {string} - */ -fos.Router.prototype.getBaseUrl = function() { - return this.context_.base_url; -}; + if ('prefix' in data) { + this.setPrefix(data['prefix']); + } -/** - * @param {string} prefix - */ -fos.Router.prototype.setPrefix = function(prefix) { - this.context_.prefix = prefix; -}; + this.setHost(data['host']); + this.setScheme(data['scheme']); + } -/** - * @param {string} scheme - */ -fos.Router.prototype.setScheme = function(scheme) { - this.context_.scheme = scheme; -}; + /** + * @param {Object.} routes + */ + setRoutes(routes) { + this.routes_ = Object.freeze(routes); + } -/** - * @return {string} - */ -fos.Router.prototype.getScheme = function() { - return this.context_.scheme; -}; + /** + * @return {Object.} routes + */ + getRoutes() { + return this.routes_; + } -/** - * @param {string} host - */ -fos.Router.prototype.setHost = function(host) { - this.context_.host = host; -}; + /** + * @param {string} baseUrl + */ + setBaseUrl(baseUrl) { + this.context_.base_url = baseUrl; + } -/** - * @return {string} - */ -fos.Router.prototype.getHost = function() { - return this.context_.host; -}; + /** + * @return {string} + */ + getBaseUrl() { + return this.context_.base_url; + } + /** + * @param {string} prefix + */ + setPrefix(prefix) { + this.context_.prefix = prefix; + } -/** - * Builds query string params added to a URL. - * Port of jQuery's $.param() function, so credit is due there. - * - * @param {string} prefix - * @param {Array|Object|string} params - * @param {Function} add - */ -fos.Router.prototype.buildQueryParams = function(prefix, params, add) { - var self = this; - var name; - var rbracket = new RegExp(/\[\]$/); - - if (params instanceof Array) { - goog.array.forEach(params, function(val, i) { - if (rbracket.test(prefix)) { - add(prefix, val); - } else { - self.buildQueryParams(prefix + '[' + (typeof val === 'object' ? i : '') + ']', val, add); + /** + * @param {string} scheme + */ + setScheme(scheme) { + this.context_.scheme = scheme; + } + + /** + * @return {string} + */ + getScheme() { + return this.context_.scheme; + } + + /** + * @param {string} host + */ + setHost(host) { + this.context_.host = host; + } + + /** + * @return {string} + */ + getHost() { + return this.context_.host; + } + + /** + * Builds query string params added to a URL. + * Port of jQuery's $.param() function, so credit is due there. + * + * @param {string} prefix + * @param {Array|Object|string} params + * @param {Function} add + */ + buildQueryParams(prefix, params, add) { + let name; + let rbracket = new RegExp(/\[\]$/); + + if (params instanceof Array) { + params.forEach((val, i) => { + if (rbracket.test(prefix)) { + add(prefix, val); + } else { + this.buildQueryParams(prefix + '[' + (typeof val === 'object' ? i : '') + ']', val, add); + } + }); + } else if (typeof params === 'object') { + for (name in params) { + this.buildQueryParams(prefix + '[' + name + ']', params[name], add); } - }); - } else if (typeof params === 'object') { - for (name in params) { - this.buildQueryParams(prefix + '[' + name + ']', params[name], add); + } else { + add(prefix, params); } - } else { - add(prefix, params); } -}; -/** - * Returns a raw route object. - * - * @param {string} name - * @return {fos.Router.Route} - */ -fos.Router.prototype.getRoute = function(name) { - var prefixedName = this.context_.prefix + name; - if (!this.routes_.containsKey(prefixedName)) { - // Check first for default route before failing - if (!this.routes_.containsKey(name)) { - throw new Error('The route "' + name + '" does not exist.'); + /** + * Returns a raw route object. + * + * @param {string} name + * @return {Router.Route} + */ + getRoute(name) { + let prefixedName = this.context_.prefix + name; + + if (!(prefixedName in this.routes_)) { + // Check first for default route before failing + if (!(name in this.routes_)) { + throw new Error('The route "' + name + '" does not exist.'); + } + } else { + name = prefixedName; } - } else { - name = prefixedName; - } - return (this.routes_.get(name)); -}; + return this.routes_[name]; + } + /** + * Generates the URL for a route. + * + * @param {string} name + * @param {Object.} opt_params + * @param {boolean} absolute + * @return {string} + */ + generate(name, opt_params, absolute) { + let route = (this.getRoute(name)), + params = opt_params || {}, + unusedParams = Object.assign({}, params), + url = '', + optional = true, + host = ''; + + route.tokens.forEach((token) => { + if ('text' === token[0]) { + url = token[1] + url; + optional = false; -/** - * Generates the URL for a route. - * - * @param {string} name - * @param {Object.} opt_params - * @param {boolean} absolute - * @return {string} - */ -fos.Router.prototype.generate = function(name, opt_params, absolute) { - var route = (this.getRoute(name)), - params = opt_params || {}, - unusedParams = goog.object.clone(params), - url = '', - optional = true, - host = ''; - - goog.array.forEach(route.tokens, function(token) { - if ('text' === token[0]) { - url = token[1] + url; - optional = false; - - return; - } + return; + } - if ('variable' === token[0]) { - var hasDefault = goog.object.containsKey(route.defaults, token[3]); - if (false === optional || !hasDefault || (goog.object.containsKey(params, token[3]) && params[token[3]] != route.defaults[token[3]])) { - var value; + if ('variable' === token[0]) { + let hasDefault = route.defaults && (token[3] in route.defaults); + if (false === optional || !hasDefault || ((token[3] in params) && params[token[3]] != route.defaults[token[3]])) { + let value; - if (goog.object.containsKey(params, token[3])) { + if (token[3] in params) { value = params[token[3]]; - goog.object.remove(unusedParams, token[3]); + delete unusedParams[token[3]]; } else if (hasDefault) { value = route.defaults[token[3]]; } else if (optional) { @@ -190,10 +210,10 @@ fos.Router.prototype.generate = function(name, opt_params, absolute) { throw new Error('The route "' + name + '" requires the parameter "' + token[3] + '".'); } - var empty = true === value || false === value || '' === value; + let empty = true === value || false === value || '' === value; if (!empty || !optional) { - var encodedValue = encodeURIComponent(value).replace(/%2F/g, '/'); + let encodedValue = encodeURIComponent(value).replace(/%2F/g, '/'); if ('null' === encodedValue && null === value) { encodedValue = ''; @@ -203,71 +223,97 @@ fos.Router.prototype.generate = function(name, opt_params, absolute) { } optional = false; - } else if (hasDefault) { - goog.object.remove(unusedParams, token[3]); + } else if (hasDefault && (token[3] in unusedParams)) { + delete unusedParams[token[3]]; } return; - } - - throw new Error('The token type "' + token[0] + '" is not supported.'); - }); + } - if (url === '') { - url = '/'; - } + throw new Error('The token type "' + token[0] + '" is not supported.'); + }); - goog.array.forEach(route.hosttokens, function (token) { - var value; + if (url === '') { + url = '/'; + } - if ('text' === token[0]) { - host = token[1] + host; + route.hosttokens.forEach((token) => { + let value; - return; - } + if ('text' === token[0]) { + host = token[1] + host; - if ('variable' === token[0]) { - if (goog.object.containsKey(params, token[3])) { - value = params[token[3]]; - goog.object.remove(unusedParams, token[3]); - } else if (goog.object.containsKey(route.defaults, token[3])) { - value = route.defaults[token[3]]; + return; } - host = token[1] + value + host; + if ('variable' === token[0]) { + if (token[3] in params) { + value = params[token[3]]; + delete unusedParams[token[3]]; + } else if (route.defaults && (token[3] in route.defaults)) { + value = route.defaults[token[3]]; + } + + host = token[1] + value + host; + } + }); + // Foo-bar! + url = this.context_.base_url + url; + if (route.requirements && ("_scheme" in route.requirements) && this.getScheme() != route.requirements["_scheme"]) { + url = route.requirements["_scheme"] + "://" + (host || this.getHost()) + url; + } else if ("undefined" !== typeof route.schemes && "undefined" !== typeof route.schemes[0] && this.getScheme() !== route.schemes[0]) { + url = route.schemes[0] + "://" + (host || this.getHost()) + url; + } else if (host && this.getHost() !== host) { + url = this.getScheme() + "://" + host + url; + } else if (absolute === true) { + url = this.getScheme() + "://" + this.getHost() + url; } - }); - - url = this.context_.base_url + url; - if (goog.object.containsKey(route.requirements, "_scheme") && this.getScheme() != route.requirements["_scheme"]) { - url = route.requirements["_scheme"] + "://" + (host || this.getHost()) + url; - } else if (goog.object.containsKey(route, "schemes") && typeof route.schemes[0] !== "undefined" && this.getScheme() != route.schemes[0]) { - url = route.schemes[0] + "://" + (host || this.getHost()) + url; - } else if (host && this.getHost() !== host) { - url = this.getScheme() + "://" + host + url; - } else if (absolute === true) { - url = this.getScheme() + "://" + this.getHost() + url; - } - if (goog.object.getCount(unusedParams) > 0) { - var prefix; - var queryParams = []; - var add = function(key, value) { - // if value is a function then call it and assign it's return value as value - value = (typeof value === 'function') ? value() : value; + if (Object.keys(unusedParams).length > 0) { + let prefix; + let queryParams = []; + let add = (key, value) => { + // if value is a function then call it and assign it's return value as value + value = (typeof value === 'function') ? value() : value; + + // change null to empty string + value = (value === null) ? '' : value; - // change null to empty string - value = (value === null) ? '' : value; + queryParams.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); + }; - queryParams.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); - }; + for (prefix in unusedParams) { + this.buildQueryParams(prefix, unusedParams[prefix], add); + } - for (prefix in unusedParams) { - this.buildQueryParams(prefix, unusedParams[prefix], add); + url = url + '?' + queryParams.join('&').replace(/%20/g, '+'); } - url = url + '?' + queryParams.join('&').replace(/%20/g, '+'); + return url; } - return url; -}; +} + +/** + * @typedef {{ + * tokens: (Array.>), + * defaults: (Object.), + * requirements: Object, + * hosttokens: (Array.) + * }} + */ +Router.Route; + +/** + * @typedef {{ + * base_url: (string) + * }} + */ +Router.Context; + +/** + * Router singleton. + * @const + * @type {Router} + */ +const Routing = new Router(); diff --git a/Resources/js/router.template.js b/Resources/js/router.template.js new file mode 100644 index 00000000..884cbc38 --- /dev/null +++ b/Resources/js/router.template.js @@ -0,0 +1,22 @@ +(function (root, factory) { + var routing = factory(); + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], routing.Routing); + } else if (typeof module === 'object' && module.exports) { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = routing.Routing; + } else { + // Browser globals (root is window) + root.Routing = routing.Routing; + root.fos = { + Router: routing.Router, + }; + } +}(this, function () { + <%= contents %> + + return { Router: Router, Routing: Routing }; +})); diff --git a/Resources/js/router.test.js b/Resources/js/router.test.js index 7d0127ef..611b09b7 100644 --- a/Resources/js/router.test.js +++ b/Resources/js/router.test.js @@ -1,4 +1,5 @@ goog.require('goog.testing.jsunit'); +goog.require('goog.structs.Map'); function testGenerate() { var router = new fos.Router({base_url: ''}, { @@ -386,7 +387,7 @@ function testGetRoutes() { blog: 'test' }); - assertObjectEquals(expected, router.getRoutes()); + assertObjectEquals(expected.toObject(), router.getRoutes()); } function testGenerateWithNullValue() { diff --git a/Resources/js/router_test.html b/Resources/js/router_test.html index ff30ecdf..bc4a6e65 100644 --- a/Resources/js/router_test.html +++ b/Resources/js/router_test.html @@ -5,8 +5,8 @@ Router Test - - + + diff --git a/Resources/package.json b/Resources/package.json new file mode 100755 index 00000000..b3355a42 --- /dev/null +++ b/Resources/package.json @@ -0,0 +1,47 @@ +{ + "name": "fos-router", + "version": "2.2.0", + "description": "A pretty nice way to use the routes generated by the FOSJsRoutingBundle in your JavaScript.", + "keywords": [ + "router", + "symfony" + ], + "license": "MIT", + "author": { + "name": "FriendsOfSymfony Community", + "url": "https://github.com/friendsofsymfony/FOSJsRoutingBundle/contributors" + }, + "contributors": [ + { + "name": "William Durand", + "email": "will+git@drnd.me" + }, + { + "name": "Bruno Sampaio", + "email": "bens.sampaio@gmail.com" + } + ], + "files": [ + "public/js/router.js", + "public/js/router.min.js", + "ts/router.d.ts" + ], + "main": "public/js/router.js", + "devDependencies": { + "babel-plugin-transform-object-assign": "^6.22.0", + "babel-polyfill": "^6.9.1", + "babel-preset-es2015": "^6.9.0", + "babel-register": "^6.11.6", + "gulp": "^3.9.1", + "gulp-babel": "^6.1.2", + "gulp-rename": "^1.2.2", + "gulp-uglify": "^1.5.4", + "gulp-wrap": "^0.13.0", + "jasmine": "^2.4.1", + "google-closure-library": "^20171203.0.0" + }, + "scripts": { + "build": "gulp", + "test": "npm run build && phantomjs js/run_jsunit.js js/router_test.html" + } +} diff --git a/Resources/public/js/router.js b/Resources/public/js/router.js index d8c46762..2b5240ad 100644 --- a/Resources/public/js/router.js +++ b/Resources/public/js/router.js @@ -1,12 +1,403 @@ +(function (root, factory) { + var routing = factory(); + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], routing.Routing); + } else if (typeof module === 'object' && module.exports) { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = routing.Routing; + } else { + // Browser globals (root is window) + root.Routing = routing.Routing; + root.fos = { + Router: routing.Router, + }; + } +}(this, function () { + 'use strict'; + /** - * Portions of this code are from the Google Closure Library, - * received from the Closure Authors under the Apache 2.0 license. + * @fileoverview This file defines the Router class. * - * All other code is (C) FriendsOfSymfony and subject to the MIT license. - */ -(function() {var f,l=this;function m(a,c){var b=a.split("."),d=l;b[0]in d||!d.execScript||d.execScript("var "+b[0]);for(var e;b.length&&(e=b.shift());)b.length||void 0===c?d=d[e]?d[e]:d[e]={}:d[e]=c};var n=Array.prototype,p=n.forEach?function(a,c,b){n.forEach.call(a,c,b)}:function(a,c,b){for(var d=a.length,e="string"==typeof a?a.split(""):a,g=0;g=} routes + */ + function Router(context, routes) { + _classCallCheck(this, Router); + + this.context_ = context || { base_url: '', prefix: '', host: '', scheme: '' }; + this.setRoutes(routes || {}); + } + + /** + * Returns the current instance. + * @returns {Router} + */ + + + _createClass(Router, [{ + key: 'setRoutingData', + + + /** + * Sets data for the current instance + * @param {Object} data + */ + value: function setRoutingData(data) { + this.setBaseUrl(data['base_url']); + this.setRoutes(data['routes']); + + if ('prefix' in data) { + this.setPrefix(data['prefix']); + } + + this.setHost(data['host']); + this.setScheme(data['scheme']); + } + + /** + * @param {Object.} routes + */ + + }, { + key: 'setRoutes', + value: function setRoutes(routes) { + this.routes_ = Object.freeze(routes); + } + + /** + * @return {Object.} routes + */ + + }, { + key: 'getRoutes', + value: function getRoutes() { + return this.routes_; + } + + /** + * @param {string} baseUrl + */ + + }, { + key: 'setBaseUrl', + value: function setBaseUrl(baseUrl) { + this.context_.base_url = baseUrl; + } + + /** + * @return {string} + */ + + }, { + key: 'getBaseUrl', + value: function getBaseUrl() { + return this.context_.base_url; + } + + /** + * @param {string} prefix + */ + + }, { + key: 'setPrefix', + value: function setPrefix(prefix) { + this.context_.prefix = prefix; + } + + /** + * @param {string} scheme + */ + + }, { + key: 'setScheme', + value: function setScheme(scheme) { + this.context_.scheme = scheme; + } + + /** + * @return {string} + */ + + }, { + key: 'getScheme', + value: function getScheme() { + return this.context_.scheme; + } + + /** + * @param {string} host + */ + + }, { + key: 'setHost', + value: function setHost(host) { + this.context_.host = host; + } + + /** + * @return {string} + */ + + }, { + key: 'getHost', + value: function getHost() { + return this.context_.host; + } + + /** + * Builds query string params added to a URL. + * Port of jQuery's $.param() function, so credit is due there. + * + * @param {string} prefix + * @param {Array|Object|string} params + * @param {Function} add + */ + + }, { + key: 'buildQueryParams', + value: function buildQueryParams(prefix, params, add) { + var _this = this; + + var name = void 0; + var rbracket = new RegExp(/\[\]$/); + + if (params instanceof Array) { + params.forEach(function (val, i) { + if (rbracket.test(prefix)) { + add(prefix, val); + } else { + _this.buildQueryParams(prefix + '[' + ((typeof val === 'undefined' ? 'undefined' : _typeof(val)) === 'object' ? i : '') + ']', val, add); + } + }); + } else if ((typeof params === 'undefined' ? 'undefined' : _typeof(params)) === 'object') { + for (name in params) { + this.buildQueryParams(prefix + '[' + name + ']', params[name], add); + } + } else { + add(prefix, params); + } + } + + /** + * Returns a raw route object. + * + * @param {string} name + * @return {Router.Route} + */ + + }, { + key: 'getRoute', + value: function getRoute(name) { + var prefixedName = this.context_.prefix + name; + + if (!(prefixedName in this.routes_)) { + // Check first for default route before failing + if (!(name in this.routes_)) { + throw new Error('The route "' + name + '" does not exist.'); + } + } else { + name = prefixedName; + } + + return this.routes_[name]; + } + + /** + * Generates the URL for a route. + * + * @param {string} name + * @param {Object.} opt_params + * @param {boolean} absolute + * @return {string} + */ + + }, { + key: 'generate', + value: function generate(name, opt_params, absolute) { + var route = this.getRoute(name), + params = opt_params || {}, + unusedParams = _extends({}, params), + url = '', + optional = true, + host = ''; + + route.tokens.forEach(function (token) { + if ('text' === token[0]) { + url = token[1] + url; + optional = false; + + return; + } + + if ('variable' === token[0]) { + var hasDefault = route.defaults && token[3] in route.defaults; + if (false === optional || !hasDefault || token[3] in params && params[token[3]] != route.defaults[token[3]]) { + var value = void 0; + + if (token[3] in params) { + value = params[token[3]]; + delete unusedParams[token[3]]; + } else if (hasDefault) { + value = route.defaults[token[3]]; + } else if (optional) { + return; + } else { + throw new Error('The route "' + name + '" requires the parameter "' + token[3] + '".'); + } + + var empty = true === value || false === value || '' === value; + + if (!empty || !optional) { + var encodedValue = encodeURIComponent(value).replace(/%2F/g, '/'); + + if ('null' === encodedValue && null === value) { + encodedValue = ''; + } + + url = token[1] + encodedValue + url; + } + + optional = false; + } else if (hasDefault && token[3] in unusedParams) { + delete unusedParams[token[3]]; + } + + return; + } + + throw new Error('The token type "' + token[0] + '" is not supported.'); + }); + + if (url === '') { + url = '/'; + } + + route.hosttokens.forEach(function (token) { + var value = void 0; + + if ('text' === token[0]) { + host = token[1] + host; + + return; + } + + if ('variable' === token[0]) { + if (token[3] in params) { + value = params[token[3]]; + delete unusedParams[token[3]]; + } else if (route.defaults && token[3] in route.defaults) { + value = route.defaults[token[3]]; + } + + host = token[1] + value + host; + } + }); + // Foo-bar! + url = this.context_.base_url + url; + if (route.requirements && "_scheme" in route.requirements && this.getScheme() != route.requirements["_scheme"]) { + url = route.requirements["_scheme"] + "://" + (host || this.getHost()) + url; + } else if ("undefined" !== typeof route.schemes && "undefined" !== typeof route.schemes[0] && this.getScheme() !== route.schemes[0]) { + url = route.schemes[0] + "://" + (host || this.getHost()) + url; + } else if (host && this.getHost() !== host) { + url = this.getScheme() + "://" + host + url; + } else if (absolute === true) { + url = this.getScheme() + "://" + this.getHost() + url; + } + + if (Object.keys(unusedParams).length > 0) { + var prefix = void 0; + var queryParams = []; + var add = function add(key, value) { + // if value is a function then call it and assign it's return value as value + value = typeof value === 'function' ? value() : value; + + // change null to empty string + value = value === null ? '' : value; + + queryParams.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); + }; + + for (prefix in unusedParams) { + this.buildQueryParams(prefix, unusedParams[prefix], add); + } + + url = url + '?' + queryParams.join('&').replace(/%20/g, '+'); + } + + return url; + } + }], [{ + key: 'getInstance', + value: function getInstance() { + return Routing; + } + + /** + * Configures the current Router instance with the provided data. + * @param {Object} data + */ + + }, { + key: 'setData', + value: function setData(data) { + var router = Router.getInstance(); + + router.setRoutingData(data); + } + }]); + + return Router; +}(); + +/** + * @typedef {{ + * tokens: (Array.>), + * defaults: (Object.), + * requirements: Object, + * hosttokens: (Array.) + * }} + */ + + +Router.Route; + +/** + * @typedef {{ + * base_url: (string) + * }} + */ +Router.Context; + +/** + * Router singleton. + * @const + * @type {Router} + */ +var Routing = new Router(); + + return { Router: Router, Routing: Routing }; +})); \ No newline at end of file diff --git a/Resources/public/js/router.min.js b/Resources/public/js/router.min.js new file mode 100644 index 00000000..77ceaea9 --- /dev/null +++ b/Resources/public/js/router.min.js @@ -0,0 +1 @@ +!function(e,t){var n=t();"function"==typeof define&&define.amd?define([],n.Routing):"object"==typeof module&&module.exports?module.exports=n.Routing:(e.Routing=n.Routing,e.fos={Router:n.Router})}(this,function(){"use strict";function e(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var t=Object.assign||function(e){for(var t=1;t0){var c=void 0,l=[],h=function(e,t){t="function"==typeof t?t():t,t=null===t?"":t,l.push(encodeURIComponent(e)+"="+encodeURIComponent(t))};for(c in s)this.buildQueryParams(c,s[c],h);u=u+"?"+l.join("&").replace(/%20/g,"+")}return u}}],[{key:"getInstance",value:function(){return r}},{key:"setData",value:function(e){var t=i.getInstance();t.setRoutingData(e)}}]),i}();i.Route,i.Context;var r=new i;return{Router:i,Routing:r}}); \ No newline at end of file