diff --git a/README.md b/README.md index 82688f6..926cd8a 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,14 @@ ![frontexpress](http://fontmeme.com/embed.php?text=frontexpress&name=Atype%201%20Light.ttf&size=90&style_color=6F6F75) +Code the front-end like on the back-end with [ExpressJS](http://expressjs.com/) + +[frontexpress demo](https://github.com/camelaissani/frontexpress-demo) repository. + [![Build Status](https://travis-ci.org/camelaissani/frontexpress.svg?branch=master)](https://travis-ci.org/camelaissani/frontexpress) [![Code Climate](https://codeclimate.com/github/camelaissani/frontexpress/badges/gpa.svg)](https://codeclimate.com/github/camelaissani/frontexpress) [![Coverage Status](https://coveralls.io/repos/github/camelaissani/frontexpress/badge.svg?branch=master)](https://coveralls.io/github/camelaissani/frontexpress?branch=master) ![dependencies](https://img.shields.io/gemnasium/mathiasbynens/he.svg) - ![Size Shield](https://img.shields.io/badge/size-2.86kb-brightgreen.svg) - -Frontexpress manages routes in browser like [ExpressJS](http://expressjs.com/) does on Node. - -Same language same API on all the stack. - -Code the front-end logic with the same style than on the back-end with express - -```js -import frontexpress from 'frontexpress'; - -// Front-end application -const app = frontexpress(); - -// front-end logic on navigation path "/page1" -app.get('/page1', (req, res) => { - document.querySelector('.content').innerHTML = `

Page 1 content

`; -}); - -// front-end logic on navigation path "/page2" -app.get('/page2', (req, res) => { - document.querySelector('.content').innerHTML = `

Page 2 content

`; -}); - -// start front-end application -app.listen(() => { - // on DOM ready -}); -``` + ![Size Shield](https://img.shields.io/badge/size-3.26kb-brightgreen.svg) ## Installation @@ -52,43 +28,31 @@ $ bower install frontexpress On [jsDelivr](https://cdn.jsdelivr.net/npm/frontexpress@1.1.0/frontexpress.min.js) -## Quick Start +## Usage - The quickest way to get started with frontexpress is to clone the [frontexpress-demo](https://github.com/camelaissani/frontexpress-demo) repository. +```js +import frontexpress from 'frontexpress'; -## Tests +// Front-end application +const app = frontexpress(); - Clone the git repository: +// front-end logic on navigation path "/page1" +app.get('/page1', (req, res) => { + document.querySelector('.content').innerHTML = res.responseText; +}); -```bash -$ git clone git@github.com:camelaissani/frontexpress.git -$ cd frontexpress +// front-end logic on navigation path "/page2" +app.get('/page2', (req, res) => { + document.querySelector('.content').innerHTML = res.responseText; +}); + +// start front-end application +app.listen(); ``` - Install the dependencies and run the test suite: +### Routes -```bash -$ npm install -$ npm test -``` - - -## Navigation path and frontexpress routing - -### Disclaimer - -> -> In this first version of frontexpress, the API is not completely the mirror of the expressjs one. -> -> There are some missing methods. Currently, the use, get, post... methods having a middleware array as parameter are not available. -> The string pattern to define route paths is not yet implemented. -> -> Obviously, the objective is to have the same API as expressjs when the methods make sense browser side. -> - -### Basic routing - -Listen navigation (GET request) on path /hello: +Listen GET requests on path /hello: ```js app.get('/hello', (req, res) => { @@ -96,7 +60,7 @@ app.get('/hello', (req, res) => { }); ``` -Listen a POST request on path /item: +Listen POST requests on path /item: ```js app.post('/item', (req, res) => { @@ -104,9 +68,7 @@ app.post('/item', (req, res) => { }); ``` -### Routing based on RegExp - -Listen navigation on paths which start with /api/: +Listen GET requests on path starting with /api/: ```js app.get(/^api\//, (req, res) => { @@ -114,6 +76,66 @@ app.get(/^api\//, (req, res) => { }); ``` +Get parameters from path + +```js +app.get('/product/:id', (req, res) => { + // if we have /product/42 then + // req.params.id = 42 +}); +``` + +```js +app.get('/user/:firstname?/:lastname', (req, res) => { + // if we have /user/camel/aissani then + // req.params.firstname = 'camel' + // req.params.lastname = 'aissani' + + // if we have /user/aissani then + // req.params.firstname = undefined + // req.params.lastname = 'aissani' +}); +``` + +```js +app.get('/user/:id', (req, res) => { + // if we have /user/1,2,3 then + // req.params.id = [1,2,3] +}); +``` +### Middleware object + +The middleware object gives access to more hooks + +```js + class MyMiddleware = new Middleware { + entered(req) { + // before request sent + } + + updated(req, res) { + // after request sent + // res has the request response + window.alert('Hello World'); + } + + exited(req) { + // before a new request sent + } + + failed(req, res) { + // on request failed + } + + next() { + // for chaining + return true; + } + + } + app.get('/hello', new MyMiddleware()); +``` + ### Chain handlers You can provide multiple handlers functions on a navigation path. Invoking ```next()``` function allows to chain the handlers. @@ -138,7 +160,7 @@ h2! h3 is ignored because ```next()``` function was not invoked. -### app.route() +#### app.route() You can create chainable route handlers for a route path by using ```app.route()```. @@ -149,7 +171,7 @@ app.route('/book') .put((req, res) => { console.log('Update the book') }); ``` -### frontexpress.Router +#### frontexpress.Router Use the ```frontexpress.Router``` class to create modular, mountable route handlers. @@ -187,121 +209,24 @@ import birds from './birds'; app.use('/birds', birds); ``` -## API +## [API](https://github.com/camelaissani/frontexpress/blob/master/docs/api.md) -| | Method | Short description | -| :------------- | :--------------| :----------------- | -|Frontexpress ||| -||[frontexpress()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpress-1)|Creates an instance of application| -||[frontexpress.Router()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressrouter)|Creates a Router object| -||[frontexpress.Middleware](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressmiddleware)|Returns the Middleware class | -|||| -|Application ||| -||[set(setting, value)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationsetsetting-val)|Assigns a setting| -||[listen(callback)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationlistencallback)|Starts the application| -||[route(uri)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationrouteuri)|Gets a Router initialized with a root path| -||[use(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationuseuri-middleware)|Sets a middleware| -|||| -||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a GET request| -||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a POST request| -||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a PUT request| -||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a DELETE request| -|||| -||[httpGet(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a GET ajax request| -||[httpPost(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a POST ajax request| -||[httpPut(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a PUT ajax request| -||[httpDelete(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a DELETE ajax request| -|||| -|Router ||| -||[use(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerusemiddleware)|Sets a middleware| -||[all(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerallmiddleware)|Sets a middleware on all HTTP method requests| -|||| -||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a GET request| -||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a POST request| -||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a PUT request| -||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a DELETE request| -|||| -|Middleware ||| -||[entered(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareenteredrequest)|Invoked by the app before an ajax request is sent| -||[exited(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareexitedrequest)|Invoked by the app before a new ajax request is sent| -||[updated(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareupdatedrequest-response)|Invoked by the app after an ajax request has responded| -||[failed(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarefailedrequest-response)|Invoked by the app after an ajax request has failed| -||[next()](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarenext)|Allows to break the middleware chain execution| +## Tests + Clone the git repository: -### middleware function - -After registering a middleware function, the application invokes it with these parameters: - -```js - (request, response, next) => { - next(); - } +```bash +$ git clone git@github.com:camelaissani/frontexpress.git +$ cd frontexpress ``` -**request**: `Object`, the ajax request information sent by the app + Install the dependencies and run the test suite: -**response**: `Object`, the response of request - -**next**: `Function`, the `next()` function to call to not break the middleware execution chain - - -### request object - -```js - { - method, - uri, - headers, - data, - history: { - state, - title, - uri - } - } +```bash +$ npm install +$ npm test ``` -**method**: `String`, HTTP methods 'GET', 'POST'... - -**uri**: `String`, path - -**headers**: `Object`, custom HTTP headers - -**data**: `Object`, data attached to the request - -**history**: `Object`, object with properties state, title and uri - -**If the history object is set, it will activate the browser history management.** See [browser pushState() method](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method) for more information about state, title, and uri (url). - -> uri and history.uri can be different. - - -### response object - -```js - { - status, - statusText, - responseText, - errorThrown, - errors - } -``` - -**status**: `Number`, HTTP status 200, 404, 401, 500... - -**statusText**: `String` - -**responseText**: `String` response content - -**errorThrown**: `Object` exception thrown (if request fails) - -**errors**: `String` error description (if request fails) - - ## License [MIT](LICENSE) - - diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..94d8453 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,109 @@ +# API + +| | Method | Short description | +| :------------- | :--------------| :----------------- | +|Frontexpress ||| +||[frontexpress()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpress-1)|Creates an instance of application| +||[frontexpress.Router()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressrouter)|Creates a Router object| +||[frontexpress.Middleware](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressmiddleware)|Returns the Middleware class | +|||| +|Application ||| +||[set(setting, value)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationsetsetting-val)|Assigns a setting| +||[listen(callback)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationlistencallback)|Starts the application| +||[route(uri)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationrouteuri)|Gets a Router initialized with a root path| +||[use(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationuseuri-middleware)|Sets a middleware| +|||| +||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a GET request| +||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a POST request| +||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a PUT request| +||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a DELETE request| +|||| +||[httpGet(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a GET ajax request| +||[httpPost(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a POST ajax request| +||[httpPut(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a PUT ajax request| +||[httpDelete(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a DELETE ajax request| +|||| +|Router ||| +||[use(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerusemiddleware)|Sets a middleware| +||[all(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerallmiddleware)|Sets a middleware on all HTTP method requests| +|||| +||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a GET request| +||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a POST request| +||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a PUT request| +||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a DELETE request| +|||| +|Middleware ||| +||[entered(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareenteredrequest)|Invoked by the app before an ajax request is sent| +||[exited(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareexitedrequest)|Invoked by the app before a new ajax request is sent| +||[updated(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareupdatedrequest-response)|Invoked by the app after an ajax request has responded| +||[failed(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarefailedrequest-response)|Invoked by the app after an ajax request has failed| +||[next()](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarenext)|Allows to break the middleware chain execution| + +# middleware function + +After registering a middleware function, the application invokes it with these parameters: + +```js + (request, response, next) => { + next(); + } +``` + +**request**: `Object`, the ajax request information sent by the app + +**response**: `Object`, the response of request + +**next**: `Function`, the `next()` function to call to not break the middleware execution chain + +# request object + +```js + { + method, + uri, + headers, + data, + history: { + state, + title, + uri + } + } +``` + +**method**: `String`, HTTP methods 'GET', 'POST'... + +**uri**: `String`, path + +**headers**: `Object`, custom HTTP headers + +**data**: `Object`, data attached to the request + +**history**: `Object`, object with properties state, title and uri + +>**If the history object is set, it will activate the browser history management.** See [browser pushState() method](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method) for more information about state, title, and uri (url). +> uri and history.uri can be different. + +**params**: `Object`, object containing the path parameters + +# response object + +```js + { + status, + statusText, + responseText, + errorThrown, + errors + } +``` + +**status**: `Number`, HTTP status 200, 404, 401, 500... + +**statusText**: `String` + +**responseText**: `String` response content + +**errorThrown**: `Object` exception thrown (if request fails) + +**errors**: `String` error description (if request fails) diff --git a/frontexpress.js b/frontexpress.js index 052c8e3..e2b6ff1 100644 --- a/frontexpress.js +++ b/frontexpress.js @@ -136,200 +136,6 @@ var toConsumableArray = function (arr) { } }; -/** - * Module dependencies. - * @private - */ - -var Requester = function () { - function Requester() { - classCallCheck(this, Requester); - } - - createClass(Requester, [{ - key: 'fetch', - - - /** - * Make an ajax request. - * - * @param {Object} request - * @param {Function} success callback - * @param {Function} failure callback - * @private - */ - - value: function fetch(request, resolve, reject) { - var method = request.method, - uri = request.uri, - headers = request.headers, - data = request.data; - - - var success = function success(responseText) { - resolve(request, { - status: 200, - statusText: 'OK', - responseText: responseText - }); - }; - - var fail = function fail(_ref) { - var status = _ref.status, - statusText = _ref.statusText, - errorThrown = _ref.errorThrown; - - reject(request, { - status: status, - statusText: statusText, - errorThrown: errorThrown, - errors: 'HTTP ' + status + ' ' + (statusText ? statusText : '') - }); - }; - - var xmlhttp = new XMLHttpRequest(); - xmlhttp.onreadystatechange = function () { - if (xmlhttp.readyState === 4) { - //XMLHttpRequest.DONE - if (xmlhttp.status === 200) { - success(xmlhttp.responseText); - } else { - fail({ status: xmlhttp.status, statusText: xmlhttp.statusText }); - } - } - }; - try { - xmlhttp.open(method, uri, true); - if (headers) { - Object.keys(headers).forEach(function (header) { - xmlhttp.setRequestHeader(header, headers[header]); - }); - } - if (data) { - xmlhttp.send(data); - } else { - xmlhttp.send(); - } - } catch (errorThrown) { - fail({ errorThrown: errorThrown }); - } - } - }]); - return Requester; -}(); - -/** - * Module dependencies. - * @private - */ - -/** - * Settings object. - * @private - */ - -var Settings = function () { - - /** - * Initialize the settings. - * - * - setup default configuration - * - * @private - */ - - function Settings() { - classCallCheck(this, Settings); - - // default settings - this.settings = { - 'http requester': new Requester(), - - 'http GET transformer': { - uri: function uri(_ref) { - var _uri = _ref.uri, - headers = _ref.headers, - data = _ref.data; - - if (!data) { - return _uri; - } - var uriWithoutAnchor = _uri, - anchor = ''; - - var match = /^(.*)(#.*)$/.exec(_uri); - if (match) { - var _$exec = /^(.*)(#.*)$/.exec(_uri); - - var _$exec2 = slicedToArray(_$exec, 3); - - uriWithoutAnchor = _$exec2[1]; - anchor = _$exec2[2]; - } - uriWithoutAnchor = Object.keys(data).reduce(function (gUri, d, index) { - gUri += '' + (index === 0 && gUri.indexOf('?') === -1 ? '?' : '&') + d + '=' + data[d]; - return gUri; - }, uriWithoutAnchor); - return uriWithoutAnchor + anchor; - } - } - // 'http POST transformer': { - // headers({uri, headers, data}) { - // if (!data) { - // return headers; - // } - // const updatedHeaders = headers || {}; - // if (!updatedHeaders['Content-Type']) { - // updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; - // } - // return updatedHeaders; - // } - // } - }; - - this.rules = { - 'http requester': function httpRequester(requester) { - if (typeof requester.fetch !== 'function') { - throw new TypeError('setting http requester has no fetch method'); - } - } - }; - } - - /** - * Assign `setting` to `val` - * - * @param {String} setting - * @param {*} [val] - * @private - */ - - createClass(Settings, [{ - key: 'set', - value: function set$$1(name, value) { - var checkRules = this.rules[name]; - if (checkRules) { - checkRules(value); - } - this.settings[name] = value; - } - - /** - * Return `setting`'s value. - * - * @param {String} setting - * @private - */ - - }, { - key: 'get', - value: function get$$1(name) { - return this.settings[name]; - } - }]); - return Settings; -}(); - /** * Middleware object. * @public @@ -543,26 +349,11 @@ var Router = function () { }, { key: 'routes', - value: function routes(uri, method) { + value: function routes(application, request) { + request.params = request.params || {}; + var isRouteMatch = application.get('route matcher'); return this._routes.filter(function (route) { - if (route.method && route.method !== method) { - return false; - } - - if (!route.uri || !uri) { - return true; - } - - //remove query string from uri to test - //remove anchor from uri to test - var match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(uri); - var baseUriToCheck = match[1] || match[2] || match[3]; - - if (route.uri instanceof RegExp) { - return baseUriToCheck.match(route.uri); - } - - return route.uri === baseUriToCheck; + return isRouteMatch(request, route); }); } @@ -724,6 +515,284 @@ HTTP_METHODS.forEach(function (method) { }; }); +function routeMatcher(request, route) { + // check if http method are equals + if (route.method && route.method !== request.method) { + return false; + } + + // route and uri not defined always match + if (!route.uri || !request.uri) { + return true; + } + + //remove query string and anchor from uri to test + var match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(request.uri); + var baseUriToCheck = match[1] || match[2] || match[3]; + + // if route is a regexp path + if (route.uri instanceof RegExp) { + return baseUriToCheck.match(route.uri) !== null; + } + + // if route is parameterized path + if (route.uri.indexOf(':') !== -1) { + + var decodeParmeterValue = function decodeParmeterValue(v) { + return !isNaN(parseFloat(v)) && isFinite(v) ? Number.isInteger(v) ? Number.parseInt(v, 10) : Number.parseFloat(v) : v; + }; + + // figure out key names + var keys = []; + var keysRE = /:([^\/\?]+)\??/g; + var keysMatch = keysRE.exec(route.uri); + while (keysMatch != null) { + keys.push(keysMatch[1]); + keysMatch = keysRE.exec(route.uri); + } + + // change parameterized path to regexp + var regExpUri = route.uri + // :parameter? + .replace(/\/:[^\/]+\?/g, '(?:\/([^\/]+))?') + // :parameter + .replace(/:[^\/]+/g, '([^\/]+)') + // escape all / + .replace('/', '\\/'); + + // checks if uri match + var routeMatch = baseUriToCheck.match(new RegExp('^' + regExpUri + '$')); + if (!routeMatch) { + return false; + } + + // update params in request with keys + request.params = Object.assign(request.params, keys.reduce(function (acc, key, index) { + var value = routeMatch[index + 1]; + if (value) { + value = value.indexOf(',') !== -1 ? value.split(',').map(function (v) { + return decodeParmeterValue(v); + }) : value = decodeParmeterValue(value); + } + acc[key] = value; + return acc; + }, {})); + return true; + } + + // if route is a simple path + return route.uri === baseUriToCheck; +} + +/** + * Module dependencies. + * @private + */ + +var Requester = function () { + function Requester() { + classCallCheck(this, Requester); + } + + createClass(Requester, [{ + key: 'fetch', + + + /** + * Make an ajax request. + * + * @param {Object} request + * @param {Function} success callback + * @param {Function} failure callback + * @private + */ + + value: function fetch(request, resolve, reject) { + var method = request.method, + uri = request.uri, + headers = request.headers, + data = request.data; + + + var success = function success(responseText) { + resolve(request, { + status: 200, + statusText: 'OK', + responseText: responseText + }); + }; + + var fail = function fail(_ref) { + var status = _ref.status, + statusText = _ref.statusText, + errorThrown = _ref.errorThrown; + + reject(request, { + status: status, + statusText: statusText, + errorThrown: errorThrown, + errors: 'HTTP ' + status + ' ' + (statusText ? statusText : '') + }); + }; + + var xmlhttp = new XMLHttpRequest(); + xmlhttp.onreadystatechange = function () { + if (xmlhttp.readyState === 4) { + //XMLHttpRequest.DONE + if (xmlhttp.status === 200) { + success(xmlhttp.responseText); + } else { + fail({ status: xmlhttp.status, statusText: xmlhttp.statusText }); + } + } + }; + try { + xmlhttp.open(method, uri, true); + if (headers) { + Object.keys(headers).forEach(function (header) { + xmlhttp.setRequestHeader(header, headers[header]); + }); + } + if (data) { + xmlhttp.send(data); + } else { + xmlhttp.send(); + } + } catch (errorThrown) { + fail({ errorThrown: errorThrown }); + } + } + }]); + return Requester; +}(); + +var httpGetTransformer = { + uri: function uri(_ref2) { + var _uri = _ref2.uri, + headers = _ref2.headers, + data = _ref2.data; + + if (!data) { + return _uri; + } + var uriWithoutAnchor = _uri, + anchor = ''; + + var match = /^(.*)(#.*)$/.exec(_uri); + if (match) { + var _$exec = /^(.*)(#.*)$/.exec(_uri); + + var _$exec2 = slicedToArray(_$exec, 3); + + uriWithoutAnchor = _$exec2[1]; + anchor = _$exec2[2]; + } + uriWithoutAnchor = Object.keys(data).reduce(function (gUri, d, index) { + gUri += '' + (index === 0 && gUri.indexOf('?') === -1 ? '?' : '&') + d + '=' + data[d]; + return gUri; + }, uriWithoutAnchor); + return uriWithoutAnchor + anchor; + } +}; + +// export const httpPostTransformer = { +// headers({uri, headers, data}) { +// if (!data) { +// return headers; +// } +// const updatedHeaders = headers || {}; +// if (!updatedHeaders['Content-Type']) { +// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; +// } +// return updatedHeaders; +// } +// }; + +/** + * Module dependencies. + * @private + */ +function errorIfNotFunction(toTest, message) { + if (typeof toTest !== 'function') { + throw new TypeError(message); + } +} + +/** + * Settings object. + * @private + */ + +var Settings = function () { + + /** + * Initialize the settings. + * + * - setup default configuration + * + * @private + */ + + function Settings() { + classCallCheck(this, Settings); + + // default settings + this.settings = { + 'http requester': new Requester(), + 'http GET transformer': httpGetTransformer, + // 'http POST transformer': httpPostTransformer, + 'route matcher': routeMatcher + }; + + this.rules = { + 'http requester': function httpRequester(requester) { + errorIfNotFunction(requester.fetch, 'setting http requester has no fetch function'); + }, + 'http GET transformer': function httpGETTransformer(transformer) { + if (!transformer || !transformer.uri && !transformer.headers && !transformer.data) { + throw new TypeError('setting http transformer one of functions: uri, headers, data is missing'); + } + }, + 'route matcher': function routeMatcher$$1(_routeMatcher) { + errorIfNotFunction(_routeMatcher, 'setting route matcher is not a function'); + } + }; + } + + /** + * Assign `setting` to `val` + * + * @param {String} setting + * @param {*} [val] + * @private + */ + + createClass(Settings, [{ + key: 'set', + value: function set$$1(name, value) { + var checkRules = this.rules[name]; + if (checkRules) { + checkRules(value); + } + this.settings[name] = value; + } + + /** + * Return `setting`'s value. + * + * @param {String} setting + * @private + */ + + }, { + key: 'get', + value: function get$$1(name) { + return this.settings[name]; + } + }]); + return Settings; +}(); + /** * Module dependencies. * @private @@ -747,9 +816,8 @@ var Application = function () { classCallCheck(this, Application); this.routers = []; - // this.isDOMLoaded = false; - // this.isDOMReady = false; this.settings = new Settings(); + this.plugins = []; } /** @@ -768,6 +836,8 @@ var Application = function () { createClass(Application, [{ key: 'set', value: function set$$1() { + var _settings; + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } @@ -778,10 +848,7 @@ var Application = function () { } // set behaviour - var name = args[0], - value = args[1]; - - this.settings.set(name, value); + (_settings = this.settings).set.apply(_settings, args); return this; } @@ -807,6 +874,12 @@ var Application = function () { value: function listen(callback) { var _this = this; + var request = { method: 'GET', uri: window.location.pathname + window.location.search }; + var response = { status: 200, statusText: 'OK' }; + var currentRoutes = this._routes(request); + + this._callMiddlewareMethod('entered', currentRoutes, request); + // manage history window.onpopstate = function (event) { if (event.state) { @@ -814,32 +887,27 @@ var Application = function () { _request = _event$state.request, _response = _event$state.response; - var _currentRoutes = _this._routes(_request.uri, _request.method); - - _this._callMiddlewareMethod('exited'); - _this._callMiddlewareMethod('entered', _currentRoutes, _request); - _this._callMiddlewareMethod('updated', _currentRoutes, _request, _response); + ['exited', 'entered', 'updated'].forEach(function (middlewareMethod) { + return _this._callMiddlewareMethod(middlewareMethod, _this._routes(_request), _request, _response); + }); } }; // manage page loading/refreshing - var request = { method: 'GET', uri: window.location.pathname + window.location.search }; - var response = { status: 200, statusText: 'OK' }; - var currentRoutes = this._routes(); + window.onbeforeunload = function () { + _this._callMiddlewareMethod('exited'); + }; var whenPageIsInteractiveFn = function whenPageIsInteractiveFn() { + _this.plugins.forEach(function (pluginObject) { + return pluginObject.plugin(_this); + }); _this._callMiddlewareMethod('updated', currentRoutes, request, response); if (callback) { callback(request, response); } }; - window.onbeforeunload = function () { - _this._callMiddlewareMethod('exited'); - }; - - this._callMiddlewareMethod('entered', currentRoutes, request); - document.onreadystatechange = function () { // DOM ready state if (document.readyState === 'interactive') { @@ -876,6 +944,7 @@ var Application = function () { /** * Use the given middleware function or object, with optional _uri_. * Default _uri_ is "/". + * Or use the given plugin * * // middleware function will be applied on path "/" * app.use((req, res, next) => {console.log('Hello')}); @@ -883,8 +952,16 @@ var Application = function () { * // middleware object will be applied on path "/" * app.use(new Middleware()); * + * // use a plugin + * app.use({ + * name: 'My plugin name', + * plugin(application) { + * // here plugin implementation + * } + * }); + * * @param {String} uri - * @param {Middleware|Function} middleware object or function + * @param {Middleware|Function|plugin} middleware object, middleware function, plugin * @return {app} for chaining * * @public @@ -900,19 +977,24 @@ var Application = function () { var _toParameters = toParameters(args), baseUri = _toParameters.baseUri, router = _toParameters.router, - middleware = _toParameters.middleware; + middleware = _toParameters.middleware, + plugin = _toParameters.plugin; - if (router) { - router.baseUri = baseUri; - } else if (middleware) { - router = new Router(baseUri); - HTTP_METHODS.forEach(function (method) { - router[method.toLowerCase()](middleware); - }); + if (plugin) { + this.plugins.push(plugin); } else { - throw new TypeError('method takes at least a middleware or a router'); + if (router) { + router.baseUri = baseUri; + } else if (middleware) { + router = new Router(baseUri); + HTTP_METHODS.forEach(function (method) { + router[method.toLowerCase()](middleware); + }); + } else { + throw new TypeError('method takes at least a middleware or a router'); + } + this.routers.push(router); } - this.routers.push(router); return this; } @@ -926,16 +1008,13 @@ var Application = function () { }, { key: '_routes', - value: function _routes() { - var uri = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.location.pathname + window.location.search; - var method = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'GET'; + value: function _routes(request) { + var _this2 = this; - var currentRoutes = []; - this.routers.forEach(function (router) { - currentRoutes.push.apply(currentRoutes, toConsumableArray(router.routes(uri, method))); - }); - - return currentRoutes; + return this.routers.reduce(function (acc, router) { + acc.push.apply(acc, toConsumableArray(router.routes(_this2, request))); + return acc; + }, []); } /** @@ -995,7 +1074,7 @@ var Application = function () { }, { key: '_fetch', value: function _fetch(req, resolve, reject) { - var _this2 = this; + var _this3 = this; var method = req.method, uri = req.uri, @@ -1019,7 +1098,7 @@ var Application = function () { this._callMiddlewareMethod('exited'); // gathers all routes impacted by the uri - var currentRoutes = this._routes(uri, method); + var currentRoutes = this._routes(req); // calls middleware entered method this._callMiddlewareMethod('entered', currentRoutes, req); @@ -1029,12 +1108,12 @@ var Application = function () { if (history) { window.history.pushState({ request: request, response: response }, history.title, history.uri); } - _this2._callMiddlewareMethod('updated', currentRoutes, request, response); + _this3._callMiddlewareMethod('updated', currentRoutes, request, response); if (resolve) { resolve(request, response); } }, function (request, response) { - _this2._callMiddlewareMethod('failed', currentRoutes, request, response); + _this3._callMiddlewareMethod('failed', currentRoutes, request, response); if (reject) { reject(request, response); } @@ -1136,29 +1215,25 @@ HTTP_METHODS.reduce(function (reqProto, method) { }, Application.prototype); function toParameters(args) { + var _args, _args2, _args3, _args4; + var baseUri = void 0, middleware = void 0, router = void 0, + plugin = void 0, which = void 0; - if (args && args.length > 0) { - if (args.length === 1) { - var _args = slicedToArray(args, 1); - which = _args[0]; - } else { - var _args2 = slicedToArray(args, 2); + args.length === 1 ? (_args = args, _args2 = slicedToArray(_args, 1), which = _args2[0], _args) : (_args3 = args, _args4 = slicedToArray(_args3, 2), baseUri = _args4[0], which = _args4[1], _args3); - baseUri = _args2[0]; - which = _args2[1]; - } - - if (which instanceof Router) { - router = which; - } else if (which instanceof Middleware || typeof which === 'function') { - middleware = which; - } + if (which instanceof Router) { + router = which; + } else if (which instanceof Middleware || typeof which === 'function') { + middleware = which; + } else if (which && which.plugin && typeof which.plugin === 'function') { + plugin = which; } - return { baseUri: baseUri, middleware: middleware, router: router, which: which }; + + return { baseUri: baseUri, middleware: middleware, router: router, plugin: plugin, which: which }; } /** diff --git a/frontexpress.min.js b/frontexpress.min.js index 89c6456..17acda0 100644 --- a/frontexpress.min.js +++ b/frontexpress.min.js @@ -1,2 +1,2 @@ -var frontexpress=function(){"use strict";function e(e){var t=void 0,r=void 0,i=void 0,n=void 0;if(e&&e.length>0){if(1===e.length)n=a(e,1)[0];else{var o=a(e,2);t=o[0],n=o[1]}n instanceof f?i=n:(n instanceof d||"function"==typeof n)&&(r=n)}return{baseUri:t,middleware:r,router:i,which:n}}var t=["GET","POST","PUT","PATCH","DELETE"],r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:"";i(this,e),this.name=t}return n(e,[{key:"entered",value:function(e){}},{key:"exited",value:function(e){}},{key:"updated",value:function(e,t){}},{key:"failed",value:function(e,t){}},{key:"next",value:function(){return!0}}]),e}(),h=function(){function e(t,r,n,a){i(this,e),this.router=t,this.uriPart=r,this.method=n,this.middleware=a,this.visited=!1}return n(e,[{key:"uri",get:function(){if(this.uriPart||this.method){if(this.uriPart instanceof RegExp)return this.uriPart;if(this.router.baseUri instanceof RegExp)return this.router.baseUri;if(this.router.baseUri){var e=this.router.baseUri.trim();return this.uriPart?(e+this.uriPart.trim()).replace(/\/{2,}/,"/"):e}return this.uriPart}}}]),e}(),f=function(){function a(e){i(this,a),this._baseUri=e,this._routes=[]}return n(a,[{key:"_add",value:function(e){return this._routes.push(e),this}},{key:"routes",value:function(e,t){return this._routes.filter(function(r){if(r.method&&r.method!==t)return!1;if(!r.uri||!e)return!0;var i=/^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(e),n=i[1]||i[2]||i[3];return r.uri instanceof RegExp?n.match(r.uri):r.uri===n})}},{key:"visited",value:function(){return this._routes.filter(function(e){return e.visited})}},{key:"use",value:function(e){if(!(e instanceof d)&&"function"!=typeof e)throw new TypeError("method takes at least a middleware");return this._add(new h(this,void 0,void 0,e)),this}},{key:"all",value:function(){for(var r=this,i=arguments.length,n=Array(i),a=0;a0&&void 0!==arguments[0]?arguments[0]:window.location.pathname+window.location.search,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"GET",r=[];return this.routers.forEach(function(i){r.push.apply(r,o(i.routes(e,t)))}),r}},{key:"_callMiddlewareMethod",value:function(e,t,r,i){if("exited"===e)return void this.routers.forEach(function(e){e.visited().forEach(function(e){e.middleware.exited&&(e.middleware.exited(e.visited),e.visited=null)})});t.some(function(t){if("updated"===e&&(t.visited=r),t.middleware[e]){if(t.middleware[e](r,i),t.middleware.next&&!t.middleware.next())return!0}else if("entered"!==e){var n=!0,a=function(){n=!1};if(t.middleware(r,i,a),n)return!0}return!1})}},{key:"_fetch",value:function(e,t,r){var i=this,n=e.method,a=e.uri,o=e.headers,u=e.data,s=e.history,d=this.get("http "+n+" transformer");if(d){var h=d.uri,f=d.headers,c=d.data;e.uri=h?h({uri:a,headers:o,data:u}):a,e.headers=f?f({uri:a,headers:o,data:u}):o,e.data=c?c({uri:a,headers:o,data:u}):u}this._callMiddlewareMethod("exited");var l=this._routes(a,n);this._callMiddlewareMethod("entered",l,e),this.settings.get("http requester").fetch(e,function(e,r){s&&window.history.pushState({request:e,response:r},s.title,s.uri),i._callMiddlewareMethod("updated",l,e,r),t&&t(e,r)},function(e,t){i._callMiddlewareMethod("failed",l,e,t),r&&r(e,t)})}}]),r}();t.reduce(function(t,r){var i=r.toLowerCase();return t[i]=function(){for(var t=arguments.length,r=Array(t),n=0;n0&&void 0!==arguments[0]?arguments[0]:"";a(this,e),this.name=t}return u(e,[{key:"entered",value:function(e){}},{key:"exited",value:function(e){}},{key:"updated",value:function(e,t){}},{key:"failed",value:function(e,t){}},{key:"next",value:function(){return!0}}]),e}(),h=function(){function e(t,r,n,i){a(this,e),this.router=t,this.uriPart=r,this.method=n,this.middleware=i,this.visited=!1}return u(e,[{key:"uri",get:function(){if(this.uriPart||this.method){if(this.uriPart instanceof RegExp)return this.uriPart;if(this.router.baseUri instanceof RegExp)return this.router.baseUri;if(this.router.baseUri){var e=this.router.baseUri.trim();return this.uriPart?(e+this.uriPart.trim()).replace(/\/{2,}/,"/"):e}return this.uriPart}}}]),e}(),d=function(){function e(t){a(this,e),this._baseUri=t,this._routes=[]}return u(e,[{key:"_add",value:function(e){return this._routes.push(e),this}},{key:"routes",value:function(e,t){t.params=t.params||{};var r=e.get("route matcher");return this._routes.filter(function(e){return r(t,e)})}},{key:"visited",value:function(){return this._routes.filter(function(e){return e.visited})}},{key:"use",value:function(e){if(!(e instanceof f)&&"function"!=typeof e)throw new TypeError("method takes at least a middleware");return this._add(new h(this,void 0,void 0,e)),this}},{key:"all",value:function(){for(var e=this,t=arguments.length,i=Array(t),a=0;a \"bar\"\n *\n * @param {String} setting\n * @param {*} [val]\n * @return {app} for chaining\n * @public\n */\n\n set(...args) {\n // get behaviour\n if (args.length === 1) {\n return this.settings.get([args]);\n }\n\n // set behaviour\n const [name, value] = args;\n this.settings.set(name, value);\n\n return this;\n }\n\n\n /**\n * Listen for DOM initialization and history state changes.\n *\n * The callback function is called once the DOM has\n * the `document.readyState` equals to 'interactive'.\n *\n * app.listen(()=> {\n * console.log('App is listening requests');\n * console.log('DOM is ready!');\n * });\n *\n *\n * @param {Function} callback\n * @public\n */\n\n listen(callback) {\n\n // manage history\n window.onpopstate = (event) => {\n if (event.state) {\n const {request, response} = event.state;\n const currentRoutes = this._routes(request.uri, request.method);\n\n this._callMiddlewareMethod('exited');\n this._callMiddlewareMethod('entered', currentRoutes, request);\n this._callMiddlewareMethod('updated', currentRoutes, request, response);\n }\n };\n\n // manage page loading/refreshing\n const request = {method: 'GET', uri: window.location.pathname + window.location.search};\n const response = {status: 200, statusText: 'OK'};\n const currentRoutes = this._routes();\n\n const whenPageIsInteractiveFn = () => {\n this._callMiddlewareMethod('updated', currentRoutes, request, response);\n if (callback) {\n callback(request, response);\n }\n };\n\n window.onbeforeunload = () => {\n this._callMiddlewareMethod('exited');\n };\n\n this._callMiddlewareMethod('entered', currentRoutes, request);\n\n document.onreadystatechange = () => {\n // DOM ready state\n if (document.readyState === 'interactive') {\n whenPageIsInteractiveFn();\n }\n };\n\n if (['interactive', 'complete'].indexOf(document.readyState) !== -1) {\n whenPageIsInteractiveFn();\n }\n\n }\n\n\n /**\n * Returns a new `Router` instance for the _uri_.\n * See the Router api docs for details.\n *\n * app.route('/');\n * // => new Router instance\n *\n * @param {String} uri\n * @return {Router} for chaining\n *\n * @public\n */\n\n route(uri) {\n const router = new Router(uri);\n this.routers.push(router);\n return router;\n }\n\n\n /**\n * Use the given middleware function or object, with optional _uri_.\n * Default _uri_ is \"/\".\n *\n * // middleware function will be applied on path \"/\"\n * app.use((req, res, next) => {console.log('Hello')});\n *\n * // middleware object will be applied on path \"/\"\n * app.use(new Middleware());\n *\n * @param {String} uri\n * @param {Middleware|Function} middleware object or function\n * @return {app} for chaining\n *\n * @public\n */\n\n use(...args) {\n let {baseUri, router, middleware} = toParameters(args);\n if (router) {\n router.baseUri = baseUri;\n } else if (middleware) {\n router = new Router(baseUri);\n HTTP_METHODS.forEach((method) => {\n router[method.toLowerCase()](middleware);\n });\n } else {\n throw new TypeError('method takes at least a middleware or a router');\n }\n this.routers.push(router);\n\n return this;\n }\n\n\n /**\n * Gather routes from all routers filtered by _uri_ and HTTP _method_.\n * See Router#routes() documentation for details.\n *\n * @private\n */\n\n _routes(uri=window.location.pathname + window.location.search, method='GET') {\n const currentRoutes = [];\n this.routers.forEach((router) => {\n currentRoutes.push(...router.routes(uri, method));\n });\n\n return currentRoutes;\n }\n\n\n /**\n * Call `Middleware` method or middleware function on _currentRoutes_.\n *\n * @private\n */\n\n _callMiddlewareMethod(meth, currentRoutes, request, response) {\n if (meth === 'exited') {\n // currentRoutes, request, response params not needed\n this.routers.forEach((router) => {\n router.visited().forEach((route) => {\n if (route.middleware.exited) {\n route.middleware.exited(route.visited);\n route.visited = null;\n }\n });\n });\n return;\n }\n\n currentRoutes.some((route) => {\n if (meth === 'updated') {\n route.visited = request;\n }\n\n if (route.middleware[meth]) {\n route.middleware[meth](request, response);\n if (route.middleware.next && !route.middleware.next()) {\n return true;\n }\n } else if (meth !== 'entered') {\n // calls middleware method\n let breakMiddlewareLoop = true;\n const next = () => {\n breakMiddlewareLoop = false;\n };\n route.middleware(request, response, next);\n if (breakMiddlewareLoop) {\n return true;\n }\n }\n\n return false;\n });\n }\n\n\n /**\n * Make an ajax request. Manage History#pushState if history object set.\n *\n * @private\n */\n\n _fetch(req, resolve, reject) {\n let {method, uri, headers, data, history} = req;\n\n const httpMethodTransformer = this.get(`http ${method} transformer`);\n if (httpMethodTransformer) {\n const {uri: _uriFn, headers: _headersFn, data: _dataFn } = httpMethodTransformer;\n req.uri = _uriFn ? _uriFn({uri, headers, data}) : uri;\n req.headers = _headersFn ? _headersFn({uri, headers, data}) : headers;\n req.data = _dataFn ? _dataFn({uri, headers, data}) : data;\n }\n\n // calls middleware exited method\n this._callMiddlewareMethod('exited');\n\n // gathers all routes impacted by the uri\n const currentRoutes = this._routes(uri, method);\n\n // calls middleware entered method\n this._callMiddlewareMethod('entered', currentRoutes, req);\n\n // invokes http request\n this.settings.get('http requester').fetch(req,\n (request, response) => {\n if (history) {\n window.history.pushState({request, response}, history.title, history.uri);\n }\n this._callMiddlewareMethod('updated', currentRoutes, request, response);\n if (resolve) {\n resolve(request, response);\n }\n },\n (request, response) => {\n this._callMiddlewareMethod('failed', currentRoutes, request, response);\n if (reject) {\n reject(request, response);\n }\n });\n }\n}\n\nHTTP_METHODS.reduce((reqProto, method) => {\n\n\n /**\n * Use the given middleware function or object, with optional _uri_ on\n * HTTP methods: get, post, put, delete...\n * Default _uri_ is \"/\".\n *\n * // middleware function will be applied on path \"/\"\n * app.get((req, res, next) => {console.log('Hello')});\n *\n * // middleware object will be applied on path \"/\" and\n * app.get(new Middleware());\n *\n * // get a setting value\n * app.set('foo', 'bar');\n * app.get('foo');\n * // => \"bar\"\n *\n * @param {String} uri or setting\n * @param {Middleware|Function} middleware object or function\n * @return {app} for chaining\n * @public\n */\n\n const middlewareMethodName = method.toLowerCase();\n reqProto[middlewareMethodName] = function(...args) {\n let {baseUri, middleware, which} = toParameters(args);\n if (middlewareMethodName === 'get' && typeof which === 'string') {\n return this.settings.get(which);\n }\n if (!middleware) {\n throw new TypeError(`method takes a middleware ${middlewareMethodName === 'get' ? 'or a string' : ''}`);\n }\n const router = new Router();\n router[middlewareMethodName](baseUri, middleware);\n\n this.routers.push(router);\n\n return this;\n };\n\n /**\n * Ajax request (get, post, put, delete...).\n *\n * // HTTP GET method\n * httpGet('/route1');\n *\n * // HTTP GET method\n * httpGet({uri: '/route1', data: {'p1': 'val1'});\n * // uri invoked => /route1?p1=val1\n *\n * // HTTP GET method with browser history management\n * httpGet({uri: '/api/users', history: {state: {foo: \"bar\"}, title: 'users page', uri: '/view/users'});\n *\n * Samples above can be applied on other HTTP methods.\n *\n * @param {String|Object} uri or object containing uri, http headers, data, history\n * @param {Function} success callback\n * @param {Function} failure callback\n * @public\n */\n const httpMethodName = 'http'+method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();\n reqProto[httpMethodName] = function(request, resolve, reject) {\n let {uri, headers, data, history} = request;\n if (!uri) {\n uri = request;\n }\n return this._fetch({\n uri,\n method,\n headers,\n data,\n history\n }, resolve, reject);\n };\n\n return reqProto;\n}, Application.prototype);\n\n\nexport function toParameters(args) {\n let baseUri, middleware, router, which;\n if (args && args.length > 0) {\n if (args.length === 1) {\n [which,] = args;\n } else {\n [baseUri, which,] = args;\n }\n\n if (which instanceof Router) {\n router = which;\n } else if ((which instanceof Middleware) || (typeof which === 'function')) {\n middleware = which;\n }\n }\n return {baseUri, middleware, router, which};\n}\n","/**\n * HTTP method list\n * @private\n */\n\n export default ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];\n // not supported yet\n // HEAD', 'CONNECT', 'OPTIONS', 'TRACE';\n","/**\n * Module dependencies.\n * @private\n */\n\nexport default class Requester {\n\n /**\n * Make an ajax request.\n *\n * @param {Object} request\n * @param {Function} success callback\n * @param {Function} failure callback\n * @private\n */\n\n fetch(request, resolve, reject) {\n const {method, uri, headers, data} = request;\n\n const success = (responseText) => {\n resolve(\n request,\n {\n status: 200,\n statusText: 'OK',\n responseText\n }\n );\n };\n\n const fail = ({status, statusText, errorThrown}) => {\n reject(\n request,\n {\n status,\n statusText,\n errorThrown,\n errors: `HTTP ${status} ${statusText?statusText:''}`\n }\n );\n };\n\n const xmlhttp = new XMLHttpRequest();\n xmlhttp.onreadystatechange = () => {\n if (xmlhttp.readyState === 4) { //XMLHttpRequest.DONE\n if (xmlhttp.status === 200) {\n success(xmlhttp.responseText);\n } else {\n fail({status: xmlhttp.status, statusText: xmlhttp.statusText});\n }\n }\n };\n try {\n xmlhttp.open(method, uri, true);\n if (headers) {\n Object.keys(headers).forEach((header) => {\n xmlhttp.setRequestHeader(header, headers[header]);\n });\n }\n if (data) {\n xmlhttp.send(data);\n } else {\n xmlhttp.send();\n }\n } catch (errorThrown) {\n fail({errorThrown});\n }\n }\n}\n","/**\n * Module dependencies.\n * @private\n */\n\nimport Requester from './requester';\n\n\n/**\n * Settings object.\n * @private\n */\n\nexport default class Settings {\n\n\n /**\n * Initialize the settings.\n *\n * - setup default configuration\n *\n * @private\n */\n\n constructor() {\n // default settings\n this.settings = {\n 'http requester': new Requester(),\n\n 'http GET transformer': {\n uri({uri, headers, data}) {\n if (!data) {\n return uri;\n }\n let [uriWithoutAnchor, anchor] = [uri, ''];\n const match = /^(.*)(#.*)$/.exec(uri);\n if (match) {\n [,uriWithoutAnchor, anchor] = /^(.*)(#.*)$/.exec(uri);\n }\n uriWithoutAnchor = Object.keys(data).reduce((gUri, d, index) => {\n gUri += `${(index === 0 && gUri.indexOf('?') === -1)?'?':'&'}${d}=${data[d]}`;\n return gUri;\n }, uriWithoutAnchor);\n return uriWithoutAnchor + anchor;\n }\n }\n // 'http POST transformer': {\n // headers({uri, headers, data}) {\n // if (!data) {\n // return headers;\n // }\n // const updatedHeaders = headers || {};\n // if (!updatedHeaders['Content-Type']) {\n // updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded';\n // }\n // return updatedHeaders;\n // }\n // }\n };\n\n this.rules = {\n 'http requester': (requester) => {\n if(typeof requester.fetch !== 'function') {\n throw new TypeError('setting http requester has no fetch method');\n }\n }\n };\n }\n\n\n /**\n * Assign `setting` to `val`\n *\n * @param {String} setting\n * @param {*} [val]\n * @private\n */\n\n set(name, value) {\n const checkRules = this.rules[name];\n if (checkRules) {\n checkRules(value);\n }\n this.settings[name] = value;\n }\n\n\n /**\n * Return `setting`'s value.\n *\n * @param {String} setting\n * @private\n */\n\n get(name) {\n return this.settings[name];\n }\n};\n","/**\n * Middleware object.\n * @public\n */\n\nexport default class Middleware {\n\n\n /**\n * Middleware initialization\n *\n * @param {String} middleware name\n */\n\n constructor(name='') {\n this.name = name;\n }\n\n /**\n * Invoked by the app before an ajax request is sent or\n * during the DOM loading (document.readyState === 'loading').\n * See Application#_callMiddlewareEntered documentation for details.\n *\n * Override this method to add your custom behaviour\n *\n * @param {Object} request\n * @public\n */\n\n entered(request) { }\n\n\n /**\n * Invoked by the app before a new ajax request is sent or before the DOM is unloaded.\n * See Application#_callMiddlewareExited documentation for details.\n *\n * Override this method to add your custom behaviour\n *\n * @param {Object} request\n * @public\n */\n\n exited(request) { }\n\n\n /**\n * Invoked by the app after an ajax request has responded or on DOM ready\n * (document.readyState === 'interactive').\n * See Application#_callMiddlewareUpdated documentation for details.\n *\n * Override this method to add your custom behaviour\n *\n * @param {Object} request\n * @param {Object} response\n * @public\n */\n\n updated(request, response) { }\n\n\n /**\n * Invoked by the app when an ajax request has failed.\n *\n * Override this method to add your custom behaviour\n *\n * @param {Object} request\n * @param {Object} response\n * @public\n */\n failed(request, response) { }\n\n\n /**\n * Allow the hand over to the next middleware object or function.\n *\n * Override this method and return `false` to break execution of\n * middleware chain.\n *\n * @return {Boolean} `true` by default\n *\n * @public\n */\n\n next() {\n return true;\n }\n}\n","/**\n * Module dependencies.\n * @private\n */\n\nimport HTTP_METHODS from './methods';\nimport {toParameters} from './application';\nimport Middleware from './middleware';\n\n\n/**\n * Route object.\n * @private\n */\n\nexport class Route {\n\n\n /**\n * Initialize the route.\n *\n * @private\n */\n\n constructor(router, uriPart, method, middleware) {\n this.router = router;\n this.uriPart = uriPart;\n this.method = method;\n this.middleware = middleware;\n this.visited = false;\n }\n\n\n /**\n * Return route's uri.\n *\n * @private\n */\n\n get uri() {\n if (!this.uriPart && !this.method) {\n return undefined;\n }\n\n if (this.uriPart instanceof RegExp) {\n return this.uriPart;\n }\n\n if (this.router.baseUri instanceof RegExp) {\n return this.router.baseUri;\n }\n\n if (this.router.baseUri) {\n const baseUri = this.router.baseUri.trim();\n if (this.uriPart) {\n return ( baseUri + this.uriPart.trim()).replace(/\\/{2,}/, '/');\n }\n return baseUri;\n }\n\n return this.uriPart;\n }\n}\n\n\n/**\n * Router object.\n * @public\n */\n\nconst error_middleware_message = 'method takes at least a middleware';\nexport default class Router {\n\n\n /**\n * Initialize the router.\n *\n * @private\n */\n\n constructor(uri) {\n this._baseUri = uri;\n this._routes = [];\n }\n\n\n /**\n * Do some checks and set _baseUri.\n *\n * @private\n */\n\n set baseUri(uri) {\n if (!uri) {\n return;\n }\n\n if (!this._baseUri) {\n this._baseUri = uri;\n return;\n }\n\n if (typeof this._baseUri !== typeof uri) {\n throw new TypeError('router cannot mix regexp and uri');\n }\n }\n\n\n /**\n * Return router's _baseUri.\n *\n * @private\n */\n\n get baseUri() {\n return this._baseUri;\n }\n\n\n /**\n * Add a route to the router.\n *\n * @private\n */\n\n _add(route) {\n this._routes.push(route);\n return this;\n }\n\n\n /**\n * Gather routes from routers filtered by _uri_ and HTTP _method_.\n *\n * @private\n */\n\n routes(uri, method) {\n return this._routes.filter((route) => {\n if (route.method && route.method !== method) {\n return false;\n }\n\n if (!route.uri || !uri) {\n return true;\n }\n\n //remove query string from uri to test\n //remove anchor from uri to test\n const match = /^(.*)\\?.*#.*|(.*)(?=\\?|#)|(.*[^\\?#])$/.exec(uri);\n const baseUriToCheck = match[1] || match[2] || match[3];\n\n if (route.uri instanceof RegExp) {\n return baseUriToCheck.match(route.uri);\n }\n\n return route.uri === baseUriToCheck;\n });\n }\n\n\n /**\n * Gather visited routes from routers.\n *\n * @private\n */\n\n visited() {\n return this._routes.filter(route => route.visited);\n }\n\n\n /**\n * Use the given middleware function or object on this router.\n *\n * // middleware function\n * router.use((req, res, next) => {console.log('Hello')});\n *\n * // middleware object\n * router.use(new Middleware());\n *\n * @param {Middleware|Function} middleware object or function\n * @return {Router} for chaining\n *\n * @public\n */\n\n use(middleware) {\n if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) {\n throw new TypeError(error_middleware_message);\n }\n\n this._add(new Route(this, undefined, undefined, middleware));\n\n return this;\n }\n\n\n /**\n * Use the given middleware function or object on this router for\n * all HTTP methods.\n *\n * // middleware function\n * router.all((req, res, next) => {console.log('Hello')});\n *\n * // middleware object\n * router.all(new Middleware());\n *\n * @param {Middleware|Function} middleware object or function\n * @return {Router} for chaining\n *\n * @public\n */\n\n all(...args) {\n const {middleware} = toParameters(args);\n if (!middleware) {\n throw new TypeError(error_middleware_message);\n }\n\n HTTP_METHODS.forEach((method) => {\n this[method.toLowerCase()](...args);\n });\n return this;\n }\n}\n\nHTTP_METHODS.forEach((method) => {\n\n\n /**\n * Use the given middleware function or object, with optional _uri_ on\n * HTTP methods: get, post, put, delete...\n * Default _uri_ is \"/\".\n *\n * // middleware function will be applied on path \"/\"\n * router.get((req, res, next) => {console.log('Hello')});\n *\n * // middleware object will be applied on path \"/\" and\n * router.get(new Middleware());\n *\n * // middleware function will be applied on path \"/user\"\n * router.post('/user', (req, res, next) => {console.log('Hello')});\n *\n * // middleware object will be applied on path \"/user\" and\n * router.post('/user', new Middleware());\n *\n * @param {String} uri\n * @param {Middleware|Function} middleware object or function\n * @return {Router} for chaining\n * @public\n */\n\n const methodName = method.toLowerCase();\n Router.prototype[methodName] = function(...args) {\n const {baseUri, middleware} = toParameters(args);\n if (!middleware) {\n throw new TypeError(error_middleware_message);\n }\n\n if (baseUri && this._baseUri && this._baseUri instanceof RegExp) {\n throw new TypeError('router cannot mix uri/regexp');\n }\n\n this._add(new Route(this, baseUri, method, middleware));\n\n return this;\n };\n});\n","/**\n * Module dependencies.\n */\n\nimport Application from './application';\nimport Router from './router';\nimport Middleware from './middleware';\n\n\n/**\n * Create a frontexpress application.\n *\n * @return {Function}\n * @api public\n */\n\nconst frontexpress = () => new Application();\n\n/**\n * Expose Router, Middleware constructors.\n */\nfrontexpress.Router = (baseUri) => new Router(baseUri);\nfrontexpress.Middleware = Middleware;\n\nexport default frontexpress;\n"],"names":["toParameters","args","baseUri","middleware","router","which","length","Router","Middleware","Requester","request","resolve","reject","method","uri","headers","data","success","responseText","fail","status","statusText","errorThrown","xmlhttp","XMLHttpRequest","onreadystatechange","readyState","open","keys","forEach","header","setRequestHeader","send","Settings","settings","uriWithoutAnchor","anchor","exec","Object","reduce","gUri","d","index","indexOf","rules","requester","fetch","TypeError","name","value","checkRules","this","response","Route","uriPart","visited","RegExp","trim","replace","_baseUri","_routes","route","push","filter","match","baseUriToCheck","_add","undefined","toLowerCase","babelHelpers.typeof","methodName","prototype","Application","routers","get","set","callback","onpopstate","event","state","currentRoutes","_this","_callMiddlewareMethod","window","location","pathname","search","whenPageIsInteractiveFn","onbeforeunload","document","routes","meth","exited","some","next","breakMiddlewareLoop","req","history","httpMethodTransformer","_uriFn","_headersFn","_dataFn","pushState","title","reqProto","middlewareMethodName","charAt","toUpperCase","slice","_fetch","frontexpress"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":null,"sources":["lib/router.js","lib/settings.js","lib/application.js","lib/methods.js","lib/middleware.js","lib/requester.js","lib/frontexpress.js"],"sourcesContent":["/**\n * Module dependencies.\n * @private\n */\n\nimport HTTP_METHODS from './methods';\nimport {toParameters} from './application';\nimport Middleware from './middleware';\n\n\n/**\n * Route object.\n * @private\n */\n\nexport class Route {\n\n\n /**\n * Initialize the route.\n *\n * @private\n */\n\n constructor(router, uriPart, method, middleware) {\n this.router = router;\n this.uriPart = uriPart;\n this.method = method;\n this.middleware = middleware;\n this.visited = false;\n }\n\n\n /**\n * Return route's uri.\n *\n * @private\n */\n\n get uri() {\n if (!this.uriPart && !this.method) {\n return undefined;\n }\n\n if (this.uriPart instanceof RegExp) {\n return this.uriPart;\n }\n\n if (this.router.baseUri instanceof RegExp) {\n return this.router.baseUri;\n }\n\n if (this.router.baseUri) {\n const baseUri = this.router.baseUri.trim();\n if (this.uriPart) {\n return ( baseUri + this.uriPart.trim()).replace(/\\/{2,}/, '/');\n }\n return baseUri;\n }\n\n return this.uriPart;\n }\n}\n\n\n/**\n * Router object.\n * @public\n */\n\nconst error_middleware_message = 'method takes at least a middleware';\nexport default class Router {\n\n\n /**\n * Initialize the router.\n *\n * @private\n */\n\n constructor(uri) {\n this._baseUri = uri;\n this._routes = [];\n }\n\n\n /**\n * Do some checks and set _baseUri.\n *\n * @private\n */\n\n set baseUri(uri) {\n if (!uri) {\n return;\n }\n\n if (!this._baseUri) {\n this._baseUri = uri;\n return;\n }\n\n if (typeof this._baseUri !== typeof uri) {\n throw new TypeError('router cannot mix regexp and uri');\n }\n }\n\n\n /**\n * Return router's _baseUri.\n *\n * @private\n */\n\n get baseUri() {\n return this._baseUri;\n }\n\n\n /**\n * Add a route to the router.\n *\n * @private\n */\n\n _add(route) {\n this._routes.push(route);\n return this;\n }\n\n\n /**\n * Gather routes from routers filtered by _uri_ and HTTP _method_.\n *\n * @private\n */\n\n routes(application, request) {\n request.params = request.params || {};\n const isRouteMatch = application.get('route matcher');\n return this._routes.filter((route) => {\n return isRouteMatch(request, route);\n });\n }\n\n\n /**\n * Gather visited routes from routers.\n *\n * @private\n */\n\n visited() {\n return this._routes.filter(route => route.visited);\n }\n\n\n /**\n * Use the given middleware function or object on this router.\n *\n * // middleware function\n * router.use((req, res, next) => {console.log('Hello')});\n *\n * // middleware object\n * router.use(new Middleware());\n *\n * @param {Middleware|Function} middleware object or function\n * @return {Router} for chaining\n *\n * @public\n */\n\n use(middleware) {\n if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) {\n throw new TypeError(error_middleware_message);\n }\n\n this._add(new Route(this, undefined, undefined, middleware));\n\n return this;\n }\n\n\n /**\n * Use the given middleware function or object on this router for\n * all HTTP methods.\n *\n * // middleware function\n * router.all((req, res, next) => {console.log('Hello')});\n *\n * // middleware object\n * router.all(new Middleware());\n *\n * @param {Middleware|Function} middleware object or function\n * @return {Router} for chaining\n *\n * @public\n */\n\n all(...args) {\n const {middleware} = toParameters(args);\n if (!middleware) {\n throw new TypeError(error_middleware_message);\n }\n\n HTTP_METHODS.forEach((method) => {\n this[method.toLowerCase()](...args);\n });\n return this;\n }\n}\n\nHTTP_METHODS.forEach((method) => {\n\n\n /**\n * Use the given middleware function or object, with optional _uri_ on\n * HTTP methods: get, post, put, delete...\n * Default _uri_ is \"/\".\n *\n * // middleware function will be applied on path \"/\"\n * router.get((req, res, next) => {console.log('Hello')});\n *\n * // middleware object will be applied on path \"/\" and\n * router.get(new Middleware());\n *\n * // middleware function will be applied on path \"/user\"\n * router.post('/user', (req, res, next) => {console.log('Hello')});\n *\n * // middleware object will be applied on path \"/user\" and\n * router.post('/user', new Middleware());\n *\n * @param {String} uri\n * @param {Middleware|Function} middleware object or function\n * @return {Router} for chaining\n * @public\n */\n\n const methodName = method.toLowerCase();\n Router.prototype[methodName] = function(...args) {\n const {baseUri, middleware} = toParameters(args);\n if (!middleware) {\n throw new TypeError(error_middleware_message);\n }\n\n if (baseUri && this._baseUri && this._baseUri instanceof RegExp) {\n throw new TypeError('router cannot mix uri/regexp');\n }\n\n this._add(new Route(this, baseUri, method, middleware));\n\n return this;\n };\n});\n\nexport function routeMatcher(request, route) {\n // check if http method are equals\n if (route.method && route.method !== request.method) {\n return false;\n }\n\n\n // route and uri not defined always match\n if (!route.uri || !request.uri) {\n return true;\n }\n\n //remove query string and anchor from uri to test\n const match = /^(.*)\\?.*#.*|(.*)(?=\\?|#)|(.*[^\\?#])$/.exec(request.uri);\n const baseUriToCheck = match[1] || match[2] || match[3];\n\n // if route is a regexp path\n if (route.uri instanceof RegExp) {\n return baseUriToCheck.match(route.uri) !== null;\n }\n\n // if route is parameterized path\n if (route.uri.indexOf(':') !== -1) {\n\n const decodeParmeterValue = (v) => {\n return !isNaN(parseFloat(v)) && isFinite(v) ? (Number.isInteger(v) ? Number.parseInt(v, 10) : Number.parseFloat(v)) : v;\n };\n\n // figure out key names\n const keys = [];\n const keysRE = /:([^\\/\\?]+)\\??/g;\n let keysMatch = keysRE.exec(route.uri);\n while (keysMatch != null) {\n keys.push(keysMatch[1]);\n keysMatch = keysRE.exec(route.uri);\n }\n\n // change parameterized path to regexp\n const regExpUri = route.uri\n // :parameter?\n .replace(/\\/:[^\\/]+\\?/g, '(?:\\/([^\\/]+))?')\n // :parameter\n .replace(/:[^\\/]+/g, '([^\\/]+)')\n // escape all /\n .replace('/', '\\\\/');\n\n // checks if uri match\n const routeMatch = baseUriToCheck.match(new RegExp(`^${regExpUri}$`));\n if (!routeMatch) {\n return false;\n }\n\n // update params in request with keys\n request.params = Object.assign(request.params, keys.reduce((acc, key, index) => {\n let value = routeMatch[index + 1];\n if (value) {\n value = value.indexOf(',') !== -1 ? value.split(',').map(v => decodeParmeterValue(v)) : value = decodeParmeterValue(value);\n }\n acc[key] = value;\n return acc;\n }, {}));\n return true;\n }\n\n // if route is a simple path\n return route.uri === baseUriToCheck;\n}\n","/**\n * Module dependencies.\n * @private\n */\nimport {routeMatcher} from './router';\nimport Requester, {httpGetTransformer} from './requester';\n\n\nfunction errorIfNotFunction(toTest, message) {\n if(typeof toTest !== 'function') {\n throw new TypeError(message);\n }\n}\n\n/**\n * Settings object.\n * @private\n */\n\nexport default class Settings {\n\n\n /**\n * Initialize the settings.\n *\n * - setup default configuration\n *\n * @private\n */\n\n constructor() {\n // default settings\n this.settings = {\n 'http requester': new Requester(),\n 'http GET transformer': httpGetTransformer,\n // 'http POST transformer': httpPostTransformer,\n 'route matcher': routeMatcher\n };\n\n this.rules = {\n 'http requester': (requester) => {\n errorIfNotFunction(requester.fetch , 'setting http requester has no fetch function');\n },\n 'http GET transformer': (transformer) => {\n if (!transformer || (!transformer.uri && !transformer.headers && !transformer.data)) {\n throw new TypeError('setting http transformer one of functions: uri, headers, data is missing');\n }\n },\n 'route matcher': (routeMatcher) => {\n errorIfNotFunction(routeMatcher, 'setting route matcher is not a function');\n }\n };\n }\n\n\n /**\n * Assign `setting` to `val`\n *\n * @param {String} setting\n * @param {*} [val]\n * @private\n */\n\n set(name, value) {\n const checkRules = this.rules[name];\n if (checkRules) {\n checkRules(value);\n }\n this.settings[name] = value;\n }\n\n\n /**\n * Return `setting`'s value.\n *\n * @param {String} setting\n * @private\n */\n\n get(name) {\n return this.settings[name];\n }\n};\n","/**\n * Module dependencies.\n * @private\n */\n\nimport HTTP_METHODS from './methods';\nimport Settings from './settings';\nimport Router, {Route} from './router';\nimport Middleware from './middleware';\n\n\n/**\n * Application class.\n */\n\nexport default class Application {\n\n\n /**\n * Initialize the application.\n *\n * - setup default configuration\n *\n * @private\n */\n\n constructor() {\n this.routers = [];\n this.settings = new Settings();\n this.plugins = [];\n }\n\n\n /**\n * Assign `setting` to `val`, or return `setting`'s value.\n *\n * app.set('foo', 'bar');\n * app.set('foo');\n * // => \"bar\"\n *\n * @param {String} setting\n * @param {*} [val]\n * @return {app} for chaining\n * @public\n */\n\n set(...args) {\n // get behaviour\n if (args.length === 1) {\n return this.settings.get([args]);\n }\n\n // set behaviour\n this.settings.set(...args);\n\n return this;\n }\n\n\n /**\n * Listen for DOM initialization and history state changes.\n *\n * The callback function is called once the DOM has\n * the `document.readyState` equals to 'interactive'.\n *\n * app.listen(()=> {\n * console.log('App is listening requests');\n * console.log('DOM is ready!');\n * });\n *\n *\n * @param {Function} callback\n * @public\n */\n\n listen(callback) {\n const request = {method: 'GET', uri: window.location.pathname + window.location.search};\n const response = {status: 200, statusText: 'OK'};\n const currentRoutes = this._routes(request);\n\n this._callMiddlewareMethod('entered', currentRoutes, request);\n\n // manage history\n window.onpopstate = (event) => {\n if (event.state) {\n const {request, response} = event.state;\n [\n 'exited',\n 'entered',\n 'updated'\n ].forEach(middlewareMethod => this._callMiddlewareMethod(middlewareMethod, this._routes(request), request, response));\n }\n };\n\n // manage page loading/refreshing\n window.onbeforeunload = () => {\n this._callMiddlewareMethod('exited');\n };\n\n const whenPageIsInteractiveFn = () => {\n this.plugins.forEach(pluginObject => pluginObject.plugin(this));\n this._callMiddlewareMethod('updated', currentRoutes, request, response);\n if (callback) {\n callback(request, response);\n }\n };\n\n document.onreadystatechange = () => {\n // DOM ready state\n if (document.readyState === 'interactive') {\n whenPageIsInteractiveFn();\n }\n };\n\n if (['interactive', 'complete'].indexOf(document.readyState) !== -1) {\n whenPageIsInteractiveFn();\n }\n }\n\n\n /**\n * Returns a new `Router` instance for the _uri_.\n * See the Router api docs for details.\n *\n * app.route('/');\n * // => new Router instance\n *\n * @param {String} uri\n * @return {Router} for chaining\n *\n * @public\n */\n\n route(uri) {\n const router = new Router(uri);\n this.routers.push(router);\n return router;\n }\n\n\n /**\n * Use the given middleware function or object, with optional _uri_.\n * Default _uri_ is \"/\".\n * Or use the given plugin\n *\n * // middleware function will be applied on path \"/\"\n * app.use((req, res, next) => {console.log('Hello')});\n *\n * // middleware object will be applied on path \"/\"\n * app.use(new Middleware());\n *\n * // use a plugin\n * app.use({\n * name: 'My plugin name',\n * plugin(application) {\n * // here plugin implementation\n * }\n * });\n *\n * @param {String} uri\n * @param {Middleware|Function|plugin} middleware object, middleware function, plugin\n * @return {app} for chaining\n *\n * @public\n */\n\n use(...args) {\n let {baseUri, router, middleware, plugin} = toParameters(args);\n if (plugin) {\n this.plugins.push(plugin);\n } else {\n if (router) {\n router.baseUri = baseUri;\n } else if (middleware) {\n router = new Router(baseUri);\n HTTP_METHODS.forEach((method) => {\n router[method.toLowerCase()](middleware);\n });\n } else {\n throw new TypeError('method takes at least a middleware or a router');\n }\n this.routers.push(router);\n }\n\n return this;\n }\n\n\n /**\n * Gather routes from all routers filtered by _uri_ and HTTP _method_.\n * See Router#routes() documentation for details.\n *\n * @private\n */\n\n _routes(request) {\n return this.routers.reduce((acc, router) => {\n acc.push(...router.routes(this, request));\n return acc;\n }, []);\n }\n\n\n /**\n * Call `Middleware` method or middleware function on _currentRoutes_.\n *\n * @private\n */\n\n _callMiddlewareMethod(meth, currentRoutes, request, response) {\n if (meth === 'exited') {\n // currentRoutes, request, response params not needed\n this.routers.forEach((router) => {\n router.visited().forEach((route) => {\n if (route.middleware.exited) {\n route.middleware.exited(route.visited);\n route.visited = null;\n }\n });\n });\n return;\n }\n\n currentRoutes.some((route) => {\n if (meth === 'updated') {\n route.visited = request;\n }\n\n if (route.middleware[meth]) {\n route.middleware[meth](request, response);\n if (route.middleware.next && !route.middleware.next()) {\n return true;\n }\n } else if (meth !== 'entered') {\n // calls middleware method\n let breakMiddlewareLoop = true;\n const next = () => {\n breakMiddlewareLoop = false;\n };\n route.middleware(request, response, next);\n if (breakMiddlewareLoop) {\n return true;\n }\n }\n\n return false;\n });\n }\n\n\n /**\n * Make an ajax request. Manage History#pushState if history object set.\n *\n * @private\n */\n\n _fetch(req, resolve, reject) {\n let {method, uri, headers, data, history} = req;\n\n const httpMethodTransformer = this.get(`http ${method} transformer`);\n if (httpMethodTransformer) {\n const {uri: _uriFn, headers: _headersFn, data: _dataFn } = httpMethodTransformer;\n req.uri = _uriFn ? _uriFn({uri, headers, data}) : uri;\n req.headers = _headersFn ? _headersFn({uri, headers, data}) : headers;\n req.data = _dataFn ? _dataFn({uri, headers, data}) : data;\n }\n\n // calls middleware exited method\n this._callMiddlewareMethod('exited');\n\n // gathers all routes impacted by the uri\n const currentRoutes = this._routes(req);\n\n // calls middleware entered method\n this._callMiddlewareMethod('entered', currentRoutes, req);\n\n // invokes http request\n this.settings.get('http requester').fetch(req,\n (request, response) => {\n if (history) {\n window.history.pushState({request, response}, history.title, history.uri);\n }\n this._callMiddlewareMethod('updated', currentRoutes, request, response);\n if (resolve) {\n resolve(request, response);\n }\n },\n (request, response) => {\n this._callMiddlewareMethod('failed', currentRoutes, request, response);\n if (reject) {\n reject(request, response);\n }\n });\n }\n}\n\nHTTP_METHODS.reduce((reqProto, method) => {\n\n\n /**\n * Use the given middleware function or object, with optional _uri_ on\n * HTTP methods: get, post, put, delete...\n * Default _uri_ is \"/\".\n *\n * // middleware function will be applied on path \"/\"\n * app.get((req, res, next) => {console.log('Hello')});\n *\n * // middleware object will be applied on path \"/\" and\n * app.get(new Middleware());\n *\n * // get a setting value\n * app.set('foo', 'bar');\n * app.get('foo');\n * // => \"bar\"\n *\n * @param {String} uri or setting\n * @param {Middleware|Function} middleware object or function\n * @return {app} for chaining\n * @public\n */\n\n const middlewareMethodName = method.toLowerCase();\n reqProto[middlewareMethodName] = function(...args) {\n let {baseUri, middleware, which} = toParameters(args);\n if (middlewareMethodName === 'get' && typeof which === 'string') {\n return this.settings.get(which);\n }\n if (!middleware) {\n throw new TypeError(`method takes a middleware ${middlewareMethodName === 'get' ? 'or a string' : ''}`);\n }\n const router = new Router();\n router[middlewareMethodName](baseUri, middleware);\n\n this.routers.push(router);\n\n return this;\n };\n\n /**\n * Ajax request (get, post, put, delete...).\n *\n * // HTTP GET method\n * httpGet('/route1');\n *\n * // HTTP GET method\n * httpGet({uri: '/route1', data: {'p1': 'val1'});\n * // uri invoked => /route1?p1=val1\n *\n * // HTTP GET method with browser history management\n * httpGet({uri: '/api/users', history: {state: {foo: \"bar\"}, title: 'users page', uri: '/view/users'});\n *\n * Samples above can be applied on other HTTP methods.\n *\n * @param {String|Object} uri or object containing uri, http headers, data, history\n * @param {Function} success callback\n * @param {Function} failure callback\n * @public\n */\n const httpMethodName = 'http'+method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();\n reqProto[httpMethodName] = function(request, resolve, reject) {\n let {uri, headers, data, history} = request;\n if (!uri) {\n uri = request;\n }\n return this._fetch({\n uri,\n method,\n headers,\n data,\n history\n }, resolve, reject);\n };\n\n return reqProto;\n}, Application.prototype);\n\n\nexport function toParameters(args) {\n let baseUri, middleware, router, plugin, which;\n\n args.length === 1 ? [which,] = args : [baseUri, which,] = args;\n\n if (which instanceof Router) {\n router = which;\n } else if (which instanceof Middleware || typeof which === 'function') {\n middleware = which;\n } else if(which && which.plugin && typeof which.plugin === 'function') {\n plugin = which;\n }\n\n return {baseUri, middleware, router, plugin, which};\n}\n","/**\n * HTTP method list\n * @private\n */\n\n export default ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];\n // not supported yet\n // HEAD', 'CONNECT', 'OPTIONS', 'TRACE';\n","/**\n * Middleware object.\n * @public\n */\n\nexport default class Middleware {\n\n\n /**\n * Middleware initialization\n *\n * @param {String} middleware name\n */\n\n constructor(name='') {\n this.name = name;\n }\n\n /**\n * Invoked by the app before an ajax request is sent or\n * during the DOM loading (document.readyState === 'loading').\n * See Application#_callMiddlewareEntered documentation for details.\n *\n * Override this method to add your custom behaviour\n *\n * @param {Object} request\n * @public\n */\n\n entered(request) { }\n\n\n /**\n * Invoked by the app before a new ajax request is sent or before the DOM is unloaded.\n * See Application#_callMiddlewareExited documentation for details.\n *\n * Override this method to add your custom behaviour\n *\n * @param {Object} request\n * @public\n */\n\n exited(request) { }\n\n\n /**\n * Invoked by the app after an ajax request has responded or on DOM ready\n * (document.readyState === 'interactive').\n * See Application#_callMiddlewareUpdated documentation for details.\n *\n * Override this method to add your custom behaviour\n *\n * @param {Object} request\n * @param {Object} response\n * @public\n */\n\n updated(request, response) { }\n\n\n /**\n * Invoked by the app when an ajax request has failed.\n *\n * Override this method to add your custom behaviour\n *\n * @param {Object} request\n * @param {Object} response\n * @public\n */\n failed(request, response) { }\n\n\n /**\n * Allow the hand over to the next middleware object or function.\n *\n * Override this method and return `false` to break execution of\n * middleware chain.\n *\n * @return {Boolean} `true` by default\n *\n * @public\n */\n\n next() {\n return true;\n }\n}\n","/**\n * Module dependencies.\n * @private\n */\n\nexport default class Requester {\n\n /**\n * Make an ajax request.\n *\n * @param {Object} request\n * @param {Function} success callback\n * @param {Function} failure callback\n * @private\n */\n\n fetch(request, resolve, reject) {\n const {method, uri, headers, data} = request;\n\n const success = (responseText) => {\n resolve(\n request,\n {\n status: 200,\n statusText: 'OK',\n responseText\n }\n );\n };\n\n const fail = ({status, statusText, errorThrown}) => {\n reject(\n request,\n {\n status,\n statusText,\n errorThrown,\n errors: `HTTP ${status} ${statusText?statusText:''}`\n }\n );\n };\n\n const xmlhttp = new XMLHttpRequest();\n xmlhttp.onreadystatechange = () => {\n if (xmlhttp.readyState === 4) { //XMLHttpRequest.DONE\n if (xmlhttp.status === 200) {\n success(xmlhttp.responseText);\n } else {\n fail({status: xmlhttp.status, statusText: xmlhttp.statusText});\n }\n }\n };\n try {\n xmlhttp.open(method, uri, true);\n if (headers) {\n Object.keys(headers).forEach((header) => {\n xmlhttp.setRequestHeader(header, headers[header]);\n });\n }\n if (data) {\n xmlhttp.send(data);\n } else {\n xmlhttp.send();\n }\n } catch (errorThrown) {\n fail({errorThrown});\n }\n }\n}\n\nexport const httpGetTransformer = {\n uri({uri, headers, data}) {\n if (!data) {\n return uri;\n }\n let [uriWithoutAnchor, anchor] = [uri, ''];\n const match = /^(.*)(#.*)$/.exec(uri);\n if (match) {\n [,uriWithoutAnchor, anchor] = /^(.*)(#.*)$/.exec(uri);\n }\n uriWithoutAnchor = Object.keys(data).reduce((gUri, d, index) => {\n gUri += `${(index === 0 && gUri.indexOf('?') === -1)?'?':'&'}${d}=${data[d]}`;\n return gUri;\n }, uriWithoutAnchor);\n return uriWithoutAnchor + anchor;\n }\n};\n\n// export const httpPostTransformer = {\n// headers({uri, headers, data}) {\n// if (!data) {\n// return headers;\n// }\n// const updatedHeaders = headers || {};\n// if (!updatedHeaders['Content-Type']) {\n// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded';\n// }\n// return updatedHeaders;\n// }\n// };\n","/**\n * Module dependencies.\n */\n\nimport Application from './application';\nimport Router from './router';\nimport Middleware from './middleware';\n\n\n/**\n * Create a frontexpress application.\n *\n * @return {Function}\n * @api public\n */\n\nconst frontexpress = () => new Application();\n\n/**\n * Expose Router, Middleware constructors.\n */\nfrontexpress.Router = (baseUri) => new Router(baseUri);\nfrontexpress.Middleware = Middleware;\n\nexport default frontexpress;\n"],"names":["routeMatcher","request","route","method","uri","match","exec","baseUriToCheck","RegExp","indexOf","decodeParmeterValue","v","isNaN","parseFloat","isFinite","Number","isInteger","parseInt","keys","keysRE","keysMatch","push","regExpUri","replace","routeMatch","params","Object","assign","reduce","acc","key","index","value","split","map","errorIfNotFunction","toTest","message","TypeError","toParameters","args","baseUri","middleware","router","plugin","which","length","Router","Middleware","name","response","Route","uriPart","visited","this","trim","_baseUri","_routes","application","isRouteMatch","get","filter","_add","undefined","forEach","toLowerCase","babelHelpers.typeof","methodName","prototype","Requester","resolve","reject","headers","data","success","responseText","fail","status","statusText","errorThrown","xmlhttp","XMLHttpRequest","onreadystatechange","readyState","open","header","setRequestHeader","send","httpGetTransformer","uriWithoutAnchor","anchor","gUri","d","Settings","settings","rules","requester","fetch","transformer","checkRules","Application","routers","plugins","set","callback","window","location","pathname","search","currentRoutes","_callMiddlewareMethod","onpopstate","event","state","_this","middlewareMethod","onbeforeunload","whenPageIsInteractiveFn","pluginObject","document","routes","meth","exited","some","next","breakMiddlewareLoop","req","history","httpMethodTransformer","_uriFn","_headersFn","_dataFn","pushState","title","reqProto","middlewareMethodName","charAt","toUpperCase","slice","_fetch","frontexpress"],"mappings":"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"} \ No newline at end of file diff --git a/lib/application.js b/lib/application.js index 42e4476..0c533fd 100755 --- a/lib/application.js +++ b/lib/application.js @@ -26,9 +26,8 @@ export default class Application { constructor() { this.routers = []; - // this.isDOMLoaded = false; - // this.isDOMReady = false; this.settings = new Settings(); + this.plugins = []; } @@ -52,8 +51,7 @@ export default class Application { } // set behaviour - const [name, value] = args; - this.settings.set(name, value); + this.settings.set(...args); return this; } @@ -76,37 +74,37 @@ export default class Application { */ listen(callback) { + const request = {method: 'GET', uri: window.location.pathname + window.location.search}; + const response = {status: 200, statusText: 'OK'}; + const currentRoutes = this._routes(request); + + this._callMiddlewareMethod('entered', currentRoutes, request); // manage history window.onpopstate = (event) => { if (event.state) { const {request, response} = event.state; - const currentRoutes = this._routes(request.uri, request.method); - - this._callMiddlewareMethod('exited'); - this._callMiddlewareMethod('entered', currentRoutes, request); - this._callMiddlewareMethod('updated', currentRoutes, request, response); + [ + 'exited', + 'entered', + 'updated' + ].forEach(middlewareMethod => this._callMiddlewareMethod(middlewareMethod, this._routes(request), request, response)); } }; // manage page loading/refreshing - const request = {method: 'GET', uri: window.location.pathname + window.location.search}; - const response = {status: 200, statusText: 'OK'}; - const currentRoutes = this._routes(); + window.onbeforeunload = () => { + this._callMiddlewareMethod('exited'); + }; const whenPageIsInteractiveFn = () => { + this.plugins.forEach(pluginObject => pluginObject.plugin(this)); this._callMiddlewareMethod('updated', currentRoutes, request, response); if (callback) { callback(request, response); } }; - window.onbeforeunload = () => { - this._callMiddlewareMethod('exited'); - }; - - this._callMiddlewareMethod('entered', currentRoutes, request); - document.onreadystatechange = () => { // DOM ready state if (document.readyState === 'interactive') { @@ -117,7 +115,6 @@ export default class Application { if (['interactive', 'complete'].indexOf(document.readyState) !== -1) { whenPageIsInteractiveFn(); } - } @@ -144,6 +141,7 @@ export default class Application { /** * Use the given middleware function or object, with optional _uri_. * Default _uri_ is "/". + * Or use the given plugin * * // middleware function will be applied on path "/" * app.use((req, res, next) => {console.log('Hello')}); @@ -151,26 +149,38 @@ export default class Application { * // middleware object will be applied on path "/" * app.use(new Middleware()); * + * // use a plugin + * app.use({ + * name: 'My plugin name', + * plugin(application) { + * // here plugin implementation + * } + * }); + * * @param {String} uri - * @param {Middleware|Function} middleware object or function + * @param {Middleware|Function|plugin} middleware object, middleware function, plugin * @return {app} for chaining * * @public */ use(...args) { - let {baseUri, router, middleware} = toParameters(args); - if (router) { - router.baseUri = baseUri; - } else if (middleware) { - router = new Router(baseUri); - HTTP_METHODS.forEach((method) => { - router[method.toLowerCase()](middleware); - }); + let {baseUri, router, middleware, plugin} = toParameters(args); + if (plugin) { + this.plugins.push(plugin); } else { - throw new TypeError('method takes at least a middleware or a router'); + if (router) { + router.baseUri = baseUri; + } else if (middleware) { + router = new Router(baseUri); + HTTP_METHODS.forEach((method) => { + router[method.toLowerCase()](middleware); + }); + } else { + throw new TypeError('method takes at least a middleware or a router'); + } + this.routers.push(router); } - this.routers.push(router); return this; } @@ -183,13 +193,11 @@ export default class Application { * @private */ - _routes(uri=window.location.pathname + window.location.search, method='GET') { - const currentRoutes = []; - this.routers.forEach((router) => { - currentRoutes.push(...router.routes(uri, method)); - }); - - return currentRoutes; + _routes(request) { + return this.routers.reduce((acc, router) => { + acc.push(...router.routes(this, request)); + return acc; + }, []); } @@ -261,7 +269,7 @@ export default class Application { this._callMiddlewareMethod('exited'); // gathers all routes impacted by the uri - const currentRoutes = this._routes(uri, method); + const currentRoutes = this._routes(req); // calls middleware entered method this._callMiddlewareMethod('entered', currentRoutes, req); @@ -368,19 +376,17 @@ HTTP_METHODS.reduce((reqProto, method) => { export function toParameters(args) { - let baseUri, middleware, router, which; - if (args && args.length > 0) { - if (args.length === 1) { - [which,] = args; - } else { - [baseUri, which,] = args; - } + let baseUri, middleware, router, plugin, which; - if (which instanceof Router) { - router = which; - } else if ((which instanceof Middleware) || (typeof which === 'function')) { - middleware = which; - } + args.length === 1 ? [which,] = args : [baseUri, which,] = args; + + if (which instanceof Router) { + router = which; + } else if (which instanceof Middleware || typeof which === 'function') { + middleware = which; + } else if(which && which.plugin && typeof which.plugin === 'function') { + plugin = which; } - return {baseUri, middleware, router, which}; + + return {baseUri, middleware, router, plugin, which}; } diff --git a/lib/requester.js b/lib/requester.js index 7af7e93..e1d1dd8 100755 --- a/lib/requester.js +++ b/lib/requester.js @@ -67,3 +67,34 @@ export default class Requester { } } } + +export const httpGetTransformer = { + uri({uri, headers, data}) { + if (!data) { + return uri; + } + let [uriWithoutAnchor, anchor] = [uri, '']; + const match = /^(.*)(#.*)$/.exec(uri); + if (match) { + [,uriWithoutAnchor, anchor] = /^(.*)(#.*)$/.exec(uri); + } + uriWithoutAnchor = Object.keys(data).reduce((gUri, d, index) => { + gUri += `${(index === 0 && gUri.indexOf('?') === -1)?'?':'&'}${d}=${data[d]}`; + return gUri; + }, uriWithoutAnchor); + return uriWithoutAnchor + anchor; + } +}; + +// export const httpPostTransformer = { +// headers({uri, headers, data}) { +// if (!data) { +// return headers; +// } +// const updatedHeaders = headers || {}; +// if (!updatedHeaders['Content-Type']) { +// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; +// } +// return updatedHeaders; +// } +// }; diff --git a/lib/router.js b/lib/router.js index 8116be5..1ff025d 100755 --- a/lib/router.js +++ b/lib/router.js @@ -135,26 +135,11 @@ export default class Router { * @private */ - routes(uri, method) { + routes(application, request) { + request.params = request.params || {}; + const isRouteMatch = application.get('route matcher'); return this._routes.filter((route) => { - if (route.method && route.method !== method) { - return false; - } - - if (!route.uri || !uri) { - return true; - } - - //remove query string from uri to test - //remove anchor from uri to test - const match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(uri); - const baseUriToCheck = match[1] || match[2] || match[3]; - - if (route.uri instanceof RegExp) { - return baseUriToCheck.match(route.uri); - } - - return route.uri === baseUriToCheck; + return isRouteMatch(request, route); }); } @@ -267,3 +252,71 @@ HTTP_METHODS.forEach((method) => { return this; }; }); + +export function routeMatcher(request, route) { + // check if http method are equals + if (route.method && route.method !== request.method) { + return false; + } + + + // route and uri not defined always match + if (!route.uri || !request.uri) { + return true; + } + + //remove query string and anchor from uri to test + const match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(request.uri); + const baseUriToCheck = match[1] || match[2] || match[3]; + + // if route is a regexp path + if (route.uri instanceof RegExp) { + return baseUriToCheck.match(route.uri) !== null; + } + + // if route is parameterized path + if (route.uri.indexOf(':') !== -1) { + + const decodeParmeterValue = (v) => { + return !isNaN(parseFloat(v)) && isFinite(v) ? (Number.isInteger(v) ? Number.parseInt(v, 10) : Number.parseFloat(v)) : v; + }; + + // figure out key names + const keys = []; + const keysRE = /:([^\/\?]+)\??/g; + let keysMatch = keysRE.exec(route.uri); + while (keysMatch != null) { + keys.push(keysMatch[1]); + keysMatch = keysRE.exec(route.uri); + } + + // change parameterized path to regexp + const regExpUri = route.uri + // :parameter? + .replace(/\/:[^\/]+\?/g, '(?:\/([^\/]+))?') + // :parameter + .replace(/:[^\/]+/g, '([^\/]+)') + // escape all / + .replace('/', '\\/'); + + // checks if uri match + const routeMatch = baseUriToCheck.match(new RegExp(`^${regExpUri}$`)); + if (!routeMatch) { + return false; + } + + // update params in request with keys + request.params = Object.assign(request.params, keys.reduce((acc, key, index) => { + let value = routeMatch[index + 1]; + if (value) { + value = value.indexOf(',') !== -1 ? value.split(',').map(v => decodeParmeterValue(v)) : value = decodeParmeterValue(value); + } + acc[key] = value; + return acc; + }, {})); + return true; + } + + // if route is a simple path + return route.uri === baseUriToCheck; +} diff --git a/lib/settings.js b/lib/settings.js index 5e1f9d7..537457c 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -2,9 +2,15 @@ * Module dependencies. * @private */ +import {routeMatcher} from './router'; +import Requester, {httpGetTransformer} from './requester'; -import Requester from './requester'; +function errorIfNotFunction(toTest, message) { + if(typeof toTest !== 'function') { + throw new TypeError(message); + } +} /** * Settings object. @@ -26,43 +32,22 @@ export default class Settings { // default settings this.settings = { 'http requester': new Requester(), - - 'http GET transformer': { - uri({uri, headers, data}) { - if (!data) { - return uri; - } - let [uriWithoutAnchor, anchor] = [uri, '']; - const match = /^(.*)(#.*)$/.exec(uri); - if (match) { - [,uriWithoutAnchor, anchor] = /^(.*)(#.*)$/.exec(uri); - } - uriWithoutAnchor = Object.keys(data).reduce((gUri, d, index) => { - gUri += `${(index === 0 && gUri.indexOf('?') === -1)?'?':'&'}${d}=${data[d]}`; - return gUri; - }, uriWithoutAnchor); - return uriWithoutAnchor + anchor; - } - } - // 'http POST transformer': { - // headers({uri, headers, data}) { - // if (!data) { - // return headers; - // } - // const updatedHeaders = headers || {}; - // if (!updatedHeaders['Content-Type']) { - // updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; - // } - // return updatedHeaders; - // } - // } + 'http GET transformer': httpGetTransformer, + // 'http POST transformer': httpPostTransformer, + 'route matcher': routeMatcher }; this.rules = { 'http requester': (requester) => { - if(typeof requester.fetch !== 'function') { - throw new TypeError('setting http requester has no fetch method'); + errorIfNotFunction(requester.fetch , 'setting http requester has no fetch function'); + }, + 'http GET transformer': (transformer) => { + if (!transformer || (!transformer.uri && !transformer.headers && !transformer.data)) { + throw new TypeError('setting http transformer one of functions: uri, headers, data is missing'); } + }, + 'route matcher': (routeMatcher) => { + errorIfNotFunction(routeMatcher, 'setting route matcher is not a function'); } }; } diff --git a/package.json b/package.json index e726af3..cb61c8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "frontexpress", - "version": "1.1.0", + "version": "1.2.0", "description": "Frontexpress manages routes in browser like ExpressJS on Node", "main": "dist/frontexpress.js", "jsnext:main": "lib/frontexpress.js", diff --git a/test/application-test.js b/test/application-test.js index 70f8e5d..b043014 100755 --- a/test/application-test.js +++ b/test/application-test.js @@ -989,4 +989,22 @@ describe('Application', () => { }); }); }); + + describe('plugin management', () => { + it('setup a plugin', (done) => { + const app = frontexpress(); + app.use({ + name: 'my plugin', + plugin(application) { + done(); + } + }); + + app.listen(); + + //simulate readystatechange + document.readyState = 'interactive'; + document.onreadystatechange(); + }); + }); }); diff --git a/test/middleware-test.js b/test/middleware-test.js new file mode 100644 index 0000000..92b738f --- /dev/null +++ b/test/middleware-test.js @@ -0,0 +1,20 @@ +/*eslint-env mocha*/ +import {assert} from 'chai'; +import Middleware from '../lib/middleware'; + +describe('Middleware', () => { + it('check exposed methods', () => { + const middleware = new Middleware(); + assert(middleware.entered); + assert(middleware.exited); + assert(middleware.updated); + assert(middleware.failed); + assert(middleware.next); + + middleware.entered(); + middleware.exited(); + middleware.updated(); + middleware.failed(); + assert(middleware.next()); + }); +}); \ No newline at end of file diff --git a/test/router-test.js b/test/router-test.js index 30346c1..2d53963 100755 --- a/test/router-test.js +++ b/test/router-test.js @@ -4,6 +4,9 @@ import sinon from 'sinon'; import frontexpress from '../lib/frontexpress'; import HTTP_METHODS from '../lib/methods'; +const application = frontexpress(); +const routeMatcher = application.get('route matcher'); + describe('Router', () => { describe('generated methods', () => { @@ -12,6 +15,7 @@ describe('Router', () => { assert(typeof router.all === 'function'); assert(typeof router.get === 'function'); assert(typeof router.put === 'function'); + assert(typeof router.patch === 'function'); assert(typeof router.post === 'function'); assert(typeof router.delete === 'function'); }); @@ -46,7 +50,7 @@ describe('Router', () => { router.get(middleware); - const r1 = router.routes('/', 'GET'); + const r1 = router.routes(application, {uri: '/', method: 'GET'}); assert(r1.length === 1); assert(r1[0].uri === undefined); assert(r1[0].method === 'GET'); @@ -64,37 +68,37 @@ describe('Router', () => { .post('/route2', middleware2) .all('/route3', middleware3); - const r1 = router.routes('/route1', 'GET'); + const r1 = router.routes(application, {uri: '/route1', method: 'GET'}); assert(r1.length === 1); assert(r1[0].uri === '/route1'); assert(r1[0].method === 'GET'); assert(r1[0].middleware === middleware1); - const r2 = router.routes('/route2', 'POST'); + const r2 = router.routes(application, {uri: '/route2', method: 'POST'}); assert(r2.length === 1); assert(r2[0].uri === '/route2'); assert(r2[0].method === 'POST'); assert(r2[0].middleware === middleware2); - let r3 = router.routes('/route3', 'GET'); + let r3 = router.routes(application, {uri: '/route3', method: 'GET'}); assert(r3.length === 1); assert(r3[0].uri === '/route3'); assert(r3[0].method === 'GET'); assert(r3[0].middleware === middleware3); - r3 = router.routes('/route3', 'POST'); + r3 = router.routes(application, {uri: '/route3', method: 'POST'}); assert(r3.length === 1); assert(r3[0].uri === '/route3'); assert(r3[0].method === 'POST'); assert(r3[0].middleware === middleware3); - r3 = router.routes('/route3', 'PUT'); + r3 = router.routes(application, {uri: '/route3', method: 'PUT'}); assert(r3.length === 1); assert(r3[0].uri === '/route3'); assert(r3[0].method === 'PUT'); assert(r3[0].middleware === middleware3); - r3 = router.routes('/route3', 'DELETE'); + r3 = router.routes(application, {uri: '/route3', method: 'DELETE'}); assert(r3.length === 1); assert(r3[0].uri === '/route3'); assert(r3[0].method === 'DELETE'); @@ -107,7 +111,7 @@ describe('Router', () => { router.get(/^\/route1/, middleware); - const r = router.routes('/route1', 'GET'); + const r = router.routes(application, {uri: '/route1', method: 'GET'}); assert(r.length === 1); assert(r[0].uri instanceof RegExp); assert(r[0].uri.toString() === new RegExp('^\/route1').toString()); @@ -120,7 +124,7 @@ describe('Router', () => { router.get('/subroute', new frontexpress.Middleware()); - const r = router.routes('/route1/subroute', 'GET'); + const r = router.routes(application, {uri: '/route1/subroute', method: 'GET'}); assert(r.length === 1); assert(r[0].uri === '/route1/subroute'); }); @@ -130,7 +134,7 @@ describe('Router', () => { router.get(new frontexpress.Middleware()); - const r = router.routes('/route1', 'GET'); + const r = router.routes(application, {uri: '/route1', method: 'GET'}); assert(r.length === 1); assert(r[0].uri === '/route1'); }); @@ -140,7 +144,7 @@ describe('Router', () => { router.get('/subroute', new frontexpress.Middleware()); - const r = router.routes('/route1/subroute', 'GET'); + const r = router.routes(application, {uri: '/route1/subroute', method: 'GET'}); assert(r.length === 1); assert(r[0].uri === '/route1/subroute'); }); @@ -150,7 +154,7 @@ describe('Router', () => { router.get('/subroute ', new frontexpress.Middleware()); - let r = router.routes('/route1/subroute', 'GET'); + let r = router.routes(application, {uri: '/route1/subroute', method: 'GET'}); assert(r.length === 1); assert(r[0].uri === '/route1/subroute'); @@ -160,7 +164,7 @@ describe('Router', () => { router.get(new frontexpress.Middleware()); - r = router.routes('/route1', 'GET'); + r = router.routes(application, {uri: '/route1', method: 'GET'}); assert(r.length === 1); assert(r[0].uri === '/route1'); }); @@ -170,7 +174,7 @@ describe('Router', () => { router.get('/subroute', new frontexpress.Middleware()); - let r = router.routes('/route1/subroute?a=b&c=d', 'GET'); + let r = router.routes(application, {uri: '/route1/subroute?a=b&c=d', method: 'GET'}); assert(r.length === 1); assert(r[0].uri === '/route1/subroute'); assert(r[0].data === undefined); @@ -181,7 +185,7 @@ describe('Router', () => { router.get('/subroute', new frontexpress.Middleware()); - let r = router.routes('/route1/subroute#a=b&c=d', 'GET'); + let r = router.routes(application, {uri: '/route1/subroute#a=b&c=d', method: 'GET'}); assert(r.length === 1); assert(r[0].uri === '/route1/subroute'); assert(r[0].data === undefined); @@ -192,7 +196,7 @@ describe('Router', () => { router.get('/subroute', new frontexpress.Middleware()); - let r = router.routes('/route1/subroute?a=b&c=d#anchor1', 'GET'); + let r = router.routes(application, {uri: '/route1/subroute?a=b&c=d#anchor1', method: 'GET'}); assert(r.length === 1); assert(r[0].uri === '/route1/subroute'); assert(r[0].data === undefined); @@ -264,7 +268,7 @@ describe('Router', () => { router.get(middleware); - const r = router.routes('/', 'GET'); + const r = router.routes(application, {uri: '/', method: 'GET'}); assert(r.length === 1); assert(r[0].uri === '/'); assert(r[0].method === 'GET'); @@ -277,7 +281,7 @@ describe('Router', () => { router.get('/route1', middleware); - const r = router.routes('/route1', 'GET'); + const r = router.routes(application, {uri: '/route1', method: 'GET'}); assert(r.length === 1); assert(r[0].uri === '/route1'); assert(r[0].method === 'GET'); @@ -295,7 +299,7 @@ describe('Router', () => { const middleware = new frontexpress.Middleware(); router.get(middleware); - const r = router.routes('/part1', 'GET'); + const r = router.routes(application, {uri: '/part1', method: 'GET'}); assert(r.length === 1); assert(r[0].uri instanceof RegExp); assert(r[0].uri.toString() === new RegExp('^\/part').toString()); @@ -303,4 +307,113 @@ describe('Router', () => { assert(r[0].middleware === middleware); }); }); + + describe('check route matcher', () => { + it('/', () => { + const route = {uri: '/', method: 'GET'}; + + const request = {uri: '/', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.deepEqual(request.params, {}); + }); + + it('/a/b/c', () => { + const route = {uri: '/a/b/c', method: 'GET'}; + + let request = {uri: '/a/b/c', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.deepEqual(request.params, {}); + + request = {uri: '/a/b/c/', method: 'GET', params: {}}; + assert.strictEqual(routeMatcher(request, route), false); + }); + + it('/^\//', () => { + const route = {uri: /^\//, method: 'GET'}; + + const request = {uri: '/a/b/c', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.deepEqual(request.params, {}); + }); + + it('/:id', () => { + const route = {uri: '/:id', method: 'GET'}; + + const request = {uri: '/1000', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.strictEqual(request.params.id, 1000); + }); + + it('/user/:id', () => { + const route = {uri: '/user/:id', method: 'GET'}; + + let request = {uri: '/user/1000', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.strictEqual(request.params.id, 1000); + + request = {uri: '/user/100.2122', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.strictEqual(request.params.id, 100.2122); + + request = {uri: '/user', method: 'GET', params: {}}; + assert.strictEqual(routeMatcher(request, route), false); + + request = {uri: '/user/', method: 'GET', params: {}}; + assert.strictEqual(routeMatcher(request, route), false); + }); + + it('/user/:id with id as coma separated values', () => { + const route = {uri: '/user/:id', method: 'GET'}; + + let request = {uri: '/user/1,2,3', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.deepEqual(request.params, {id: [1,2,3]}); + + request = {uri: '/user/1.5,2.55,4.25', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.deepEqual(request.params, {id: [1.5,2.55,4.25]}); + + request = {uri: '/user/a,b,c', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.deepEqual(request.params, {id: ['a','b','c']}); + }); + + it('/user/:id?', () => { + const route = {uri: '/user/:id?', method: 'GET'}; + + let request = {uri: '/user/1000', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.strictEqual(request.params.id, 1000); + + request = {uri: '/user', method: 'GET', params: {}}; + assert.strictEqual(routeMatcher(request, route), true); + assert.deepEqual(request.params, {id: undefined}); + + request = {uri: '/user/', method: 'GET', params: {}}; + assert.strictEqual(routeMatcher(request, route), false); + }); + + it('/user/:firstname/:lastname', () => { + const route = {uri: '/user/:firstname/:lastname', method: 'GET'}; + + let request = {uri: '/user/camel/aissani', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.deepEqual(request.params, {firstname: 'camel', lastname:'aissani'} ); + + request = {uri: '/user/camel', method: 'GET', params: {}}; + assert.strictEqual(routeMatcher(request, route), false); + }); + + it('/user/:firstname?/:lastname', () => { + const route = {uri: '/user/:firstname?/:lastname', method: 'GET'}; + + let request = {uri: '/user/camel/aissani', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.deepEqual(request.params, {firstname: 'camel', lastname:'aissani'} ); + + request = {uri: '/user/aissani', method: 'GET', params: {}}; + assert(routeMatcher(request, route)); + assert.deepEqual(request.params, {firstname: undefined, lastname:'aissani'} ); + }); + }); }); diff --git a/test/settings-test.js b/test/settings-test.js index 27eeacf..cee3644 100644 --- a/test/settings-test.js +++ b/test/settings-test.js @@ -1,11 +1,43 @@ /*eslint-env mocha*/ -import {assert} from 'chai'; +import chai, {assert} from 'chai'; import Settings from '../lib/settings'; describe('Settings', () => { const settings = new Settings(); describe('http GET method transformer', () => { + it('check setting rule', () => { + const defaultHttpGetTransformer = settings.get('http GET transformer'); + + chai.expect(() => settings.set('http GET transformer', null)).to.throw(TypeError); + chai.expect(() => settings.set('http GET transformer', {})).to.throw(TypeError); + chai.expect(() => settings.set('http GET transformer', {foo:()=>{}})).to.throw(TypeError); + + const uri = () => {}; + settings.set('http GET transformer', {uri}); + assert.deepEqual(settings.get('http GET transformer'), {uri}); + + const headers = () => {}; + settings.set('http GET transformer', {headers}); + assert.deepEqual(settings.get('http GET transformer'), {headers}); + + const data = () => {}; + settings.set('http GET transformer', {data}); + assert.deepEqual(settings.get('http GET transformer'), {data}); + + settings.set('http GET transformer', defaultHttpGetTransformer); + + + const defaultRouteMatcher = settings.get('route matcher'); + chai.expect(() => settings.set('route matcher', null)).to.throw(TypeError); + chai.expect(() => settings.set('route matcher', {})).to.throw(TypeError); + chai.expect(() => settings.set('route matcher', 1)).to.throw(TypeError); + + const routeMatcher = () => {}; + settings.set('route matcher', routeMatcher); + assert.strictEqual(settings.get('route matcher'), routeMatcher); + }); + it('simple uri', () => { const uriFn = settings.get('http GET transformer').uri; const dataFn = settings.get('http GET transformer').data;