/* =================================================== * tagmanager.js v3.0.1 * http://welldonethings.com/tags/manager * =================================================== * Copyright 2012 Max Favilli * * Licensed under the Mozilla Public License, Version 2.0 You may not use this work except in compliance with the License. * * http://www.mozilla.org/MPL/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. * ========================================================== */ (function($) { "use strict"; var defaults = { prefilled: null, CapitalizeFirstLetter: false, preventSubmitOnEnter: true, // deprecated isClearInputOnEsc: true, // deprecated AjaxPush: null, AjaxPushAllTags: null, AjaxPushParameters: null, delimiters: [9, 13, 44], // tab, enter, comma backspace: [8], maxTags: 0, hiddenTagListName: null, // deprecated hiddenTagListId: null, // deprecated replace: true, output: null, deleteTagsOnBackspace: true, // deprecated tagsContainer: null, tagCloseIcon: 'x', tagClass: '', validator: null, onlyTagList: false, tagList: null, }, publicMethods = { pushTag : function (tag, ignoreEvents) { var $self = $(this), opts = $self.data('opts'), alreadyInList, tlisLowerCase, max, tagId, tlis = $self.data("tlis"), tlid = $self.data("tlid"), idx, newTagId, newTagRemoveId, escaped, html, $el, lastTagId, lastTagObj; tag = privateMethods.trimTag(tag, opts.delimiterChars); if (!tag || tag.length <= 0) { return; } // check if restricted only to the tagList suggestions if (opts.onlyTagList && undefined !== opts.tagList ){ //if the list has been updated by look pushed tag in the tagList. if not found return if (opts.tagList){ var $tagList = opts.tagList; // change each array item to lower case $.each($tagList, function(index, item) { $tagList[index] = item.toLowerCase(); }); var suggestion = $.inArray(tag.toLowerCase(), $tagList); if ( -1 === suggestion ) { //console.log("tag:" + tag + " not in tagList, not adding it"); return; } } } if (opts.CapitalizeFirstLetter && tag.length > 1) { tag = tag.charAt(0).toUpperCase() + tag.slice(1).toLowerCase(); } // call the validator (if any) and do not let the tag pass if invalid if (opts.validator && !opts.validator(tag)) { return; } // dont accept new tags beyond the defined maximum if (opts.maxTags > 0 && tlis.length >= opts.maxTags) { return; } alreadyInList = false; //use jQuery.map to make this work in IE8 (pure JS map is JS 1.6 but IE8 only supports JS 1.5) tlisLowerCase = jQuery.map(tlis, function(elem) { return elem.toLowerCase(); }); idx = $.inArray(tag.toLowerCase(), tlisLowerCase); if (-1 !== idx) { // console.log("tag:" + tag + " !!already in list!!"); alreadyInList = true; } if (alreadyInList) { $self.trigger('tm:duplicated', tag); $("#" + $self.data("tm_rndid") + "_" + tlid[idx]).stop() .animate({backgroundColor: opts.blinkBGColor_1}, 100) .animate({backgroundColor: opts.blinkBGColor_2}, 100) .animate({backgroundColor: opts.blinkBGColor_1}, 100) .animate({backgroundColor: opts.blinkBGColor_2}, 100) .animate({backgroundColor: opts.blinkBGColor_1}, 100) .animate({backgroundColor: opts.blinkBGColor_2}, 100); } else { if (!ignoreEvents) { $self.trigger('tm:pushing', tag); } max = Math.max.apply(null, tlid); max = max === -Infinity ? 0 : max; tagId = ++max; tlis.push(tag); tlid.push(tagId); if (!ignoreEvents) if (opts.AjaxPush !== null && opts.AjaxPushAllTags == null) { if ($.inArray(tag, opts.prefilled) === -1) { $.post(opts.AjaxPush, $.extend({tag: tag}, opts.AjaxPushParameters)); } } // console.log("tagList: " + tlis); newTagId = $self.data("tm_rndid") + '_' + tagId; newTagRemoveId = $self.data("tm_rndid") + '_Remover_' + tagId; escaped = $("").text(tag).html(); html = ''; html+= '' + escaped + ''; html+= ''; html+= opts.tagCloseIcon + ' '; $el = $(html); if (opts.tagsContainer !== null) { $(opts.tagsContainer).append($el); } else { if (tagId > 1) { lastTagId = tagId - 1; lastTagObj = $("#" + $self.data("tm_rndid") + "_" + lastTagId); lastTagObj.after($el); } else { $self.before($el); } } $el.find("#" + newTagRemoveId).on("click", $self, function(e) { e.preventDefault(); var TagIdToRemove = parseInt($(this).attr("TagIdToRemove")); privateMethods.spliceTag.call($self, TagIdToRemove, e.data); }); privateMethods.refreshHiddenTagList.call($self); if (!ignoreEvents) { $self.trigger('tm:pushed', tag); } privateMethods.showOrHide.call($self); //if (tagManagerOptions.maxTags > 0 && tlis.length >= tagManagerOptions.maxTags) { // obj.hide(); //} } $self.val(""); }, popTag : function () { var $self = $(this), tagId, tagBeingRemoved, tlis = $self.data("tlis"), tlid = $self.data("tlid"); if (tlid.length > 0) { tagId = tlid.pop(); tagBeingRemoved = tlis[tlis.length - 1]; $self.trigger('tm:popping', tagBeingRemoved); tlis.pop(); // console.log("TagIdToRemove: " + tagId); $("#" + $self.data("tm_rndid") + "_" + tagId).remove(); privateMethods.refreshHiddenTagList.call($self); $self.trigger('tm:popped', tagBeingRemoved); // console.log(tlis); } }, empty : function() { var $self = $(this), tlis = $self.data("tlis"), tlid = $self.data("tlid"), tagId; while (tlid.length > 0) { tagId = tlid.pop(); tlis.pop(); // console.log("TagIdToRemove: " + tagId); $("#" + $self.data("tm_rndid") + "_" + tagId).remove(); privateMethods.refreshHiddenTagList.call($self); // console.log(tlis); } $self.trigger('tm:emptied', null); privateMethods.showOrHide.call($self); //if (tagManagerOptions.maxTags > 0 && tlis.length < tagManagerOptions.maxTags) { // obj.show(); //} }, tags : function() { var $self = this, tlis = $self.data("tlis"); return tlis; } }, privateMethods = { showOrHide : function () { var $self = this, opts = $self.data('opts'), tlis = $self.data("tlis"); if (opts.maxTags > 0 && tlis.length < opts.maxTags) { $self.show(); $self.trigger('tm:show'); } if (opts.maxTags > 0 && tlis.length >= opts.maxTags) { $self.hide(); $self.trigger('tm:hide'); } }, tagClasses : function () { var $self = $(this), opts = $self.data('opts'), tagBaseClass = opts.tagBaseClass, inputBaseClass = opts.inputBaseClass, cl; // 1) default class (tm-tag) cl = tagBaseClass; // 2) interpolate from input class: tm-input-xxx --> tm-tag-xxx if ($self.attr('class')) { $.each($self.attr('class').split(' '), function (index, value) { if (value.indexOf(inputBaseClass + '-') !== -1) { cl += ' ' + tagBaseClass + value.substring(inputBaseClass.length); } }); } // 3) tags from tagClass option cl += (opts.tagClass ? ' ' + opts.tagClass : ''); return cl; }, trimTag : function (tag, delimiterChars) { var i; tag = $.trim(tag); // truncate at the first delimiter char i = 0; for (i; i < tag.length; i++) { if ($.inArray(tag.charCodeAt(i), delimiterChars) !== -1) { break; } } return tag.substring(0, i); }, refreshHiddenTagList : function () { var $self = $(this), tlis = $self.data("tlis"), lhiddenTagList = $self.data("lhiddenTagList"); if (lhiddenTagList) { $(lhiddenTagList).val(tlis.join($self.data('opts').baseDelimiter)).change(); } $self.trigger('tm:refresh', tlis.join($self.data('opts').baseDelimiter)); }, killEvent : function (e) { e.cancelBubble = true; e.returnValue = false; e.stopPropagation(); e.preventDefault(); }, keyInArray : function (e, ary) { return $.inArray(e.which, ary) !== -1; }, applyDelimiter : function (e) { var $self = $(this); publicMethods.pushTag.call($self,$(this).val()); e.preventDefault(); }, prefill : function (pta) { var $self = $(this); $.each(pta, function (key, val) { publicMethods.pushTag.call($self, val, true); }); }, pushAllTags : function (e, tag) { var $self = $(this), opts = $self.data('opts'), tlis = $self.data("tlis"); if (opts.AjaxPushAllTags) { if (e.type !== 'tm:pushed' || $.inArray(tag, opts.prefilled) === -1) { $.post(opts.AjaxPush, $.extend({ tags: tlis.join(opts.baseDelimiter) }, opts.AjaxPushParameters)); } } }, spliceTag : function (tagId) { var $self = this, tlis = $self.data("tlis"), tlid = $self.data("tlid"), idx = $.inArray(tagId, tlid), tagBeingRemoved; // console.log("TagIdToRemove: " + tagId); // console.log("position: " + idx); if (-1 !== idx) { tagBeingRemoved = tlis[idx]; $self.trigger('tm:splicing', tagBeingRemoved); $("#" + $self.data("tm_rndid") + "_" + tagId).remove(); tlis.splice(idx, 1); tlid.splice(idx, 1); privateMethods.refreshHiddenTagList.call($self); $self.trigger('tm:spliced', tagBeingRemoved); // console.log(tlis); } privateMethods.showOrHide.call($self); //if (tagManagerOptions.maxTags > 0 && tlis.length < tagManagerOptions.maxTags) { // obj.show(); //} }, init : function (options) { var opts = $.extend({}, defaults, options), delimiters, keyNums; opts.hiddenTagListName = (opts.hiddenTagListName === null) ? 'hidden-' + this.attr('name') : opts.hiddenTagListName; delimiters = opts.delimeters || opts.delimiters; // 'delimeter' is deprecated keyNums = [9, 13, 17, 18, 19, 37, 38, 39, 40]; // delimiter values to be handled as key codes opts.delimiterChars = []; opts.delimiterKeys = []; $.each(delimiters, function (i, v) { if ($.inArray(v, keyNums) !== -1) { opts.delimiterKeys.push(v); } else { opts.delimiterChars.push(v); } }); opts.baseDelimiter = String.fromCharCode(opts.delimiterChars[0] || 44); opts.tagBaseClass = 'tm-tag'; opts.inputBaseClass = 'tm-input'; if (!$.isFunction(opts.validator)) { opts.validator = null; } this.each(function() { var $self = $(this), hiddenObj ='', rndid ='', albet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // prevent double-initialization of TagManager if ($self.data('tagManager')) { return false; } $self.data('tagManager', true); for (var i = 0; i < 5; i++) { rndid += albet.charAt(Math.floor(Math.random() * albet.length)); } $self.data("tm_rndid", rndid); // store instance-specific data in the DOM object $self.data('opts',opts) .data('tlis', []) //list of string tags .data('tlid', []); //list of ID of the string tags if (opts.output === null) { hiddenObj = $('', { type: 'hidden', name: opts.hiddenTagListName }); $self.after(hiddenObj); $self.data("lhiddenTagList", hiddenObj); } else { $self.data("lhiddenTagList", $(opts.output)); } if (opts.AjaxPushAllTags) { $self.on('tm:spliced', privateMethods.pushAllTags); $self.on('tm:popped', privateMethods.pushAllTags); $self.on('tm:pushed', privateMethods.pushAllTags); } // hide popovers on focus and keypress events $self.on('focus keypress', function(e) { if ($(this).popover) { $(this).popover('hide'); } }); // handle ESC (keyup used for browser compatibility) if (opts.isClearInputOnEsc) { $self.on('keyup', function(e) { if (e.which === 27) { // console.log('esc detected'); $(this).val(''); privateMethods.killEvent(e); } }); } $self.on('keypress', function(e) { // push ASCII-based delimiters if (privateMethods.keyInArray(e, opts.delimiterChars)) { privateMethods.applyDelimiter.call($self, e); } }); $self.on('keydown', function(e) { // disable ENTER if (e.which === 13) { if (opts.preventSubmitOnEnter) { privateMethods.killEvent(e); } } // push key-based delimiters (includes by default) if (privateMethods.keyInArray(e, opts.delimiterKeys)) { privateMethods.applyDelimiter.call($self, e); } }); // BACKSPACE (keydown used for browser compatibility) if (opts.deleteTagsOnBackspace) { $self.on('keydown', function(e) { if (privateMethods.keyInArray(e, opts.backspace)) { // console.log("backspace detected"); if ($(this).val().length <= 0) { publicMethods.popTag.call($self); privateMethods.killEvent(e); } } }); } $self.change(function(e) { if (!/webkit/.test(navigator.userAgent.toLowerCase())) { $self.focus(); } // why? /* unimplemented mode to push tag on blur else if (tagManagerOptions.pushTagOnBlur) { console.log('change: pushTagOnBlur ' + tag); pushTag($(this).val()); } */ privateMethods.killEvent(e); }); if (opts.prefilled !== null) { if (typeof (opts.prefilled) === "object") { privateMethods.prefill.call($self, opts.prefilled); } else if (typeof (opts.prefilled) === "string") { privateMethods.prefill.call($self, opts.prefilled.split(opts.baseDelimiter)); } else if (typeof (opts.prefilled) === "function") { privateMethods.prefill.call($self, opts.prefilled()); } } else if (opts.output !== null) { if ($(opts.output) && $(opts.output).val()) { var existing_tags = $(opts.output); } privateMethods.prefill.call($self,$(opts.output).val().split(opts.baseDelimiter)); } }); return this; } }; $.fn.tagsManager = function(method) { var $self = $(this); if (!(0 in this)) { return this; } if ( publicMethods[method] ) { return publicMethods[method].apply( $self, Array.prototype.slice.call(arguments, 1) ); } else if ( typeof method === 'object' || ! method ) { return privateMethods.init.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist.' ); return false; } }; }(jQuery));