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
-
-
+
+