PHP Classes

File: FrontEndScripts/libs/jquery.tag-editor.js

Recommend this page to a friend!
  Classes of Aleksey Nemiro   PHP Small Server Administrator   FrontEndScripts/libs/jquery.tag-editor.js   Download  
File: FrontEndScripts/libs/jquery.tag-editor.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: PHP Small Server Administrator
Web panel for small Debian and Ubuntu servers
Author: By
Last change:
Date: 7 years ago
Size: 19,552 bytes
 

Contents

Class file image Download
/* jQuery tagEditor v1.0.18 Copyright (c) 2014 Simon Steinberger / Pixabay GitHub: https://github.com/Pixabay/jQuery-tagEditor License: http://www.opensource.org/licenses/mit-license.php */ (function($){ // auto grow input (stackoverflow.com/questions/931207) $.fn.tagEditorInput=function(){var t=" ",e=$(this),n=parseInt(e.css("fontSize")),i=$("<span/>").css({position:"absolute",top:-9999,left:-9999,width:"auto",fontSize:e.css("fontSize"),fontFamily:e.css("fontFamily"),fontWeight:e.css("fontWeight"),letterSpacing:e.css("letterSpacing"),whiteSpace:"nowrap"}),s=function(){if(t!==(t=e.val())){i.html(t.replace(/&/g,"&amp;").replace(/\s/g,"&nbsp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"));var s=i.width()+n;20>s&&(s=20),s!=e.width()&&e.width(s)}};return i.insertAfter(e),e.bind("keyup keydown focus",s)}; // plugin with val as parameter for public methods $.fn.tagEditor = function(options, val, blur){ // helper function escape(tag) { return tag.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;"); } // build options dictionary with default values var blur_result, o = $.extend({}, $.fn.tagEditor.defaults, options), selector = this; // store regex and default delimiter in options for later use o.dregex = new RegExp('['+o.delimiter.replace('-', '\-')+']', 'g'); // public methods if (typeof options == 'string') { // depending on selector, response may contain tag lists of multiple editor instances var response = []; selector.each(function(){ // the editor is the next sibling to the hidden, original field var el = $(this), o = el.data('options'), ed = el.next('.tag-editor'); if (options == 'getTags') response.push({field: el[0], editor: ed, tags: ed.data('tags')}); else if (options == 'addTag') { if (o.maxTags && ed.data('tags').length >= o.maxTags) return false; // insert new tag $('<li><div class="tag-editor-spacer">&nbsp;'+o.delimiter[0]+'</div><div class="tag-editor-tag"></div><div class="tag-editor-delete"><i></i></div></li>').appendTo(ed).find('.tag-editor-tag') .html('<input type="text" maxlength="'+o.maxLength+'">').addClass('active').find('input').val(val).blur(); if (!blur) ed.click(); else $('.placeholder', ed).remove(); } else if (options == 'removeTag') { // trigger delete on matching tag, then click editor to create a new tag $('.tag-editor-tag', ed).filter(function(){return $(this).html()==val;}).closest('li').find('.tag-editor-delete').click(); if (!blur) ed.click(); } else if (options == 'destroy') { el.removeClass('tag-editor-hidden-src').removeData('options').off('focus.tag-editor').next('.tag-editor').remove(); } }); return options == 'getTags' ? response : this; } // delete selected tags on backspace, delete, ctrl+x function delete_selected_tags(e){ if (e.which == 8 || e.which == 46 || e.ctrlKey && e.which == 88) { try { var sel = getSelection(), el = $(sel.getRangeAt(0).commonAncestorContainer); } catch(e){ el = 0; } if (el && el.hasClass('tag-editor')) { var tags = [], splits = sel.toString().split(el.prev().data('options').dregex); for (i=0; i<splits.length; i++){ var tag = $.trim(splits[i]); if (tag) tags.push(tag); } $('.tag-editor-tag', el).each(function(){ if (~$.inArray($(this).html(), tags)) $(this).closest('li').find('.tag-editor-delete').click(); }); return false; } } } if (window.getSelection) $(document).off('keydown.tag-editor').on('keydown.tag-editor', delete_selected_tags); return selector.each(function(){ var el = $(this), tag_list = []; // cache current tags // create editor (ed) instance var ed = $('<ul '+(o.clickDelete ? 'oncontextmenu="return false;" ' : '')+'class="tag-editor"></ul>').insertAfter(el); el.addClass('tag-editor-hidden-src') // hide original field .data('options', o) // set data on hidden field .on('focus.tag-editor', function(){ ed.click(); }); // simulate tabindex // add dummy item for min-height on empty editor ed.append('<li style="width:1px;height:1px;">&nbsp;</li>'); // markup for new tag var new_tag = '<li><div class="tag-editor-spacer">&nbsp;'+o.delimiter[0]+'</div><div class="tag-editor-tag"></div><div class="tag-editor-delete"><i></i></div></li>'; // helper: update global data function set_placeholder(){ if (o.placeholder && !tag_list.length && !$('.deleted, .placeholder, input', ed).length) ed.append('<li class="placeholder"><div>'+o.placeholder+'</div></li>'); } // helper: update global data function update_globals(init){ var old_tags = tag_list.toString(); tag_list = $('.tag-editor-tag:not(.deleted)', ed).map(function(i, e) { var val = $.trim($(this).hasClass('active') ? $(this).find('input').val() : $(e).text()); if (val) return val; }).get(); ed.data('tags', tag_list); el.val(tag_list.join(o.delimiter[0])); // change callback except for plugin init if (!init) if (old_tags != tag_list.toString()) o.onChange(el, ed, tag_list); set_placeholder(); } ed.click(function(e, closest_tag){ var d, dist = 99999, loc; // do not create tag when user selects tags by text selection if (window.getSelection && getSelection() != '') return; if (o.maxTags && ed.data('tags').length >= o.maxTags) { ed.find('input').blur(); return false; } blur_result = true $('input:focus', ed).blur(); if (!blur_result) return false; blur_result = true // always remove placeholder on click $('.placeholder', ed).remove(); if (closest_tag && closest_tag.length) loc = 'before'; else { // calculate tag closest to click position $('.tag-editor-tag', ed).each(function(){ var tag = $(this), to = tag.offset(), tag_x = to.left, tag_y = to.top; if (e.pageY >= tag_y && e.pageY <= tag_y+tag.height()) { if (e.pageX < tag_x) loc = 'before', d = tag_x - e.pageX; else loc = 'after', d = e.pageX - tag_x - tag.width(); if (d < dist) dist = d, closest_tag = tag; } }); } if (loc == 'before') { $(new_tag).insertBefore(closest_tag.closest('li')).find('.tag-editor-tag').click(); } else if (loc == 'after') $(new_tag).insertAfter(closest_tag.closest('li')).find('.tag-editor-tag').click(); else // empty editor $(new_tag).appendTo(ed).find('.tag-editor-tag').click(); return false; }); ed.on('click', '.tag-editor-delete', function(e){ // delete icon is hidden when input is visible; place cursor near invisible delete icon on click if ($(this).prev().hasClass('active')) { $(this).closest('li').find('input').caret(-1); return false; } var li = $(this).closest('li'), tag = li.find('.tag-editor-tag'); if (o.beforeTagDelete(el, ed, tag_list, tag.html()) === false) return false; tag.addClass('deleted').animate({width: 0}, o.animateDelete, function(){ li.remove(); set_placeholder(); }); update_globals(); return false; }); // delete on right mouse click or ctrl+click if (o.clickDelete) ed.on('mousedown', '.tag-editor-tag', function(e){ if (e.ctrlKey || e.which > 1) { var li = $(this).closest('li'), tag = li.find('.tag-editor-tag'); if (o.beforeTagDelete(el, ed, tag_list, tag.html()) === false) return false; tag.addClass('deleted').animate({width: 0}, o.animateDelete, function(){ li.remove(); set_placeholder(); }); update_globals(); return false; } }); ed.on('click', '.tag-editor-tag', function(e){ // delete on right click or ctrl+click -> exit if (o.clickDelete && (e.ctrlKey || e.which > 1)) return false; if (!$(this).hasClass('active')) { var tag = $(this).html(); // guess cursor position in text input var left_percent = Math.abs(($(this).offset().left - e.pageX)/$(this).width()), caret_pos = parseInt(tag.length*left_percent), input = $(this).html('<input type="text" maxlength="'+o.maxLength+'" value="'+escape(tag)+'">').addClass('active').find('input'); input.data('old_tag', tag).tagEditorInput().focus().caret(caret_pos); if (o.autocomplete) { var aco = $.extend({}, o.autocomplete); // extend user provided autocomplete select method var ac_select = 'select' in aco ? o.autocomplete.select : ''; aco.select = function(e, ui){ if (ac_select) ac_select(e, ui); setTimeout(function(){ ed.trigger('click', [$('.active', ed).find('input').closest('li').next('li').find('.tag-editor-tag')]); }, 20); }; input.autocomplete(aco); } } return false; }); // helper: split into multiple tags, e.g. after paste function split_cleanup(input){ var li = input.closest('li'), sub_tags = input.val().replace(/ +/, ' ').split(o.dregex), old_tag = input.data('old_tag'), old_tags = tag_list.slice(0), exceeded = false, cb_val; // copy tag_list for (var i=0; i<sub_tags.length; i++) { tag = $.trim(sub_tags[i]).slice(0, o.maxLength); if (o.forceLowercase) tag = tag.toLowerCase(); cb_val = o.beforeTagSave(el, ed, old_tags, old_tag, tag); tag = cb_val || tag; if (cb_val === false || !tag) continue; // remove duplicates if (o.removeDuplicates && ~$.inArray(tag, old_tags)) $('.tag-editor-tag', ed).each(function(){ if ($(this).html() == tag) $(this).closest('li').remove(); }); old_tags.push(tag); li.before('<li><div class="tag-editor-spacer">&nbsp;'+o.delimiter[0]+'</div><div class="tag-editor-tag">'+escape(tag)+'</div><div class="tag-editor-delete"><i></i></div></li>'); if (o.maxTags && old_tags.length >= o.maxTags) { exceeded = true; break; } } input.attr('maxlength', o.maxLength).removeData('old_tag').val('') if (exceeded) input.blur(); else input.focus(); update_globals(); } ed.on('blur', 'input', function(e){ e.stopPropagation(); var input = $(this), old_tag = input.data('old_tag'), tag = $.trim(input.val().replace(/ +/, ' ').replace(o.dregex, o.delimiter[0])); if (!tag) { if (old_tag && o.beforeTagDelete(el, ed, tag_list, old_tag) === false) { input.val(old_tag).focus(); blur_result = false; update_globals(); return; } try { input.closest('li').remove(); } catch(e){} if (old_tag) update_globals(); } else if (tag.indexOf(o.delimiter[0])>=0) { split_cleanup(input); return; } else if (tag != old_tag) { if (o.forceLowercase) tag = tag.toLowerCase(); cb_val = o.beforeTagSave(el, ed, tag_list, old_tag, tag); tag = cb_val || tag; if (cb_val === false) { if (old_tag) { input.val(old_tag).focus(); blur_result = false; update_globals(); return; } try { input.closest('li').remove(); } catch(e){} if (old_tag) update_globals(); } // remove duplicates else if (o.removeDuplicates) $('.tag-editor-tag:not(.active)', ed).each(function(){ if ($(this).html() == tag) $(this).closest('li').remove(); }); } input.parent().html(escape(tag)).removeClass('active'); if (tag != old_tag) update_globals(); set_placeholder(); }); var pasted_content; ed.on('paste', 'input', function(e){ $(this).removeAttr('maxlength'); pasted_content = $(this); setTimeout(function(){ split_cleanup(pasted_content); }, 30); }); // keypress delimiter var inp; ed.on('keypress', 'input', function(e){ if (o.delimiter.indexOf(String.fromCharCode(e.which))>=0) { inp = $(this); setTimeout(function(){ split_cleanup(inp); }, 20); } }); ed.on('keydown', 'input', function(e){ var $t = $(this); // left/up key + backspace key on empty field if ((e.which == 37 || !o.autocomplete && e.which == 38) && !$t.caret() || e.which == 8 && !$t.val()) { var prev_tag = $t.closest('li').prev('li').find('.tag-editor-tag'); if (prev_tag.length) prev_tag.click().find('input').caret(-1); else if ($t.val() && !(o.maxTags && ed.data('tags').length >= o.maxTags)) $(new_tag).insertBefore($t.closest('li')).find('.tag-editor-tag').click(); return false; } // right/down key else if ((e.which == 39 || !o.autocomplete && e.which == 40) && ($t.caret() == $t.val().length)) { var next_tag = $t.closest('li').next('li').find('.tag-editor-tag'); if (next_tag.length) next_tag.click().find('input').caret(0); else if ($t.val()) ed.click(); return false; } // tab key else if (e.which == 9) { // shift+tab if (e.shiftKey) { var prev_tag = $t.closest('li').prev('li').find('.tag-editor-tag'); if (prev_tag.length) prev_tag.click().find('input').caret(0); else if ($t.val() && !(o.maxTags && ed.data('tags').length >= o.maxTags)) $(new_tag).insertBefore($t.closest('li')).find('.tag-editor-tag').click(); // allow tabbing to previous element else { el.attr('disabled', 'disabled'); setTimeout(function(){ el.removeAttr('disabled'); }, 30); return; } return false; // tab } else { var next_tag = $t.closest('li').next('li').find('.tag-editor-tag'); if (next_tag.length) next_tag.click().find('input').caret(0); else if ($t.val()) ed.click(); else return; // allow tabbing to next element return false; } } // del key else if (e.which == 46 && (!$.trim($t.val()) || ($t.caret() == $t.val().length))) { var next_tag = $t.closest('li').next('li').find('.tag-editor-tag'); if (next_tag.length) next_tag.click().find('input').caret(0); else if ($t.val()) ed.click(); return false; } // enter key else if (e.which == 13) { ed.trigger('click', [$t.closest('li').next('li').find('.tag-editor-tag')]); return false; } // pos1 else if (e.which == 36 && !$t.caret()) ed.find('.tag-editor-tag').first().click(); // end else if (e.which == 35 && $t.caret() == $t.val().length) ed.find('.tag-editor-tag').last().click(); // esc else if (e.which == 27) { $t.val($t.data('old_tag') ? $t.data('old_tag') : '').blur(); return false; } }); // create initial tags var tags = o.initialTags.length ? o.initialTags : el.val().split(o.dregex); for (var i=0; i<tags.length; i++) { if (o.maxTags && i >= o.maxTags) break; var tag = $.trim(tags[i].replace(/ +/, ' ')); if (tag) { if (o.forceLowercase) tag = tag.toLowerCase(); tag_list.push(tag); ed.append('<li><div class="tag-editor-spacer">&nbsp;'+o.delimiter[0]+'</div><div class="tag-editor-tag">'+escape(tag)+'</div><div class="tag-editor-delete"><i></i></div></li>'); } } update_globals(true); // true -> no onChange callback // init sortable if (o.sortable && $.fn.sortable) ed.sortable({ distance: 5, cancel: '.tag-editor-spacer, input', helper: 'clone', update: function(){ update_globals(); } }); }); }; $.fn.tagEditor.defaults = { initialTags: [], maxTags: 0, maxLength: 50, delimiter: ',;', placeholder: '', forceLowercase: true, removeDuplicates: true, clickDelete: false, animateDelete: 175, sortable: true, // jQuery UI sortable autocomplete: null, // options dict for jQuery UI autocomplete // callbacks onChange: function(){}, beforeTagSave: function(){}, beforeTagDelete: function(){} }; }(jQuery));