define('application/widgets/grid', [
'rofl/widgets/container',
'rofl/widgets/label',
'rofl/widgets/button',
'rofl/widgets/horizontallist',
'rofl/widgets/verticallist',
'rofl/lib/l10n',
'rofl/lib/utils',
'rofl/widgets/carousel',
'antie/widgets/carousel/keyhandlers/alignfirsthandler',
'antie/widgets/carousel/keyhandlers/activatefirsthandler',
'antie/widgets/carousel/strips/cullingstrip',
'antie/events/keyevent',
'antie/runtimecontext'
], function (
Container,
Label,
Button,
HorizontalList,
VerticalList,
L10N,
Utils,
Carousel,
AlignFirstHandler,
ActivateFirstHandler,
CullingStrip,
KeyEvent,
RuntimeContext
) {
'use strict';
var PAGINATION = {
DISABLED: 'disabled',
MANUAL: 'manual',
AUTOMATIC: 'automatic'
},
device = RuntimeContext.getDevice(),
Grid;
Grid = VerticalList.extend({
/**
* Initialises the grid.
*
* @param {string} id - The id.
* @param {Object} config - The config.
*/
init: function init (id, config) {
var list,
classNames;
init.base.call(this, id, config);
if (!config) {
throw 'Grid configuration required.';
}
classNames = config.grid.classNames || [];
this.addClass(['grid', 'partials-grid'].concat(classNames));
this.setSettings(config);
this.create();
list = this._list;
list.addEventListener('selecteditemchange', Utils.bind(this.onSelectedItemChange, this));
list.addEventListener('focus', Utils.bind(this.onFocus, this));
list.addEventListener('keydown', Utils.bind(this.onKeyDown, this));
list.addEventListener('mousefocus', Utils.bind(this.onMouseFocus, this));
if (config.grid.continuousListener) {
list.setContinuousListener(true);
}
this.setActiveChildWidget(list);
switch (this._paginationSettings.type) {
case PAGINATION.MANUAL:
this.showPaginationButton();
this._paginationButton.addEventListener('select', Utils.bind(this.onSelectPartialsButton, this));
break;
case PAGINATION.DISABLED:
case PAGINATION.AUTOMATIC:
this.hidePaginationButton();
break;
}
},
/**
* Sets the grid settings.
*
* @param {Object} config - Config object.
*/
setSettings: function (config) {
var assetConfig = config.asset || {},
gridConfig = config.grid || {},
gridSettings = {},
assetSettings = {};
/*
* Determines the grid columns based on defined columns or heigh and width.
* If columns are supplied, use columns.
* If asset width and grid width are supplied, calculate columns.
* If neither is supplied, throw error.
*/
if (gridConfig.columns) {
this._columns = gridConfig.columns;
} else {
if (!assetConfig.width && !gridConfig.width) {
throw 'Columns not defined nor calculable.';
}
assetSettings.width = assetConfig.width;
gridSettings.width = gridConfig.width;
this._columns = Math.floor(gridConfig.width / assetConfig.width);
}
/*
* Sets the asset formatter used to create the grid items.
* Throws error if the formatter is not defined.
*/
if (!assetConfig.formatter) {
throw 'Grid requires an asset formatter to me supplied.';
} else {
assetSettings.formatter = new assetConfig.formatter();
}
/*
* Determines if the grid should support culling.
* Requires: asset height and grid height.
* Throws error if culling is enabled but heights are missing.
*/
if (gridConfig.culling) {
if (!gridConfig.height || !assetConfig.height) {
throw 'Culling requires the grid and asset height to be defined.';
}
gridSettings.height = gridConfig.height;
assetSettings.height = assetConfig.height;
this._culling = true;
}
/*
* Sets the alignpoint of the grid.
* Defaults to middle (0.5) if not defined.
*/
if (!Utils.isUndefined(gridConfig.alignPoint)) {
gridSettings.alignPoint = gridConfig.alignPoint;
} else {
// Default to middle aligning.
gridSettings.alignPoint = 0.5;
}
/*
* Determines the end and begin of row navigation
*/
if (gridConfig.navigateNext) {
gridSettings.navigateNext = true;
}
if (gridConfig.navigatePrevious) {
gridSettings.navigatePrevious = true;
}
if (gridConfig.widgetStrip) {
gridSettings.widgetStrip = gridConfig.widgetStrip;
}
gridSettings.animOptions = gridConfig.animOptions;
gridSettings.continuousListener = gridConfig.continuousListener;
this._setPaginationSettings(config);
this._gridSettings = gridSettings;
this._assetSettings = assetSettings;
},
/**
* Determines pagination settings.
* Requires: callback if pagination is set to manual or automatic.
* Throws error if callback is not defined but pagination is manual or automatic.
*
* @param {Object} gridConfig - The grid configuration.
*/
_setPaginationSettings: function (gridConfig) {
var paginationConfig = gridConfig.pagination || {},
paginationType = paginationConfig.type || PAGINATION.DISABLED,
paginationSettings = {},
paginationCallback;
if (paginationType !== PAGINATION.DISABLED) {
paginationCallback = Utils.getNested(gridConfig, 'pagination', 'callback');
if (!paginationCallback || !Utils.isFunction(paginationCallback)) {
throw 'Pagination setting requires a pagination callback function.';
}
paginationSettings.callback = paginationCallback;
/*
* When the pagination type is automatic,
* we want to start loading new content from a specific row.
* Defaults to 3.
*/
if (paginationType === PAGINATION.AUTOMATIC) {
paginationSettings.loadFrom = !Utils.isUndefined(paginationConfig.loadFrom)
? paginationConfig.loadFrom : 3;
} else if (paginationType === PAGINATION.MANUAL) {
paginationSettings.label = paginationConfig.showMoreText;
}
}
paginationSettings.type = paginationType;
this._paginationSettings = paginationSettings;
},
/**
* Creates the widget.
*/
create: function () {
this.createTitle();
this.createSkeleton();
this.createPaginationButton();
},
/**
* Creates the name.
*/
createTitle: function () {
var title = this._title = new Label({ text: '', classNames: ['title', 'main-title'], enableHTML: true });
this.appendChildWidget(title);
},
/**
* Creates the skeleton.
*/
createSkeleton: function () {
var list = this._list = new Carousel(null, Carousel.orientations.VERTICAL),
gridSettings = this._gridSettings,
assetHeight = this._assetSettings.height,
handler;
if (this._culling) {
list.setWidgetStrip(CullingStrip);
list.setMaskLength(gridSettings.height);
list.setWidgetLengths(assetHeight);
}
if (gridSettings.widgetStrip) {
list.setWidgetStrip(gridSettings.widgetStrip);
}
if (gridSettings.activateFirstHandler) {
handler = new ActivateFirstHandler();
} else {
handler = new AlignFirstHandler();
}
list.setNormalisedAlignPoint(gridSettings.alignPoint);
handler.setAnimationOptions(gridSettings.animOptions || {
easing: 'easeIn',
fps: 60,
duration: 200,
skipAnim: true
});
handler.attach(list);
list.addClass('skeleton');
this.appendChildWidget(list);
},
/**
* Creates the pagination button.
*/
createPaginationButton: function () {
var button = this._paginationButton = new Button(),
label = new Label({ text: this._paginationSettings.label || '' });
button.addClass('pagination');
button.appendChildWidget(label);
button.render(device);
this.appendChildWidget(button);
},
/**
* Creates a row.
*
* @returns {Object} - A row.
*/
createRow: function () {
var row = new HorizontalList(null, this._assetSettings.formatter);
if (this._gridSettings.continuousListener) {
row.setContinuousListener(true);
}
row.addClass('row');
return row;
},
/**
* Sets the data item.
*
* @param {Object} data - The data.
* @param {string} data.name - The name.
* @param {Array} data.items - The items.
* @param {boolean} [shouldFocus] - True to focus to the grid.
* @param {boolean} [shouldAlign] - True if the grid should align.
*/
setDataItem: function setDataItem (data, shouldFocus, shouldAlign) {
var list = this._list;
shouldFocus = shouldFocus !== undefined ? shouldFocus : false;
shouldAlign = shouldAlign !== undefined ? shouldAlign : true;
setDataItem.base.call(this, data);
if (list && list.outputElement) {
list.alignToIndex(0, {
skipAnim: true
});
}
list.removeChildWidgets();
if (data.title) {
this._title.setText(data.title);
} else {
this._title.addClass('display-none');
this._title.setText('');
}
this.appendData(data.items, shouldFocus, shouldAlign);
},
/**
* Prepends more data to the grid.
*
* @param {Array} items - The items.
* @param {boolean} [shouldFocus] - If the new row should be focused.
* @param {boolean} [shouldAlign] - If aligning should happen.
*/
prependData: function (items, shouldFocus, shouldAlign) {
var rowItemCount = this._columns,
list = this._list,
lastIndex = list.getActiveChildWidgetIndex(),
formatter = this._assetSettings.formatter,
rowIndex = 0,
index,
tempRow,
lastRow,
length;
shouldFocus = shouldFocus !== undefined ? shouldFocus : false;
shouldAlign = shouldAlign !== undefined ? shouldAlign : true;
// First check if we can append something to the previous row.
lastRow = list.getChildWidgetByIndex(0);
if (lastRow && lastRow.getChildWidgetCount() < rowItemCount) {
tempRow = lastRow;
}
for (index = 0, length = items.length; index < length; index++) {
// Create new row if the max columns for the row has been reached.
if (!tempRow || tempRow.getChildWidgetCount() >= rowItemCount) {
tempRow = this.createRow();
list.insert(rowIndex, tempRow, this._assetSettings.height);
rowIndex++;
}
tempRow.appendChildWidget(formatter.format(items[index]));
}
lastIndex++;
if (list.getMask().getWidgetStrip().getChildWidgetCount() > lastIndex) {
if (!shouldFocus && shouldAlign) {
list.setActiveChildIndex(lastIndex);
if (!list.getMask().getWidgetStrip().outputElement) {
list.getMask().getWidgetStrip().render(device);
}
list.alignToIndex(lastIndex);
}
if (shouldFocus) {
list.setActiveChildIndex(lastIndex);
if (!list.getMask().getWidgetStrip().outputElement) {
list.getMask().getWidgetStrip().render(device);
}
list.alignToIndex(lastIndex);
list.getChildWidgetByIndex(lastIndex).focus();
}
}
},
/**
* Appends more data to the grid.
*
* @param {Array} items - The items.
* @param {boolean} [shouldFocus] - If the new row should be focused.
* @param {boolean} [shouldAlign] - If aligning should happen.
*/
appendData: function (items, shouldFocus, shouldAlign) {
var rowItemCount = this._columns,
list = this._list,
lastIndex = list.getActiveChildWidgetIndex(),
formatter = this._assetSettings.formatter,
index,
tempRow,
lastRow,
length;
shouldFocus = shouldFocus !== undefined ? shouldFocus : false;
shouldAlign = shouldAlign !== undefined ? shouldAlign : true;
// First check if we can append something to the previous row.
lastRow = list.getChildWidgetByIndex(list.getChildWidgetCount() - 1);
if (lastRow && lastRow.getChildWidgetCount() < rowItemCount) {
tempRow = lastRow;
}
for (index = 0, length = items.length; index < length; index++) {
// Create new row if the max columns for the row has been reached.
if (!tempRow || tempRow.getChildWidgetCount() >= rowItemCount) {
tempRow = this.createRow();
list.append(tempRow, this._assetSettings.height);
}
tempRow.appendChildWidget(formatter.format(items[index]));
}
lastIndex++;
if (list.getMask().getWidgetStrip().getChildWidgetCount() > lastIndex) {
if (!shouldFocus && shouldAlign) {
list.setActiveChildIndex(lastIndex);
if (!list.getMask().getWidgetStrip().outputElement) {
list.getMask().getWidgetStrip().render(device);
}
list.alignToIndex(lastIndex);
}
if (shouldFocus) {
list.setActiveChildIndex(lastIndex);
if (!list.getMask().getWidgetStrip().outputElement) {
list.getMask().getWidgetStrip().render(device);
}
list.alignToIndex(lastIndex);
list.getChildWidgetByIndex(lastIndex).focus();
}
}
},
/**
* Updated the given asset with the new data.
*
* @param {Object} data - New data to be set.
* @param {Object} currentAsset - Current asset to update.
*/
updateAsset: function (data, currentAsset) {
var formatter = this._assetSettings.formatter;
formatter.format(data, currentAsset);
},
/**
* Displays the pagination button.
*/
showPaginationButton: function () {
this._paginationButton.setDisabled(false);
this._paginationButton.show();
},
/**
* Hides the pagination button.
*/
hidePaginationButton: function () {
this._paginationButton.setDisabled(true);
this._paginationButton.hide();
},
/**
* Gets executed when the selected item changes.
*
* @param {Object} e - The event data.
*/
onSelectedItemChange: function (e) {
if (e.item.hasClass('listitem') && e.target.isFocussed()) {
this._lastIndex = e.index;
}
},
/**
* Gets the Grid carousel.
*
* @returns {rofl.widgets.carousel} - The Grid Carousel.
*/
getList: function () {
return this._list;
},
/**
* Gets the Grid carousel's items.
*
* @returns {Array} - The Grid Carousel's items.
*/
getCarouselItems: function () {
var strip = this._list.getMask().getWidgetStrip(),
items = [],
carousels = strip.getChildWidgets();
Utils.each(carousels, function (carousel) {
items = items.concat(carousel.getChildWidgets());
});
return items;
},
/**
* Gets executed when the focus changes.
*
* @param {Object} e - The event data.
*/
onFocus: function (e) {
var target = e.target,
lastIndex,
widgetCount;
if (target.hasClass('carouselItem')) {
lastIndex = this._lastIndex || 0;
widgetCount = target.getChildWidgetCount();
if (lastIndex >= widgetCount) {
lastIndex = widgetCount - 1;
}
e.target.setActiveChildIndex(lastIndex);
}
},
/**
* Gets executed on mouse focus.
*
* @param {Object} e - The event data.
*/
onMouseFocus: function (e) {
var list = this._list;
list.alignToIndex(list.getIndexOfChildWidget(e.target.parentWidget));
},
/**
* On select partials button handler.
*/
onSelectPartialsButton: function () {
this._loadMore();
},
/**
* Loads more content.
*
* @private
*/
_loadMore: function () {
if (this._isLoading) {
return;
}
this._isLoading = true;
this._paginationSettings.callback(this.getTotal())
.then(Utils.bind(function (response) {
if (Utils.getNested(response, 'items', 'length')) {
this.appendData(response.items, true);
}
this._isLoading = false;
}, this));
},
/**
* Disposes the widget.
*/
dispose: function () {
this._paginationSettings = null;
this._assetSettings = null;
this._gridSettings = null;
this._columns = null;
this._gridSettings = null;
this._assetSettings = null;
},
/**
* Resets the widget.
*/
reset: function () {
if (this._title) {
this._title.setText('');
}
this._list.removeChildWidgets();
this._gridSettings = {};
this._assetSettings = {};
},
/**
* Removes all data.
*/
removeAll: function () {
if (this._title) {
this._title.setText('');
}
Utils.each(this._list.getChildWidgets(), function (widget) {
Utils.each(widget.getChildWidgets(), function (innerWidget) {
innerWidget.dispose();
});
});
this._list.removeChildWidgets();
},
/**
* Shows the widget.
*
* @param {Object} e - The event data.
*/
show: function show (e) {
show.base.call(this, e);
this.removeClass('display-none');
},
/**
* Hides the widget.
*
* @param {Object} e - The event data.
*/
hide: function hide (e) {
hide.base.call(this, e);
this.addClass('display-none');
},
/**
* Determines if the widget is focusable.
*
* @returns {boolean} - The focusable state.
*/
isFocusable: function isFocusable () {
if (!this.isVisible()) {
return false;
}
return isFocusable.base.call(this);
},
/**
* Aligns the grid to the active index.
*/
alignToActiveIndex: function () {
var list = this._list,
index = list.getActiveChildIndex();
list.alignToIndex(index);
},
/**
* Returns the active child index.
*
* @returns {number} - The active child index.
*/
getActiveChildIndex: function () {
return this._list.getActiveChildIndex();
},
/**
* Get the total number of items in the grid.
*
* @returns {number} - The number of items.
*/
getTotal: function () {
var rows = this._list.getChildWidgets(),
total = 0;
Utils.each(rows, function (row) {
total = total + row.getChildWidgetCount();
});
return total;
},
/**
* Get the total number of rows in the grid.
*
* @returns {number} - The number of rows.
*/
getRowsTotal: function () {
return this._list.getChildWidgetCount();
},
/**
* Aligns to proper index.
*
* @param {number} index - Index.
*/
alignToIndexFunc: function (index) {
var list = this._list;
list.alignToIndex(index);
},
/**
* Aligns to the first item.
*/
alignToFirstItem: function () {
var list = this._list;
this._lastIndex = 0;
list.alignToIndex(0);
},
/**
* KeyDown event listener.
*
* @param {Object} e - The event params.
*/
onKeyDown: function (e) {
var rowIndex;
switch (e.keyCode) {
case KeyEvent.VK_LEFT:
if (this._gridSettings.navigatePrevious) {
rowIndex = this._getPreviousIndex();
}
break;
case KeyEvent.VK_RIGHT:
if (this._gridSettings.navigateNext) {
rowIndex = this._getNextIndex();
}
break;
}
if (!Utils.isUndefined(rowIndex)) {
this._list.alignToIndex(rowIndex);
}
},
/**
* Returns the next index.
*
* @returns {number|null} - The index or null.
* @private
*/
_getNextIndex: function () {
var list = this._list,
strip = list.getMask().getWidgetStrip(),
activeIndex = strip.getActiveChildWidgetIndex(),
active = strip.getActiveChildWidget(),
newIndex,
nextRow;
// There is nothing to navigate to, already on the last row.
if (activeIndex + 1 === strip.getChildWidgetCount()) {
return undefined;
}
if (active.getChildWidgetCount() !== active.getActiveChildWidgetIndex() + 1) {
return undefined;
}
nextRow = strip.getChildWidgetByIndex(activeIndex + 1);
if (nextRow) {
newIndex = 0;
this._lastIndex = newIndex;
return activeIndex + 1;
}
},
/**
* Returns the previous index.
*
* @returns {number|null} - The previous index or null.
* @private
*/
_getPreviousIndex: function () {
var list = this._list,
strip = list.getMask().getWidgetStrip(),
activeIndex = strip.getActiveChildWidgetIndex(),
active = strip.getActiveChildWidget(),
newIndex,
previousRow;
// There is nothing to navigate to, already on the first row.
if (activeIndex === 0) {
return undefined;
}
if (active.getActiveChildWidgetIndex() !== 0) {
return undefined;
}
previousRow = strip.getChildWidgetByIndex(activeIndex - 1);
if (previousRow) {
newIndex = previousRow.getChildWidgetCount() - 1;
this._lastIndex = newIndex;
return activeIndex - 1;
}
},
/**
* Returns the active row.
*
* @returns {Object} - The active row.
*/
getActiveRow: function () {
return this._list.getActiveChildWidget();
},
/**
* Returns the number of rows.
*
* @returns {number} - Gets count of the rows.
*/
getRowChildWidgetCount: function () {
return this._list.getChildWidgetCount();
},
/**
* Returns the active item index.
*
* @returns {number} - The active item index.
*/
getActiveItemIndex: function () {
return this.getActiveRow().getActiveChildWidgetIndex();
},
/**
* Returns the title.
*
* @returns {Object} - The title label.
*/
getTitle: function () {
return this._title;
}
});
Grid.PAGINATION = PAGINATION;
return Grid;
});