/**
* The Layout Move Component
*
* @module aui-layout-move
*/
var CSS_MOVING = A.getClassName('layout', 'builder', 'moving'),
CSS_MOVE_CANCEL = A.getClassName('layout', 'builder', 'move', 'cancel'),
CSS_MOVE_COL_TARGET = A.getClassName('layout', 'builder', 'move', 'col', 'target'),
CSS_MOVE_CUT_BUTTON = A.getClassName('layout', 'builder', 'move', 'cut', 'button'),
CSS_MOVE_CUT_ROW_BUTTON = A.getClassName('layout', 'builder', 'move', 'cut', 'row', 'button'),
CSS_MOVE_CUT_COL_BUTTON = A.getClassName('layout', 'builder', 'move', 'cut', 'col', 'button'),
CSS_MOVE_ROW_TARGET = A.getClassName('layout', 'builder', 'move', 'row', 'target'),
CSS_MOVE_TARGET = A.getClassName('layout', 'builder', 'move', 'target'),
CSS_MOVE_TARGET_LABEL = A.getClassName('layout', 'builder', 'move', 'target', 'label'),
CSS_ROW_CONTAINER_ROW = A.getClassName('layout', 'row', 'container', 'row'),
SELECTOR_COL = '.col',
SELECTOR_ROW = '.layout-row',
TPL_MOVE_CUT = '<div class="' + CSS_MOVE_CUT_BUTTON + '" tabindex="0"></div>',
TPL_MOVE_TARGET = '<div class="' + CSS_MOVE_TARGET + '"><span class="' + CSS_MOVE_TARGET_LABEL + '">{pasteHere}<span></div>';
/**
* A base class for Layout Move.
*
* @class A.LayoutBuilderMove
* @param {Object} config Object literal specifying layout configuration
* properties.
* @constructor
*/
function LayoutBuilderMove() {}
LayoutBuilderMove.prototype = {
/**
* Col that will be moved.
*
* @property _colToBeMoved
* @type {Node}
* @protected
*/
_colToBeMoved: null,
/**
* Row that will be moved.
*
* @property _rowToBeMoved
* @type {Node}
* @protected
*/
_rowToBeMoved: null,
/**
* Construction logic executed during instantiation.
* Lifecycle.
*
* @method initializer
* @protected
*/
initializer: function() {
this._eventHandles.push(
this.after('enableMoveColsChange', A.bind(this._afterEnableMoveColsChange, this)),
this.after('enableMoveRowsChange', A.bind(this._afterEnableMoveRowsChange, this)),
this.after('layout-row:colsChange', A.bind(this._afterMoveColsChange, this)),
this.after('layout-row:movableChange', A.bind(this._afterMoveMovableChange, this)),
this.after('layout:rowsChange', A.bind(this._afterMoveRowsChange, this)),
this.after('layout:isColumnModeChange', A.bind(this._afterMoveIsColumnModeChange, this)),
this.after('layoutChange', A.bind(this._afterMoveLayoutChange, this)),
this.after('moveStart', A.bind(this._addMovingClass, this)),
this.after('moveEnd', A.bind(this._afterMoveEnd, this)),
A.one('doc').on('key', this._onEscKey, 'esc', this)
);
this._uiSetEnableMoveRows(this.get('enableMoveRows'));
this._uiSetEnableMoveCols(this.get('enableMoveCols'));
},
/**
* Destructor implementation for the `A.LayoutBuilderMove` class. Lifecycle.
*
* @method destructor
* @protected
*/
destructor: function() {
this._unbindMoveColEvents();
this._unbindMoveRowEvents();
},
/**
* Cancels the ongoing move operation.
*
* @method cancelMove
*/
cancelMove: function() {
this.fire('moveEnd');
},
/**
* Default value for `addColMoveButton` attribute.
*
* @method _addColMoveButton
* @param {Node} colNode
* @param {Node} rowNode
* @protected
*/
_addColMoveButton: function(colNode, rowNode) {
var cutButton = A.Node.create(TPL_MOVE_CUT);
cutButton.setData('node-row', rowNode);
cutButton.setData('node-col', colNode);
cutButton.addClass(CSS_MOVE_CUT_COL_BUTTON);
colNode.append(cutButton);
},
/**
* Default value for `addColMoveTarget` attribute.
*
* @method _addColMoveTarget
* @param {A.LayoutCol} col
* @param {Number} index
* @protected
*/
_addColMoveTarget: function(col, index) {
var target = A.Node.create(A.Lang.sub(TPL_MOVE_TARGET, {
pasteHere: this.get('strings').pasteHere
}));
target.setData('col-index', index);
target.addClass(CSS_MOVE_COL_TARGET);
col.get('node').append(target);
},
/**
* Adds CSS class to indicate that any element is moving.
*
* @method _addMovingClass
* @protected
*/
_addMovingClass: function() {
this._layoutContainer.addClass(CSS_MOVING);
},
/**
* Fired after the `enableMoveCols` attribute changes.
*
* @method _afterEnableMoveColsChange
* @protected
*/
_afterEnableMoveColsChange: function() {
this._resetMoveUI();
},
/**
* Fired after the `enableMoveRows` attribute changes.
*
* @method _afterEnableMoveRowsChange
* @protected
*/
_afterEnableMoveRowsChange: function() {
this._resetMoveUI();
},
/**
* Fired after `layout-row:movable` attribute changes.
*
* @method _afterMoveMovableChange
* @param {EventFacade} event
* @protected
*/
_afterMoveMovableChange: function(event) {
var row = event.target.get('node');
if (event.newVal) {
this._insertCutButtonOnRow(row.one(SELECTOR_ROW));
}
else {
this._removeCutButtonFromRow(row);
}
},
/**
* Fired after `cols` attribute changes.
*
* @method _afterMoveColsChange
* @protected
*/
_afterMoveColsChange: function() {
this._resetMoveUI();
},
/**
* Fired after `layout:isColumnMode` attribute changes.
*
* @method _afterMoveIsColumnModeChange
* @param {EventFacade} event
* @protected
*/
_afterMoveIsColumnModeChange: function(event) {
var instance = this,
rows = this._layoutContainer.all(SELECTOR_ROW);
if (event.newVal) {
rows.each(function(row) {
if (instance._hasAnythingMovable(row)) {
instance._insertCutButtonOnCols(row);
}
});
}
else {
this.get('removeColMoveButtons')();
}
},
/**
* Fired after `layout` attribute changes.
*
* @method _afterMoveLayoutChange
* @protected
*/
_afterMoveLayoutChange: function() {
this._resetMoveUI();
},
/**
* Fired after `rows` attribute changes.
*
* @method _afterMoveRowsChange
* @protected
*/
_afterMoveRowsChange: function() {
this._resetMoveUI();
},
/**
* Appends one move button before each row.
*
* @method _appendMoveButtonToRows
* @protected
*/
_appendMoveButtonToRows: function() {
var instance = this,
rows = this._layoutContainer.all(SELECTOR_ROW);
rows.each(function(row) {
if (instance._hasAnythingMovable(row)) {
instance._insertCutButtonOnRow(row);
}
});
},
/**
* Appends a move button for each col.
*
* @method _appendMoveButtonToCols
* @protected
*/
_appendMoveButtonToCols: function() {
var instance = this,
rows = this._layoutContainer.all(SELECTOR_ROW);
rows.each(function(row) {
if (instance._hasAnythingMovable(row)) {
instance._insertCutButtonOnCols(row);
}
});
},
/**
* Binds the necessary events for the functionality of moving cols on layout.
*
* @method _bindMoveColsEvents
* @protected
*/
_bindMoveColEvents: function() {
var colCutButtonSelector = '.' + CSS_MOVE_CUT_BUTTON + '.' + CSS_MOVE_CUT_COL_BUTTON,
colTargetSelector = '.' + CSS_MOVE_TARGET + '.' + CSS_MOVE_COL_TARGET;
this._moveColEventHandles = [
this._layoutContainer.delegate('click', A.bind(this._onMouseClickOnMoveCutButton, this), colCutButtonSelector),
this._layoutContainer.delegate('key', A.bind(this._onKeyPressOnMoveCutButton, this), 'press:13', colCutButtonSelector),
this._layoutContainer.delegate('click', A.bind(this._onMouseClickOnMoveTarget, this), colTargetSelector),
this._layoutContainer.delegate('key', A.bind(this._onKeyPressOnMoveTarget, this), 'press:13', colTargetSelector)
];
},
/**
* Binds the necessary events for the functionality of moving rows on layout.
*
* @method _bindMoveRowEvents
* @protected
*/
_bindMoveRowEvents: function() {
var rowCutButtonSelector = '.' + CSS_MOVE_CUT_BUTTON + '.' + CSS_MOVE_CUT_ROW_BUTTON,
rowTargetSelector = '.' + CSS_MOVE_TARGET + '.' + CSS_MOVE_ROW_TARGET;
this._moveRowEventHandles = [
this._layoutContainer.delegate('click', A.bind(this._onMouseClickOnMoveCutButton, this), rowCutButtonSelector),
this._layoutContainer.delegate('key', A.bind(this._onKeyPressOnMoveCutButton, this), 'press:13', rowCutButtonSelector),
this._layoutContainer.delegate('click', A.bind(this._onMouseClickOnMoveTarget, this), rowTargetSelector),
this._layoutContainer.delegate('key', A.bind(this._onKeyPressOnMoveTarget, this), 'press:13', rowTargetSelector)
];
},
/**
* Default value for `chooseColMoveTarget` attribute.
*
* @method _chooseColMoveTarget
* @param {Node} cutButton
* @param {A.LayoutCol} col
* @protected
*/
_chooseColMoveTarget: function(cutButton, col) {
var instance = this,
rows = this.get('layout').get('rows');
this._colToBeMoved = col;
A.Array.forEach(rows, function(row) {
A.Array.forEach(row.get('cols'), function(col, index) {
if (col !== instance._colToBeMoved) {
instance.get('addColMoveTarget')(col, index);
}
});
});
},
/**
* Default value for `clickColMoveTarget` attribute.
*
* @method _clickColMoveTarget
* @param {Node} target
* @protected
*/
_clickColMoveTarget: function(target) {
var row = target.ancestor(SELECTOR_ROW).getData('layout-row');
this._resetMoveUI();
row.moveColContent(target.getData('col-index'), this._colToBeMoved);
},
/**
* Starts or cancels movement of rows and cols.
*
* @method _clickOnCutButton
* @param {Node} cutButton
* @protected
*/
_clickOnCutButton: function(cutButton) {
var itemToBeMoved = this._rowToBeMoved = cutButton.getData('layout-row');
this._removeAllCutButton(cutButton);
if (cutButton.hasClass(CSS_MOVE_CANCEL)) {
this.cancelMove();
return;
}
cutButton.toggleClass(CSS_MOVE_CANCEL);
if (cutButton.hasClass(CSS_MOVE_CUT_ROW_BUTTON)) {
this.get('chooseRowMoveTarget')();
}
else {
itemToBeMoved = cutButton.getData('node-col').getData('layout-col');
this.get('chooseColMoveTarget')(cutButton, cutButton.getData('node-col').getData('layout-col'));
}
this.fire('moveStart', {moveElement: itemToBeMoved});
},
/**
* Default value for `chooseRowMoveTarget` attribute.
*
* @method _createRowTargetArea
* @protected
*/
_createRowTargetArea: function() {
this._createRowTargetAreaInOneDirection('before');
this._createRowTargetAreaInOneDirection('after');
},
/**
* Create target area to move the row to one direction.
*
* @method _createRowTargetAreaInOneDirection
* @param {String} direction Should be either 'before' or 'after'.
* @protected
*/
_createRowTargetAreaInOneDirection: function(direction) {
var containerRow,
currentIndex = A.Array.indexOf(this.get('layout').get('rows'), this._rowToBeMoved),
currentRow,
method = direction === 'before' ? 'previous' : 'next',
target;
containerRow = this._rowToBeMoved.get('node')[method]('.' + CSS_ROW_CONTAINER_ROW);
while (containerRow) {
currentRow = containerRow.one(SELECTOR_ROW);
if (direction === 'before') {
currentIndex -= 1;
}
else {
currentIndex += 1;
}
if (currentRow.getData('layout-row').get('movable')) {
target = A.Node.create(A.Lang.sub(TPL_MOVE_TARGET, {
pasteHere: this.get('strings').pasteHere
}));
target.addClass(CSS_MOVE_ROW_TARGET);
target.setData('row-index', currentIndex);
containerRow.insert(target, direction);
containerRow = containerRow[method]('.' + CSS_ROW_CONTAINER_ROW);
}
else {
break;
}
}
},
/**
* Checks if the given row either can be moved or has at least one column
* that can.
*
* @method _hasAnythingMovable
* @param {A.LayoutRow} row
* @return {Boolean}
* @protected
*/
_hasAnythingMovable: function(row) {
var index,
layoutCols,
layoutRow = row.getData('layout-row');
if (layoutRow.get('movable')) {
return true;
}
layoutCols = layoutRow.get('cols');
for (index = 0; index < layoutCols.length; index++) {
if (layoutCols[index].get('movableContent')) {
return true;
}
}
return false;
},
/**
* Inserts cut buttons on cols.
*
* @method _insertCutButtonOnCols
* @param {Node} row
* @protected
*/
_insertCutButtonOnCols: function(row) {
var instance = this,
cols,
layoutCol,
rows = this._layoutContainer.all(SELECTOR_ROW);
cols = row.all(SELECTOR_COL);
if (!this.get('layout').get('isColumnMode')) {
return;
}
if (cols.size() === 1 && rows.size() === 1) {
return;
}
cols.each(function(col) {
layoutCol = col.getData('layout-col');
if (layoutCol.get('movableContent')) {
instance.get('addColMoveButton')(col, row);
}
});
},
/**
* Inserts cut button on a row.
*
* @method _insertCutButtonOnRow
* @param {Node} row
* @protected
*/
_insertCutButtonOnRow: function(row) {
var cutButton = A.Node.create(TPL_MOVE_CUT),
layoutRow = row.getData('layout-row'),
rows = this._layoutContainer.all(SELECTOR_ROW);
if (rows.size() === 1) {
return;
}
if (!layoutRow.get('movable')) {
return;
}
cutButton.addClass(CSS_MOVE_CUT_ROW_BUTTON);
cutButton.setData('node-row', row);
cutButton.setData('layout-row', layoutRow);
this._layoutContainer.insertBefore(cutButton, row);
},
/**
* Move col or row to another place.
*
* @method _moveToTarget
* @param {EventFacade} event
* @protected
*/
_moveToTarget: function(event) {
var layout = this.get('layout'),
target = event.currentTarget;
if (target.hasClass(CSS_MOVE_COL_TARGET)) {
this.get('clickColMoveTarget')(target);
}
else {
layout.moveRow(target.getData('row-index'), this._rowToBeMoved);
}
this.fire('moveEnd', event);
},
/**
* Fires when the esc key is pressed.
*
* @method _onEscKey
* @protected
*/
_onEscKey: function () {
this.cancelMove();
},
/**
* Fires when key press on cut button.
*
* @method _onKeyPressOnMoveCutButton
* @param {EventFacade} event
* @protected
*/
_onKeyPressOnMoveCutButton: function(event) {
event.preventDefault();
this._clickOnCutButton(event.currentTarget);
},
/**
* Fires when click on cut button.
*
* @method _onMouseClickOnMoveCutButton
* @param {EventFacade} event
* @protected
*/
_onMouseClickOnMoveCutButton: function(event) {
event.stopPropagation();
this._clickOnCutButton(event.currentTarget);
},
/**
* Fires when key press on target area.
*
* @method _onKeyPressOnMoveTarget
* @param {EventFacade} event
* @protected
*/
_onKeyPressOnMoveTarget: function(event) {
this._moveToTarget(event);
},
/**
* Fires when click on target area.
*
* @method _onMouseClickOnMoveTarget
* @param {EventFacade} event
* @protected
*/
_onMouseClickOnMoveTarget: function(event) {
this._moveToTarget(event);
},
/**
* Removes all cut buttons.
*
* @method _removeAllCutButton
* @param {Node} cutButton
* @protected
*/
_removeAllCutButton: function(cutButton) {
var cutRowButtons = this._layoutContainer.all('.' + CSS_MOVE_CUT_ROW_BUTTON);
this.get('removeColMoveButtons')(cutButton);
cutRowButtons.each(function(cutRowButton) {
if (cutRowButton !== cutButton) {
cutRowButton.remove();
}
});
},
/**
* Default value for `removeColMoveButtons` attribute.
*
* @method _removeColMoveButtons
* @param {Node} clickedButton
* @protected
*/
_removeColMoveButtons: function(clickedButton) {
var cutButtons = this._layoutContainer.all('.' + CSS_MOVE_CUT_COL_BUTTON);
cutButtons.each(function(cutButton) {
if (cutButton !== clickedButton) {
cutButton.remove();
}
});
},
/**
* Default value for `removeColMoveTargets` attribute.
*
* @method _removeColMoveTargets
* @protected
*/
_removeColMoveTargets: function() {
this._layoutContainer.all('.' + CSS_MOVE_COL_TARGET).remove();
},
/**
* Removes cut button from row.
*
* @method _removeCutButtonFromRow
* @param {Node} row
* @protected
*/
_removeCutButtonFromRow: function(row) {
row.one('.' + CSS_MOVE_CUT_ROW_BUTTON).remove();
},
/**
* Removes the CSS class indicating that some element has moving.
*
* @method _removeMovingClass
* @protected
*/
_afterMoveEnd: function() {
this._removeMovingClass();
this._resetMoveUI();
},
/**
* Removes the CSS class indicating that some element has moving.
*
* @method _removeMovingClass
* @protected
*/
_removeMovingClass: function() {
this._layoutContainer.removeClass(CSS_MOVING);
},
/**
* Removes all target area.
*
* @method _removeTargetArea
* @protected
*/
_removeTargetArea: function() {
this._layoutContainer.all('.' + CSS_MOVE_ROW_TARGET).remove();
this.get('removeColMoveTargets')();
},
/**
* Reset the UI to a initial state.
*
* @method _resetMoveUI
* @protected
*/
_resetMoveUI: function() {
this._removeAllCutButton();
this._removeTargetArea();
this._unbindMoveColEvents();
this._unbindMoveRowEvents();
this._uiSetEnableMoveCols(this.get('enableMoveCols'));
this._uiSetEnableMoveRows(this.get('enableMoveRows'));
},
/**
* Updates the UI according to the value of the `enableMoveCols` attribute.
*
* @method _uiSetEnableMoveCols
* @param {Boolean} enableMoveCols
* @protected
*/
_uiSetEnableMoveCols: function(enableMoveCols) {
if (enableMoveCols) {
this._appendMoveButtonToCols();
this._bindMoveColEvents();
}
else {
this._unbindMoveColEvents();
}
},
/**
* Updates the UI according to the value of the `enableMoveRows` attribute.
*
* @method _uiSetEnableMoveRows
* @param {Boolean} enableMoveRows
* @protected
*/
_uiSetEnableMoveRows: function(enableMoveRows) {
if (enableMoveRows) {
this._appendMoveButtonToRows();
this._bindMoveRowEvents();
}
else {
this._unbindMoveRowEvents();
}
},
/**
* Unbinds the events related to the functionality of moving cols from layout.
*
* @method _unbindMoveColEvents
* @protected
*/
_unbindMoveColEvents: function() {
if (this._moveColEventHandles) {
(new A.EventHandle(this._moveColEventHandles)).detach();
}
},
/**
* Unbinds the events related to the functionality of moving rows from layout.
*
* @method _unbindMoveRowEvents
* @protected
*/
_unbindMoveRowEvents: function() {
if (this._moveRowEventHandles) {
(new A.EventHandle(this._moveRowEventHandles)).detach();
}
}
};
/**
* Static property used to define the default attribute configuration for the
* `A.LayoutBuilderMove`.
*
* @property ATTRS
* @type {Object}
* @static
*/
LayoutBuilderMove.ATTRS = {
/**
* Default function to add move button to cols.
*
* @attribute addColMoveButton
* @type {Function}
*/
addColMoveButton: {
validator: A.Lang.isFunction,
valueFn: function() {
return A.bind(this._addColMoveButton, this);
}
},
/**
* Default function to add target to cols.
*
* @attribute addColMoveTarget
* @type {Function}
*/
addColMoveTarget: {
validator: A.Lang.isFunction,
valueFn: function() {
return A.bind(this._addColMoveTarget, this);
}
},
/**
* Default function to choose target on cols.
*
* @attribute chooseColMoveTarget
* @type {Function}
*/
chooseColMoveTarget: {
validator: A.Lang.isFunction,
valueFn: function() {
return A.bind(this._chooseColMoveTarget, this);
}
},
/**
* Default function to choose target on rows.
*
* @attribute chooseRowMoveTarget
* @type {Function}
*/
chooseRowMoveTarget: {
validator: A.Lang.isFunction,
valueFn: function() {
return A.bind(this._createRowTargetArea, this);
}
},
/**
* Default function to click on targets in cols.
*
* @attribute clickColMoveTarget
* @type {Function}
*/
clickColMoveTarget: {
validator: A.Lang.isFunction,
valueFn: function() {
return A.bind(this._clickColMoveTarget, this);
}
},
/**
* Flag indicating if the feature of moving cols from layout is
* enabled or not.
*
* @attribute enableMoveCols
* @default true
* @type {Boolean}
*/
enableMoveCols: {
validator: A.Lang.isBoolean,
value: true
},
/**
* Flag indicating if the feature of moving rows from layout is
* enabled or not.
*
* @attribute enableMoveRows
* @default true
* @type {Boolean}
*/
enableMoveRows: {
validator: A.Lang.isBoolean,
value: true
},
/**
* Default function to remove move button from cols.
*
* @attribute removeColMoveButtons
* @type {Function}
*/
removeColMoveButtons: {
validator: A.Lang.isFunction,
valueFn: function() {
return A.bind(this._removeColMoveButtons, this);
}
},
/**
* Default function to remove targets from cols.
*
* @attribute removeColMoveTargets
* @type {Function}
*/
removeColMoveTargets: {
validator: A.Lang.isFunction,
valueFn: function() {
return A.bind(this._removeColMoveTargets, this);
}
}
};
A.LayoutBuilderMove = LayoutBuilderMove;