if(typeof window.econda == 'undefined') {
window.econda = {};
var econda = window.econda;
}
if(typeof window.econda.recengine == 'undefined') {
econda.recengine = {};
}
/**
* Cross sell widget
*
* The following example shows a browser only implementation of a widget. If you are looking for a server side rendering, check #setRendererUri.
*
* <div id="ecRecommendationsContainer">Loading...</div>
* <script type="text/javascript">
* var myTemplates = {};
*
* // Renderer function for horizontal widgets.
* myTemplates.horizontal = function(data, element, esc) {
* var products = data.products;
*
* var header = [
* '<div class="widgetContainer">',
* '<div>Recommendations Title</div>',
* '<ul class="productsContainer">'
* ];
*
* var recs = [];
* for(var i=0; i<products.length; i++) {
* var p = products[i];
* recs.push('<li>' + esc.html(p.id) + '</li>');
* }
*
* var footer = ['</ul></div>'];
*
* var html = header.concat(recs, footer);
* return html.join('');
* };
*
* // Setup widget, load data and render using defined rendering function
* var widget = new econda.recengine.Widget({
* element: document.getElementById('ecRecommendationsContainer'),
* rendererFn: myTemplates.horizontal,
* accountId: '00000cec-d98025a8-912b-46a4-a57d-7a691ba7a376-1',
* id: 6,
* context: {
* productIds: ['prodId1', 'prodId2'],
* category: {
* type: 'brand',
* path: 'econda'
* }
* }
* });
* widget.render();
* </script>
*
*
* Deferred widget initialization
*
* There might be situations, where it's
*
* @class econda.recengine.Widget
*/
econda.recengine.Widget = function(config)
{
var cmp = this;
/**
* HTML container element where we'll insert the widget HTML
* @cfg element
*/
/**
* Widget id as defined in cross sell management interface
* @cfg {Number} id
*/
/**
* Uri of renderer. We'll send widget data as json encoded parameter to this renderer
* and replace content of widget element with response. If this property is not set,
* we'll send a request use an cross domain ajax call.
* @cfg {String} rendererUri
*/
/**
* Cross sell account id
* @cfg {String} accountId
*/
/**
* Request context
* @cfg {econda.recengine.Context} context
*/
/**
* Index of first product redommendation
* @cfg {Number} startIndex
*/
/**
* Number of recommendations to load
* @cfg {Number} chunkSize
*/
/**
* Uri or renderer. We'll send widget data as json encoded parameter to this renderer
* and replace content of widget element with response. If this property is not set,
* we'll send a request use an cross domain ajax call.
* @cfg {Function} rendererFn
*/
/**
* HTML container element where we'll insert the widget HTML
* Existing content will be removed.
* @property element
* @private
*/
this.element = null;
/**
* Widget id as defined in cross sell management interface
* @property {Number} id
* @private
*/
this.id = null;
/**
* Cross sell account id
* @property {String} accountId
* @private
*/
this.accountId = null;
/**
* Request context
* @property {Object|econda.recengine.Context} context
* @private
*/
this.context = null;
/**
* Index of first product redommendation
* @property {Number} [startIndex=0]
* @private
*/
this.startIndex = 0;
/**
* Number of recommendations to load
* @property {Number} [chunkSize=null]
* @private
*/
this.chunkSize = null;
/**
* Uri or renderer. We'll send widget data as json encoded parameter to this renderer
* and replace content of widget element with response. If this property is not set,
* we'll send a request use an cross domain ajax call.
* @property {String} rendererUri
* @private
*/
this.rendererUri = null;
/**
* Renderer function. Used to render result of cross domain ajax call to container specified in element
* property
* @property {Function} rendererFn
* @private
*/
this.rendererFn = null;
/**
* Set html element node, allowed is what jquery accepts.
* This can be a css selector or a javascript reference to a dom node (result of document.getElementBy...)
* @param {String|HTMLElement} element
* @method
* @return econda.recengine.Widget
*/
this.setElement = function(element)
{
this.element = element;
return this;
};
/**
* Widget id as defined in cross sell management interface.
*
* @param {Number} id
* @returns econda.recengine.Widget
*/
this.setId = function(id)
{
this.id = id;
return this;
};
/**
* Cross sell account id
* @param {String} accountId
* @returns {econda.recengine.Widget}
*/
this.setAccountId = function(accountId)
{
this.accountId = accountId;
return this;
};
/**
* Set request context (that's what we want to get recommendations for). See econda.recengine.Context for details.
*
* @param {Object|econda.recengine.Context} context
* @returns {econda.recengine.Widget}
*/
this.setContext = function(context)
{
if(context instanceof econda.recengine.Context == false) {
context = new econda.recengine.Context(context);
}
this.context = context;
return this;
};
/**
* Index of first result. Used for paging widgets.
* @param {Number} startIndex
* @returns {econda.recengine.Widget}
*/
this.setStartIndex = function(startIndex)
{
this.startIndex = startIndex;
return this;
};
/**
* Chunk size is the maximum number of results the recommendation service may return.
*
* @param {Number|null} chunkSize
* @returns {econda.recengine.Widget}
*/
this.setChunkSize = function(chunkSize)
{
this.chunkSize = chunkSize;
return this;
};
/**
* Uri on shop server where we'll send the widget configuration to. Response will be written
* to #element html container.
*
* @param {String} rendererUri
* @returns {econda.recengine.Widget}
*/
this.setRendererUri = function(rendererUri)
{
this.rendererUri = rendererUri;
return this;
};
/**
* A function we'll pass the recommendations to and use the return value as html for #element.
*
* Renderer function has the following parameters:
* <ul>
* <li>data: Recommendation server response as javascript object.</li>
* <li>element: Element as given in #setElement</li>
* <li>esc: Escape helper econda.util.EscapeHelper</li>
* </ul>
*
* @param rendererFn
* @returns {econda.recengine.Widget}
*/
this.setRendererFn = function(rendererFn)
{
this.rendererFn = rendererFn;
return this;
};
/**
* Creates a json string from widget configuration
* @return {String}
*/
this.toJson = function()
{
var cmp = this,
data = {},
allowedProperties = {id: true, accountId: true, context: true, startIndex: true, chunkSize: true};
for(var p in cmp) {
if(typeof(allowedProperties[p]) != 'undefined' && allowedProperties[p] && cmp[p] != null) {
data[p] = cmp[p];
}
}
return window.JSON.stringify(data);
};
/**
* Render widget.
*
* <ul>
* <li>If #rendererUri is set: Send widget configuration as json string to given uri
* and write result to html element specified in #element</li>
* <li>Else: Send request to cross sell server from javascript and render result using renderer
* function given in #rendererFn</li>
* </ul>
*/
this.render = function()
{
if(cmp.rendererUri) {
cmp._callRemoteRenderer();
} else {
cmp._getDataFromCrossSellServer({
success: cmp._renderDataResult
});
}
};
/**
* Call defined renderer function and pass data, target element and
* escape helper as arguments. Replace element content if there's a
* return value.
* @private
*/
this._renderDataResult = function(data)
{
if(!cmp.rendererFn) {
throw "Trying to render result but no template function set.";
}
// add property names as used in js api
if(typeof(data) == 'undefined') {
data = {};
}
if(typeof(data.products) == 'undefined') {
data.products = data.items || [];
}
if(typeof(data.startIndex)) {
data.startIndex = data.start || 0;
}
var html = cmp.rendererFn(data, cmp.element, econda.util.EscapeHelper);
if(typeof(html) != 'undefined' && html) {
$(cmp.element).html(html);
}
};
/**
* @param {Object} cfg
* @private
*/
this._getDataFromCrossSellServer = function(cfg)
{
var cfg = cfg || {};
if(typeof(cfg) == 'undefined') {
throw "Missing success callback configuration in getDataFromCrossSellServer.";
}
var widgetDataLoader = new econda.widgets._Widget(
{
"wid": cmp.id,
"aid": cmp.accountId,
"handleData": cfg.success
});
var csRequest = cmp._buildCsRequest();
widgetDataLoader._loadData(csRequest);
};
/**
* Read data from widget and return cross sell request as
* object key => value
* @private
*/
this._buildCsRequest = function()
{
var ctx = cmp.context,
csRequest = {};
csRequest.type = 'cs';
csRequest.start = cmp.startIndex;
if(cmp.chunkSize) {
csRequest.csize = cmp.chunkSize;
}
if(ctx.productIds) {
csRequest.pid = window.JSON.stringify(ctx.productIds);
}
if(ctx.category) {
csRequest['ctxcat.ct'] = ctx.category.type;
if(ctx.category.id) {
csRequest['ctxcat.id'] = ctx.category.id;
}
if(ctx.category.path) {
csRequest['ctxcat.paa'] = ctx.category.path;
}
if(ctx.category.variant) {
csRequest['ctxcat.pv'] = ctx.category.variant;
}
}
return csRequest;
};
/**
* Call remote renderer as defined in rendererUri
* @private
*/
this._callRemoteRenderer = function()
{
var cmp = this,
widgetJson;
widgetJson = cmp.toJson();
$.ajax({
type: "POST",
url: cmp.rendererUri,
data: "data=" + widgetJson,
success: function(html) {
if(cmp.rendererFn) {
// if there's a renderer function set, call it (assuming that the server returned json)
cmp._renderDataResult(html);
} else {
$(cmp.element).html(html);
}
}
});
};
// constructor
if(typeof config != 'undefined') {
if(typeof config != 'object') {
throw "Invalid config object given for recommendation widget."
}
for(var p in config) {
var setter = 'set' + p.charAt(0).toUpperCase() + p.slice(1);
if(typeof(cmp[setter]) == 'undefined') {
throw "There's no setter function for property: " + p;
}
cmp[setter](config[p]);
}
}
};
/**
* Initializes global widget definitions stored in window.csWidgets
*/
econda.recengine.Widget.init = function()
{
if(typeof window.csWidgets == 'object') {
var csWidgets = window.csWidgets;
if(csWidgets instanceof Array == false) {
csWidgets = [csWidgets];
}
for(var n=0; n<csWidgets.length; n++) {
if(csWidgets[n] instanceof econda.recengine.Widget == false) {
csWidgets[n] = new econda.recengine.Widget(csWidgets[n]);
csWidgets[n].render();
}
}
}
}
// init existing widget definitions
$(document).ready(econda.recengine.Widget.init);