Adds SFRA 6.0
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/*
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Arnout Kazemier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
'use strict';
|
||||
|
||||
//
|
||||
// We store our EE objects in a plain object whose properties are event names.
|
||||
// If `Object.create(null)` is not supported we prefix the event names with a
|
||||
// `~` to make sure that the built-in object properties are not overridden or
|
||||
// used as an attack vector.
|
||||
// We also assume that `Object.create(null)` is available when the event name
|
||||
// is an ES6 Symbol.
|
||||
//
|
||||
var prefix = typeof Object.create !== 'function' ? '~' : false;
|
||||
|
||||
/**
|
||||
* Representation of a single EventEmitter function.
|
||||
*
|
||||
* @param {Function} fn Event handler to be called.
|
||||
* @param {Mixed} context Context for function execution.
|
||||
* @param {Boolean} [once=false] Only emit once
|
||||
* @api private
|
||||
*/
|
||||
function EE(fn, context, once) {
|
||||
this.fn = fn;
|
||||
this.context = context;
|
||||
this.once = once || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal EventEmitter interface that is molded against the Node.js
|
||||
* EventEmitter interface.
|
||||
*
|
||||
* @constructor
|
||||
* @api public
|
||||
*/
|
||||
function EventEmitter() { /* Nothing to set */ }
|
||||
|
||||
/**
|
||||
* Holds the assigned EventEmitters by name.
|
||||
*
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
EventEmitter.prototype._events = undefined;
|
||||
|
||||
/**
|
||||
* Return a list of assigned event listeners.
|
||||
*
|
||||
* @param {String} event The events that should be listed.
|
||||
* @param {Boolean} exists We only need to know if there are listeners.
|
||||
* @returns {Array|Boolean}
|
||||
* @api public
|
||||
*/
|
||||
EventEmitter.prototype.listeners = function listeners(event, exists) {
|
||||
var evt = prefix ? prefix + event : event
|
||||
, available = this._events && this._events[evt];
|
||||
|
||||
if (exists) {
|
||||
return !!available;
|
||||
}
|
||||
if (!available) {
|
||||
return [];
|
||||
}
|
||||
if (available.fn) {
|
||||
return [available.fn];
|
||||
}
|
||||
|
||||
for (var i = 0, l = available.length, ee = new Array(l); i < l; i++) {
|
||||
ee[i] = available[i].fn;
|
||||
}
|
||||
|
||||
return ee;
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an event to all registered event listeners.
|
||||
*
|
||||
* @param {String} event The name of the event.
|
||||
* @returns {Boolean} Indication if we've emitted an event.
|
||||
* @api public
|
||||
*/
|
||||
EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
|
||||
var evt = prefix ? prefix + event : event;
|
||||
|
||||
if (!this._events || !this._events[evt]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var listeners = this._events[evt]
|
||||
, len = arguments.length
|
||||
, args
|
||||
, i;
|
||||
|
||||
if ('function' === typeof listeners.fn) {
|
||||
if (listeners.once) {
|
||||
this.removeListener(event, listeners.fn, undefined, true);
|
||||
}
|
||||
|
||||
switch (len) {
|
||||
case 1: return listeners.fn.call(listeners.context), true;
|
||||
case 2: return listeners.fn.call(listeners.context, a1), true;
|
||||
case 3: return listeners.fn.call(listeners.context, a1, a2), true;
|
||||
case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;
|
||||
case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
|
||||
case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
|
||||
}
|
||||
|
||||
for (i = 1, args = new Array(len - 1); i < len; i++) {
|
||||
args[i - 1] = arguments[i];
|
||||
}
|
||||
|
||||
listeners.fn.apply(listeners.context, args);
|
||||
} else {
|
||||
var length = listeners.length
|
||||
, j;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
if (listeners[i].once) {
|
||||
this.removeListener(event, listeners[i].fn, undefined, true);
|
||||
}
|
||||
|
||||
switch (len) {
|
||||
case 1: listeners[i].fn.call(listeners[i].context); break;
|
||||
case 2: listeners[i].fn.call(listeners[i].context, a1); break;
|
||||
case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;
|
||||
default:
|
||||
if (!args) {
|
||||
for (j = 1, args = new Array(len - 1); j < len; j++) {
|
||||
args[j - 1] = arguments[j];
|
||||
}
|
||||
}
|
||||
|
||||
listeners[i].fn.apply(listeners[i].context, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a new EventListener for the given event.
|
||||
*
|
||||
* @param {String} event Name of the event.
|
||||
* @param {Function} fn Callback function.
|
||||
* @param {Mixed} [context=this] The context of the function.
|
||||
* @api public
|
||||
*/
|
||||
EventEmitter.prototype.on = function on(event, fn, context) {
|
||||
var listener = new EE(fn, context || this)
|
||||
, evt = prefix ? prefix + event : event;
|
||||
|
||||
if (!this._events) {
|
||||
this._events = prefix ? {} : Object.create(null);
|
||||
}
|
||||
if (!this._events[evt]) {
|
||||
this._events[evt] = listener;
|
||||
} else {
|
||||
if (!this._events[evt].fn) {
|
||||
this._events[evt].push(listener);
|
||||
} else {
|
||||
this._events[evt] = [
|
||||
this._events[evt], listener
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an EventListener that's only called once.
|
||||
*
|
||||
* @param {String} event Name of the event.
|
||||
* @param {Function} fn Callback function.
|
||||
* @param {Mixed} [context=this] The context of the function.
|
||||
* @api public
|
||||
*/
|
||||
EventEmitter.prototype.once = function once(event, fn, context) {
|
||||
var listener = new EE(fn, context || this, true)
|
||||
, evt = prefix ? prefix + event : event;
|
||||
|
||||
if (!this._events) {
|
||||
this._events = prefix ? {} : Object.create(null);
|
||||
}
|
||||
if (!this._events[evt]) {
|
||||
this._events[evt] = listener;
|
||||
} else {
|
||||
if (!this._events[evt].fn) {
|
||||
this._events[evt].push(listener);
|
||||
} else {
|
||||
this._events[evt] = [
|
||||
this._events[evt], listener
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove event listeners.
|
||||
*
|
||||
* @param {String} event The event we want to remove.
|
||||
* @param {Function} fn The listener that we need to find.
|
||||
* @param {Mixed} context Only remove listeners matching this context.
|
||||
* @param {Boolean} once Only remove once listeners.
|
||||
* @api public
|
||||
*/
|
||||
EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
|
||||
var evt = prefix ? prefix + event : event;
|
||||
|
||||
if (!this._events || !this._events[evt]) {
|
||||
return this;
|
||||
}
|
||||
|
||||
var listeners = this._events[evt]
|
||||
, events = [];
|
||||
|
||||
if (fn) {
|
||||
if (listeners.fn) {
|
||||
if (listeners.fn !== fn ||
|
||||
(once && !listeners.once) ||
|
||||
(context && listeners.context !== context)) {
|
||||
events.push(listeners);
|
||||
}
|
||||
} else {
|
||||
for (var i = 0, length = listeners.length; i < length; i++) {
|
||||
if (listeners[i].fn !== fn ||
|
||||
(once && !listeners[i].once) ||
|
||||
(context && listeners[i].context !== context)) {
|
||||
events.push(listeners[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Reset the array, or remove it completely if we have no more listeners.
|
||||
//
|
||||
if (events.length) {
|
||||
this._events[evt] = events.length === 1 ? events[0] : events;
|
||||
} else {
|
||||
delete this._events[evt];
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove all listeners or only the listeners for the specified event.
|
||||
*
|
||||
* @param {String} event The event want to remove all listeners for.
|
||||
* @api public
|
||||
*/
|
||||
EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
|
||||
if (!this._events) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (event) {
|
||||
delete this._events[prefix ? prefix + event : event];
|
||||
}
|
||||
else {
|
||||
this._events = prefix ? {} : Object.create(null);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
//
|
||||
// Alias methods names because people roll like that.
|
||||
//
|
||||
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
|
||||
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
|
||||
|
||||
//
|
||||
// This function doesn't apply anymore.
|
||||
//
|
||||
EventEmitter.prototype.setMaxListeners = function setMaxListeners() {
|
||||
return this;
|
||||
};
|
||||
|
||||
//
|
||||
// Expose the prefix.
|
||||
//
|
||||
EventEmitter.prefixed = prefix;
|
||||
|
||||
//
|
||||
// Expose the module.
|
||||
//
|
||||
module.exports = EventEmitter;
|
||||
|
||||
/* eslint-enable */
|
@@ -0,0 +1,146 @@
|
||||
# Server Module
|
||||
|
||||
The server module replaces guard functionality that existed in the SiteGenesis JavaScript Controllers (SGJC) reference application. The server module also provides a different approach to extensibility that is new to Storefront Reference Architecture (SFRA).
|
||||
|
||||
The server module uses a modern JavaScript approach and borrows heavily from NodeJS's [Express](http://expressjs.com/). It also provides features specific to SFRA.
|
||||
|
||||
The server module registers routes that create a mapping between a URL and the code the server runs in response. For example:
|
||||
|
||||
```js
|
||||
var server = require('server');
|
||||
|
||||
server.get('Show', function(req, res, next) {
|
||||
res.json({ value: 'Hello World'});
|
||||
next();
|
||||
});
|
||||
|
||||
module.exports = server.exports();
|
||||
```
|
||||
|
||||
If you save this code to a file named `Page.js`, you register a new route for the URL matching this pattern:
|
||||
|
||||
`http://sandbox-host-name/on/demandware.store/site-name/en_US/Page-Show.`
|
||||
|
||||
Whenever that URL is requested, the function you passed to `server.get` is executed and renders a page whose body is `{ value: 'Hello World '}` and whose content-type is `Content-Type: application/json`.
|
||||
|
||||
The first parameter of the `server.get` and `server.post` functions is always the name of the route (the URL endpoint). The last parameter is always the main function for the endpoint. In between these parameters, you can add as many parameters as you need.
|
||||
|
||||
For example, you could add the `server.middleware.https` parameter after the `Show` paramenter to limit the route only to HTTPS requests.
|
||||
|
||||
Each parameter that you add specifies a corresponding function. The functions are executed in left-to-right order. Each function can either execute the next function (by calling `next()`) or reject the URL request (by calling `next(new Error())`).
|
||||
|
||||
The code executed between the first and last parameter is referred to as **middleware** and the whole process is called **chaining**.
|
||||
|
||||
You can create your own middleware functions. You can create functions to limit route access, to add information to the `pdict` variable, or for any other reason. One limitation of this approach is that you always have to call the `next()` function at the end of every step in the chain; otherwise, the next function in the chain is not executed.
|
||||
|
||||
## Middleware
|
||||
|
||||
Every step of the middleware chain is a function that takes three arguments. `req`, `res` and `next`.
|
||||
|
||||
### `req`
|
||||
|
||||
`req` stands for Request and contains information about the user request. For example, if you are looking for information about the user's input, accepted content-types, or login and locale, you can access this information by using the `req` object. The `req` argument automatically pre-parses query string parameters and assigns them to `req.querystring` object.
|
||||
|
||||
### `res`
|
||||
|
||||
`res` stands for Response and contains functionality for outputting data back to the client. For example:
|
||||
|
||||
* `res.cacheExpiration(24);` which sets cache expiration to 24 hours from now. `res.render(templateName, data)` outputs an ISML template back to the client and assigns `data` to `pdict`.
|
||||
* `res.json(data)` prints out a JSON object back to the screen. It's helpful in creating AJAX service endpoints that you want to execute from the client-side scripts.
|
||||
* `res.setViewData(data)` does not render anything, but sets the output object. This can be helpful if you want to add multiple objects to the `pdict` of the template, which contains all of in the information for rendering that is passed to the template. `setViewData` merges all of the data that you passed in into a single object, so you can call it at every step of the middleware chain. For example, you might want to have a separate middleware function that retrieves information about user's locale to render a language switch on the page. Actual output of the ISML template or JSON happens after every step of the middleware chain is complete.
|
||||
|
||||
### `next`
|
||||
|
||||
Executing the `next` function notifies the server that you are done with this middleware step, and the server can execute next step in the chain.
|
||||
|
||||
## Extending Routes
|
||||
|
||||
The power of this approach is that by chaining multiple middleware functions, you can compartmentalize your code better and extend existing or modify routes without having to rewrite them.
|
||||
|
||||
### Changing Wording in a Template
|
||||
For example, you might have a controller `Page` with the following route:
|
||||
|
||||
```js
|
||||
var server = require('server');
|
||||
|
||||
server.get('Show', function(req, res, next) {
|
||||
res.render('someTemplate', { value: 'Hello World' });
|
||||
next();
|
||||
});
|
||||
|
||||
module.exports = server.exports();
|
||||
```
|
||||
|
||||
Let's say that you are a client who is fine with the look and feel of the Page-Show template, but you want to change the wording. Instead of creating your own controller and route or modifying SFRA code, you can extend this route with the following code:
|
||||
|
||||
```js
|
||||
var page = require('app_storefront_base/cartridge/controller/Page');
|
||||
var server = require('server');
|
||||
|
||||
server.extend(page);
|
||||
|
||||
server.append('Show', function(req, res, next) {
|
||||
res.setViewData({ value: 'Hello Commerce Cloud' });
|
||||
next();
|
||||
});
|
||||
|
||||
module.exports = server.exports();
|
||||
```
|
||||
|
||||
Once the user loads this page, the text on the page now says "Hello Commerce Cloud", since the data passed to the template was overwritten.
|
||||
|
||||
### Changing Template Styles
|
||||
It is simple to change the template style if you are fine with the data but don't like the look and feel of the template. Instead of setting ViewData, you can call the `render` function and pass it a new template like this:
|
||||
|
||||
```js
|
||||
var page = require('app_storefront_base/cartridge/controller/Page');
|
||||
var server = require('server');
|
||||
|
||||
server.extend(page);
|
||||
|
||||
server.append('Show', function(req, res, next) {
|
||||
res.render('myNewTemplate');
|
||||
next();
|
||||
});
|
||||
|
||||
module.exports = server.exports();
|
||||
```
|
||||
|
||||
Your new template still has the `pdict.value` variable with a value of `Hello World`, but you can render it using your own template without modifying any of the SFRA code.
|
||||
|
||||
We recommend that you never modify anything in app\_storefront_base, but instead to create your own cartridge and overlay it in the Business Manager cartridge path. This enables you to upgrade to a newer version of SFRA without having to manually cherry-pick changes and perform manual merges. This doesn't mean that every new version of SFRA will not modify your client's site, but upgrade and feature adoption process is much quicker and less painful.
|
||||
|
||||
### Replacing a Route
|
||||
Sometimes you might want to reuse the route's name, but do not want any of the existing functionality. In those cases, you can use `replace` command to completely remove and re-add a new route.
|
||||
|
||||
```js
|
||||
var page = require('app_storefront_base/cartridge/controller/Page');
|
||||
var server = require('server);
|
||||
|
||||
server.extend(page);
|
||||
|
||||
server.replace('Show', server.middleware.get, function(req, res, next){
|
||||
res.render('myNewTemplate');
|
||||
next();
|
||||
});
|
||||
|
||||
module.exports = server.exports();
|
||||
```
|
||||
## Overriding Routes with module.superModule
|
||||
A typical storefront can have several layers of SFRA cartridges that overlay one another. Each cartridge can import from the previous cartridge and overlay it. To make this easy, Commerce Cloud provides a chaining mechanism that lets you access modules that you intend to override.
|
||||
|
||||
The `module.superModule` global property provides access to the most recent module on the cartridge path module with the same path and name as the current module.
|
||||
|
||||
For more information, see [SFRA Modules](https://documentation.b2c.commercecloud.salesforce.com/DOC2/topic/com.demandware.dochelp/SFRA/SFRAModules.html)
|
||||
|
||||
## Middleware Chain Events
|
||||
|
||||
The server module emits events at every step of execution, and you can subscribe to events and unsubscribe from events for a given route. Here's the list of currently supported events:
|
||||
|
||||
* `route:Start` - emitted as a first thing before middleware chain execution.
|
||||
* `route:Redirect` - emitted right before `res.redirect` execution.
|
||||
* `route:Step` - emitted before execution of every step in the middleware chain.
|
||||
* `route:Complete` - emitted after every step in the chain finishes execution. Currently subscribed to by the server to render ISML or JSON back to the client.
|
||||
|
||||
All of the events provide both the `req` and `res` objects as parameters to all handlers.
|
||||
|
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function () {
|
||||
var result = {};
|
||||
Array.prototype.forEach.call(arguments, function (argument) {
|
||||
if (typeof argument === 'object') {
|
||||
Object.keys(argument).forEach(function (key) {
|
||||
result[key] = argument[key];
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (action) {
|
||||
return {
|
||||
description: action.description || null,
|
||||
label: action.label || null,
|
||||
submitted: action.submitted,
|
||||
triggered: action.triggered,
|
||||
formType: 'formAction'
|
||||
};
|
||||
};
|
@@ -0,0 +1,134 @@
|
||||
'use strict';
|
||||
|
||||
var resource = require('dw/web/Resource');
|
||||
var secureEncoder = require('dw/util/SecureEncoder');
|
||||
|
||||
/**
|
||||
* Function to conver <dw.web.FormField> object to plain JS object.
|
||||
* @param {dw.web.FormField} field original formField object.
|
||||
* @return {Object} Plain JS object representing formField.
|
||||
*/
|
||||
function formField(field) {
|
||||
var result = {};
|
||||
Object.defineProperty(result, 'attributes', {
|
||||
get: function () {
|
||||
var attributes = '';
|
||||
attributes += 'name="' + result.htmlName + '"';
|
||||
if (result.mandatory) {
|
||||
attributes += ' required';
|
||||
attributes += ' aria-required="true"';
|
||||
}
|
||||
if (field.options && field.options.optionsCount > 0) {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
if (field.type === field.FIELD_TYPE_BOOLEAN && result.checked) {
|
||||
attributes += ' checked="' + result.checked + '"';
|
||||
}
|
||||
|
||||
var value = field.htmlValue == null ? '' : field.htmlValue;
|
||||
attributes += ' value="' + secureEncoder.forHtmlInDoubleQuoteAttribute(value) + '"';
|
||||
|
||||
if (result.maxValue) {
|
||||
attributes += ' max="' + result.maxValue + '"';
|
||||
}
|
||||
if (result.minValue) {
|
||||
attributes += ' min="' + result.minValue + '"';
|
||||
}
|
||||
if (result.maxLength) {
|
||||
attributes += ' maxLength="' + result.maxLength + '"';
|
||||
}
|
||||
if (result.minLength) {
|
||||
attributes += ' minLength="' + result.minLength + '"';
|
||||
}
|
||||
if (result.regEx) {
|
||||
attributes += ' pattern="' + result.regEx + '"';
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(result, 'value', {
|
||||
configurable: true,
|
||||
get: function () {
|
||||
return field.value;
|
||||
},
|
||||
set: function (value) {
|
||||
field.value = value; // eslint-disable-line no-param-reassign
|
||||
// reset htmlValue
|
||||
result.htmlValue = field.htmlValue || '';
|
||||
}
|
||||
});
|
||||
var attributesToCopy = {
|
||||
string: ['maxLength', 'minLength', 'regEx'],
|
||||
bool: ['checked', 'selected'],
|
||||
int: ['maxValue', 'minValue'],
|
||||
common: ['htmlValue', 'mandatory',
|
||||
'dynamicHtmlName', 'htmlName', 'valid'
|
||||
],
|
||||
resources: ['error', 'description', 'label']
|
||||
};
|
||||
|
||||
if (field.type === field.FIELD_TYPE_BOOLEAN && field.mandatory && !field.checked) {
|
||||
field.invalidateFormElement();
|
||||
}
|
||||
|
||||
attributesToCopy.common.forEach(function (item) {
|
||||
if (item !== 'valid') {
|
||||
result[item] = field[item] || '';
|
||||
} else {
|
||||
result.valid = field.valid;
|
||||
}
|
||||
});
|
||||
|
||||
attributesToCopy.resources.forEach(function (item) {
|
||||
if (field[item]) {
|
||||
result[item] = resource.msg(field[item], 'forms', null);
|
||||
}
|
||||
});
|
||||
|
||||
if (field.options && field.options.optionsCount > 0) {
|
||||
result.options = [];
|
||||
for (var i = 0, l = field.options.optionsCount; i < l; i++) {
|
||||
result.options.push({
|
||||
checked: field.options[i].checked,
|
||||
htmlValue: field.options[i].htmlValue,
|
||||
label: field.options[i].label
|
||||
? resource.msg(field.options[i].label, 'forms', null)
|
||||
: '',
|
||||
id: field.options[i].optionId,
|
||||
selected: field.options[i].selected,
|
||||
value: field.options[i].value
|
||||
});
|
||||
}
|
||||
|
||||
result.selectedOption = field.selectedOption ? field.selectedOption.optionId : '';
|
||||
}
|
||||
|
||||
switch (field.type) {
|
||||
case field.FIELD_TYPE_BOOLEAN:
|
||||
attributesToCopy.bool.forEach(function (item) {
|
||||
result[item] = field[item];
|
||||
});
|
||||
break;
|
||||
case field.FIELD_TYPE_DATE:
|
||||
case field.FIELD_TYPE_INTEGER:
|
||||
case field.FIELD_TYPE_NUMBER:
|
||||
attributesToCopy.int.forEach(function (item) {
|
||||
result[item] = field[item];
|
||||
});
|
||||
break;
|
||||
case field.FIELD_TYPE_STRING:
|
||||
attributesToCopy.string.forEach(function (item) {
|
||||
result[item] = field[item];
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
result.formType = 'formField';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = formField;
|
@@ -0,0 +1,117 @@
|
||||
'use strict';
|
||||
|
||||
var field = require('./formField');
|
||||
var action = require('./formAction');
|
||||
|
||||
/**
|
||||
* Convert dw.web.Form or dw.web.FormGroup to plain JS object
|
||||
* @param {dw.web.Form|dw.web.FormGroup} form Form to be parsed
|
||||
* @return {Object} Plain JS form object
|
||||
*/
|
||||
function parseForm(form) {
|
||||
var formField = require('dw/web/FormField');
|
||||
var formAction = require('dw/web/FormAction');
|
||||
var formGroup = require('dw/web/FormGroup');
|
||||
var result = {
|
||||
valid: form.valid,
|
||||
htmlName: form.htmlName,
|
||||
dynamicHtmlName: form.dynamicHtmlName,
|
||||
error: form.error || null,
|
||||
attributes: 'name = "' + form.htmlName + '" id = "' + form.htmlName + '"',
|
||||
formType: 'formGroup'
|
||||
};
|
||||
Object.keys(form).forEach(function (key) {
|
||||
if (form[key] instanceof formField) {
|
||||
result[key] = field(form[key]);
|
||||
} else if (form[key] instanceof formAction) {
|
||||
result[key] = action(form[key]);
|
||||
} else if (form[key] instanceof formGroup) {
|
||||
result[key] = parseForm(form[key]);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the values of an object to form
|
||||
* @param {Object} object - the object to set the new form values to
|
||||
* @param {dw.web.Form|dw.web.FormGroup} currentForm - Form to be parsed
|
||||
*/
|
||||
function copyObjectToForm(object, currentForm) {
|
||||
Object.keys(currentForm).forEach(function (key) {
|
||||
if (currentForm[key] && currentForm[key].formType === 'formGroup') {
|
||||
copyObjectToForm(object, currentForm[key]);
|
||||
} else if (object[key] && !Object.hasOwnProperty.call(currentForm[key], 'options')) {
|
||||
currentForm[key].value = object[key]; // eslint-disable-line no-param-reassign
|
||||
} else if (object[key] && Object.hasOwnProperty.call(currentForm[key], 'options')) {
|
||||
currentForm[key].options.forEach(function (option) {
|
||||
if (option.value === object[key]) {
|
||||
option.selected = true; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get values of a formGroup to object
|
||||
* @param {dw.web.FormGroup} formGroup - Form group
|
||||
* @param {string} name - The name of the formGroup
|
||||
* @return {Object} Object with nested values
|
||||
*/
|
||||
function findValue(formGroup, name) {
|
||||
var ObjectWrapper = {};
|
||||
ObjectWrapper[name] = {};
|
||||
|
||||
Object.keys(formGroup).forEach(function (key) {
|
||||
var formField = formGroup[key];
|
||||
if (formField instanceof Object) {
|
||||
if (formField.formType === 'formField') {
|
||||
ObjectWrapper[name][key] = formField.value;
|
||||
} else if (formField.formType === 'formGroup') {
|
||||
ObjectWrapper[name][key] = findValue(formField, key)[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ObjectWrapper;
|
||||
}
|
||||
|
||||
module.exports = function (session) {
|
||||
return {
|
||||
getForm: function (name) {
|
||||
var currentForm = session.forms[name];
|
||||
var result = parseForm(currentForm);
|
||||
result.base = currentForm;
|
||||
result.clear = function () {
|
||||
currentForm.clearFormElement();
|
||||
var clearedForm = parseForm(currentForm);
|
||||
Object.keys(clearedForm).forEach(function (key) {
|
||||
this[key] = clearedForm[key];
|
||||
}, this);
|
||||
};
|
||||
result.copyFrom = function (object) {
|
||||
copyObjectToForm(object, result);
|
||||
};
|
||||
result.toObject = function () {
|
||||
var formObj = {};
|
||||
var form = this;
|
||||
Object.keys(form).forEach(function (key) {
|
||||
var formField = form[key];
|
||||
if (typeof form[key] !== 'function'
|
||||
&& formField instanceof Object) {
|
||||
if (formField.formType === 'formField') {
|
||||
formObj[key] = formField.value;
|
||||
} else if (formField.formType === 'formGroup') {
|
||||
var nested = findValue(formField, key);
|
||||
formObj[key] = nested[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
return formObj;
|
||||
};
|
||||
return result;
|
||||
}
|
||||
};
|
||||
};
|
@@ -0,0 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Middleware filter for get requests
|
||||
* @param {Object} req - Request object
|
||||
* @param {Object} res - Response object
|
||||
* @param {Function} next - Next call in the middleware chain
|
||||
* @returns {void}
|
||||
*/
|
||||
function get(req, res, next) {
|
||||
if (req.httpMethod === 'GET') {
|
||||
next();
|
||||
} else {
|
||||
next(new Error('Params do not match route'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware filter for post requests
|
||||
* @param {Object} req - Request object
|
||||
* @param {Object} res - Response object
|
||||
* @param {Function} next - Next call in the middleware chain
|
||||
* @returns {void}
|
||||
*/
|
||||
function post(req, res, next) {
|
||||
if (req.httpMethod === 'POST') {
|
||||
next();
|
||||
} else {
|
||||
next(new Error('Params do not match route'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware filter for https requests
|
||||
* @param {Object} req - Request object
|
||||
* @param {Object} res - Response object
|
||||
* @param {Function} next - Next call in the middleware chain
|
||||
* @returns {void}
|
||||
*/
|
||||
function https(req, res, next) {
|
||||
if (req.https) {
|
||||
next();
|
||||
} else {
|
||||
next(new Error('Params do not match route'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware filter for http requests
|
||||
* @param {Object} req - Request object
|
||||
* @param {Object} res - Response object
|
||||
* @param {Function} next - Next call in the middleware chain
|
||||
* @returns {void}
|
||||
*/
|
||||
function http(req, res, next) {
|
||||
if (!req.https) {
|
||||
next();
|
||||
} else {
|
||||
next(new Error('Params do not match route'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware to filter for remote includes
|
||||
* @param {Object} req - Request object
|
||||
* @param {Object} res - Response object
|
||||
* @param {Function} next - Next call in the middleware chain
|
||||
* @returns {void}
|
||||
*/
|
||||
function include(req, res, next) {
|
||||
if (req.includeRequest) {
|
||||
next();
|
||||
} else {
|
||||
next(new Error('Params do not match route'));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
get: get,
|
||||
post: post,
|
||||
https: https,
|
||||
http: http,
|
||||
include: include
|
||||
};
|
@@ -0,0 +1,153 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Zips the pref[n|v] http query params
|
||||
*
|
||||
* The Platform allows the use of multiple preference names and values to filter search results,
|
||||
* eg.: http://<sandbox>.com/../Search-Show?prefn1=refinementColor&prefv1=Blue&prefn2=size&prefv2=16
|
||||
*
|
||||
* @param {Object} preferences - HTTP query parameters map specific to selected refinement values
|
||||
* @return {Object} Map of provided preference name/value pairs
|
||||
*/
|
||||
function parsePreferences(preferences) {
|
||||
var params = {};
|
||||
var count = Math.floor(Object.keys(preferences).length / 2);
|
||||
var key = '';
|
||||
var value = '';
|
||||
|
||||
for (var i = 1; i < count + 1; i++) {
|
||||
key = preferences['prefn' + i];
|
||||
if (preferences['prefmin' + i]) {
|
||||
value = {
|
||||
min: preferences['prefmin' + i],
|
||||
max: preferences['prefmax' + i]
|
||||
};
|
||||
} else {
|
||||
value = preferences['prefv' + i];
|
||||
}
|
||||
params[key] = value;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect duplicate parameters and be sure to set object[key] as an array of those parameter values
|
||||
*
|
||||
* @param {Object} object The object to check for existing values.
|
||||
* @param {string} key The key to set on object for the new value.
|
||||
* @param {string} value The new value to be added to object[key].
|
||||
* @return {Object} Value or array of values if object[key] has already exists.
|
||||
*/
|
||||
function parameterToArray(object, key, value) {
|
||||
var result = value;
|
||||
if (object[key]) {
|
||||
result = object[key];
|
||||
if (!(result instanceof Array)) {
|
||||
result = [object[key]];
|
||||
}
|
||||
result.push(value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var querystring = function (raw) {
|
||||
var pair;
|
||||
var left;
|
||||
var preferences = {};
|
||||
|
||||
if (raw && raw.length > 0) {
|
||||
var qs = raw.substring(raw.indexOf('?') + 1).replace(/\+/g, '%20').split('&');
|
||||
for (var i = qs.length - 1; i >= 0; i--) {
|
||||
pair = qs[i].split('=');
|
||||
left = decodeURIComponent(pair[0]);
|
||||
if (left.indexOf('dwvar_') === 0) {
|
||||
left = left.replace(/__/g, '++');
|
||||
var variableParts = left.split('_');
|
||||
if (variableParts.length >= 3) {
|
||||
if (!this.variables) {
|
||||
this.variables = {};
|
||||
}
|
||||
this.variables[variableParts.slice(2).join('_')] = {
|
||||
id: variableParts[1].replace(/\+\+/g, '_'),
|
||||
value: decodeURIComponent(pair[1])
|
||||
};
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
} else if (left.indexOf('dwopt_') === 0) {
|
||||
left = left.replace(/__/g, '++');
|
||||
var optionParts = left.split('_');
|
||||
var productId = optionParts[1].replace(/\+\+/g, '_');
|
||||
var optionId = optionParts.slice(2).join('_');
|
||||
var selectedOptionValueId = decodeURIComponent(pair[1]);
|
||||
if (optionParts.length >= 3) {
|
||||
if (!this.options) {
|
||||
this.options = [];
|
||||
}
|
||||
this.options.push({
|
||||
optionId: optionId,
|
||||
selectedValueId: selectedOptionValueId,
|
||||
productId: productId
|
||||
});
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
} else if (left.indexOf('pref') === 0) {
|
||||
preferences[left] = decodeURIComponent(pair[1]);
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
|
||||
this[left] = parameterToArray(this, left, decodeURIComponent(pair[1]));
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(preferences).length) {
|
||||
this.preferences = parsePreferences(preferences);
|
||||
}
|
||||
};
|
||||
|
||||
querystring.prototype.toString = function () {
|
||||
var result = [];
|
||||
var prefKeyIdx = 1;
|
||||
var preferences = {};
|
||||
|
||||
Object.keys(this).forEach(function (key) {
|
||||
if (key === 'variables' && this.variables instanceof Object) {
|
||||
Object.keys(this.variables).forEach(function (variable) {
|
||||
result.push('dwvar_' +
|
||||
this.variables[variable].id.replace(/_/g, '__') + '_' +
|
||||
variable + '=' + encodeURIComponent(this.variables[variable].value));
|
||||
}, this);
|
||||
} else if (key === 'options' && this.options instanceof Array) {
|
||||
this.options.forEach(function (option) {
|
||||
result.push('dwopt_' +
|
||||
option.productId.replace(/_/g, '__') + '_' +
|
||||
option.optionId + '=' + encodeURIComponent(option.selectedValueId));
|
||||
});
|
||||
} else if (key === 'preferences' && this.preferences instanceof Object) {
|
||||
preferences = this.preferences;
|
||||
Object.keys(preferences).forEach(function (prefKey) {
|
||||
// Due to the nature of what is passed to this function this may be the literal string "undefined"
|
||||
if (prefKey !== 'undefined' && preferences[prefKey]) {
|
||||
result.push('prefn' + prefKeyIdx + '=' + encodeURIComponent(prefKey));
|
||||
if (preferences[prefKey].min) {
|
||||
result.push('prefmin' + prefKeyIdx + '=' + encodeURIComponent(preferences[prefKey].min));
|
||||
result.push('prefmax' + prefKeyIdx + '=' + encodeURIComponent(preferences[prefKey].max));
|
||||
} else {
|
||||
result.push('prefv' + prefKeyIdx + '=' + encodeURIComponent(preferences[prefKey]));
|
||||
}
|
||||
} else {
|
||||
var Logger = require('dw/system/Logger');
|
||||
Logger.warn('We were passed a key "undefined in the preferences object in queryString.js');
|
||||
}
|
||||
prefKeyIdx++;
|
||||
});
|
||||
} else {
|
||||
result.push(encodeURIComponent(key) + '=' + encodeURIComponent(this[key]));
|
||||
}
|
||||
}, this);
|
||||
|
||||
return result.sort().join('&');
|
||||
};
|
||||
|
||||
module.exports = querystring;
|
@@ -0,0 +1,134 @@
|
||||
'use strict';
|
||||
/* global XML */
|
||||
|
||||
var isml = require('dw/template/ISML');
|
||||
var PageMgr = require('dw/experience/PageMgr');
|
||||
|
||||
/**
|
||||
* Render an ISML template
|
||||
* @param {string} view - Path to an ISML template
|
||||
* @param {Object} viewData - Data to be passed as pdict
|
||||
* @param {Object} response - Response object
|
||||
* @returns {void}
|
||||
*/
|
||||
function template(view, viewData) {
|
||||
// create a shallow copy of the data
|
||||
var data = {};
|
||||
Object.keys(viewData).forEach(function (key) {
|
||||
data[key] = viewData[key];
|
||||
});
|
||||
|
||||
try {
|
||||
isml.renderTemplate(view, data);
|
||||
} catch (e) {
|
||||
throw new Error(e.javaMessage + '\n\r' + e.stack, e.fileName, e.lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render JSON as an output
|
||||
* @param {Object} data - Object to be turned into JSON
|
||||
* @param {Object} response - Response object
|
||||
* @returns {void}
|
||||
*/
|
||||
function json(data, response) {
|
||||
response.setContentType('application/json');
|
||||
response.base.writer.print(JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render XML as an output
|
||||
* @param {Object} viewData - Object to be turned into XML
|
||||
* @param {Object} response - Response object
|
||||
* @returns {void}
|
||||
*/
|
||||
function xml(viewData, response) {
|
||||
var XML_CHAR_MAP = {
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'&': '&',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
// Valid XML needs a single root.
|
||||
var xmlData = '<response>';
|
||||
|
||||
Object.keys(viewData).forEach(function (key) {
|
||||
if (key === 'xml') {
|
||||
xmlData += viewData[key];
|
||||
} else {
|
||||
xmlData +=
|
||||
'<' + key + '>' + viewData[key].replace(/[<>&"']/g, function (ch) {
|
||||
return XML_CHAR_MAP[ch];
|
||||
}) + '</' + key + '>';
|
||||
}
|
||||
});
|
||||
|
||||
// Close the root
|
||||
xmlData += '</response>';
|
||||
|
||||
response.setContentType('application/xml');
|
||||
|
||||
try {
|
||||
response.base.writer.print(new XML(xmlData));
|
||||
} catch (e) {
|
||||
throw new Error(e.message + '\n\r' + e.stack, e.fileName, e.lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a page designer page
|
||||
* @param {string} pageID - Path to an ISML template
|
||||
* @param {dw.util.HashMap} aspectAttributes - aspectAttributes to be passed to the PageMgr
|
||||
* @param {Object} data - Data to be passed
|
||||
* @param {Object} response - Response object
|
||||
* @returns {void}
|
||||
*/
|
||||
function page(pageID, aspectAttributes, data, response) {
|
||||
if (aspectAttributes && !aspectAttributes.isEmpty()) {
|
||||
response.base.writer.print(PageMgr.renderPage(pageID, aspectAttributes, JSON.stringify(data)));
|
||||
} else {
|
||||
response.base.writer.print(PageMgr.renderPage(pageID, JSON.stringify(data)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines what to render
|
||||
* @param {Object} res - Response object
|
||||
* @returns {void}
|
||||
*/
|
||||
function applyRenderings(res) {
|
||||
if (res.renderings.length) {
|
||||
res.renderings.forEach(function (element) {
|
||||
if (element.type === 'render') {
|
||||
switch (element.subType) {
|
||||
case 'isml':
|
||||
template(element.view, res.viewData);
|
||||
break;
|
||||
case 'json':
|
||||
json(res.viewData, res);
|
||||
break;
|
||||
case 'xml':
|
||||
xml(res.viewData, res);
|
||||
break;
|
||||
case 'page':
|
||||
page(element.page, element.aspectAttributes, res.viewData, res);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Cannot render template without name or data');
|
||||
}
|
||||
} else if (element.type === 'print') {
|
||||
res.base.writer.print(element.message);
|
||||
} else {
|
||||
throw new Error('Cannot render template without name or data');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Error('Cannot render template without name or data');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
applyRenderings: applyRenderings
|
||||
};
|
@@ -0,0 +1,394 @@
|
||||
'use strict';
|
||||
|
||||
var QueryString = require('./queryString');
|
||||
var SimpleCache = require('./simpleCache');
|
||||
|
||||
/**
|
||||
* Translates global session object into local object
|
||||
* @param {dw.session} session - Global customer object
|
||||
* @returns {Object} local instance of session object
|
||||
*/
|
||||
function getSessionObject(session) {
|
||||
var sessionObject = {
|
||||
privacyCache: new SimpleCache(session.privacy),
|
||||
raw: session,
|
||||
currency: {
|
||||
currencyCode: session.currency.currencyCode,
|
||||
defaultFractionDigits: session.currency.defaultFractionDigits,
|
||||
name: session.currency.name,
|
||||
symbol: session.currency.symbol
|
||||
},
|
||||
setCurrency: function (value) {
|
||||
session.setCurrency(value);
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperty(sessionObject, 'clickStream', {
|
||||
get: function () {
|
||||
var clickStreamEntries = session.clickStream.clicks.toArray();
|
||||
var clicks = clickStreamEntries.map(function (clickObj) {
|
||||
return {
|
||||
host: clickObj.host,
|
||||
locale: clickObj.locale,
|
||||
path: clickObj.path,
|
||||
pipelineName: clickObj.pipelineName,
|
||||
queryString: clickObj.queryString,
|
||||
referer: clickObj.referer,
|
||||
remoteAddress: clickObj.remoteAddress,
|
||||
timestamp: clickObj.timestamp,
|
||||
url: clickObj.url,
|
||||
userAgent: clickObj.userAgent
|
||||
};
|
||||
});
|
||||
return {
|
||||
clicks: clicks,
|
||||
first: clicks[0],
|
||||
last: clicks[clicks.length - 1],
|
||||
partial: session.clickStream.partial
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return sessionObject;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Retrieves and normalizes form data from httpParameterMap
|
||||
* @param {dw.web.httpParameterMap} items - original parameters
|
||||
* @param {Object} qs - Object containing querystring
|
||||
* @return {Object} Object containing key value pairs submitted from the form
|
||||
*/
|
||||
function getFormData(items, qs) {
|
||||
if (!items || !items.parameterNames) {
|
||||
return {};
|
||||
}
|
||||
var allKeys = items.parameterNames;
|
||||
var result = {};
|
||||
if (allKeys.length > 0) {
|
||||
var iterator = allKeys.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
var key = iterator.next();
|
||||
var value = items.get(key);
|
||||
|
||||
if (value.rawValue && !qs[key]) {
|
||||
result[key] = value.rawValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves session locale info
|
||||
*
|
||||
* @param {string} locale - Session locale code, xx_XX
|
||||
* @param {dw.util.Currency} currency - Session currency
|
||||
* @return {Object} - Session locale info
|
||||
*/
|
||||
function getCurrentLocale(locale, currency) {
|
||||
return {
|
||||
id: locale,
|
||||
currency: {
|
||||
currencyCode: currency.currencyCode,
|
||||
defaultFractionDigits: currency.defaultFractionDigits,
|
||||
name: currency.name,
|
||||
symbol: currency.symbol
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates global customer's preferredAddress into local object
|
||||
* @param {Object} address - a CustomerAddress or OrderAddress
|
||||
* @returns {Object} local instance of address object
|
||||
*/
|
||||
function getAddressObject(address) {
|
||||
var addressObject = null;
|
||||
if (address) {
|
||||
addressObject = {
|
||||
address1: address.address1,
|
||||
address2: address.address2,
|
||||
city: address.city,
|
||||
companyName: address.companyName,
|
||||
countryCode: {
|
||||
displayValue: address.countryCode.displayValue,
|
||||
value: address.countryCode.value
|
||||
},
|
||||
firstName: address.firstName,
|
||||
lastName: address.lastName,
|
||||
ID: address.ID,
|
||||
phone: address.phone,
|
||||
postalCode: address.postalCode,
|
||||
stateCode: address.stateCode,
|
||||
postBox: address.postBox,
|
||||
salutation: address.salutation,
|
||||
secondName: address.secondName,
|
||||
suffix: address.suffix,
|
||||
suite: address.suite,
|
||||
title: address.title,
|
||||
raw: address
|
||||
};
|
||||
}
|
||||
return addressObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of payment instruments for the current user
|
||||
* @param {Array} rawPaymentInstruments - current customer's payment instruments
|
||||
* @returns {Array} an array of payment instruments
|
||||
*/
|
||||
function getPaymentInstruments(rawPaymentInstruments) {
|
||||
var paymentInstruments = [];
|
||||
|
||||
if (rawPaymentInstruments.getLength() > 0) {
|
||||
var iterator = rawPaymentInstruments.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
var item = iterator.next();
|
||||
paymentInstruments.push({
|
||||
creditCardHolder: item.creditCardHolder,
|
||||
maskedCreditCardNumber: item.maskedCreditCardNumber,
|
||||
creditCardType: item.creditCardType,
|
||||
creditCardExpirationMonth: item.creditCardExpirationMonth,
|
||||
creditCardExpirationYear: item.creditCardExpirationYear,
|
||||
UUID: item.UUID,
|
||||
creditCardNumber: Object.hasOwnProperty.call(item, 'creditCardNumber')
|
||||
? item.creditCardNumber
|
||||
: null,
|
||||
raw: item
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return paymentInstruments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates global customer object into local object
|
||||
* @param {dw.customer.Customer} customer - Global customer object
|
||||
* @returns {Object} local instance of customer object
|
||||
*/
|
||||
function getCustomerObject(customer) {
|
||||
if (!customer || !customer.profile) {
|
||||
return {
|
||||
raw: customer
|
||||
};
|
||||
}
|
||||
if (!customer.authenticated) {
|
||||
return {
|
||||
raw: customer,
|
||||
credentials: {
|
||||
username: customer.profile.credentials.login
|
||||
}
|
||||
};
|
||||
}
|
||||
var preferredAddress = customer.addressBook.preferredAddress;
|
||||
var result;
|
||||
result = {
|
||||
raw: customer,
|
||||
profile: {
|
||||
lastName: customer.profile.lastName,
|
||||
firstName: customer.profile.firstName,
|
||||
email: customer.profile.email,
|
||||
phone: customer.profile.phoneHome,
|
||||
customerNo: customer.profile.customerNo
|
||||
},
|
||||
addressBook: {
|
||||
preferredAddress: getAddressObject(preferredAddress),
|
||||
addresses: []
|
||||
},
|
||||
wallet: {
|
||||
paymentInstruments: getPaymentInstruments(customer.profile.wallet.paymentInstruments)
|
||||
}
|
||||
};
|
||||
if (customer.addressBook.addresses && customer.addressBook.addresses.length > 0) {
|
||||
for (var i = 0, ii = customer.addressBook.addresses.length; i < ii; i++) {
|
||||
result.addressBook.addresses.push(getAddressObject(customer.addressBook.addresses[i]));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* set currency of the current locale if there's a mismatch
|
||||
* @param {Object} request - Global request object
|
||||
* @param {dw.system.Session} session - Global session object
|
||||
*/
|
||||
function setCurrency(request, session) {
|
||||
var Locale = require('dw/util/Locale');
|
||||
var currency = require('dw/util/Currency');
|
||||
var countries = require('*/cartridge/config/countries');
|
||||
var currentLocale = Locale.getLocale(request.locale);
|
||||
|
||||
var currentCountry = !currentLocale
|
||||
? countries[0]
|
||||
: countries.filter(function (country) {
|
||||
return country.id === currentLocale.ID;
|
||||
})[0];
|
||||
|
||||
if (session.currency
|
||||
&& currentCountry
|
||||
&& session.currency.currencyCode !== currentCountry.currencyCode
|
||||
&& (!currentCountry.alternativeCurrencyCodes
|
||||
|| currentCountry.alternativeCurrencyCodes.indexOf(session.currency.currencyCode) < 0
|
||||
)
|
||||
) {
|
||||
session.setCurrency(currency.getCurrency(currentCountry.currencyCode));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get a local instance of the geo location object
|
||||
* @param {Object} request - Global request object
|
||||
* @returns {Object} object containing geo location information
|
||||
*/
|
||||
function getGeolocationObject(request) {
|
||||
var Locale = require('dw/util/Locale');
|
||||
var currentLocale = Locale.getLocale(request.locale);
|
||||
|
||||
return {
|
||||
countryCode: request.geolocation ? request.geolocation.countryCode : currentLocale.country,
|
||||
latitude: request.geolocation ? request.geolocation.latitude : 90.0000,
|
||||
longitude: request.geolocation ? request.geolocation.longitude : 0.0000
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request body as string if it is a POST or PUT
|
||||
* @param {Object} request - Global request object
|
||||
* @returns {string|Null} the request body as string
|
||||
*/
|
||||
function getRequestBodyAsString(request) {
|
||||
var result = null;
|
||||
|
||||
if (request
|
||||
&& (request.httpMethod === 'POST' || request.httpMethod === 'PUT')
|
||||
&& request.httpParameterMap
|
||||
) {
|
||||
result = request.httpParameterMap.requestBodyAsString;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a local instance of the pageMetaData object
|
||||
* @param {Object} pageMetaData - Global request pageMetaData object
|
||||
* @returns {Object} object containing pageMetaData information
|
||||
*/
|
||||
function getPageMetaData(pageMetaData) {
|
||||
var pageMetaDataObject = {
|
||||
title: pageMetaData.title,
|
||||
description: pageMetaData.description,
|
||||
keywords: pageMetaData.keywords,
|
||||
pageMetaTags: pageMetaData.pageMetaTags,
|
||||
addPageMetaTags: function (pageMetaTags) {
|
||||
pageMetaData.addPageMetaTags(pageMetaTags);
|
||||
},
|
||||
setTitle: function (title) {
|
||||
pageMetaData.setTitle(title);
|
||||
},
|
||||
setDescription: function (description) {
|
||||
pageMetaData.setDescription(description);
|
||||
},
|
||||
setKeywords: function (keywords) {
|
||||
pageMetaData.setKeywords(keywords);
|
||||
}
|
||||
};
|
||||
|
||||
return pageMetaDataObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @classdesc Local instance of request object with customer object in it
|
||||
*
|
||||
* Translates global request and customer object to local one
|
||||
* @param {Object} request - Global request object
|
||||
* @param {dw.customer.Customer} customer - Global customer object
|
||||
* @param {dw.system.Session} session - Global session object
|
||||
*/
|
||||
function Request(request, customer, session) {
|
||||
// Avoid currency check for remote includes
|
||||
if (!request.includeRequest) {
|
||||
setCurrency(request, session);
|
||||
}
|
||||
|
||||
this.httpMethod = request.httpMethod;
|
||||
this.host = request.httpHost;
|
||||
this.path = request.httpPath;
|
||||
this.httpHeaders = request.httpHeaders;
|
||||
this.https = request.isHttpSecure();
|
||||
this.includeRequest = request.includeRequest;
|
||||
this.setLocale = function (localeID) {
|
||||
return request.setLocale(localeID);
|
||||
};
|
||||
|
||||
Object.defineProperty(this, 'session', {
|
||||
get: function () {
|
||||
return getSessionObject(session);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'httpParameterMap', {
|
||||
get: function () {
|
||||
return request.httpParameterMap;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'querystring', {
|
||||
get: function () {
|
||||
return new QueryString(request.httpQueryString);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'form', {
|
||||
get: function () {
|
||||
return getFormData(request.httpParameterMap, this.querystring);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'body', {
|
||||
get: function () {
|
||||
return getRequestBodyAsString(request);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'geolocation', {
|
||||
get: function () {
|
||||
return getGeolocationObject(request);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'currentCustomer', {
|
||||
get: function () {
|
||||
return getCustomerObject(customer);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'locale', {
|
||||
get: function () {
|
||||
return getCurrentLocale(request.locale, session.currency);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'remoteAddress', {
|
||||
get: function () {
|
||||
return request.getHttpRemoteAddress();
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'referer', {
|
||||
get: function () {
|
||||
return request.getHttpReferer();
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'pageMetaData', {
|
||||
get: function () {
|
||||
return getPageMetaData(request.pageMetaData);
|
||||
}
|
||||
});
|
||||
}
|
||||
module.exports = Request;
|
@@ -0,0 +1,194 @@
|
||||
'use strict';
|
||||
|
||||
var assign = require('./assign');
|
||||
var httpHeadersConfig = require('*/cartridge/config/httpHeadersConf');
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @classdesc Creates writtable response object
|
||||
*
|
||||
* @param {Object} response - Global response object
|
||||
*/
|
||||
function Response(response) {
|
||||
this.view = null;
|
||||
this.viewData = {};
|
||||
this.redirectUrl = null;
|
||||
this.redirectStatus = null;
|
||||
this.messageLog = [];
|
||||
this.base = response;
|
||||
this.cachePeriod = null;
|
||||
this.cachePeriodUnit = null;
|
||||
this.personalized = false;
|
||||
this.renderings = [];
|
||||
httpHeadersConfig.forEach(function (httpHeader) {
|
||||
this.setHttpHeader(httpHeader.id, httpHeader.value);
|
||||
}, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a list of rendering steps.
|
||||
* @param {Array} renderings - The array of rendering steps
|
||||
* @param {Object} object - An object containing what type to render
|
||||
* @returns {void}
|
||||
*/
|
||||
function appendRenderings(renderings, object) {
|
||||
var hasRendering = false;
|
||||
|
||||
if (renderings.length) {
|
||||
for (var i = renderings.length - 1; i >= 0; i--) {
|
||||
if (renderings[i].type === 'render') {
|
||||
renderings[i] = object; // eslint-disable-line no-param-reassign
|
||||
hasRendering = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasRendering) {
|
||||
renderings.push(object);
|
||||
}
|
||||
}
|
||||
|
||||
Response.prototype = {
|
||||
/**
|
||||
* Stores template name and data for rendering at the later time
|
||||
* @param {string} name - Path to a template
|
||||
* @param {Object} data - Data to be passed to the template
|
||||
* @returns {void}
|
||||
*/
|
||||
render: function render(name, data) {
|
||||
this.view = name;
|
||||
this.viewData = assign(this.viewData, data);
|
||||
|
||||
appendRenderings(this.renderings, { type: 'render', subType: 'isml', view: name });
|
||||
},
|
||||
/**
|
||||
* Stores data to be rendered as json
|
||||
* @param {Object} data - Data to be rendered as json
|
||||
* @returns {void}
|
||||
*/
|
||||
json: function json(data) {
|
||||
this.isJson = true;
|
||||
this.viewData = assign(this.viewData, data);
|
||||
|
||||
appendRenderings(this.renderings, { type: 'render', subType: 'json' });
|
||||
},
|
||||
/**
|
||||
* Stores data to be rendered as XML
|
||||
* @param {string} xmlString - The XML to print.
|
||||
* @returns {void}
|
||||
*/
|
||||
xml: function xml(xmlString) {
|
||||
this.isXml = true;
|
||||
this.viewData = assign(this.viewData, { xml: xmlString });
|
||||
|
||||
appendRenderings(this.renderings, { type: 'render', subType: 'xml' });
|
||||
},
|
||||
/**
|
||||
* Stores data to be rendered as a page designer page
|
||||
* @param {string} page - ID of the page to be rendered
|
||||
* @param {Object} data - Data to be passed to the template
|
||||
* @param {dw.util.HashMap} aspectAttributes - (optional) aspect attributes to be passed to the PageMgr
|
||||
* @returns {void}
|
||||
*/
|
||||
page: function (page, data, aspectAttributes) {
|
||||
this.viewData = assign(this.viewData, data);
|
||||
appendRenderings(this.renderings, { type: 'render', subType: 'page', page: page, aspectAttributes: aspectAttributes });
|
||||
},
|
||||
/**
|
||||
* Redirects to a given url right away
|
||||
* @param {string} url - Url to be redirected to
|
||||
* @returns {void}
|
||||
*/
|
||||
redirect: function redirect(url) {
|
||||
this.redirectUrl = url;
|
||||
},
|
||||
/**
|
||||
* Sets an optional redirect status, standard cases being 301 or 302.
|
||||
* @param {string} redirectStatus - HTTP redirect status code
|
||||
* @returns {void}
|
||||
*/
|
||||
setRedirectStatus: function setRedirectStatus(redirectStatus) {
|
||||
this.redirectStatus = redirectStatus;
|
||||
},
|
||||
/**
|
||||
* Get data that was setup for a template
|
||||
* @returns {Object} Data for the template
|
||||
*/
|
||||
getViewData: function () {
|
||||
return this.viewData;
|
||||
},
|
||||
/**
|
||||
* Updates data for the template
|
||||
* @param {Object} data - Data for template
|
||||
* @returns {void}
|
||||
*/
|
||||
setViewData: function (data) {
|
||||
this.viewData = assign(this.viewData, data);
|
||||
},
|
||||
/**
|
||||
* Logs information for output on the error page
|
||||
* @param {string[]} arguments - List of items to be logged
|
||||
* @returns {void}
|
||||
*/
|
||||
log: function log() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
|
||||
var output = args.map(function (item) {
|
||||
if (typeof item === 'object' || Array.isArray(item)) {
|
||||
return JSON.stringify(item);
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
this.messageLog.push(output.join(' '));
|
||||
},
|
||||
/**
|
||||
* Set content type for the output
|
||||
* @param {string} type - Type of the output
|
||||
* @returns {void}
|
||||
*/
|
||||
setContentType: function setContentType(type) {
|
||||
this.base.setContentType(type);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set status code of the response
|
||||
* @param {int} code - Valid HTTP return code
|
||||
* @returns {void}
|
||||
*/
|
||||
setStatusCode: function setStatusCode(code) {
|
||||
this.base.setStatus(code);
|
||||
},
|
||||
|
||||
/**
|
||||
* creates a print step to the renderings
|
||||
* @param {string} message - Message to be printed
|
||||
* @returns {void}
|
||||
*/
|
||||
print: function print(message) {
|
||||
this.renderings.push({ type: 'print', message: message });
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets current page cache expiration period value in hours
|
||||
* @param {int} period Number of hours from current time
|
||||
* @return {void}
|
||||
*/
|
||||
cacheExpiration: function cacheExpiration(period) {
|
||||
this.cachePeriod = period;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a response header with the given name and value
|
||||
* @param {string} name - the name to use for the response header
|
||||
* @param {string} value - the value to use
|
||||
* @return {void}
|
||||
*/
|
||||
setHttpHeader: function setHttpHeader(name, value) {
|
||||
this.base.setHttpHeader(name, value);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = Response;
|
@@ -0,0 +1,118 @@
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('./EventEmitter');
|
||||
|
||||
/**
|
||||
* @param {Object} req - Request object
|
||||
* @returns {Object} object containing the querystring of the loaded page
|
||||
*/
|
||||
function getPageMetadata(req) {
|
||||
var pageMetadata = {};
|
||||
var action = req.path.split('/');
|
||||
|
||||
pageMetadata.action = action[action.length - 1];
|
||||
pageMetadata.queryString = req.querystring.toString();
|
||||
pageMetadata.locale = req.locale.id;
|
||||
|
||||
return pageMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {string} name - Name of the route, corresponds to the second part of the URL
|
||||
* @param {Function[]} chain - List of functions to be executed
|
||||
* @param {Object} req - Request object
|
||||
* @param {Object} res - Response object
|
||||
*/
|
||||
function Route(name, chain, req, res) {
|
||||
this.name = name;
|
||||
this.chain = chain;
|
||||
this.req = req;
|
||||
this.res = res;
|
||||
res.setViewData(getPageMetadata(req));
|
||||
EventEmitter.call(this);
|
||||
}
|
||||
|
||||
Route.prototype = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Create a single function that chains all of the calls together, one after another
|
||||
* @returns {Function} Function to be executed when URL is hit
|
||||
*/
|
||||
Route.prototype.getRoute = function () {
|
||||
var me = this;
|
||||
return (function (err) {
|
||||
var i = 0;
|
||||
|
||||
if (err && err.ErrorText) {
|
||||
var system = require('dw/system/System');
|
||||
var showError = system.getInstanceType() !== system.PRODUCTION_SYSTEM;
|
||||
me.req.error = {
|
||||
errorText: showError ? err.ErrorText : '',
|
||||
controllerName: showError ? err.ControllerName : '',
|
||||
startNodeName: showError ? err.CurrentStartNodeName || me.name : ''
|
||||
};
|
||||
}
|
||||
|
||||
// freeze request object to avoid mutations
|
||||
Object.freeze(me.req);
|
||||
|
||||
/**
|
||||
* Go to the next step in the chain or complete the chain after the last step
|
||||
* @param {Object} error - Error object from the prevous step
|
||||
* @returns {void}
|
||||
*/
|
||||
function next(error) {
|
||||
if (error) {
|
||||
// process error here and output error template
|
||||
me.res.log(error);
|
||||
throw new Error(error.message, error.fileName, error.lineNumber);
|
||||
}
|
||||
|
||||
if (me.res.redirectUrl) {
|
||||
// if there's a pending redirect, break the chain
|
||||
me.emit('route:Redirect', me.req, me.res);
|
||||
if (me.res.redirectStatus) {
|
||||
me.res.base.redirect(me.res.redirectUrl, me.res.redirectStatus);
|
||||
} else {
|
||||
me.res.base.redirect(me.res.redirectUrl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (i < me.chain.length) {
|
||||
me.emit('route:Step', me.req, me.res);
|
||||
me.chain[i++].call(me, me.req, me.res, next);
|
||||
} else {
|
||||
me.done.call(me, me.req, me.res);
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
me.emit('route:Start', me.req, me.res);
|
||||
me.chain[0].call(me, me.req, me.res, next);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Append a middleware step into current chain
|
||||
*
|
||||
* @param {Function} step - New middleware step
|
||||
* @return {void}
|
||||
*/
|
||||
Route.prototype.append = function append(step) {
|
||||
this.chain.push(step);
|
||||
};
|
||||
|
||||
/**
|
||||
* Last step in the chain, this will render a template or output a json string
|
||||
* @param {Object} req - Request object
|
||||
* @param {Object} res - Response object
|
||||
* @returns {void}
|
||||
*/
|
||||
Route.prototype.done = function done(req, res) {
|
||||
this.emit('route:BeforeComplete', req, res);
|
||||
this.emit('route:Complete', req, res);
|
||||
};
|
||||
|
||||
module.exports = Route;
|
@@ -0,0 +1,231 @@
|
||||
/* globals request:false, response:false, customer:false, session:false */
|
||||
|
||||
'use strict';
|
||||
|
||||
var HookMgr = require('dw/system/HookMgr');
|
||||
var middleware = require('./middleware');
|
||||
var Request = require('./request');
|
||||
var Response = require('./response');
|
||||
var Route = require('./route');
|
||||
var render = require('./render');
|
||||
|
||||
//--------------------------------------------------
|
||||
// Private helpers
|
||||
//--------------------------------------------------
|
||||
|
||||
/**
|
||||
* Validate that first item is a string and all of the following items are functions
|
||||
* @param {string} name - Arguments that were passed into function
|
||||
* @param {Array} middlewareChain - middleware chain
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkParams(name, middlewareChain) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new Error('First argument should be a string');
|
||||
}
|
||||
|
||||
if (!middlewareChain.every(function (item) { return typeof item === 'function'; })) {
|
||||
throw new Error('Middleware chain can only contain functions');
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// Public Interface
|
||||
//--------------------------------------------------
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @classdesc Server is a routing solution
|
||||
*/
|
||||
function Server() {
|
||||
this.routes = {};
|
||||
}
|
||||
|
||||
Server.prototype = {
|
||||
/**
|
||||
* Creates a new route with a name and a list of middleware
|
||||
* @param {string} name - Name of the route
|
||||
* @param {Function[]} arguments - List of functions to be executed
|
||||
* @returns {void}
|
||||
*/
|
||||
use: function use(name) {
|
||||
var args = Array.isArray(arguments) ? arguments : Array.prototype.slice.call(arguments);
|
||||
var middlewareChain = args.slice(1);
|
||||
var rq = new Request(
|
||||
typeof request !== 'undefined' ? request : {},
|
||||
typeof customer !== 'undefined' ? customer : {},
|
||||
typeof session !== 'undefined' ? session : {});
|
||||
checkParams(args[0], middlewareChain);
|
||||
|
||||
var rs = new Response(typeof response !== 'undefined' ? response : {});
|
||||
|
||||
if (this.routes[name]) {
|
||||
throw new Error('Route with this name already exists');
|
||||
}
|
||||
|
||||
var route = new Route(name, middlewareChain, rq, rs);
|
||||
// Add event handler for rendering out view on completion of the request chain
|
||||
route.on('route:Complete', function onRouteCompleteHandler(req, res) {
|
||||
// compute cache value and set on response when we have a non-zero number
|
||||
if (res.cachePeriod && typeof res.cachePeriod === 'number') {
|
||||
var currentTime = new Date(Date.now());
|
||||
if (res.cachePeriodUnit && res.cachePeriodUnit === 'minutes') {
|
||||
currentTime.setMinutes(currentTime.getMinutes() + res.cachePeriod);
|
||||
} else {
|
||||
// default to hours
|
||||
currentTime.setHours(currentTime.getHours() + res.cachePeriod);
|
||||
}
|
||||
res.base.setExpires(currentTime);
|
||||
}
|
||||
// add vary by
|
||||
if (res.personalized) {
|
||||
res.base.setVaryBy('price_promotion');
|
||||
}
|
||||
|
||||
if (res.redirectUrl) {
|
||||
// if there's a pending redirect, break the chain
|
||||
route.emit('route:Redirect', req, res);
|
||||
if (res.redirectStatus) {
|
||||
res.base.redirect(res.redirectUrl, res.redirectStatus);
|
||||
} else {
|
||||
res.base.redirect(res.redirectUrl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
render.applyRenderings(res);
|
||||
});
|
||||
|
||||
this.routes[name] = route;
|
||||
|
||||
if (HookMgr.hasHook('app.server.registerRoute')) {
|
||||
// register new route, allowing route events to be registered against
|
||||
HookMgr.callHook('app.server.registerRoute', 'registerRoute', route);
|
||||
}
|
||||
|
||||
return route;
|
||||
},
|
||||
/**
|
||||
* Shortcut to "use" method that adds a check for get request
|
||||
* @param {string} name - Name of the route
|
||||
* @param {Function[]} arguments - List of functions to be executed
|
||||
* @returns {void}
|
||||
*/
|
||||
get: function get() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.splice(1, 0, middleware.get);
|
||||
return this.use.apply(this, args);
|
||||
},
|
||||
/**
|
||||
* Shortcut to "use" method that adds a check for post request
|
||||
* @param {string} name - Name of the route
|
||||
* @param {Function[]} arguments - List of functions to be executed
|
||||
* @returns {void}
|
||||
*/
|
||||
post: function post() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.splice(1, 0, middleware.post);
|
||||
return this.use.apply(this, args);
|
||||
},
|
||||
/**
|
||||
* Output an object with all of the registered routes
|
||||
* @returns {Object} Object with properties that match registered routes
|
||||
*/
|
||||
exports: function exports() {
|
||||
var exportStatement = {};
|
||||
Object.keys(this.routes).forEach(function (key) {
|
||||
exportStatement[key] = this.routes[key].getRoute();
|
||||
exportStatement[key].public = true;
|
||||
}, this);
|
||||
if (!exportStatement.__routes) {
|
||||
exportStatement.__routes = this.routes;
|
||||
}
|
||||
return exportStatement;
|
||||
},
|
||||
/**
|
||||
* Extend existing server object with a list of registered routes
|
||||
* @param {Object} server - Object that corresponds to the output of "exports" function
|
||||
* @returns {void}
|
||||
*/
|
||||
extend: function (server) {
|
||||
var newRoutes = {};
|
||||
if (!server.__routes) {
|
||||
throw new Error('Cannot extend non-valid server object');
|
||||
}
|
||||
if (Object.keys(server.__routes).length === 0) {
|
||||
throw new Error('Cannot extend server without routes');
|
||||
}
|
||||
|
||||
Object.keys(server.__routes).forEach(function (key) {
|
||||
newRoutes[key] = server.__routes[key];
|
||||
});
|
||||
|
||||
this.routes = newRoutes;
|
||||
},
|
||||
/**
|
||||
* Modify a given route by prepending additional middleware to it
|
||||
* @param {string} name - Name of the route to modify
|
||||
* @param {Function[]} arguments - List of functions to be appended
|
||||
* @returns {void}
|
||||
*/
|
||||
prepend: function prepend(name) {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var middlewareChain = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
checkParams(args[0], middlewareChain);
|
||||
|
||||
if (!this.routes[name]) {
|
||||
throw new Error('Route with this name does not exist');
|
||||
}
|
||||
|
||||
this.routes[name].chain = middlewareChain.concat(this.routes[name].chain);
|
||||
}, /**
|
||||
* Modify a given route by appending additional middleware to it
|
||||
* @param {string} name - Name of the route to modify
|
||||
* @param {Function[]} arguments - List of functions to be appended
|
||||
* @returns {void}
|
||||
*/
|
||||
append: function append(name) {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var middlewareChain = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
checkParams(args[0], middlewareChain);
|
||||
|
||||
if (!this.routes[name]) {
|
||||
throw new Error('Route with this name does not exist');
|
||||
}
|
||||
|
||||
this.routes[name].chain = this.routes[name].chain.concat(middlewareChain);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace a given route with the new one
|
||||
* @param {string} name - Name of the route to replace
|
||||
* @param {Function[]} arguments - List of functions for the route
|
||||
* @returns {void}
|
||||
*/
|
||||
replace: function replace(name) {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var middlewareChain = Array.prototype.slice.call(arguments, 1);
|
||||
checkParams(args[0], middlewareChain);
|
||||
|
||||
if (!this.routes[name]) {
|
||||
throw new Error('Route with this name does not exist');
|
||||
}
|
||||
|
||||
delete this.routes[name];
|
||||
|
||||
this.use.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a given route from the server
|
||||
* @param {string} name - Name of the route
|
||||
* @returns {Object} Route that matches the name that was passed in
|
||||
*/
|
||||
getRoute: function getRoute(name) {
|
||||
return this.routes[name];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = new Server();
|
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Represents a simple key/value store
|
||||
* @param {Object} [store] - a bracket notation-compatible object
|
||||
*/
|
||||
function SimpleCache(store) {
|
||||
this.store = store || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value in key/value store
|
||||
* @param {string} key - the Key
|
||||
* @returns {Object} the stored value
|
||||
*/
|
||||
SimpleCache.prototype.get = function (key) {
|
||||
return this.store[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a value in key/value store
|
||||
* @param {string} key - the Key
|
||||
* @param {Object} [value] - the Value to store
|
||||
*/
|
||||
SimpleCache.prototype.set = function (key, value) {
|
||||
this.store[key] = value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears values from KV store
|
||||
*/
|
||||
SimpleCache.prototype.clear = function () {
|
||||
var store = this.store;
|
||||
Object.keys(store).forEach(function (key) {
|
||||
store[key] = null;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = SimpleCache;
|
Reference in New Issue
Block a user