/**
* The Options Data Editor Component
*
* @module aui-options-data-editor
*/
var CSS_EDITOR = A.getClassName('options', 'data', 'editor'),
CSS_EDITOR_ADD = A.getClassName('options', 'data', 'editor', 'add'),
CSS_EDITOR_OPTION = A.getClassName('options', 'data', 'editor', 'option'),
CSS_EDITOR_OPTION_HANDLE = A.getClassName('options', 'data', 'editor', 'option', 'handle'),
CSS_EDITOR_OPTION_REMOVE = A.getClassName('options', 'data', 'editor', 'option', 'remove'),
CSS_EDITOR_OPTION_TEXT = A.getClassName('options', 'data', 'editor', 'option', 'text'),
CSS_EDITOR_OPTIONS = A.getClassName('options', 'data', 'editor', 'options'),
SOURCE_UI = 'ui';
/**
* A base class for Options Data Editor.
*
* @class A.OptionsDataEditor
* @extends A.DataEditor
* @param {Object} config Object literal specifying widget configuration
* properties.
* @constructor
*/
A.OptionsDataEditor = A.Base.create('options-data-editor', A.DataEditor, [], {
TPL_EDITOR_CONTENT: '<div class="' + CSS_EDITOR + '">' +
'<div class="' + CSS_EDITOR_OPTIONS + '"></div>' +
'<button class="' + CSS_EDITOR_ADD + '">{addOption}</button></div>',
TPL_EDITOR_OPTION: '<div class="' + CSS_EDITOR_OPTION + '">' +
'<span class="' + CSS_EDITOR_OPTION_HANDLE + ' glyphicon glyphicon-sort"></span>' +
'<input class="' + CSS_EDITOR_OPTION_TEXT + ' type="text"" value="{text}"></input>' +
'<button class="' + CSS_EDITOR_OPTION_REMOVE + '">X</button></div>',
/**
* Constructor for the `A.OptionsDataEditor`. Lifecycle.
*
* @method initializer
* @protected
*/
initializer: function() {
var node = this.get('node');
this._uiSetEditedValue(this.get('editedValue'));
node.one('.' + CSS_EDITOR_ADD).after('click', A.bind(this._afterClickAddButton, this));
node.delegate('click', A.bind(this._onClickRemoveButton, this), '.' + CSS_EDITOR_OPTION_REMOVE);
node.delegate('valuechange', A.bind(this._onValueChange, this), '.' + CSS_EDITOR_OPTION_TEXT);
this.after('editedValueChange', this._afterEditedValueChange);
this._setUpDrag();
},
/**
* Returns `true` if this edited value has no elements.
*
* @method isEmpty
* @return {Boolean}
*/
isEmpty: function() {
return !this.get('editedValue').length;
},
/**
* If the Option Data Editor has a String in each option field this will return true.
*
* @method isValid
* @return {Boolean}
*/
isValid: function() {
var instance = this,
i,
options = this.get('editedValue'),
optionsLength = options.length;
if (A.OptionsDataEditor.superclass.isValid.call(instance)) {
for (i = 0; i < optionsLength; i++) {
if (!A.Lang.trim(options[i])) {
return false;
}
}
return true;
}
else {
return false;
}
},
/**
* Fired after the button for adding options is clicked.
*
* @method _afterClickAddButton
* @protected
*/
_afterClickAddButton: function() {
var editedValue = this.get('editedValue'),
optionsContainer = this.get('node').one('.' + CSS_EDITOR_OPTIONS);
optionsContainer.append(this._createOptionNode(''));
editedValue.push('');
this.set('editedValue', editedValue, {
src: SOURCE_UI
});
},
/**
* Fired after the `editedValue` attribute is set.
*
* @method _afterEditedValueChange
* @param {CustomEvent} event The fired event
* @protected
*/
_afterEditedValueChange: function(event) {
if (event.src !== SOURCE_UI) {
this._uiSetEditedValue(this.get('editedValue'));
}
},
/**
* Creates an option node.
*
* @method _createOptionNode
* @param {String} text
* @return {Node}
* @protected
*/
_createOptionNode: function(text) {
var optionNode = A.Node.create(A.Lang.sub(this.TPL_EDITOR_OPTION, {
text: text
}));
optionNode.plug(A.Plugin.Drop);
return optionNode;
},
/**
* Gets the option node's index in the options list.
*
* @method _getOptionNodeIndex
* @param {Node} node
* @protected
*/
_getOptionNodeIndex: function(node) {
var optionNode = node.ancestor('.' + CSS_EDITOR_OPTION, true),
optionNodes = this.get('node').all('.' + CSS_EDITOR_OPTION);
return optionNodes.indexOf(optionNode);
},
/**
* Moves the item from an index to another in the items array.
*
* @method _moveItem
* @param {Number} from
* @param {Number} to
* @protected
*/
_moveItem: function(from, to) {
var editedValue = this.get('editedValue'),
item = editedValue[from];
editedValue.splice(from, 1);
if (from < to) {
to--;
}
editedValue.splice(to, 0, item);
this.set('editedValue', editedValue, {
src: SOURCE_UI
});
},
/**
* Fired when a button for removing an option is clicked.
*
* @method _onClickRemoveButton
* @param {EventFacade} event
* @protected
*/
_onClickRemoveButton: function(event) {
var editedValue = this.get('editedValue'),
index = this._getOptionNodeIndex(event.currentTarget),
optionNode = event.currentTarget.ancestor('.' + CSS_EDITOR_OPTION);
this._removeOptionNode(optionNode);
editedValue.splice(index, 1);
this.set('editedValue', editedValue, {
src: SOURCE_UI
});
},
/**
* Fired when the value of one of the editors text fields changes.
*
* @method _onValueChange
* @param {EventFacade} event
* @protected
*/
_onValueChange: function(event) {
var editedValue = this.get('editedValue'),
index = this._getOptionNodeIndex(event.currentTarget);
editedValue[index] = event.currentTarget.get('value');
this.set('editedValue', editedValue, {
src: SOURCE_UI
});
},
/**
* Fired after the `drag:drag` event is triggered.
*
* @method _onDrag
* @param {EventFacade} event
* @protected
*/
_onDrag: function(event) {
if (event.target.lastXY[1] < this._lastY) {
this._draggingUp = true;
} else {
this._draggingUp = false;
}
this._lastY = event.target.lastXY[1];
},
/**
* Fired after the `drag:over` event is triggered.
*
* @method _onDragOver
* @param {EventFacade} event
* @protected
*/
_onDragOver: function(event) {
var dragNode = event.drag.get('node'),
dropNode = event.drop.get('node'),
indexDrag = this._getOptionNodeIndex(dragNode),
indexDrop;
if (!this._draggingUp) {
dropNode = dropNode.get('nextSibling');
}
indexDrop = this._getOptionNodeIndex(dropNode);
event.drop.get('node').get('parentNode').insertBefore(dragNode, dropNode);
event.drop.sizeShim();
this._moveItem(indexDrag, indexDrop);
},
/**
* Removes the given option node.
*
* @method _removeOptionNode
* @param {Node} optionNode
* @protected
*/
_removeOptionNode: function(optionNode) {
optionNode.unplug(A.Plugin.Drop);
optionNode.remove();
},
/**
* Sets up everything needed for dragging options to different positions.
*
* @method _setUpDrag
* @protected
*/
_setUpDrag: function() {
this._delegateDrag = new A.DD.Delegate({
container: this.get('node'),
handles: ['.' + CSS_EDITOR_OPTION_HANDLE],
nodes: '.' + CSS_EDITOR_OPTION
});
this._delegateDrag.dd.plug(A.Plugin.DDConstrained, {
stickY: true
});
this._delegateDrag.dd.plug(A.Plugin.DDProxy, {
cloneNode: true,
moveOnEnd: false
});
this._delegateDrag.after('drag:drag', A.bind(this._onDrag, this));
this._delegateDrag.after('drag:over', A.bind(this._onDragOver, this));
},
/**
* Updates the ui according to the value of the `editedValue` attribute.
*
* @method _uiSetEditedValue
* @param {Array} editedValue
* @protected
*/
_uiSetEditedValue: function(editedValue) {
var instance = this,
optionsContainer = this.get('node').one('.' + CSS_EDITOR_OPTIONS);
optionsContainer.all('.' + CSS_EDITOR_OPTION).each(function(optionNode) {
instance._removeOptionNode(optionNode);
});
A.Array.each(editedValue, function(option) {
optionsContainer.append(instance._createOptionNode(option));
});
}
}, {
/**
* Static property used to define the default attribute configuration
* for the `A.OptionsDataEditor`.
*
* @property ATTRS
* @type Object
* @static
*/
ATTRS: {
/**
* The value after edition.
*
* @attribute editedValue
* @default []
* @type Boolean
*/
editedValue: {
value: []
},
/**
* The value to be edited.
*
* @attribute originalValue
* @default []
* @type Array
*/
originalValue: {
value: []
},
/**
* Collection of strings used to label elements of the UI.
*
* @attribute strings
* @type {Object}
*/
strings: {
value: {
addOption: 'Tap to add an option',
required: 'REQUIRED'
}
}
}
});