/* * aciTree jQuery Plugin v4.5.0-rc.7 * http://acoderinsights.ro * * Copyright (c) 2014 Dragos Ursu * Dual licensed under the MIT or GPL Version 2 licenses. * * Require jQuery Library >= v1.9.0 http://jquery.com * + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin */ /* * This extension adds inplace edit support to aciTree, * should be used with the selectable extension. */ (function($, window, undefined) { // extra default options var options = { editable: false, // if TRUE then each item will be inplace editable editDelay: 250 // how many [ms] to wait (with mouse down) before starting the edit (on mouse release) }; // aciTree editable extension // add inplace item editing by pressing F2 key or mouse click (to enter edit mode) // press enter/escape to save/cancel the text edit var aciTree_editable = { __extend: function() { // add extra data $.extend(this._private, { editTimestamp: null }); // call the parent this._super(); }, // init editable _editableInit: function() { this._instance.jQuery.bind('acitree' + this._private.nameSpace, function(event, api, item, eventName, options) { switch (eventName) { case 'blurred': // support `selectable` extension var item = api.edited(); if (item.length) { // cancel edit/save the changes api.endEdit(); } break; case 'deselected': // support `selectable` extension if (api.isEdited(item)) { // cancel edit/save the changes api.endEdit(); } break; } }).bind('click' + this._private.nameSpace, this.proxy(function() { // click on the tree var item = this.edited(); if (item.length) { // cancel edit/save the changes this.endEdit(); } })).bind('keydown' + this._private.nameSpace, this.proxy(function(e) { switch (e.which) { case 113: // F2 // support `selectable` extension if (this.extSelectable && this.extSelectable()) { var item = this.focused(); if (item.length && !this.isEdited(item) && this.isEnabled(item)) { // enable edit on F2 key this.edit(item); // prevent default F2 key function e.preventDefault(); } } break; } })).on('mousedown' + this._private.nameSpace, '.aciTreeItem', this.proxy(function(e) { if ($(e.target).is('.aciTreeItem,.aciTreeText')) { this._private.editTimestamp = $.now(); } })).on('mouseup' + this._private.nameSpace, '.aciTreeItem', this.proxy(function(e) { if ($(e.target).is('.aciTreeItem,.aciTreeText')) { var passed = $.now() - this._private.editTimestamp; // start edit only after N [ms] but before N * 4 [ms] have passed if ((passed > this._instance.options.editDelay) && (passed < this._instance.options.editDelay * 4)) { var item = this.itemFrom(e.target); if ((!this.extSelectable || !this.extSelectable() || (this.isFocused(item) && (this.selected().length == 1))) && this.isEnabled(item)) { // edit on mouseup this.edit(item); } } } })).on('keydown' + this._private.nameSpace, 'input[type=text]', this.proxy(function(e) { // key handling switch (e.which) { case 13: // enter this.itemFrom(e.target).focus(); this.endEdit(); e.stopPropagation(); break; case 27: // escape this.itemFrom(e.target).focus(); this.endEdit({ save: false }); e.stopPropagation(); // prevent default action on ESC e.preventDefault(); break; case 38: // up case 40: // down case 37: // left case 39: // right case 33: // pgup case 34: // pgdown case 36: // home case 35: // end case 32: // space case 107: // numpad [+] case 109: // numpad [-] case 106: // numpad [*] e.stopPropagation(); break; } })).on('blur' + this._private.nameSpace, 'input[type=text]', this.proxy(function() { if (!this.extSelectable || !this.extSelectable()) { // cancel edit/save the changes this.endEdit(); } })).on('click' + this._private.nameSpace + ' dblclick' + this._private.nameSpace, 'input[type=text]', function(e) { e.stopPropagation(); }); }, // override `_initHook` _initHook: function() { if (this.extEditable()) { this._editableInit(); } // call the parent this._super(); }, // low level DOM functions _editableDOM: { // add edit field add: function(item) { var line = item.addClass('aciTreeEdited').children('.aciTreeLine'); line.find('.aciTreeText').html(''); line.find('label').attr('for', 'aciTree-editable-tree-item'); this._editableDOM.get(item).val(this.getLabel(item)); }, // remove edit field remove: function(item, label) { var line = item.removeClass('aciTreeEdited').children('.aciTreeLine'); line.find('.aciTreeText').html(this.getLabel(item)); line.find('label').removeAttr('for'); }, // return edit field get: function(item) { return item ? item.children('.aciTreeLine').find('input[type=text]') : $([]); } }, // get edited item edited: function() { return this._instance.jQuery.find('.aciTreeEdited'); }, // test if item is edited isEdited: function(item) { return item && domApi.hasClass(item[0], 'aciTreeEdited'); }, // set focus to the input _focusEdit: function(item) { var field = this._editableDOM.get(item).focus().trigger('click')[0]; if (field) { if (typeof field.selectionStart == 'number') { field.selectionStart = field.selectionEnd = field.value.length; } else if (field.createTextRange !== undefined) { var range = field.createTextRange(); range.collapse(false); range.select(); } } }, // override `setLabel` setLabel: function(item, options) { if (!this.extEditable() || !this.isEdited(item)) { // call the parent this._super(item, options); } }, // edit item inplace edit: function(item, options) { options = this._options(options, 'edit', 'editfail', 'wasedit', item); if (this.extEditable() && this.isItem(item)) { // a way to cancel the operation if (!this._trigger(item, 'beforeedit', options)) { this._fail(item, options); return; } var edited = this.edited(); if (edited.length) { if (edited[0] == item[0]) { this._notify(item, options); return; } else { this._editableDOM.remove.call(this, edited); this._trigger(edited, 'endedit', options); } } this._editableDOM.add.call(this, item); this._focusEdit(item); this._success(item, options); } else { this._fail(item, options); } }, // end edit // `options.save` when set to FALSE will not save the changes endEdit: function(options) { var item = this.edited(); options = this._options(options, 'edited', 'endeditfail', 'endedit', item); if (this.extEditable() && this.isItem(item)) { // a way to cancel the operation if (!this._trigger(item, 'beforeendedit', options)) { this._fail(item, options); return; } var text = this._editableDOM.get(item).val(); this._editableDOM.remove.call(this, item); if ((options.save === undefined) || options.save) { this.setLabel(item, { label: text }); this._success(item, options); } else { this._notify(item, options); } } else { this._fail(item, options); } }, // test if editable is enabled extEditable: function() { return this._instance.options.editable; }, // override set `option` option: function(option, value) { if (this.wasInit() && !this.isLocked()) { if ((option == 'editable') && (value != this.extEditable())) { if (value) { this._editableInit(); } else { this._editableDone(); } } } // call the parent this._super(option, value); }, // done editable _editableDone: function() { this._instance.jQuery.unbind(this._private.nameSpace); this._instance.jQuery.off(this._private.nameSpace, '.aciTreeItem'); this._instance.jQuery.off(this._private.nameSpace, 'input[type=text]'); var edited = this.edited(); if (edited.length) { this.endEdit(); } }, // override `_destroyHook` _destroyHook: function(unloaded) { if (unloaded) { this._editableDone(); } // call the parent this._super(unloaded); } }; // extend the base aciTree class and add the editable stuff aciPluginClass.plugins.aciTree = aciPluginClass.plugins.aciTree.extend(aciTree_editable, 'aciTreeEditable'); // add extra default options aciPluginClass.defaults('aciTree', options); // for internal access var domApi = aciPluginClass.plugins.aciTree_dom; })(jQuery, this);