source: trunk/template-common/lib/plugins/jquery.fcbkcomplete.js @ 5067

Last change on this file since 5067 was 5067, checked in by plg, 14 years ago

feature 724: improved "add tags" form. Instead of a big list of checkboxes,
displays a dynamic list of tags with jQuery, with suggestions based on
existing tags and the ability to create new tags on the fly.

The change was applied only on admin/picture_modify.php for test purpose.

Note : FCBKcomplete 2.7 had a bug on "remote tag" click, and the bug was fixed
on 2.7.1. But the suggestions were not working with 2.7.1. So I took the 2.7
and applied the tiny change to make the "remove tag" click work.

File size: 23.6 KB
Line 
1/*
2 FCBKcomplete 2.7
3 - Jquery version required: 1.2.x, 1.3.x, 1.4.x
4 
5 Changelog:
6 - 2.00 new version of fcbkcomplete
7 
8 - 2.01 fixed bugs & added features
9                fixed filter bug for preadded items
10                focus on the input after selecting tag
11                the element removed pressing backspace when the element is selected
12                input tag in the control has a border in IE7
13                added iterate over each match and apply the plugin separately
14                set focus on the input after selecting tag
15 
16 - 2.02 fixed fist element selected bug
17                fixed defaultfilter error bug
18 
19 - 2.5  removed selected="selected" attribute due ie bug
20                element search algorithm changed
21                better performance fix added
22                fixed many small bugs
23                onselect event added
24                onremove event added
25 
26 - 2.6  ie6/7 support fix added
27                added new public method addItem due request
28                added new options "firstselected" that you can set true/false to select first element on dropdown list
29                autoexpand input element added
30                removeItem bug fixed
31                and many more bug fixed
32                fixed public method to use it $("elem").trigger("addItem",[{"title": "test", "value": "test"}]);
33               
34- 2.7 jquery 1.4 compability
35                item lock possability added by adding locked class to preadded option <option value="value" class="selected locked">text</option>
36                maximum item that can be added to the list             
37 */
38/* Coded by: emposha <admin@emposha.com> */
39/* Copyright: Emposha.com <http://www.emposha.com/> - Distributed under MIT - Keep this message! */
40/*
41 * json_url         - url to fetch json object
42 * cache                - use cache
43 * height           - maximum number of element shown before scroll will apear
44 * newel            - show typed text like a element
45 * firstselected        - automaticly select first element from dropdown
46 * filter_case      - case sensitive filter
47 * filter_selected  - filter selected items from list
48 * complete_text    - text for complete page
49 * maxshownitems        - maximum numbers that will be shown at dropdown list (less better performance)
50 * onselect                     - fire event on item select
51 * onremove                     - fire event on item remove
52 * maxitimes            - maximum items that can be added
53 */
54jQuery(function($){
55    $.fn.fcbkcomplete = function(opt){
56        return this.each(function(){
57            function init(){
58                createFCBK();
59                preSet();
60                addInput(0);
61            }
62           
63            function createFCBK(){
64                element.hide();
65                element.attr("multiple", "multiple");
66                if (element.attr("name").indexOf("[]") == -1) {
67                    element.attr("name", element.attr("name") + "[]");
68                }
69               
70                holder = $(document.createElement("ul"));
71                holder.attr("class", "holder");
72                element.after(holder);
73               
74                complete = $(document.createElement("div"));
75                complete.addClass("facebook-auto");
76                complete.append('<div class="default">' + options.complete_text + "</div>");
77               
78                if (browser_msie) {
79                    complete.append('<iframe class="ie6fix" scrolling="no" frameborder="0"></iframe>');
80                    browser_msie_frame = complete.children('.ie6fix');
81                }
82               
83                feed = $(document.createElement("ul"));
84                feed.attr("id", elemid + "_feed");
85               
86                complete.prepend(feed);
87                holder.after(complete);
88                feed.css("width", complete.width());
89            }
90           
91            function preSet(){
92                element.children("option").each(function(i, option){
93                    option = $(option);
94                    if (option.hasClass("selected")) {
95                        addItem(option.text(), option.val(), true, option.hasClass("locked"));
96                        option.attr("selected", "selected");
97                    }
98                    else {
99                        option.removeAttr("selected");
100                    }
101                   
102                    cache.push({
103                        caption: option.text(),
104                        value: option.val()
105                    });
106                    search_string += "" + (cache.length - 1) + ":" + option.text() + ";";
107                });
108            }
109           
110            //public method to add new item
111            $(this).bind("addItem", function(event, data){
112                addItem(data.title, data.value);
113            });
114           
115            function addItem(title, value, preadded, locked){
116                if (!maxItems()) {
117                    return false;
118                }
119                var li = document.createElement("li");
120                var txt = document.createTextNode(title);
121                var aclose = document.createElement("a");
122                var liclass = "bit-box" + (locked ? " locked" : "");
123                $(li).attr({
124                    "class": liclass,
125                    "rel": value
126                });
127                $(li).prepend(txt);
128                $(aclose).attr({
129                    "class": "closebutton",
130                    "href": "#"
131                });
132               
133                li.appendChild(aclose);
134                holder.append(li);
135               
136                $(aclose).click(function(){
137                    removeItem($(this).parent("li"));
138                    return false;
139                });
140               
141                if (!preadded) {
142                    $("#" + elemid + "_annoninput").remove();
143                    var _item;
144                    addInput(1);
145                    if (element.children("option[value=" + value + "]").length) {
146                        _item = element.children("option[value=" + value + "]");
147                        _item.get(0).setAttribute("selected", "selected");
148                        if (!_item.hasClass("selected")) {
149                            _item.addClass("selected");
150                        }
151                    }
152                    else {
153                        var _item = $(document.createElement("option"));
154                        _item.attr("value", value).get(0).setAttribute("selected", "selected");
155                        _item.attr("value", value).addClass("selected");
156                        _item.text(title);
157                        element.append(_item);
158                    }
159                    if (options.onselect.length) {
160                        funCall(options.onselect, _item)
161                    }
162                }
163                holder.children("li.bit-box.deleted").removeClass("deleted");
164                feed.hide();
165                browser_msie ? browser_msie_frame.hide() : '';
166            }
167           
168            function removeItem(item){
169                var parent = item.parent("li");
170                if (!parent.hasClass('locked')) {
171                    parent.fadeOut("fast");
172                    if (options.onremove.length) {
173                        var _item = element.children("option[value=" + item.attr("rel") + "]");
174                        funCall(options.onremove, _item)
175                    }
176                    element.children("option[value=" + item.attr("rel") + "]").removeAttr("selected");
177                    element.children("option[value=" + item.attr("rel") + "]").removeClass("selected");
178                    item.remove();
179                    deleting = 0;
180                }
181            }
182           
183            function addInput(focusme){
184                var li = $(document.createElement("li"));
185                var input = $(document.createElement("input"));
186               
187                li.attr({
188                    "class": "bit-input",
189                    "id": elemid + "_annoninput"
190                });
191                input.attr({
192                    "type": "text",
193                    "class": "maininput",
194                    "size": "1"
195                });
196                holder.append(li.append(input));
197               
198                input.focus(function(){
199                    complete.fadeIn("fast");
200                });
201               
202                input.blur(function(){
203                    complete.fadeOut("fast");
204                });
205               
206                holder.click(function(){
207                    input.focus();
208                    if (feed.length && input.val().length) {
209                        feed.show();
210                    }
211                    else {
212                        feed.hide();
213                        browser_msie ? browser_msie_frame.hide() : '';
214                        complete.children(".default").show();
215                    }
216                });
217               
218                input.keypress(function(event){
219                    if (event.keyCode == 13) {
220                        return false;
221                    }
222                    //auto expand input                                                 
223                    input.attr("size", input.val().length + 1);
224                });
225               
226                input.keydown(function(event){
227                    //prevent to enter some bad chars when input is empty
228                    if (event.keyCode == 191) {
229                        event.preventDefault();
230                        return false;
231                    }
232                });
233               
234                input.keyup(function(event){
235                    var etext = xssPrevent(input.val());
236                   
237                    if (event.keyCode == 8 && etext.length == 0) {
238                        feed.hide();
239                        browser_msie ? browser_msie_frame.hide() : '';
240                        if (!holder.children("li.bit-box:last").hasClass('locked')) {
241                            if (holder.children("li.bit-box.deleted").length == 0) {
242                                holder.children("li.bit-box:last").addClass("deleted");
243                                return false;
244                            }
245                            else {
246                                if (deleting) {
247                                    return;
248                                }
249                                deleting = 1;
250                                holder.children("li.bit-box.deleted").fadeOut("fast", function(){
251                                    removeItem($(this));
252                                    return false;
253                                });
254                            }
255                        }
256                    }
257                   
258                    if (event.keyCode != 40 && event.keyCode != 38 && etext.length != 0) {
259                        counter = 0;
260                       
261                        if (options.json_url) {
262                            if (options.cache && json_cache) {
263                                addMembers(etext);
264                                bindEvents();
265                            }
266                            else {
267                                $.getJSON(options.json_url + "?tag=" + etext, null, function(data){
268                                    addMembers(etext, data);
269                                    json_cache = true;
270                                    bindEvents();
271                                });
272                            }
273                        }
274                        else {
275                            addMembers(etext);
276                            bindEvents();
277                        }
278                        complete.children(".default").hide();
279                        feed.show();
280                    }
281                });
282                if (focusme) {
283                    setTimeout(function(){
284                        input.focus();
285                        complete.children(".default").show();
286                    }, 1);
287                }
288            }
289           
290            function addMembers(etext, data){
291                feed.html('');
292               
293                if (!options.cache) {
294                    cache = new Array();
295                    search_string = "";
296                }
297               
298                addTextItem(etext);
299               
300                if (data != null && data.length) {
301                    $.each(data, function(i, val){
302                        cache.push({
303                            caption: val.caption,
304                            value: val.value
305                        });
306                        search_string += "" + (cache.length - 1) + ":" + val.caption + ";";
307                    });
308                }
309               
310                var maximum = options.maxshownitems < cache.length ? options.maxshownitems : cache.length;
311                var filter = "i";
312                if (options.filter_case) {
313                    filter = "";
314                }
315               
316                var myregexp, match;
317                try {
318                    myregexp = eval('/(?:^|;)\\s*(\\d+)\\s*:[^;]*?' + etext + '[^;]*/g' + filter);
319                    match = myregexp.exec(search_string);
320                } 
321                catch (ex) {
322                };
323               
324                var content = '';
325                while (match != null && maximum > 0) {
326                    var id = match[1];
327                    var object = cache[id];
328                    if (options.filter_selected && element.children("option[value=" + object.value + "]").hasClass("selected")) {
329                        //nothing here...
330                    }
331                    else {
332                        content += '<li rel="' + object.value + '">' + itemIllumination(object.caption, etext) + '</li>';
333                        counter++;
334                        maximum--;
335                    }
336                    match = myregexp.exec(search_string);
337                }
338                feed.append(content);
339               
340                if (options.firstselected) {
341                    focuson = feed.children("li:visible:first");
342                    focuson.addClass("auto-focus");
343                }
344               
345                if (counter > options.height) {
346                    feed.css({
347                        "height": (options.height * 24) + "px",
348                        "overflow": "auto"
349                    });
350                    if (browser_msie) {
351                        browser_msie_frame.css({
352                            "height": (options.height * 24) + "px",
353                            "width": feed.width() + "px"
354                        }).show();
355                    }
356                }
357                else {
358                    feed.css("height", "auto");
359                    if (browser_msie) {
360                        browser_msie_frame.css({
361                            "height": feed.height() + "px",
362                            "width": feed.width() + "px"
363                        }).show();
364                    }
365                }
366            }
367           
368            function itemIllumination(text, etext){
369                if (options.filter_case) {
370                    try {
371                        eval("var text = text.replace(/(.*)(" + etext + ")(.*)/gi,'$1<em>$2</em>$3');");
372                    } 
373                    catch (ex) {
374                    };
375                                    }
376                else {
377                    try {
378                        eval("var text = text.replace(/(.*)(" + etext.toLowerCase() + ")(.*)/gi,'$1<em>$2</em>$3');");
379                    } 
380                    catch (ex) {
381                    };
382                                    }
383                return text;
384            }
385           
386            function bindFeedEvent(){
387                feed.children("li").mouseover(function(){
388                    feed.children("li").removeClass("auto-focus");
389                    $(this).addClass("auto-focus");
390                    focuson = $(this);
391                });
392               
393                feed.children("li").mouseout(function(){
394                    $(this).removeClass("auto-focus");
395                    focuson = null;
396                });
397            }
398           
399            function removeFeedEvent(){
400                feed.children("li").unbind("mouseover");
401                feed.children("li").unbind("mouseout");
402                feed.mousemove(function(){
403                    bindFeedEvent();
404                    feed.unbind("mousemove");
405                });
406            }
407           
408            function bindEvents(){
409                var maininput = $("#" + elemid + "_annoninput").children(".maininput");
410                bindFeedEvent();
411                feed.children("li").unbind("mousedown");
412                feed.children("li").mousedown(function(){
413                    var option = $(this);
414                    addItem(option.text(), option.attr("rel"));
415                    feed.hide();
416                    browser_msie ? browser_msie_frame.hide() : '';
417                    complete.hide();
418                });
419               
420                maininput.unbind("keydown");
421                maininput.keydown(function(event){
422                    if (event.keyCode == 191) {
423                        event.preventDefault();
424                        return false;
425                    }
426                   
427                    if (event.keyCode != 8) {
428                        holder.children("li.bit-box.deleted").removeClass("deleted");
429                    }
430                   
431                    if (event.keyCode == 13 && checkFocusOn()) {
432                        var option = focuson;
433                        addItem(option.text(), option.attr("rel"));
434                        complete.hide();
435                        event.preventDefault();
436                        focuson = null;
437                        return false;
438                    }
439                   
440                    if (event.keyCode == 13 && !checkFocusOn()) {
441                        if (options.newel) {
442                            var value = xssPrevent($(this).val());
443                            addItem(value, value);
444                            complete.hide();
445                            event.preventDefault();
446                            focuson = null;
447                        }
448                        return false;
449                    }
450                   
451                    if (event.keyCode == 40) {
452                        removeFeedEvent();
453                        if (focuson == null || focuson.length == 0) {
454                            focuson = feed.children("li:visible:first");
455                            feed.get(0).scrollTop = 0;
456                        }
457                        else {
458                            focuson.removeClass("auto-focus");
459                            focuson = focuson.nextAll("li:visible:first");
460                            var prev = parseInt(focuson.prevAll("li:visible").length, 10);
461                            var next = parseInt(focuson.nextAll("li:visible").length, 10);
462                            if ((prev > Math.round(options.height / 2) || next <= Math.round(options.height / 2)) && typeof(focuson.get(0)) != "undefined") {
463                                feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (prev - Math.round(options.height / 2));
464                            }
465                        }
466                        feed.children("li").removeClass("auto-focus");
467                        focuson.addClass("auto-focus");
468                    }
469                    if (event.keyCode == 38) {
470                        removeFeedEvent();
471                        if (focuson == null || focuson.length == 0) {
472                            focuson = feed.children("li:visible:last");
473                            feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (parseInt(feed.children("li:visible").length, 10) - Math.round(options.height / 2));
474                        }
475                        else {
476                            focuson.removeClass("auto-focus");
477                            focuson = focuson.prevAll("li:visible:first");
478                            var prev = parseInt(focuson.prevAll("li:visible").length, 10);
479                            var next = parseInt(focuson.nextAll("li:visible").length, 10);
480                            if ((next > Math.round(options.height / 2) || prev <= Math.round(options.height / 2)) && typeof(focuson.get(0)) != "undefined") {
481                                feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (prev - Math.round(options.height / 2));
482                            }
483                        }
484                        feed.children("li").removeClass("auto-focus");
485                        focuson.addClass("auto-focus");
486                    }
487                });
488            }
489           
490            function maxItems(){
491                if (options.maxitems != 0) {
492                    if (holder.children("li.bit-box").length < options.maxitems) {
493                        return true;
494                    }
495                    else {
496                        return false;
497                    }
498                }
499            }
500           
501            function addTextItem(value){
502                if (options.newel && maxItems()) {
503                    feed.children("li[fckb=1]").remove();
504                    if (value.length == 0) {
505                        return;
506                    }
507                    var li = $(document.createElement("li"));
508                    li.attr({
509                        "rel": value,
510                        "fckb": "1"
511                    }).html(value);
512                    feed.prepend(li);
513                    counter++;
514                }
515                else {
516                    return;
517                }
518            }
519           
520            function funCall(func, item){
521                var _object = "";
522                for (i = 0; i < item.get(0).attributes.length; i++) {
523                    if (item.get(0).attributes[i].nodeValue != null) {
524                        _object += "\"_" + item.get(0).attributes[i].nodeName + "\": \"" + item.get(0).attributes[i].nodeValue + "\",";
525                    }
526                }
527                _object = "{" + _object + " notinuse: 0}";
528                try {
529                    eval(func + "(" + _object + ")");
530                } 
531                catch (ex) {
532                };
533                            }
534           
535            function checkFocusOn(){
536                if (focuson == null) {
537                    return false;
538                }
539                if (focuson.length == 0) {
540                    return false;
541                }
542                return true;
543            }
544           
545            function xssPrevent(string){
546                string = string.replace(/[\"\'][\s]*javascript:(.*)[\"\']/g, "\"\"");
547                string = string.replace(/script(.*)/g, "");
548                string = string.replace(/eval\((.*)\)/g, "");
549                string = string.replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '');
550                return string;
551            }
552           
553            var options = $.extend({
554                json_url: null,
555                cache: false,
556                height: "10",
557                newel: false,
558                firstselected: false,
559                filter_case: false,
560                filter_hide: false,
561                complete_text: "Start to type...",
562                maxshownitems: 30,
563                maxitems: 0,
564                onselect: "",
565                onremove: ""
566            }, opt);
567           
568            //system variables
569            var holder = null;
570            var feed = null;
571            var complete = null;
572            var counter = 0;
573            var cache = new Array();
574            var json_cache = false;
575            var search_string = "";
576            var focuson = null;
577            var deleting = 0;
578            var browser_msie = "\v" == "v";
579            var browser_msie_frame;
580           
581            var element = $(this);
582            var elemid = element.attr("id");
583            init();
584           
585            return this;
586        });
587    };
588});
Note: See TracBrowser for help on using the repository browser.