/*
* Copyright 2013 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This class provides a orderable tag field.<br>
* The items of the list are not orderable.
*
* To set a simple local store :
*
* var field = Ext.create('Ametys.form.OrderableTagField', {
* multiSelect: true,
* field: ['id', 'label'],
* data: [
* {id: 0, label: 'Battlestar Galactica'},
* {id: 1, label: 'Doctor Who'},
* {id: 2, label: 'Farscape'},
* {id: 3, label: 'Firefly'},
* {id: 4, label: 'Star Trek'},
* {id: 5, label: 'Star Wars: Christmas Special'}
* ]
* });
*
*/
Ext.define('Ametys.form.widget.OrderableTagField', {
extend: 'Ext.form.field.Tag',
xtype: 'orderable-tagfield',
canDisplayComparisons: true,
/**
* @cfg {Boolean|String} [orderable=true] true if the items can be orderable by drag&drop
*/
/**
* @cfg {Boolean} [naturalOrder=false] True to sort drop down list by natural order. By default alphabetical order is applied to the store.
*/
/**
* @cfg {Boolean} [anyMatch=true] True to allow matching of the typed characters at any position in the valueField's value.
*/
/**
* @cfg {String} noResultText The text when there is no result found.
*/
noResultText: "{{i18n PLUGINS_CORE_UI_FORM_TAG_FIELD_NO_RESULT}}",
constructor: function(config)
{
var storeCfg = {
fields: config.fields || ([ 'value', {name: 'text', type: 'string'}]),
data: config.data || []
};
config.naturalOrder = Ext.isBoolean(config.naturalOrder) ? config.naturalOrder : config.naturalOrder == 'true';
if (!config.naturalOrder)
{
storeCfg.sorters = [{property: config.displayField || 'text', direction:'ASC'}]; // default order
}
config.anyMatch = Ext.isBoolean(config.anyMatch) ? config.anyMatch : config.anyMatch != "false"; // default to true
config.orderable = Ext.isBoolean(config.orderable) ? config.orderable : config.orderable != "false"; // default to true
config = Ext.applyIf(config, {
cls: 'ametys',
mode: 'local',
queryMode: 'local',
encodeSubmitValue: false,
typeAhead: true,
triggerAction: 'all',
enableKeyEvents: true,
store: new Ext.data.ArrayStore(storeCfg),
displayField: 'text',
valueField: 'value',
allowBlank: this.allowBlank,
multiSelect: config.multiSelect,
listConfig: {
emptyText: '<span class="x-tagfield-noresult-text">' + this.noResultText + '<span>'
}
});
this.callParent(arguments);
},
/**
* @protected
* Convert a value to be comparable
*/
_convertToComparableValue: function(item)
{
if (!item)
{
return "";
}
else if (Ext.isString(item))
{
return item;
}
else
{
return item[this.valueField]; // this is a guess since value are never records
}
},
/**
* When used in readonly mode, settting the comparison value will display ins/del tags
* @param {String} otherValue The value to compare the current value with
* @param {boolean} base When true, the value to compare is a base version (old) ; when false it is a future value
*/
setComparisonValue: function(otherValue, base)
{
if (base)
{
this._baseValue = otherValue || null;
this._futureValue = undefined;
}
else
{
this._baseValue = undefined;
this._futureValue = otherValue || null;
}
this.updateValue();
},
_updateComparisonRendering: function()
{
if (!this.bodyEl)
{
return;
}
if (this._baseValue !== undefined || this._futureValue !== undefined)
{
var base = this._baseValue !== undefined;
var comparison = Ametys.form.widget.Comparison.compareCanonicalValues(this.getValue(), base ? this._baseValue : this._futureValue, base, Ext.bind(this._convertToComparableValue, this));
for (var c = 1; c <= comparison.length; c++)
{
var elt = this.bodyEl.query('.x-tagfield-item:nth-child(' + c + ')', false, true);
if (elt == null)
{
// not really rendered
return;
}
elt.removeCls(["ametys-tagfield-new", "ametys-tagfield-old", "ametys-tagfield-mod"]);
switch (comparison[c-1])
{
case "added": elt.addCls("ametys-tagfield-new"); break;
case "moved": elt.addCls("ametys-tagfield-mod"); break;
case "deleted": elt.addCls("ametys-tagfield-old"); break;
default:
case "none": break;
}
}
}
},
afterRender: function()
{
this.callParent(arguments);
this._updateComparisonRendering();
if (this.multiSelect && this.orderable)
{
var me = this,
ddGroup = 'ametys-box-select-' + me.getId();
new Ext.dd.DragZone(me.listWrapper, {
ddGroup: ddGroup,
getDragData: function(e)
{
var sourceEl = e.getTarget(".x-tagfield-item", 10), d;
if (sourceEl)
{
d = sourceEl.cloneNode(true);
d.id = Ext.id();
return (me.dragData = {
sourceEl: sourceEl,
repairXY: Ext.fly(sourceEl).getXY(),
ddel: d,
rec: me.getRecordByListItemNode(sourceEl)
});
}
},
getRepairXY: function()
{
return me.dragData.repairXY;
}
});
new Ext.dd.DropZone(me.listWrapper, {
ddGroup: ddGroup,
getTargetFromEvent: function(e)
{
return e.getTarget('.x-tagfield-item') || e.getTarget('.x-tagfield-input') || e.getTarget('.x-tagfield-list');
},
onNodeEnter : function(target, dd, e, data)
{
var t = Ext.fly(target);
var r = t.getRegion();
if (t.hasCls('x-tagfield-item') && e.getX() > r.left + (r.right - r.left) / 2)
{
t.removeCls('x-tagfield-target-hoverbefore');
t.addCls('x-tagfield-target-hoverafter');
}
else
{
t.addCls('x-tagfield-target-hoverbefore');
t.removeCls('x-tagfield-target-hoverafter');
}
},
onNodeOut : function(target, dd, e, data)
{
Ext.fly(target).removeCls(['x-tagfield-target-hoverbefore', 'x-tagfield-target-hoverafter']);
},
onNodeOver : function(target, dd, e, data)
{
this.onNodeEnter(target, dd, e, data);
return Ext.dd.DropZone.prototype.dropAllowed;
},
onNodeDrop : function(target, dd, e, data)
{
var targetRecord;
var t = Ext.get(target);
if (t.hasCls("x-tagfield-item"))
{
targetRecord = me.getRecordByListItemNode(target)
}
var currentValue = me.getValue();
var movedValue = data.rec.get(me.valueField);
var newPosition = targetRecord != null ? currentValue.indexOf(targetRecord.get(me.valueField)) : currentValue.length;
var currentPosition = currentValue.indexOf(movedValue);
currentValue = Ext.Array.remove(currentValue, movedValue);
newPosition += (newPosition <= currentPosition ? 0 : -1) + (newPosition != currentPosition && t.hasCls('x-tagfield-target-hoverafter') ? 1 : 0);
currentValue = Ext.Array.insert(currentValue, newPosition, [movedValue]);
// This is to avoid setValue to be pointless
me.suspendEvents(false);
me.setValue(null);
me.resumeEvents();
me.setValue(currentValue);
return true;
}
});
}
}
});