PageSourceSearch

https://www.thelist.tas.gov.au/app/export/system/modules/au.gov.ta…/resources_v11/script/jqueryui-editable.js

js thelist.tas.gov.au collected 2026-09-24 09:33:24 UTC 168,399 bytes, 4,895 lines download raw bytes

1/*! X-editable - v1.4.6 
2* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
3* http://github.com/vitalets/x-editable
4* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
5
6/**
7Form with single input element, two buttons and two states: normal/loading.
8Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown.
9Editableform is linked with one of input types, e.g. 'text', 'select' etc.
10
11@class editableform
12@uses text
13@uses textarea
14**/
15(function ($) {
16    "use strict";
17    
18    var EditableForm = function (div, options) {
19        this.options = $.extend({}, $.fn.editableform.defaults, options);
20        this.$div = $(div); //div, containing form. Not form tag. Not editable-element.
21        if(!this.options.scope) {
22            this.options.scope = this;
23        }
24        //nothing shown after init
25    };
26
27    EditableForm.prototype = {
28        constructor: EditableForm,
29        initInput: function() {  //called once
30            //take input from options (as it is created in editable-element)
31            this.input = this.options.input;
32            
33            //set initial value
34            //todo: may be add check: typeof str === 'string' ? 
35            this.value = this.input.str2value(this.options.value); 
36        },
37        initTemplate: function() {
38            this.$form = $($.fn.editableform.template); 
39        },
40        initButtons: function() {
41            var $btn = this.$form.find('.editable-buttons');
42            $btn.append($.fn.editableform.buttons);
43            if(this.options.showbuttons === 'bottom') {
44                $btn.addClass('editable-buttons-bottom');
45            }
46        },
47        /**
48        Renders editableform
49
50        @method render
51        **/        
52        render: function() {
53            //init loader
54            this.$loading = $($.fn.editableform.loading);        
55            this.$div.empty().append(this.$loading);
56            
57            //init form template and buttons
58            this.initTemplate();
59            if(this.options.showbuttons) {
60                this.initButtons();
61            } else {
62                this.$form.find('.editable-buttons').remove();
63            }
64
65            //show loading state
66            this.showLoading();            
67            
68            //flag showing is form now saving value to server. 
69            //It is needed to wait when closing form.
70            this.isSaving = false;
71            
72            /**        
73            Fired when rendering starts
74            @event rendering 
75            @param {Object} event event object
76            **/            
77            this.$div.triggerHandler('rendering');
78            
79            //init input
80            this.initInput();
81            
82            //append input to form
83            this.input.prerender();
84            this.$form.find('div.editable-input').append(this.input.$tpl);            
85
86            //append form to container
87            this.$div.append(this.$form);
88            
89            //render input
90            $.when(this.input.render())
91            .then($.proxy(function () {
92                //setup input to submit automatically when no buttons shown
93                if(!this.options.showbuttons) {
94                    this.input.autosubmit(); 
95                }
96                 
97                //attach 'cancel' handler
98                this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
99                
100                if(this.input.error) {
101                    this.error(this.input.error);
102                    this.$form.find('.editable-submit').attr('disabled', true);
103                    this.input.$input.attr('disabled', true);
104                    //prevent form from submitting
105                    this.$form.submit(function(e){ e.preventDefault(); });
106                } else {
107                    this.error(false);
108                    this.input.$input.removeAttr('disabled');
109                    this.$form.find('.editable-submit').removeAttr('disabled');
110                    var value = (this.value === null || this.value === undefined || this.value === '') ? this.options.defaultValue : this.value;
111                    this.input.value2input(value);
112                    //attach submit handler
113                    this.$form.submit($.proxy(this.submit, this));
114                }
115
116                /**        
117                Fired when form is rendered
118                @event rendered
119                @param {Object} event event object
120                **/            
121                this.$div.triggerHandler('rendered');                
122
123                this.showForm();
124                
125                //call postrender method to perform actions required visibility of form
126                if(this.input.postrender) {
127                    this.input.postrender();
128                }                
129            }, this));
130        },
131        cancel: function() {   
132            /**        
133            Fired when form was cancelled by user
134            @event cancel 
135            @param {Object} event event object
136            **/              
137            this.$div.triggerHandler('cancel');
138        },
139        showLoading: function() {
140            var w, h;
141            if(this.$form) {
142                //set loading size equal to form
143                w = this.$form.outerWidth();
144                h = this.$form.outerHeight(); 
145                if(w) {
146                    this.$loading.width(w);
147                }
148                if(h) {
149                    this.$loading.height(h);
150                }
151                this.$form.hide();
152            } else {
153                //stretch loading to fill container width
154                w = this.$loading.parent().width();
155                if(w) {
156                    this.$loading.width(w);
157                }
158            }
159            this.$loading.show(); 
160        },
161
162        showForm: function(activate) {
163            this.$loading.hide();
164            this.$form.show();
165            if(activate !== false) {
166                this.input.activate(); 
167            }
168            /**        
169            Fired when form is shown
170            @event show 
171            @param {Object} event event object
172            **/                    
173            this.$div.triggerHandler('show');
174        },
175
176        error: function(msg) {
177            var $group = this.$form.find('.control-group'),
178                $block = this.$form.find('.editable-error-block'),
179                lines;
180
181            if(msg === false) {
182                $group.removeClass($.fn.editableform.errorGroupClass);
183                $block.removeClass($.fn.editableform.errorBlockClass).empty().hide(); 
184            } else {
185                //convert newline to <br> for more pretty error display
186                if(msg) {
187                    lines = msg.split("\n");
188                    for (var i = 0; i < lines.length; i++) {
189                        lines[i] = $('<div>').text(lines[i]).html();
190                    }
191                    msg = lines.join('<br>');
192                }
193                $group.addClass($.fn.editableform.errorGroupClass);
194                $block.addClass($.fn.editableform.errorBlockClass).html(msg).show();
195            }
196        },
197
198        submit: function(e) {
199            e.stopPropagation();
200            e.preventDefault();
201            
202            var error,
203                newValue = this.input.input2value(); //get new value from input
204
205            //validation
206            if (error = this.validate(newValue)) {
207                this.error(error);
208                this.showForm();
209                return;
210            } 
211            
212            //if value not changed --> trigger 'nochange' event and return
213            /*jslint eqeq: true*/
214            if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) {
215            /*jslint eqeq: false*/                
216                /**        
217                Fired when value not changed but form is submitted. Requires savenochange = false.
218                @event nochange 
219                @param {Object} event event object
220                **/                    
221                this.$div.triggerHandler('nochange');            
222                return;
223            } 
224
225            //convert value for submitting to server
226            var submitValue = this.input.value2submit(newValue);
227            
228            this.isSaving = true;
229            
230            //sending data to server
231            $.when(this.save(submitValue))
232            .done($.proxy(function(response) {
233                this.isSaving = false;
234
235                //run success callback
236                var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
237
238                //if success callback returns false --> keep form open and do not activate input
239                if(res === false) {
240                    this.error(false);
241                    this.showForm(false);
242                    return;
243                }
244
245                //if success callback returns string -->  keep form open, show error and activate input               
246                if(typeof res === 'string') {
247                    this.error(res);
248                    this.showForm();
249                    return;
250                }
251
252                //if success callback returns object like {newValue: <something>} --> use that value instead of submitted
253                //it is usefull if you want to chnage value in url-function
254                if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
255                    newValue = res.newValue;
256                }
257
258                //clear error message
259                this.error(false);   
260                this.value = newValue;
261                /**        
262                Fired when form is submitted
263                @event save 
264                @param {Object} event event object
265                @param {Object} params additional params
266                @param {mixed} params.newValue raw new value
267                @param {mixed} params.submitValue submitted value as string
268                @param {Object} params.response ajax response
269
270                @example
271                $('#form-div').on('save'), function(e, params){
272                    if(params.newValue === 'username') {...}
273                });
274                **/
275                this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response});
276            }, this))
277            .fail($.proxy(function(xhr) {
278                this.isSaving = false;
279
280                var msg;
281                if(typeof this.options.error === 'function') {
282                    msg = this.options.error.call(this.options.scope, xhr, newValue);
283                } else {
284                    msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';
285                }
286
287                this.error(msg);
288                this.showForm();
289            }, this));
290        },
291
292        save: function(submitValue) {
293            //try parse composite pk defined as json string in data-pk 
294            this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true); 
295            
296            var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
297            /*
298              send on server in following cases:
299              1. url is function
300              2. url is string AND (pk defined OR send option = always) 
301            */
302            send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),
303            params;
304
305            if (send) { //send to server
306                this.showLoading();
307
308                //standard params
309                params = {
310                    name: this.options.name || '',
311                    value: submitValue,
312                    pk: pk 
313                };
314
315                //additional params
316                if(typeof this.options.params === 'function') {
317                    params = this.options.params.call(this.options.scope, params);  
318                } else {
319                    //try parse json in single quotes (from data-params attribute)
320                    this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true);   
321                    $.extend(params, this.options.params);
322                }
323
324                if(typeof this.options.url === 'function') { //user's function
325                    return this.options.url.call(this.options.scope, params);
326                } else {  
327                    //send ajax to server and return deferred object
328                    return $.ajax($.extend({
329                        url     : this.options.url,
330                        data    : params,
331                        type    : 'POST'
332                    }, this.options.ajaxOptions));
333                }
334            }
335        }, 
336
337        validate: function (value) {
338            if (value === undefined) {
339                value = this.value;
340            }
341            if (typeof this.options.validate === 'function') {
342                return this.options.validate.call(this.options.scope, value);
343            }
344        },
345
346        option: function(key, value) {
347            if(key in this.options) {
348                this.options[key] = value;
349            }
350            
351            if(key === 'value') {
352                this.setValue(value);
353            }
354            
355            //do not pass option to input as it is passed in editable-element
356        },
357
358        setValue: function(value, convertStr) {
359            if(convertStr) {
360                this.value = this.input.str2value(value);
361            } else {
362                this.value = value;
363            }
364            
365            //if form is visible, update input
366            if(this.$form && this.$form.is(':visible')) {
367                this.input.value2input(this.value);
368            }            
369        }               
370    };
371
372    /*
373    Initialize editableform. Applied to jQuery object.
374
375    @method $().editableform(options)
376    @params {Object} options
377    @example
378    var $form = $('&lt;div&gt;').editableform({
379        type: 'text',
380        name: 'username',
381        url: '/post',
382        value: 'vitaliy'
383    });
384
385    //to display form you should call 'render' method
386    $form.editableform('render');     
387    */
388    $.fn.editableform = function (option) {
389        var args = arguments;
390        return this.each(function () {
391            var $this = $(this), 
392            data = $this.data('editableform'), 
393            options = typeof option === 'object' && option; 
394            if (!data) {
395                $this.data('editableform', (data = new EditableForm(this, options)));
396            }
397
398            if (typeof option === 'string') { //call method 
399                data[option].apply(data, Array.prototype.slice.call(args, 1));
400            } 
401        });
402    };
403
404    //keep link to constructor to allow inheritance
405    $.fn.editableform.Constructor = EditableForm;    
406
407    //defaults
408    $.fn.editableform.defaults = {
409        /* see also defaults for input */
410
411        /**
412        Type of input. Can be <code>
412text|textarea|select|date|checklist</code>
413
414        @property type 
415        @type string
416        @default 'text'
417        **/
418        type: 'text',
419        /**
420        Url for submit, e.g. <code>'/post'</code>  
421        If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks.
422
423        @property url 
424        @type string|function
425        @default null
426        @example
427        url: function(params) {
428            var d = new $.Deferred;
429            if(params.value === 'abc') {
430                return d.reject('error message'); //returning error via deferred object
431            } else {
432                //async saving data in js model
433                someModel.asyncSaveMethod({
434                   ..., 
435                   success: function(){
436                      d.resolve();
437                   }
438                }); 
439                return d.promise();
440            }
441        } 
442        **/        
443        url:null,
444        /**
445        Additional params for submit. If defined as <code>object</code> - it is **appended** to original ajax data (pk, name and value).  
446        If defined as <code>function</code> - returned object **overwrites** original ajax data.
447        @example
448        params: function(params) {
449            //originally params contain pk, name and value
450            params.a = 1;
451            return params;
452        }
453
454        @property params 
455        @type object|function
456        @default null
457        **/          
458        params:null,
459        /**
460        Name of field. Will be submitted on server. Can be taken from <code>id</code> attribute
461
462        @property name 
463        @type string
464        @default null
465        **/         
466        name: null,
467        /**
468        Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>.
469        Can be calculated dynamically via function.
470
471        @property pk 
472        @type string|object|function
473        @default null
474        **/         
475        pk: null,
476        /**
477        Initial value. If not defined - will be taken from element's content.
478        For __select__ type should be defined (as it is ID of shown text).
479
480        @property value 
481        @type string|object
482        @default null
483        **/        
484        value: null,
485        /**
486        Value that will be displayed in input if original field value is empty (`null|undefined|''`).
487
488        @property defaultValue 
489        @type string|object
490        @default null
491        @since 1.4.6
492        **/        
493        defaultValue: null,
494        /**
495        Strategy for sending data on server. Can be `auto|always|never`.
496        When 'auto' data will be sent on server **only if pk and url defined**, otherwise new value will be stored locally.
497
498        @property send 
499        @type string
500        @default 'auto'
501        **/          
502        send: 'auto', 
503        /**
504        Function for client-side validation. If returns string - means validation not passed and string showed as error.
505
506        @property validate 
507        @type function
508        @default null
509        @example
510        validate: function(value) {
511            if($.trim(value) == '') {
512                return 'This field is required';
513            }
514        }
515        **/         
516        validate: null,
517        /**
518        Success callback. Called when value successfully sent on server and **response status = 200**.  
519        Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code>
520        or <code>{success: false, msg: "server error"}</code> you can check it inside this callback.  
521        If it returns **string** - means error occured and string is shown as error message.  
522        If it returns **object like** <code>{newValue: &lt;something&gt;}</code> - it overwrites value, submitted by user.  
523        Otherwise newValue simply rendered into element.
524        
525        @property success 
526        @type function
527        @default null
528        @example
529        success: function(response, newValue) {
530            if(!response.success) return response.msg;
531        }
532        **/          
533        success: null,
534        /**
535        Error callback. Called when request failed (response status != 200).  
536        Usefull when you want to parse error response and display a custom message.
537        Must return **string** - the message to be displayed in the error block.
538                
539        @property error 
540        @type function
541        @default null
542        @since 1.4.4
543        @example
544        error: function(response, newValue) {
545            if(response.status === 500) {
546                return 'Service unavailable. Please try later.';
547            } else {
548                return response.responseText;
549            }
550        }
551        **/          
552        error: null,
553        /**
554        Additional options for submit ajax request.
555        List of values: http://api.jquery.com/jQuery.ajax
556        
557        @property ajaxOptions 
558        @type object
559        @default null
560        @since 1.1.1        
561        @example 
562        ajaxOptions: {
563            type: 'put',
564            dataType: 'json'
565        }        
566        **/        
567        ajaxOptions: null,
568        /**
569        Where to show buttons: left(true)|bottom|false  
570        Form without buttons is auto-submitted.
571
572        @property showbuttons 
573        @type boolean|string
574        @default true
575        @since 1.1.1
576        **/         
577        showbuttons: true,
578        /**
579        Scope for callback methods (success, validate).  
580        If <code>null</code> means editableform instance itself. 
581
582        @property scope 
583        @type DOMElement|object
584        @default null
585        @since 1.2.0
586        @private
587        **/            
588        scope: null,
589        /**
590        Whether to save or cancel value when it was not changed but form was submitted
591
592        @property savenochange 
593        @type boolean
594        @default false
595        @since 1.2.0
596        **/
597        savenochange: false
598    };   
599
600    /*
601    Note: following params could redefined in engine: bootstrap or jqueryui:
602    Classes 'control-group' and 'editable-error-block' must always present!
603    */      
604    $.fn.editableform.template = '<form class="form-inline editableform">'+
605    '<div class="control-group">' + 
606    '<div><div class="editable-input"></div><div class="editable-buttons"></div></div>'+
607    '<div class="editable-error-block"></div>' + 
608    '</div>' + 
609    '</form>';
610
611    //loading div
612    $.fn.editableform.loading = '<div class="editableform-loading"></div>';
613
614    //buttons
615    $.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+
616    '<button type="button" class="editable-cancel">cancel</button>';      
617
618    //error class attached to control-group
619    $.fn.editableform.errorGroupClass = null;  
620
621    //error class attached to editable-error-block
622    $.fn.editableform.errorBlockClass = 'editable-error';
623}(window.jQuery));
624
625/**
626* EditableForm utilites
627*/
628(function ($) {
629    "use strict";
630    
631    //utils
632    $.fn.editableutils = {
633        /**
634        * classic JS inheritance function
635        */  
636        inherit: function (Child, Parent) {
637            var F = function() { };
638            F.prototype = Parent.prototype;
639            Child.prototype = new F();
640            Child.prototype.constructor = Child;
641            Child.superclass = Parent.prototype;
642        },
643
644        /**
645        * set caret position in input
646        * see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
647        */        
648        setCursorPosition: function(elem, pos) {
649            if (elem.setSelectionRange) {
650                elem.setSelectionRange(pos, pos);
651            } else if (elem.createTextRange) {
652                var range = elem.createTextRange();
653                range.collapse(true);
654                range.moveEnd('character', pos);
655                range.moveStart('character', pos);
656                range.select();
657            }
658        },
659
660        /**
661        * function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
662        * That allows such code as: <a data-source="{'a': 'b', 'c': 'd'}">
663        * safe = true --> means no exception will be thrown
664        * for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
665        */
666        tryParseJson: function(s, safe) {
667            if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
668                if (safe) {
669                    try {
670                        /*jslint evil: true*/
671                        s = (new Function('return ' + s))();
672                        /*jslint evil: false*/
673                    } catch (e) {} finally {
674                        return s;
675                    }
676                } else {
677                    /*jslint evil: true*/
678                    s = (new Function('return ' + s))();
679                    /*jslint evil: false*/
680                }
681            }
682            return s;
683        },
684
685        /**
686        * slice object by specified keys
687        */
688        sliceObj: function(obj, keys, caseSensitive /* default: false */) {
689            var key, keyLower, newObj = {};
690
691            if (!$.isArray(keys) || !keys.length) {
692                return newObj;
693            }
694
695            for (var i = 0; i < keys.length; i++) {
696                key = keys[i];
697                if (obj.hasOwnProperty(key)) {
698                    newObj[key] = obj[key];
699                }
700
701                if(caseSensitive === true) {
702                    continue;
703                }
704
705                //when getting data-* attributes via $.data() it's converted to lowercase.
706                //details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
707                //workaround is code below.
708                keyLower = key.toLowerCase();
709                if (obj.hasOwnProperty(keyLower)) {
710                    newObj[key] = obj[keyLower];
711                }
712            }
713
714            return newObj;
715        },
716
717        /*
718        exclude complex objects from $.data() before pass to config
719        */
720        getConfigData: function($element) {
721            var data = {};
722            $.each($element.data(), function(k, v) {
723                if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
724                    data[k] = v;
725                }
726            });
727            return data;
728        },
729
730        /*
731         returns keys of object
732        */
733        objectKeys: function(o) {
734            if (Object.keys) {
735                return Object.keys(o);  
736            } else {
737                if (o !== Object(o)) {
738                    throw new TypeError('Object.keys called on a non-object');
739                }
740                var k=[], p;
741                for (p in o) {
742                    if (Object.prototype.hasOwnProperty.call(o,p)) {
743                        k.push(p);
744                    }
745                }
746                return k;
747            }
748
749        },
750        
751       /**
752        method to escape html.
753       **/
754       escape: function(str) {
755           return $('<div>').text(str).html();
756       },
757       
758       /*
759        returns array items from sourceData having value property equal or inArray of 'value'
760       */
761       itemsByValue: function(value, sourceData, valueProp) {
762           if(!sourceData || value === null) {
763               return [];
764           }
765           
766           if (typeof(valueProp) !== "function") {
767               var idKey = valueProp || 'value';
768               valueProp = function (e) { return e[idKey]; };
769           }
770                      
771           var isValArray = $.isArray(value),
772           result = [], 
773           that = this;
774
775           $.each(sourceData, function(i, o) {
776               if(o.children) {
777                   result = result.concat(that.itemsByValue(value, o.children, valueProp));
778               } else {
779                   /*jslint eqeq: true*/
780                   if(isValArray) {
781                       if($.grep(value, function(v){  return v == (o && typeof o === 'object' ? valueProp(o) : o); }).length) {
782                           result.push(o); 
783                       }
784                   } else {
785                       var itemValue = (o && (typeof o === 'object')) ? valueProp(o) : o;
786                       if(value == itemValue) {
787                           result.push(o); 
788                       }
789                   }
790                   /*jslint eqeq: false*/
791               }
792           });
793           
794           return result;
795       },
796       
797       /*
798       Returns input by options: type, mode. 
799       */
800       createInput: function(options) {
801           var TypeConstructor, typeOptions, input,
802           type = options.type;
803
804           //`date` is some kind of virtual type that is transformed to one of exact types
805           //depending on mode and core lib
806           if(type === 'date') {
807               //inline
808               if(options.mode === 'inline') {
809                   if($.fn.editabletypes.datefield) {
810                       type = 'datefield';
811                   } else if($.fn.editabletypes.dateuifield) {
812                       type = 'dateuifield';
813                   }
814               //popup
815               } else {
816                   if($.fn.editabletypes.date) {
817                       type = 'date';
818                   } else if($.fn.editabletypes.dateui) {
819                       type = 'dateui';
820                   }
821               }
822               
823               //if type still `date` and not exist in types, replace with `combodate` that is base input
824               if(type === 'date' && !$.fn.editabletypes.date) {
825                   type = 'combodate';
826               } 
827           }
828           
829           //`datetime` should be datetimefield in 'inline' mode
830           if(type === 'datetime' && options.mode === 'inline') {
831             type = 'datetimefield';  
832           }           
833
834           //change wysihtml5 to textarea for jquery UI and plain versions
835           if(type === 'wysihtml5' && !$.fn.editabletypes[type]) {
836               type = 'textarea';
837           }
838
839           //create input of specified type. Input will be used for converting value, not in form
840           if(typeof $.fn.editabletypes[type] === 'function') {
841               TypeConstructor = $.fn.editabletypes[type];
842               typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults));
843               input = new TypeConstructor(typeOptions);
844               return input;
845           } else {
846               $.error('Unknown type: '+ type);
847               return false; 
848           }  
849       },
850       
851       //see http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr
852       supportsTransitions: function () {
853           var b = document.body || document.documentElement,
854               s = b.style,
855               p = 'transition',
856               v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
857               
858           if(typeof s[p] === 'string') {
859               return true; 
860           }
861
862           // Tests for vendor specific prop
863           p = p.charAt(0).toUpperCase() + p.substr(1);
864           for(var i=0; i<v.length; i++) {
865               if(typeof s[v[i] + p] === 'string') { 
866                   return true; 
867               }
868           }
869           return false;
870       }            
871       
872    };      
873}(window.jQuery));
874
875/**
876Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
877This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
878Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br>
879Applied as jQuery method.
880
881@class editableContainer
882@uses editableform
883**/
884(function ($) {
885    "use strict";
886
887    var Popup = function (element, options) {
888        this.init(element, options);
889    };
890    
891    var Inline = function (element, options) {
892        this.init(element, options);
893    };    
894
895    //methods
896    Popup.prototype = {
897        containerName: null, //method to call container on element
898        containerDataName: null, //object name in element's .data()
899        innerCss: null, //tbd in child class
900        containerClass: 'editable-container editable-popup', //css class applied to container element
901        init: function(element, options) {
902            this.$element = $(element);
903            //since 1.4.1 container do not use data-* directly as they already merged into options.
904            this.options = $.extend({}, $.fn.editableContainer.defaults, options);         
905            this.splitOptions();
906            
907            //set scope of form callbacks to element
908            this.formOptions.scope = this.$element[0]; 
909            
910            this.initContainer();
911            
912            //flag to hide container, when saving value will finish
913            this.delayedHide = false;
914
915            //bind 'destroyed' listener to destroy container when element is removed from dom
916            this.$element.on('destroyed', $.proxy(function(){
917                this.destroy();
918            }, this)); 
919            
920            //attach document handler to close containers on click / escape
921            if(!$(document).data('editable-handlers-attached')) {
922                //close all on escape
923                $(document).on('keyup.editable', function (e) {
924                    if (e.which === 27) {
925                        $('.editable-open').editableContainer('hide');
926                        //todo: return focus on element 
927                    }
928                });
929
930                //close containers when click outside 
931                //(mousedown could be better than click, it closes everything also on drag drop)
932                $(document).on('click.editable', function(e) {
933                    var $target = $(e.target), i,
934                        exclude_classes = ['.editable-container', 
935                                           '.ui-datepicker-header', 
936                                           '.datepicker', //in inline mode datepicker is rendered into body
937                                           '.modal-backdrop', 
938                                           '.bootstrap-wysihtml5-insert-image-modal', 
939                                           '.bootstrap-wysihtml5-insert-link-modal'
940                                           ];
941                    
942                    //check if element is detached. It occurs when clicking in bootstrap datepicker
943                    if (!$.contains(document.documentElement, e.target)) {
944                      return;
945                    }
946
947                    //for some reason FF 20 generates extra event (click) in select2 widget with e.target = document
948                    //we need to filter it via construction below. See https://github.com/vitalets/x-editable/issues/199
949                    //Possibly related to http://stackoverflow.com/questions/10119793/why-does-firefox-react-differently-from-webkit-and-ie-to-click-event-on-selec
950                    if($target.is(document)) {
951                       return; 
952                    }
953                    
954                    //if click inside one of exclude classes --> no nothing
955                    for(i=0; i<exclude_classes.length; i++) {
956                         if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) {
957                             return;
958                         }
959                    }
960                      
961                    //close all open containers (except one - target)
962                    Popup.prototype.closeOthers(e.target);
963                });
964                
965                $(document).data('editable-handlers-attached', true);
966            }                        
967        },
968
969        //split options on containerOptions and formOptions
970        splitOptions: function() {
971            this.containerOptions = {};
972            this.formOptions = {};
973            
974            if(!$.fn[this.containerName]) {
975                throw new Error(this.containerName + ' not found. Have you included corresponding js file?');   
976            }
977            
978            var cDef = $.fn[this.containerName].defaults;
979            //keys defined in container defaults go to container, others go to form
980            for(var k in this.options) {
981              if(k in cDef) {
982                 this.containerOptions[k] = this.options[k];
983              } else {
984                 this.formOptions[k] = this.options[k];
985              } 
986            }
987        },
988        
989        /*
990        Returns jquery object of container
991        @method tip()
992        */         
993        tip: function() {
994            return this.container() ? this.container().$tip : null;
995        },
996
997        /* returns container object */
998        container: function() {
999            var container;
1000            //first, try get it by `containerDataName`
1001            if(this.containerDataName) {
1002                if(container = this.$element.data(this.containerDataName)) {
1003                    return container;
1004                }
1005            }
1006            //second, try `containerName`
1007            container = this.$element.data(this.containerName);
1008            return container;
1009        },
1010
1011        /* call native method of underlying container, e.g. this.$element.popover('method') */ 
1012        call: function() {
1013            this.$element[this.containerName].apply(this.$element, arguments); 
1014        },        
1015        
1016        initContainer: function(){
1017            this.call(this.containerOptions);
1018        },
1019
1020        renderForm: function() {
1021            this.$form
1022            .editableform(this.formOptions)
1023            .on({
1024                save: $.proxy(this.save, this), //click on submit button (value changed)
1025                nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)                
1026                cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button
1027                show: $.proxy(function() {
1028                    if(this.delayedHide) {
1029                        this.hide(this.delayedHide.reason);
1030                        this.delayedHide = false;
1031                    } else {
1032                        this.setPosition();
1033                    }
1034                }, this), //re-position container every time form is shown (occurs each time after loading state)
1035                rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
1036                resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed 
1037                rendered: $.proxy(function(){
1038                    /**        
1039                    Fired when container is shown and form is rendered (for select will wait for loading dropdown options).  
1040                    **Note:** Bootstrap popover has own `shown` event that now cannot be separated from x-editable's one.
1041                    The workaround is to check `arguments.length` that is always `2` for x-editable.                     
1042                    
1043                    @event shown 
1044                    @param {Object} event event object
1045                    @example
1046                    $('#username').on('shown', function(e, editable) {
1047                        editable.input.$input.val('overwriting value of input..');
1048                    });                     
1049                    **/                      
1050                    /*
1051                     TODO: added second param mainly to distinguish from bootstrap's shown event. It's a hotfix that will be solved in future versions via namespaced events.  
1052                    */
1053                    this.$element.triggerHandler('shown', $(this.options.scope).data('editable')); 
1054                }, this) 
1055            })
1056            .editableform('render');
1057        },        
1058
1059        /**
1060        Shows container with form
1061        @method show()
1062        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
1063        **/
1064        /* Note: poshytip owerwrites this method totally! */          
1065        show: function (closeAll) {
1066            this.$element.addClass('editable-open');
1067            if(closeAll !== false) {
1068                //close all open containers (except this)
1069                this.closeOthers(this.$element[0]);  
1070            }
1071            
1072            //show container itself
1073            this.innerShow();
1074            this.tip().addClass(this.containerClass);
1075
1076            /*
1077            Currently, form is re-rendered on every show. 
1078            The main reason is that we dont know, what will container do with content when closed:
1079            remove(), detach() or just hide() - it depends on container.
1080            
1081            Detaching form itself before hide and re-insert before show is good solution, 
1082            but visually it looks ugly --> container changes size before hide.  
1083            */             
1084            
1085            //if form already exist - delete previous data 
1086            if(this.$form) {
1087                //todo: destroy prev data!
1088                //this.$form.destroy();
1089            }
1090
1091            this.$form = $('<div>');
1092            
1093            //insert form into container body
1094            if(this.tip().is(this.innerCss)) {
1095                //for inline container
1096                this.tip().append(this.$form); 
1097            } else {
1098                this.tip().find(this.innerCss).append(this.$form);
1099            } 
1100            
1101            //render form
1102            this.renderForm();
1103        },
1104
1105        /**
1106        Hides container with form
1107        @method hide()
1108        @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
1109        **/         
1110        hide: function(reason) {  
1111            if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
1112                return;
1113            }
1114            
1115            //if form is saving value, schedule hide
1116            if(this.$form.data('editableform').isSaving) {
1117                this.delayedHide = {reason: reason};
1118                return;    
1119            } else {
1120                this.delayedHide = false;
1121            }
1122
1123            this.$element.removeClass('editable-open');   
1124            this.innerHide();
1125
1126            /**
1127            Fired when container was hidden. It occurs on both save or cancel.  
1128            **Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.
1129            The workaround is to check `arguments.length` that is always `2` for x-editable. 
1130
1131            @event hidden 
1132            @param {object} event event object
1133            @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|manual</code>
1134            @example
1135            $('#username').on('hidden', function(e, reason) {
1136                if(reason === 'save' || reason === 'cancel') {
1137                    //auto-open next editable
1138                    $(this).closest('tr').next().find('.editable').editable('show');
1139                } 
1140            });
1141            **/
1142            this.$element.triggerHandler('hidden', reason || 'manual');   
1143        },
1144
1145        /* internal show method. To be overwritten in child classes */
1146        innerShow: function () {
1147             
1148        },        
1149
1150        /* internal hide method. To be overwritten in child classes */
1151        innerHide: function () {
1152
1153        },
1154        
1155        /**
1156        Toggles container visibility (show / hide)
1157        @method toggle()
1158        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
1159        **/          
1160        toggle: function(closeAll) {
1161            if(this.container() && this.tip() && this.tip().is(':visible')) {
1162                this.hide();
1163            } else {
1164                this.show(closeAll);
1165            } 
1166        },
1167
1168        /*
1169        Updates the position of container when content changed.
1170        @method setPosition()
1171        */       
1172        setPosition: function() {
1173            //tbd in child class
1174        },
1175
1176        save: function(e, params) {
1177            /**        
1178            Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
1179            
1180            @event save 
1181            @param {Object} event event object
1182            @param {Object} params additional params
1183            @param {mixed} params.newValue submitted value
1184            @param {Object} params.response ajax response
1185            @example
1186            $('#username').on('save', function(e, params) {
1187                //assuming server response: '{success: true}'
1188                var pk = $(this).data('editableContainer').options.pk;
1189                if(params.response && params.response.success) {
1190                    alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
1191                } else {
1192                    alert('error!'); 
1193                } 
1194            });
1195            **/             
1196            this.$element.triggerHandler('save', params);
1197            
1198            //hide must be after trigger, as saving value may require methods of plugin, applied to input
1199            this.hide('save');
1200        },
1201
1202        /**
1203        Sets new option
1204        
1205        @method option(key, value)
1206        @param {string} key 
1207        @param {mixed} value 
1208        **/         
1209        option: function(key, value) {
1210            this.options[key] = value;
1211            if(key in this.containerOptions) {
1212                this.containerOptions[key] = value;
1213                this.setContainerOption(key, value); 
1214            } else {
1215                this.formOptions[key] = value;
1216                if(this.$form) {
1217                    this.$form.editableform('option', key, value);  
1218                }
1219            }
1220        },
1221        
1222        setContainerOption: function(key, value) {
1223            this.call('option', key, value);
1224        },
1225
1226        /**
1227        Destroys the container instance
1228        @method destroy()
1229        **/        
1230        destroy: function() {
1231            this.hide();
1232            this.innerDestroy();
1233            this.$element.off('destroyed');
1234            this.$element.removeData('editableContainer');
1235        },
1236        
1237        /* to be overwritten in child classes */
1238        innerDestroy: function() {
1239            
1240        }, 
1241        
1242        /*
1243        Closes other containers except one related to passed element. 
1244        Other containers can be cancelled or submitted (depends on onblur option)
1245        */
1246        closeOthers: function(element) {
1247            $('.editable-open').each(function(i, el){
1248                //do nothing with passed element and it's children
1249                if(el === element || $(el).find(element).length) {
1250                    return;
1251                }
1252
1253                //otherwise cancel or submit all open containers 
1254                var $el = $(el),
1255                ec = $el.data('editableContainer');
1256
1257                if(!ec) {
1258                    return;  
1259                }
1260                
1261                if(ec.options.onblur === 'cancel') {
1262                    $el.data('editableContainer').hide('onblur');
1263                } else if(ec.options.onblur === 'submit') {
1264                    $el.data('editableContainer').tip().find('form').submit();
1265                }
1266            });
1267
1268        },
1269        
1270        /**
1271        Activates input of visible container (e.g. set focus)
1272        @method activate()
1273        **/         
1274        activate: function() {
1275            if(this.tip && this.tip().is(':visible') && this.$form) {
1276               this.$form.data('editableform').input.activate(); 
1277            }
1278        } 
1279
1280    };
1281
1282    /**
1283    jQuery method to initialize editableContainer.
1284    
1285    @method $().editableContainer(options)
1286    @params {Object} options
1287    @example
1288    $('#edit').editableContainer({
1289        type: 'text',
1290        url: '/post',
1291        pk: 1,
1292        value: 'hello'
1293    });
1294    **/  
1295    $.fn.editableContainer = function (option) {
1296        var args = arguments;
1297        return this.each(function () {
1298            var $this = $(this),
1299            dataKey = 'editableContainer', 
1300            data = $this.data(dataKey),
1301            options = typeof option === 'object' && option,
1302            Constructor = (options.mode === 'inline') ? Inline : Popup;             
1303
1304            if (!data) {
1305                $this.data(dataKey, (data = new Constructor(this, options)));
1306            }
1307
1308            if (typeof option === 'string') { //call method 
1309                data[option].apply(data, Array.prototype.slice.call(args, 1));
1310            }            
1311        });
1312    };     
1313
1314    //store constructors
1315    $.fn.editableContainer.Popup = Popup;
1316    $.fn.editableContainer.Inline = Inline;
1317
1318    //defaults
1319    $.fn.editableContainer.defaults = {
1320        /**
1321        Initial value of form input
1322
1323        @property value 
1324        @type mixed
1325        @default null
1326        @private
1327        **/        
1328        value: null,
1329        /**
1330        Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container.
1331
1332        @property placement 
1333        @type string
1334        @default 'top'
1335        **/        
1336        placement: 'top',
1337        /**
1338        Whether to hide container on save/cancel.
1339
1340        @property autohide 
1341        @type boolean
1342        @default true
1343        @private 
1344        **/        
1345        autohide: true,
1346        /**
1347        Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>.  
1348        Setting <code>ignore</code> allows to have several containers open. 
1349
1350        @property onblur 
1351        @type string
1352        @default 'cancel'
1353        @since 1.1.1
1354        **/        
1355        onblur: 'cancel',
1356        
1357        /**
1358        Animation speed (inline mode only)
1359        @property anim 
1360        @type string
1361        @default false
1362        **/        
1363        anim: false,
1364        
1365        /**
1366        Mode of editable, can be `popup` or `inline` 
1367        
1368        @property mode 
1369        @type string         
1370        @default 'popup'
1371        @since 1.4.0        
1372        **/        
1373        mode: 'popup'        
1374    };
1375
1376    /* 
1377    * workaround to have 'destroyed' event to destroy popover when element is destroyed
1378    * see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
1379    */
1380    jQuery.event.special.destroyed = {
1381        remove: function(o) {
1382            if (o.handler) {
1383                o.handler();
1384            }
1385        }
1386    };    
1387
1388}(window.jQuery));
1389
1390/**
1391* Editable Inline 
1392* ---------------------
1393*/
1394(function ($) {
1395    "use strict";
1396    
1397    //copy prototype from EditableContainer
1398    //extend methods
1399    $.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, {
1400        containerName: 'editableform',
1401        innerCss: '.editable-inline',
1402        containerClass: 'editable-container editable-inline', //css class applied to container element
1403                 
1404        initContainer: function(){
1405            //container is <span> element
1406            this.$tip = $('<span></span>');
1407            
1408            //convert anim to miliseconds (int)
1409            if(!this.options.anim) {
1410                this.options.anim = 0;
1411            }         
1412        },
1413        
1414        splitOptions: function() {
1415            //all options are passed to form
1416            this.containerOptions = {};
1417            this.formOptions = this.options;
1418        },
1419        
1420        tip: function() {
1421           return this.$tip; 
1422        },
1423        
1424        innerShow: function () {
1425            this.$element.hide();
1426            this.tip().insertAfter(this.$element).show();
1427        }, 
1428        
1429        innerHide: function () {
1430            this.$tip.hide(this.options.anim, $.proxy(function() {
1431                this.$element.show();
1432                this.innerDestroy();
1433            }, this)); 
1434        },
1435        
1436        innerDestroy: function() {
1437            if(this.tip()) {
1438                this.tip().empty().remove();
1439            }
1440        } 
1441    });
1442
1443}(window.jQuery));
1444/**
1445Makes editable any HTML element on the page. Applied as jQuery method.
1446
1447@class editable
1448@uses editableContainer
1449**/
1450(function ($) {
1451    "use strict";
1452
1453    var Editable = function (element, options) {
1454        this.$element = $(element);
1455        //data-* has more priority over js options: because dynamically created elements may change data-* 
1456        this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element));  
1457        if(this.options.selector) {
1458            this.initLive();
1459        } else {
1460            this.init();
1461        }
1462        
1463        //check for transition support
1464        if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
1465            this.options.highlight = false;
1466        }
1467    };
1468
1469    Editable.prototype = {
1470        constructor: Editable, 
1471        init: function () {
1472            var isValueByText = false, 
1473                doAutotext, finalize;
1474
1475            //name
1476            this.options.name = this.options.name || this.$element.attr('id');
1477             
1478            //create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select)
1479            //also we set scope option to have access to element inside input specific callbacks (e. g. source as function)
1480            this.options.scope = this.$element[0]; 
1481            this.input = $.fn.editableutils.createInput(this.options);
1482            if(!this.input) {
1483                return; 
1484            }            
1485
1486            //set value from settings or by element's text
1487            if (this.options.value === undefined || this.options.value === null) {
1488                this.value = this.input.html2value($.trim(this.$element.html()));
1489                isValueByText = true;
1490            } else {
1491                /*
1492                  value can be string when received from 'data-value' attribute
1493                  for complext objects value can be set as json string in data-value attribute, 
1494                  e.g. data-value="{city: 'Moscow', street: 'Lenina'}"
1495                */
1496                this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true); 
1497                if(typeof this.options.value === 'string') {
1498                    this.value = this.input.str2value(this.options.value);
1499                } else {
1500                    this.value = this.options.value;
1501                }
1502            }
1503            
1504            //add 'editable' class to every editable element
1505            this.$element.addClass('editable');
1506            
1507            //specifically for "textarea" add class .editable-pre-wrapped to keep linebreaks
1508            if(this.input.type === 'textarea') {
1509                this.$element.addClass('editable-pre-wrapped');
1510            }
1511            
1512            //attach handler activating editable. In disabled mode it just prevent default action (useful for links)
1513            if(this.options.toggle !== 'manual') {
1514                this.$element.addClass('editable-click');
1515                this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
1516                    //prevent following link if editable enabled
1517                    if(!this.options.disabled) {
1518                        e.preventDefault();
1519                    }
1520                    
1521                    //stop propagation not required because in document click handler it checks event target
1522                    //e.stopPropagation();
1523                    
1524                    if(this.options.toggle === 'mouseenter') {
1525                        //for hover only show container
1526                        this.show();
1527                    } else {
1528                        //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
1529                        var closeAll = (this.options.toggle !== 'click');
1530                        this.toggle(closeAll);
1531                    }
1532                }, this));
1533            } else {
1534                this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
1535            }
1536            
1537            //if display is function it's far more convinient to have autotext = always to render correctly on init
1538            //see https://github.com/vitalets/x-editable-yii/issues/34
1539            if(typeof this.options.display === 'function') {
1540                this.options.autotext = 'always';
1541            }
1542            
1543            //check conditions for autotext:
1544            switch(this.options.autotext) {
1545              case 'always':
1546               doAutotext = true;
1547              break;
1548              case 'auto':
1549                //if element text is empty and value is defined and value not generated by text --> run autotext
1550                doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText;
1551              break;
1552              default:
1553               doAutotext = false;
1554            }
1555
1556            //depending on autotext run render() or just finilize init
1557            $.when(doAutotext ? this.render() : true).then($.proxy(function() {
1558                if(this.options.disabled) {
1559                    this.disable();
1560                } else {
1561                    this.enable(); 
1562                }
1563               /**        
1564               Fired when element was initialized by `$().editable()` method. 
1565               Please note that you should setup `init` handler **before** applying `editable`. 
1566                              
1567               @event init 
1568               @param {Object} event event object
1569               @param {Object} editable editable instance (as here it cannot accessed via data('editable'))
1570               @since 1.2.0
1571               @example
1572               $('#username').on('init', function(e, editable) {
1573                   alert('initialized ' + editable.options.name);
1574               });
1575               $('#username').editable();
1576               **/                  
1577                this.$element.triggerHandler('init', this);
1578            }, this));
1579        },
1580
1581        /*
1582         Initializes parent element for live editables 
1583        */
1584        initLive: function() {
1585           //store selector 
1586           var selector = this.options.selector;
1587           //modify options for child elements
1588           this.options.selector = false; 
1589           this.options.autotext = 'never';
1590           //listen toggle events
1591           this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){
1592               var $target = $(e.target);
1593               if(!$target.data('editable')) {
1594                   //if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user)
1595                   //see https://github.com/vitalets/x-editable/issues/137 
1596                   if($target.hasClass(this.options.emptyclass)) {
1597                      $target.empty();
1598                   }
1599                   $target.editable(this.options).trigger(e);
1600               }
1601           }, this)); 
1602        },
1603        
1604        /*
1605        Renders value into element's text.
1606        Can call custom display method from options.
1607        Can return deferred object.
1608        @method render()
1609        @param {mixed} response server response (if exist) to pass into display function
1610        */          
1611        render: function(response) {
1612            //do not display anything
1613            if(this.options.display === false) {
1614                return;
1615            }
1616            
1617            //if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded
1618            if(this.input.value2htmlFinal) {
1619                return this.input.value2html(this.value, this.$element[0], this.options.display, response); 
1620            //if display method defined --> use it    
1621            } else if(typeof this.options.display === 'function') {
1622                return this.options.display.call(this.$element[0], this.value, response);
1623            //else use input's original value2html() method    
1624            } else {
1625                return this.input.value2html(this.value, this.$element[0]); 
1626            }
1627        },
1628        
1629        /**
1630        Enables editable
1631        @method enable()
1632        **/          
1633        enable: function() {
1634            this.options.disabled = false;
1635            this.$element.removeClass('editable-disabled');
1636            this.handleEmpty(this.isEmpty);
1637            if(this.options.toggle !== 'manual') {
1638                if(this.$element.attr('tabindex') === '-1') {    
1639                    this.$element.removeAttr('tabindex');                                
1640                }
1641            }
1642        },
1643        
1644        /**
1645        Disables editable
1646        @method disable()
1647        **/         
1648        disable: function() {
1649            this.options.disabled = true; 
1650            this.hide();           
1651            this.$element.addClass('editable-disabled');
1652            this.handleEmpty(this.isEmpty);
1653            //do not stop focus on this element
1654            this.$element.attr('tabindex', -1);                
1655        },
1656        
1657        /**
1658        Toggles enabled / disabled state of editable element
1659        @method toggleDisabled()
1660        **/         
1661        toggleDisabled: function() {
1662            if(this.options.disabled) {
1663                this.enable();
1664            } else { 
1665                this.disable(); 
1666            }
1667        },  
1668        
1669        /**
1670        Sets new option
1671        
1672        @method option(key, value)
1673        @param {string|object} key option name or object with several options
1674        @param {mixed} value option new value
1675        @example
1676        $('.editable').editable('option', 'pk', 2);
1677        **/          
1678        option: function(key, value) {
1679            //set option(s) by object
1680            if(key && typeof key === 'object') {
1681               $.each(key, $.proxy(function(k, v){
1682                  this.option($.trim(k), v); 
1683               }, this)); 
1684               return;
1685            }
1686
1687            //set option by string             
1688            this.options[key] = value;                          
1689            
1690            //disabled
1691            if(key === 'disabled') {
1692               return value ? this.disable() : this.enable();
1693            } 
1694            
1695            //value
1696            if(key === 'value') {
1697                this.setValue(value);
1698            }
1699            
1700            //transfer new option to container! 
1701            if(this.container) {
1702                this.container.option(key, value);  
1703            }
1704             
1705            //pass option to input directly (as it points to the same in form)
1706            if(this.input.option) {
1707                this.input.option(key, value);
1708            }
1709            
1710        },              
1711        
1712        /*
1713        * set emptytext if element is empty
1714        */
1715        handleEmpty: function (isEmpty) {
1716            //do not handle empty if we do not display anything
1717            if(this.options.display === false) {
1718                return;
1719            }
1720
1721            /* 
1722            isEmpty may be set directly as param of method.
1723            It is required when we enable/disable field and can't rely on content 
1724            as node content is text: "Empty" that is not empty %)
1725            */
1726            if(isEmpty !== undefined) { 
1727                this.isEmpty = isEmpty;
1728            } else {
1729                //detect empty
1730                if($.trim(this.$element.html()) === '') { 
1731                    this.isEmpty = true;
1732                } else if($.trim(this.$element.text()) !== '') {
1733                    this.isEmpty = false;
1734                } else {
1735                    //e.g. '<img>'
1736                    this.isEmpty = !this.$element.height() || !this.$element.width();
1737                }
1738            }           
1739            
1740            //emptytext shown only for enabled
1741            if(!this.options.disabled) {
1742                if (this.isEmpty) {
1743                    this.$element.html(this.options.emptytext);
1744                    if(this.options.emptyclass) {
1745                        this.$element.addClass(this.options.emptyclass);
1746                    }
1747                } else if(this.options.emptyclass) {
1748                    this.$element.removeClass(this.options.emptyclass);
1749                }
1750            } else {
1751                //below required if element disable property was changed
1752                if(this.isEmpty) {
1753                    this.$element.empty();
1754                    if(this.options.emptyclass) {
1755                        this.$element.removeClass(this.options.emptyclass);
1756                    }
1757                }
1758            }
1759        },        
1760        
1761        /**
1762        Shows container with form
1763        @method show()
1764        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
1765        **/  
1766        show: function (closeAll) {
1767            if(this.options.disabled) {
1768                return;
1769            }
1770            
1771            //init editableContainer: popover, tooltip, inline, etc..
1772            if(!this.container) {
1773                var containerOptions = $.extend({}, this.options, {
1774                    value: this.value,
1775                    input: this.input //pass input to form (as it is already created)
1776                });
1777                this.$element.editableContainer(containerOptions);
1778                //listen `save` event 
1779                this.$element.on("save.internal", $.proxy(this.save, this));
1780                this.container = this.$element.data('editableContainer'); 
1781            } else if(this.container.tip().is(':visible')) {
1782                return;
1783            }      
1784            
1785            //show container
1786            this.container.show(closeAll);
1787        },
1788        
1789        /**
1790        Hides container with form
1791        @method hide()
1792        **/       
1793        hide: function () {   
1794            if(this.container) {  
1795                this.container.hide();
1796            }
1797        },
1798        
1799        /**
1800        Toggles container visibility (show / hide)
1801        @method toggle()
1802        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
1803        **/  
1804        toggle: function(closeAll) {
1805            if(this.container && this.container.tip().is(':visible')) {
1806                this.hide();
1807            } else {
1808                this.show(closeAll);
1809            }
1810        },
1811        
1812        /*
1813        * called when form was submitted
1814        */          
1815        save: function(e, params) {
1816            //mark element with unsaved class if needed
1817            if(this.options.unsavedclass) {
1818                /*
1819                 Add unsaved css to element if:
1820                  - url is not user's function 
1821                  - value was not sent to server
1822                  - params.response === undefined, that means data was not sent
1823                  - value changed 
1824                */
1825                var sent = false;
1826                sent = sent || typeof this.options.url === 'function';
1827                sent = sent || this.options.display === false; 
1828                sent = sent || params.response !== undefined; 
1829                sent = sent || (this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(params.newValue)); 
1830                
1831                if(sent) {
1832                    this.$element.removeClass(this.options.unsavedclass); 
1833                } else {
1834                    this.$element.addClass(this.options.unsavedclass);                    
1835                }
1836            }
1837            
1838            //highlight when saving
1839            if(this.options.highlight) {
1840                var $e = this.$element,
1841                    bgColor = $e.css('background-color');
1842                    
1843                $e.css('background-color', this.options.highlight);
1844                setTimeout(function(){
1845                    if(bgColor === 'transparent') {
1846                        bgColor = ''; 
1847                    }
1848                    $e.css('background-color', bgColor);
1849                    $e.addClass('editable-bg-transition');
1850                    setTimeout(function(){
1851                       $e.removeClass('editable-bg-transition');  
1852                    }, 1700);
1853                }, 10);
1854            }
1855            
1856            //set new value
1857            this.setValue(params.newValue, false, params.response);
1858            
1859            /**        
1860            Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
1861            
1862            @event save 
1863            @param {Object} event event object
1864            @param {Object} params additional params
1865            @param {mixed} params.newValue submitted value
1866            @param {Object} params.response ajax response
1867            @example
1868            $('#username').on('save', function(e, params) {
1869                alert('Saved value: ' + params.newValue);
1870            });
1871            **/
1872            //event itself is triggered by editableContainer. Description here is only for documentation              
1873        },
1874
1875        validate: function () {
1876            if (typeof this.options.validate === 'function') {
1877                return this.options.validate.call(this, this.value);
1878            }
1879        },
1880        
1881        /**
1882        Sets new value of editable
1883        @method setValue(value, convertStr)
1884        @param {mixed} value new value 
1885        @param {boolean} convertStr whether to convert value from string to internal format
1886        **/         
1887        setValue: function(value, convertStr, response) {
1888            if(convertStr) {
1889                this.value = this.input.str2value(value);
1890            } else {
1891                this.value = value;
1892            }
1893            if(this.container) {
1894                this.container.option('value', this.value);
1895            }
1896            $.when(this.render(response))
1897            .then($.proxy(function() {
1898                this.handleEmpty();
1899            }, this));
1900        },
1901        
1902        /**
1903        Activates input of visible container (e.g. set focus)
1904        @method activate()
1905        **/         
1906        activate: function() {
1907            if(this.container) {
1908               this.container.activate(); 
1909            }
1910        },
1911        
1912        /**
1913        Removes editable feature from element
1914        @method destroy()
1915        **/        
1916        destroy: function() {
1917            this.disable();
1918            
1919            if(this.container) {
1920               this.container.destroy(); 
1921            }
1922            
1923            this.input.destroy();
1924
1925            if(this.options.toggle !== 'manual') {
1926                this.$element.removeClass('editable-click');
1927                this.$element.off(this.options.toggle + '.editable');
1928            } 
1929            
1930            this.$element.off("save.internal");
1931            
1932            this.$element.removeClass('editable editable-open editable-disabled');
1933            this.$element.removeData('editable');
1934        }        
1935    };
1936
1937    /* EDITABLE PLUGIN DEFINITION
1938    * ======================= */
1939
1940    /**
1941    jQuery method to initialize editable element.
1942    
1943    @method $().editable(options)
1944    @params {Object} options
1945    @example
1946    $('#username').editable({
1947        type: 'text',
1948        url: '/post',
1949        pk: 1
1950    });
1951    **/
1952    $.fn.editable = function (option) {
1953        //special API methods returning non-jquery object
1954        var result = {}, args = arguments, datakey = 'editable';
1955        switch (option) {
1956            /**
1957            Runs client-side validation for all matched editables
1958            
1959            @method validate()
1960            @returns {Object} validation errors map
1961            @example
1962            $('#username, #fullname').editable('validate');
1963            // possible result:
1964            {
1965              username: "username is required",
1966              fullname: "fullname should be minimum 3 letters length"
1967            }
1968            **/
1969            case 'validate':
1970                this.each(function () {
1971                    var $this = $(this), data = $this.data(datakey), error;
1972                    if (data && (error = data.validate())) {
1973                        result[data.options.name] = error;
1974                    }
1975                });
1976            return result;
1977
1978            /**
1979            Returns current values of editable elements.   
1980            Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.    
1981            If value of some editable is `null` or `undefined` it is excluded from result object.
1982            When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object.   
1983             
1984            @method getValue()
1985            @param {bool} isSingle whether to return just value of single element
1986            @returns {Object} object of element names and values
1987            @example
1988            $('#username, #fullname').editable('getValue');
1989            //result:
1990            {
1991            username: "superuser",
1992            fullname: "John"
1993            }
1994            //isSingle = true
1995            $('#username').editable('getValue', true);
1996            //result "superuser" 
1997            **/
1998            case 'getValue':
1999                if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
2000                    result = this.eq(0).data(datakey).value;
2001                } else {
2002                    this.each(function () {
2003                        var $this = $(this), data = $this.data(datakey);
2004                        if (data && data.value !== undefined && data.value !== null) {
2005                            result[data.options.name] = data.input.value2submit(data.value);
2006                        }
2007                    });
2008                }
2009            return result;
2010
2011            /**
2012            This method collects values from several editable elements and submit them all to server.   
2013            Internally it runs client-side validation for all fields and submits only in case of success.  
2014            See <a href="#newrecord">creating new records</a> for details.
2015            
2016            @method submit(options)
2017            @param {object} options 
2018            @param {object} options.url url to submit data 
2019            @param {object} options.data additional data to submit
2020            @param {object} options.ajaxOptions additional ajax options
2021            @param {function} options.error(obj) error handler 
2022            @param {function} options.success(obj,config) success handler
2023            @returns {Object} jQuery object
2024            **/
2025            case 'submit':  //collects value, validate and submit to server for creating new record
2026                var config = arguments[1] || {},
2027                $elems = this,
2028                errors = this.editable('validate'),
2029                values;
2030
2031                if($.isEmptyObject(errors)) {
2032                    values = this.editable('getValue'); 
2033                    if(config.data) {
2034                        $.extend(values, config.data);
2035                    }                    
2036                    
2037                    $.ajax($.extend({
2038                        url: config.url, 
2039                        data: values, 
2040                        type: 'POST'                        
2041                    }, config.ajaxOptions))
2042                    .success(function(response) {
2043                        //successful response 200 OK
2044                        if(typeof config.success === 'function') {
2045                            config.success.call($elems, response, config);
2046                        } 
2047                    })
2048                    .error(function(){  //ajax error
2049                        if(typeof config.error === 'function') {
2050                            config.error.apply($elems, arguments);
2051                        }
2052                    });
2053                } else { //client-side validation error
2054                    if(typeof config.error === 'function') {
2055                        config.error.call($elems, errors);
2056                    }
2057                }
2058            return this;
2059        }
2060
2061        //return jquery object
2062        return this.each(function () {
2063            var $this = $(this), 
2064                data = $this.data(datakey), 
2065                options = typeof option === 'object' && option;
2066
2067            //for delegated targets do not store `editable` object for element
2068            //it's allows several different selectors.
2069            //see: https://github.com/vitalets/x-editable/issues/312    
2070            if(options && options.selector) {
2071                data = new Editable(this, options);
2072                return; 
2073            }    
2074            
2075            if (!data) {
2076                $this.data(datakey, (data = new Editable(this, options)));
2077            }
2078
2079            if (typeof option === 'string') { //call method 
2080                data[option].apply(data, Array.prototype.slice.call(args, 1));
2081            } 
2082        });
2083    };    
2084            
2085
2086    $.fn.editable.defaults = {
2087        /**
2088        Type of input. Can be <code>
2088text|textarea|select|date|checklist</code> and more
2089
2090        @property type 
2091        @type string
2092        @default 'text'
2093        **/
2094        type: 'text',        
2095        /**
2096        Sets disabled state of editable
2097
2098        @property disabled 
2099        @type boolean
2100        @default false
2101        **/         
2102        disabled: false,
2103        /**
2104        How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>.   
2105        When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable.    
2106        **Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element, 
2107        you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document.
2108        
2109        @example
2110        $('#edit-button').click(function(e) {
2111            e.stopPropagation();
2112            $('#username').editable('toggle');
2113        });
2114
2115        @property toggle 
2116        @type string
2117        @default 'click'
2118        **/          
2119        toggle: 'click',
2120        /**
2121        Text shown when element is empty.
2122
2123        @property emptytext 
2124        @type string
2125        @default 'Empty'
2126        **/         
2127        emptytext: 'Empty',
2128        /**
2129        Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date.
2130        For example, if dropdown list is <code>{1: 'a', 2: 'b'}</code> and element's value set to <code>1</code>, it's html will be automatically set to <code>'a'</code>.  
2131        <code>auto</code> - text will be automatically set only if element is empty.  
2132        <code>always|never</code> - always(never) try to set element's text.
2133
2134        @property autotext 
2135        @type string
2136        @default 'auto'
2137        **/          
2138        autotext: 'auto', 
2139        /**
2140        Initial value of input. If not set, taken from element's text.  
2141        Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option).  
2142        For example, to display currency sign:
2143        @example
2144        <a id="price" data-type="text" data-value="100"></a>
2145        <script>
2146        $('#price').editable({
2147            ...
2148            display: function(value) {
2149              $(this).text(value + '$');
2150            } 
2151        }) 
2152        </script>
2153                
2154        @property value 
2155        @type mixed
2156        @default element's text
2157        **/
2158        value: null,
2159        /**
2160        Callback to perform custom displaying of value in element's text.  
2161        If `null`, default input's display used.  
2162        If `false`, no displaying methods will be called, element's text will never change.  
2163        Runs under element's scope.  
2164        _**Parameters:**_  
2165        
2166        * `value` current value to be displayed
2167        * `response` server response (if display called after ajax submit), since 1.4.0
2168         
2169        For _inputs with source_ (select, checklist) parameters are different:  
2170          
2171        * `value` current value to be displayed
2172        * `sourceData` array of items for current input (e.g. dropdown items) 
2173        * `response` server response (if display called after ajax submit), since 1.4.0
2174                  
2175        To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`.
2176        
2177        @property display 
2178        @type function|boolean
2179        @default null
2180        @since 1.2.0
2181        @example
2182        display: function(value, sourceData) {
2183           //display checklist as comma-separated values
2184           var html = [],
2185               checked = $.fn.editableutils.itemsByValue(value, sourceData);
2186               
2187           if(checked.length) {
2188               $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
2189               $(this).html(html.join(', '));
2190           } else {
2191               $(this).empty(); 
2192           }
2193        }
2194        **/          
2195        display: null,
2196        /**
2197        Css class applied when editable text is empty.
2198
2199        @property emptyclass 
2200        @type string
2201        @since 1.4.1        
2202        @default editable-empty
2203        **/        
2204        emptyclass: 'editable-empty',
2205        /**
2206        Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`).  
2207        You may set it to `null` if you work with editables locally and submit them together.  
2208
2209        @property unsavedclass 
2210        @type string
2211        @since 1.4.1        
2212        @default editable-unsaved
2213        **/        
2214        unsavedclass: 'editable-unsaved',
2215        /**
2216        If selector is provided, editable will be delegated to the specified targets.  
2217        Usefull for dynamically generated DOM elements.  
2218        **Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options, 
2219        as they actually become editable only after first click.  
2220        You should manually set class `editable-click` to these elements.  
2221        Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element:
2222
2223        @property selector 
2224        @type string
2225        @since 1.4.1        
2226        @default null
2227        @example
2228        <div id="user">
2229          <!-- empty -->
2230          <a href="#" data-name="username" data-type="text" class="editable-click editable-empty" data-value="" title="Username">Empty</a>
2231          <!-- non-empty -->
2232          <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" class="editable-click" title="Group">Operator</a>
2233        </div>     
2234        
2235        <script>
2236        $('#user').editable({
2237            selector: 'a',
2238            url: '/post',
2239            pk: 1
2240        });
2241        </script>
2242        **/         
2243        selector: null,
2244        /**
2245        Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers.
2246        
2247        @property highlight 
2248        @type string|boolean
2249        @since 1.4.5        
2250        @default #FFFF80 
2251        **/
2252        highlight: '#FFFF80'        
2253    };
2254    
2255}(window.jQuery));
2256
2257/**
2258AbstractInput - base class for all editable inputs.
2259It defines interface to be implemented by any input type.
2260To create your own input you can inherit from this class.
2261
2262@class abstractinput
2263**/
2264(function ($) {
2265    "use strict";
2266
2267    //types
2268    $.fn.editabletypes = {};
2269
2270    var AbstractInput = function () { };
2271
2272    AbstractInput.prototype = {
2273       /**
2274        Initializes input
2275
2276        @method init() 
2277        **/
2278       init: function(type, options, defaults) {
2279           this.type = type;
2280           this.options = $.extend({}, defaults, options);
2281       },
2282
2283       /*
2284       this method called before render to init $tpl that is inserted in DOM
2285       */
2286       prerender: function() {
2287           this.$tpl = $(this.options.tpl); //whole tpl as jquery object    
2288           this.$input = this.$tpl;         //control itself, can be changed in render method
2289           this.$clear = null;              //clear button
2290           this.error = null;               //error message, if input cannot be rendered           
2291       },
2292       
2293       /**
2294        Renders input from tpl. Can return jQuery deferred object.
2295        Can be overwritten in child objects
2296
2297        @method render()
2298       **/
2299       render: function() {
2300
2301       }, 
2302
2303       /**
2304        Sets element's html by value. 
2305
2306        @method value2html(value, element)
2307        @param {mixed} value
2308        @param {DOMElement} element
2309       **/
2310       value2html: function(value, element) {
2311           $(element).text($.trim(value));
2312       },
2313
2314       /**
2315        Converts element's html to value
2316
2317        @method html2value(html)
2318        @param {string} html
2319        @returns {mixed}
2320       **/
2321       html2value: function(html) {
2322           return $('<div>').html(html).text();
2323       },
2324
2325       /**
2326        Converts value to string (for internal compare). For submitting to server used value2submit().
2327
2328        @method value2str(value) 
2329        @param {mixed} value
2330        @returns {string}
2331       **/
2332       value2str: function(value) {
2333           return value;
2334       }, 
2335
2336       /**
2337        Converts string received from server into value. Usually from `data-value` attribute.
2338
2339        @method str2value(str)
2340        @param {string} str
2341        @returns {mixed}
2342       **/
2343       str2value: function(str) {
2344           return str;
2345       }, 
2346       
2347       /**
2348        Converts value for submitting to server. Result can be string or object.
2349
2350        @method value2submit(value) 
2351        @param {mixed} value
2352        @returns {mixed}
2353       **/
2354       value2submit: function(value) {
2355           return value;
2356       },
2357
2358       /**
2359        Sets value of input.
2360
2361        @method value2input(value) 
2362        @param {mixed} value
2363       **/
2364       value2input: function(value) {
2365           this.$input.val(value);
2366       },
2367
2368       /**
2369        Returns value of input. Value can be object (e.g. datepicker)
2370
2371        @method input2value() 
2372       **/
2373       input2value: function() { 
2374           return this.$input.val();
2375       }, 
2376
2377       /**
2378        Activates input. For text it sets focus.
2379
2380        @method activate() 
2381       **/
2382       activate: function() {
2383           if(this.$input.is(':visible')) {
2384               this.$input.focus();
2385           }
2386       },
2387
2388       /**
2389        Creates input.
2390
2391        @method clear() 
2392       **/        
2393       clear: function() {
2394           this.$input.val(null);
2395       },
2396
2397       /**
2398        method to escape html.
2399       **/
2400       escape: function(str) {
2401           return $('<div>').text(str).html();
2402       },
2403       
2404       /**
2405        attach handler to automatically submit form when value changed (useful when buttons not shown)
2406       **/
2407       autosubmit: function() {
2408        
2409       },
2410       
2411       /**
2412       Additional actions when destroying element 
2413       **/
2414       destroy: function() {
2415       },
2416
2417       // -------- helper functions --------
2418       setClass: function() {
2419           if(this.options.inputclass) {
2420               this.$input.addClass(this.options.inputclass); 
2421           } 
2422       },
2423
2424       setAttr: function(attr) {
2425           if (this.options[attr] !== undefined && this.options[attr] !== null) {
2426               this.$input.attr(attr, this.options[attr]);
2427           } 
2428       },
2429       
2430       option: function(key, value) {
2431            this.options[key] = value;
2432       }
2433       
2434    };
2435        
2436    AbstractInput.defaults = {  
2437        /**
2438        HTML template of input. Normally you should not change it.
2439
2440        @property tpl 
2441        @type string
2442        @default ''
2443        **/   
2444        tpl: '',
2445        /**
2446        CSS class automatically applied to input
2447        
2448        @property inputclass 
2449        @type string
2450        @default input-medium
2451        **/         
2452        inputclass: 'input-medium',
2453        //scope for external methods (e.g. source defined as function)
2454        //for internal use only
2455        scope: null,
2456        
2457        //need to re-declare showbuttons here to get it's value from common config (passed only options existing in defaults)
2458        showbuttons: true 
2459    };
2460    
2461    $.extend($.fn.editabletypes, {abstractinput: AbstractInput});
2462        
2463}(window.jQuery));
2464
2465/**
2466List - abstract class for inputs that have source option loaded from js array or via ajax
2467
2468@class list
2469@extends abstractinput
2470**/
2471(function ($) {
2472    "use strict";
2473    
2474    var List = function (options) {
2475       
2476    };
2477
2478    $.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput);
2479
2480    $.extend(List.prototype, {
2481        render: function () {
2482            var deferred = $.Deferred();
2483
2484            this.error = null;
2485            this.onSourceReady(function () {
2486                this.renderList();
2487                deferred.resolve();
2488            }, function () {
2489                this.error = this.options.sourceError;
2490                deferred.resolve();
2491            });
2492
2493            return deferred.promise();
2494        },
2495
2496        html2value: function (html) {
2497            return null; //can't set value by text
2498        },
2499        
2500        value2html: function (value, element, display, response) {
2501            var deferred = $.Deferred(),
2502                success = function () {
2503                    if(typeof display === 'function') {
2504                        //custom display method
2505                        display.call(element, value, this.sourceData, response); 
2506                    } else {
2507                        this.value2htmlFinal(value, element);
2508                    }
2509                    deferred.resolve();
2510               };
2511            
2512            //for null value just call success without loading source
2513            if(value === null) {
2514               success.call(this);   
2515            } else {
2516               this.onSourceReady(success, function () { deferred.resolve(); });
2517            }
2518
2519            return deferred.promise();
2520        },  
2521
2522        // ------------- additional functions ------------
2523
2524        onSourceReady: function (success, error) {
2525            //run source if it function
2526            var source;
2527            if ($.isFunction(this.options.source)) {
2528                source = this.options.source.call(this.options.scope);
2529                this.sourceData = null;
2530                //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed
2531            } else {
2532                source = this.options.source;
2533            }            
2534            
2535            //if allready loaded just call success
2536            if(this.options.sourceCache && $.isArray(this.sourceData)) {
2537                success.call(this);
2538                return; 
2539            }
2540
2541            //try parse json in single quotes (for double quotes jquery does automatically)
2542            try {
2543                source = $.fn.editableutils.tryParseJson(source, false);
2544            } catch (e) {
2545                error.call(this);
2546                return;
2547            }
2548
2549            //loading from url
2550            if (typeof source === 'string') {
2551                //try to get sourceData from cache
2552                if(this.options.sourceCache) {
2553                    var cacheID = source,
2554                    cache;
2555
2556                    if (!$(document).data(cacheID)) {
2557                        $(document).data(cacheID, {});
2558                    }
2559                    cache = $(document).data(cacheID);
2560
2561                    //check for cached data
2562                    if (cache.loading === false && cache.sourceData) { //take source from cache
2563                        this.sourceData = cache.sourceData;
2564                        this.doPrepend();
2565                        success.call(this);
2566                        return;
2567                    } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
2568                        cache.callbacks.push($.proxy(function () {
2569                            this.sourceData = cache.sourceData;
2570                            this.doPrepend();
2571                            success.call(this);
2572                        }, this));
2573
2574                        //also collecting error callbacks
2575                        cache.err_callbacks.push($.proxy(error, this));
2576                        return;
2577                    } else { //no cache yet, activate it
2578                        cache.loading = true;
2579                        cache.callbacks = [];
2580                        cache.err_callbacks = [];
2581                    }
2582                }
2583                
2584                //loading sourceData from server
2585                $.ajax({
2586                    url: source,
2587                    type: 'get',
2588                    cache: false,
2589                    dataType: 'json',
2590                    success: $.proxy(function (data) {
2591                        if(cache) {
2592                            cache.loading = false;
2593                        }
2594                        this.sourceData = this.makeArray(data);
2595                        if($.isArray(this.sourceData)) {
2596                            if(cache) {
2597                                //store result in cache
2598                                cache.sourceData = this.sourceData;
2599                                //run success callbacks for other fields waiting for this source
2600                                $.each(cache.callbacks, function () { this.call(); }); 
2601                            }
2602                            this.doPrepend();
2603                            success.call(this);
2604                        } else {
2605                            error.call(this);
2606                            if(cache) {
2607                                //run error callbacks for other fields waiting for this source
2608                                $.each(cache.err_callbacks, function () { this.call(); }); 
2609                            }
2610                        }
2611                    }, this),
2612                    error: $.proxy(function () {
2613                        error.call(this);
2614                        if(cache) {
2615                             cache.loading = false;
2616                             //run error callbacks for other fields
2617                             $.each(cache.err_callbacks, function () { this.call(); }); 
2618                        }
2619                    }, this)
2620                });
2621            } else { //options as json/array
2622                this.sourceData = this.makeArray(source);
2623                    
2624                if($.isArray(this.sourceData)) {
2625                    this.doPrepend();
2626                    success.call(this);   
2627                } else {
2628                    error.call(this);
2629                }
2630            }
2631        },
2632
2633        doPrepend: function () {
2634            if(this.options.prepend === null || this.options.prepend === undefined) {
2635                return;  
2636            }
2637            
2638            if(!$.isArray(this.prependData)) {
2639                //run prepend if it is function (once)
2640                if ($.isFunction(this.options.prepend)) {
2641                    this.options.prepend = this.options.prepend.call(this.options.scope);
2642                }
2643              
2644                //try parse json in single quotes
2645                this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
2646                
2647                //convert prepend from string to object
2648                if (typeof this.options.prepend === 'string') {
2649                    this.options.prepend = {'': this.options.prepend};
2650                }
2651                
2652                this.prependData = this.makeArray(this.options.prepend);
2653            }
2654
2655            if($.isArray(this.prependData) && $.isArray(this.sourceData)) {
2656                this.sourceData = this.prependData.concat(this.sourceData);
2657            }
2658        },
2659
2660        /*
2661         renders input list
2662        */
2663        renderList: function() {
2664            // this method should be overwritten in child class
2665        },
2666       
2667         /*
2668         set element's html by value
2669        */
2670        value2htmlFinal: function(value, element) {
2671            // this method should be overwritten in child class
2672        },        
2673
2674        /**
2675        * convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}]
2676        */
2677        makeArray: function(data) {
2678            var count, obj, result = [], item, iterateItem;
2679            if(!data || typeof data === 'string') {
2680                return null; 
2681            }
2682
2683            if($.isArray(data)) { //array
2684                /* 
2685                   function to iterate inside item of array if item is object.
2686                   Caclulates count of keys in item and store in obj. 
2687                */
2688                iterateItem = function (k, v) {
2689                    obj = {value: k, text: v};
2690                    if(count++ >= 2) {
2691                        return false;// exit from `each` if item has more than one key.
2692                    }
2693                };
2694            
2695                for(var i = 0; i < data.length; i++) {
2696                    item = data[i]; 
2697                    if(typeof item === 'object') {
2698                        count = 0; //count of keys inside item
2699                        $.each(item, iterateItem);
2700                        //case: [{val1: 'text1'}, {val2: 'text2} ...]
2701                        if(count === 1) { 
2702                            result.push(obj); 
2703                            //case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...]
2704                        } else if(count > 1) {
2705                            //removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text')
2706                            if(item.children) {
2707                                item.children = this.makeArray(item.children);   
2708                            }
2709                            result.push(item);
2710                        }
2711                    } else {
2712                        //case: ['text1', 'text2' ...]
2713                        result.push({value: item, text: item}); 
2714                    }
2715                }
2716            } else {  //case: {val1: 'text1', val2: 'text2, ...}
2717                $.each(data, function (k, v) {
2718                    result.push({value: k, text: v});
2719                });  
2720            }
2721            return result;
2722        },
2723        
2724        option: function(key, value) {
2725            this.options[key] = value;
2726            if(key === 'source') {
2727                this.sourceData = null;
2728            }
2729            if(key === 'prepend') {
2730                this.prependData = null;
2731            }            
2732        }        
2733
2734    });      
2735
2736    List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
2737        /**
2738        Source data for list.  
2739        If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`  
2740        For compability, object format is also supported: `{"1": "text1", "2"
2740: "text2" ...}` but it does not guarantee elements order.
2741        
2742        If **string** - considered ajax url to load items. In that case results will be cached for fields with the same source and name. See also `sourceCache` option.
2743          
2744        If **function**, it should return data in format above (since 1.4.0).
2745        
2746        Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only).  
2747        `[{text: "group1", children: [{value: 1, text: "text1"}, {value: 2, text: "text2"}]}, ...]` 
2748
2749		
2750        @property source 
2751        @type string | array | object | function
2752        @default null
2753        **/         
2754        source: null, 
2755        /**
2756        Data automatically prepended to the beginning of dropdown list.
2757        
2758        @property prepend 
2759        @type string | array | object | function
2760        @default false
2761        **/         
2762        prepend: false,
2763        /**
2764        Error message when list cannot be loaded (e.g. ajax error)
2765        
2766        @property sourceError 
2767        @type string
2768        @default Error when loading list
2769        **/          
2770        sourceError: 'Error when loading list',
2771        /**
2772        if <code>true</code> and source is **string url** - results will be cached for fields with the same source.    
2773        Usefull for editable column in grid to prevent extra requests.
2774        
2775        @property sourceCache 
2776        @type boolean
2777        @default true
2778        @since 1.2.0
2779        **/        
2780        sourceCache: true
2781    });
2782
2783    $.fn.editabletypes.list = List;      
2784
2785}(window.jQuery));
2786
2787/**
2788Text input
2789
2790@class text
2791@extends abstractinput
2792@final
2793@example
2794<a href="#" id="username" data-type="text" data-pk="1">awesome</a>
2795<script>
2796$(function(){
2797    $('#username').editable({
2798        url: '/post',
2799        title: 'Enter username'
2800    });
2801});
2802</script>
2803**/
2804(function ($) {
2805    "use strict";
2806    
2807    var Text = function (options) {
2808        this.init('text', options, Text.defaults);
2809    };
2810
2811    $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
2812
2813    $.extend(Text.prototype, {
2814        render: function() {
2815           this.renderClear();
2816           this.setClass();
2817           this.setAttr('placeholder');
2818        },
2819        
2820        activate: function() {
2821            if(this.$input.is(':visible')) {
2822                this.$input.focus();
2823                $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
2824                if(this.toggleClear) {
2825                    this.toggleClear();
2826                }
2827            }
2828        },
2829        
2830        //render clear button
2831        renderClear:  function() {
2832           if (this.options.clear) {
2833               this.$clear = $('<span class="editable-clear-x"></span>');
2834               this.$input.after(this.$clear)
2835                          .css('padding-right', 24)
2836                          .keyup($.proxy(function(e) {
2837                              //arrows, enter, tab, etc
2838                              if(~$.inArray(e.keyCode, [40,38,9,13,27])) {
2839                                return;
2840                              }                            
2841
2842                              clearTimeout(this.t);
2843                              var that = this;
2844                              this.t = setTimeout(function() {
2845                                that.toggleClear(e);
2846                              }, 100);
2847                              
2848                          }, this))
2849                          .parent().css('position', 'relative');
2850                          
2851               this.$clear.click($.proxy(this.clear, this));                       
2852           }            
2853        },
2854        
2855        postrender: function() {
2856            /*
2857            //now `clear` is positioned via css
2858            if(this.$clear) {
2859                //can position clear button only here, when form is shown and height can be calculated
2860//                var h = this.$input.outerHeight(true) || 20,
2861                var h = this.$clear.parent().height(),
2862                    delta = (h - this.$clear.height()) / 2;
2863                    
2864                //this.$clear.css({bottom: delta, right: delta});
2865            }
2866            */ 
2867        },
2868        
2869        //show / hide clear button
2870        toggleClear: function(e) {
2871            if(!this.$clear) {
2872                return;
2873            }
2874            
2875            var len = this.$input.val().length,
2876                visible = this.$clear.is(':visible');
2877                 
2878            if(len && !visible) {
2879                this.$clear.show();
2880            } 
2881            
2882            if(!len && visible) {
2883                this.$clear.hide();
2884            } 
2885        },
2886        
2887        clear: function() {
2888           this.$clear.hide();
2889           this.$input.val('').focus();
2890        }          
2891    });
2892
2893    Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
2894        /**
2895        @property tpl 
2896        @default <input type="text">
2897        **/         
2898        tpl: '<input type="text">',
2899        /**
2900        Placeholder attribute of input. Shown when input is empty.
2901
2902        @property placeholder 
2903        @type string
2904        @default null
2905        **/             
2906        placeholder: null,
2907        
2908        /**
2909        Whether to show `clear` button 
2910        
2911        @property clear 
2912        @type boolean
2913        @default true        
2914        **/
2915        clear: true
2916    });
2917
2918    $.fn.editabletypes.text = Text;
2919
2920}(window.jQuery));
2921
2922/**
2923Textarea input
2924
2925@class textarea
2926@extends abstractinput
2927@final
2928@example
2929<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a>
2930<script>
2931$(function(){
2932    $('#comments').editable({
2933        url: '/post',
2934        title: 'Enter comments',
2935        rows: 10
2936    });
2937});
2938</script>
2939**/
2940(function ($) {
2941    "use strict";
2942    
2943    var Textarea = function (options) {
2944        this.init('textarea', options, Textarea.defaults);
2945    };
2946
2947    $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
2948
2949    $.extend(Textarea.prototype, {
2950        render: function () {
2951            this.setClass();
2952            this.setAttr('placeholder');
2953            this.setAttr('rows');                        
2954            
2955            //ctrl + enter
2956            this.$input.keydown(function (e) {
2957                if (e.ctrlKey && e.which === 13) {
2958                    $(this).closest('form').submit();
2959                }
2960            });
2961        },
2962        
2963       //using `white-space: pre-wrap` solves \n  <--> BR conversion very elegant!
2964       /* 
2965       value2html: function(value, element) {
2966            var html = '', lines;
2967            if(value) {
2968                lines = value.split("\n");
2969                for (var i = 0; i < lines.length; i++) {
2970                    lines[i] = $('<div>').text(lines[i]).html();
2971                }
2972                html = lines.join('<br>');
2973            }
2974            $(element).html(html);
2975        },
2976       
2977        html2value: function(html) {
2978            if(!html) {
2979                return '';
2980            }
2981
2982            var regex = new RegExp(String.fromCharCode(10), 'g');
2983            var lines = html.split(/<br\s*\/?>/i);
2984            for (var i = 0; i < lines.length; i++) {
2985                var text = $('<div>').html(lines[i]).text();
2986
2987                // Remove newline characters (\n) to avoid them being converted by value2html() method
2988                // thus adding extra <br> tags
2989                text = text.replace(regex, '');
2990
2991                lines[i] = text;
2992            }
2993            return lines.join("\n");
2994        },
2995         */
2996        activate: function() {
2997            $.fn.editabletypes.text.prototype.activate.call(this);
2998        }
2999    });
3000
3001    Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
3002        /**
3003        @property tpl
3004        @default <textarea></textarea>
3005        **/
3006        tpl:'<textarea></textarea>',
3007        /**
3008        @property inputclass
3009        @default input-large
3010        **/
3011        inputclass: 'input-large',
3012        /**
3013        Placeholder attribute of input. Shown when input is empty.
3014
3015        @property placeholder
3016        @type string
3017        @default null
3018        **/
3019        placeholder: null,
3020        /**
3021        Number of rows in textarea
3022
3023        @property rows
3024        @type integer
3025        @default 7
3026        **/        
3027        rows: 7        
3028    });
3029
3030    $.fn.editabletypes.textarea = Textarea;
3031
3032}(window.jQuery));
3033
3034/**
3035Select (dropdown)
3036
3037@class select
3038@extends list
3039@final
3040@example
3041<a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-original-title="Select status"></a>
3042<script>
3043$(function(){
3044    $('#status').editable({
3045        value: 2,    
3046        source: [
3047              {value: 1, text: 'Active'},
3048              {value: 2, text: 'Blocked'},
3049              {value: 3, text: 'Deleted'}
3050           ]
3051    });
3052});
3053</script>
3054**/
3055(function ($) {
3056    "use strict";
3057    
3058    var Select = function (options) {
3059        this.init('select', options, Select.defaults);
3060    };
3061
3062    $.fn.editableutils.inherit(Select, $.fn.editabletypes.list);
3063
3064    $.extend(Select.prototype, {
3065        renderList: function() {
3066            this.$input.empty();
3067
3068            var fillItems = function($el, data) {
3069                var attr;
3070                if($.isArray(data)) {
3071                    for(var i=0; i<data.length; i++) {
3072                        attr = {};
3073                        if(data[i].children) {
3074                            attr.label = data[i].text;
3075                            $el.append(fillItems($('<optgroup>', attr), data[i].children)); 
3076                        } else {
3077                            attr.value = data[i].value;
3078                            if(data[i].disabled) {
3079                                attr.disabled = true;
3080                            }
3081                            $el.append($('<option>', attr).text(data[i].text)); 
3082                        }
3083                    }
3084                }
3085                return $el;
3086            };        
3087
3088            fillItems(this.$input, this.sourceData);
3089            
3090            this.setClass();
3091            
3092            //enter submit
3093            this.$input.on('keydown.editable', function (e) {
3094                if (e.which === 13) {
3095                    $(this).closest('form').submit();
3096                }
3097            });            
3098        },
3099       
3100        value2htmlFinal: function(value, element) {
3101            var text = '', 
3102                items = $.fn.editableutils.itemsByValue(value, this.sourceData);
3103                
3104            if(items.length) {
3105                text = items[0].text;
3106            }
3107            
3108            $(element).text(text);
3109        },
3110        
3111        autosubmit: function() {
3112            this.$input.off('keydown.editable').on('change.editable', function(){
3113                $(this).closest('form').submit();
3114            });
3115        }
3116    });      
3117
3118    Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
3119        /**
3120        @property tpl 
3121        @default <select></select>
3122        **/         
3123        tpl:'<select></select>'
3124    });
3125
3126    $.fn.editabletypes.select = Select;      
3127
3128}(window.jQuery));
3129
3130/**
3131List of checkboxes. 
3132Internally value stored as javascript array of values.
3133
3134@class checklist
3135@extends list
3136@final
3137@example
3138<a href="#" id="options" data-type="checklist" data-pk="1" data-url="/post" data-original-title="Select options"></a>
3139<script>
3140$(function(){
3141    $('#options').editable({
3142        value: [2, 3],    
3143        source: [
3144              {value: 1, text: 'option1'},
3145              {value: 2, text: 'option2'},
3146              {value: 3, text: 'option3'}
3147           ]
3148    });
3149});
3150</script>
3151**/
3152(function ($) {
3153    "use strict";
3154    
3155    var Checklist = function (options) {
3156        this.init('checklist', options, Checklist.defaults);
3157    };
3158
3159    $.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);
3160
3161    $.extend(Checklist.prototype, {
3162        renderList: function() {
3163            var $label, $div;
3164            
3165            this.$tpl.empty();
3166            
3167            if(!$.isArray(this.sourceData)) {
3168                return;
3169            }
3170
3171            for(var i=0; i<this.sourceData.length; i++) {
3172                $label = $('<label>').append($('<input>', {
3173                                           type: 'checkbox',
3174                                           value: this.sourceData[i].value 
3175                                     }))
3176                                     .append($('<span>').text(' '+this.sourceData[i].text));
3177                
3178                $('<div>').append($label).appendTo(this.$tpl);
3179            }
3180            
3181            this.$input = this.$tpl.find('input[type="checkbox"]');
3182            this.setClass();
3183        },
3184       
3185       value2str: function(value) {
3186           return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : '';
3187       },  
3188       
3189       //parse separated string
3190        str2value: function(str) {
3191           var reg, value = null;
3192           if(typeof str === 'string' && str.length) {
3193               reg = new RegExp('\\s*'+$.trim(this.options.separator)+'\\s*');
3194               value = str.split(reg);
3195           } else if($.isArray(str)) {
3196               value = str; 
3197           } else {
3198               value = [str];
3199           }
3200           return value;
3201        },       
3202       
3203       //set checked on required checkboxes
3204       value2input: function(value) {
3205            this.$input.prop('checked', false);
3206            if($.isArray(value) && value.length) {
3207               this.$input.each(function(i, el) {
3208                   var $el = $(el);
3209                   // cannot use $.inArray as it performs strict comparison
3210                   $.each(value, function(j, val){
3211                       /*jslint eqeq: true*/
3212                       if($el.val() == val) {
3213                       /*jslint eqeq: false*/                           
3214                           $el.prop('checked', true);
3215                       }
3216                   });
3217               }); 
3218            }  
3219        },  
3220        
3221       input2value: function() { 
3222           var checked = [];
3223           this.$input.filter(':checked').each(function(i, el) {
3224               checked.push($(el).val());
3225           });
3226           return checked;
3227       },            
3228          
3229       //collect text of checked boxes
3230        value2htmlFinal: function(value, element) {
3231           var html = [],
3232               checked = $.fn.editableutils.itemsByValue(value, this.sourceData);
3233               
3234           if(checked.length) {
3235               $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
3236               $(element).html(html.join('<br>'));
3237           } else {
3238               $(element).empty(); 
3239           }
3240        },
3241        
3242       activate: function() {
3243           this.$input.first().focus();
3244       },
3245       
3246       autosubmit: function() {
3247           this.$input.on('keydown', function(e){
3248               if (e.which === 13) {
3249                   $(this).closest('form').submit();
3250               }
3251           });
3252       }
3253    });      
3254
3255    Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
3256        /**
3257        @property tpl 
3258        @default <div></div>
3259        **/         
3260        tpl:'<div class="editable-checklist"></div>',
3261        
3262        /**
3263        @property inputclass 
3264        @type string
3265        @default null
3266        **/         
3267        inputclass: null,        
3268        
3269        /**
3270        Separator of values when reading from `data-value` attribute
3271
3272        @property separator 
3273        @type string
3274        @default ','
3275        **/         
3276        separator: ','
3277    });
3278
3279    $.fn.editabletypes.checklist = Checklist;      
3280
3281}(window.jQuery));
3282
3283/**
3284HTML5 input types.
3285Following types are supported:
3286
3287* password
3288* email
3289* url
3290* tel
3291* number
3292* range
3293* time
3294
3295Learn more about html5 inputs:  
3296http://www.w3.org/wiki/HTML5_form_additions  
3297To check browser compatibility please see:  
3298https://developer.mozilla.org/en-US/docs/HTML/Element/Input
3299            
3300@class html5types 
3301@extends text
3302@final
3303@since 1.3.0
3304@example
3305<a href="#" id="email" data-type="email" data-pk="1">[email protected]</a>
3306<script>
3307$(function(){
3308    $('#email').editable({
3309        url: '/post',
3310        title: 'Enter email'
3311    });
3312});
3313</script>
3314**/
3315
3316/**
3317@property tpl 
3318@default depends on type
3319**/ 
3320
3321/*
3322Password
3323*/
3324(function ($) {
3325    "use strict";
3326    
3327    var Password = function (options) {
3328        this.init('password', options, Password.defaults);
3329    };
3330    $.fn.editableutils.inherit(Password, $.fn.editabletypes.text);
3331    $.extend(Password.prototype, {
3332       //do not display password, show '[hidden]' instead
3333       value2html: function(value, element) {
3334           if(value) {
3335               $(element).text('[hidden]');
3336           } else {
3337               $(element).empty(); 
3338           }
3339       },
3340       //as password not displayed, should not set value by html
3341       html2value: function(html) {
3342           return null;
3343       }       
3344    });    
3345    Password.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
3346        tpl: '<input type="password">'
3347    });
3348    $.fn.editabletypes.password = Password;
3349}(window.jQuery));
3350
3351
3352/*
3353Email
3354*/
3355(function ($) {
3356    "use strict";
3357    
3358    var Email = function (options) {
3359        this.init('email', options, Email.defaults);
3360    };
3361    $.fn.editableutils.inherit(Email, $.fn.editabletypes.text);
3362    Email.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
3363        tpl: '<input type="email">'
3364    });
3365    $.fn.editabletypes.email = Email;
3366}(window.jQuery));
3367
3368
3369/*
3370Url
3371*/
3372(function ($) {
3373    "use strict";
3374    
3375    var Url = function (options) {
3376        this.init('url', options, Url.defaults);
3377    };
3378    $.fn.editableutils.inherit(Url, $.fn.editabletypes.text);
3379    Url.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
3380        tpl: '<input type="url">'
3381    });
3382    $.fn.editabletypes.url = Url;
3383}(window.jQuery));
3384
3385
3386/*
3387Tel
3388*/
3389(function ($) {
3390    "use strict";
3391    
3392    var Tel = function (options) {
3393        this.init('tel', options, Tel.defaults);
3394    };
3395    $.fn.editableutils.inherit(Tel, $.fn.editabletypes.text);
3396    Tel.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
3397        tpl: '<input type="tel">'
3398    });
3399    $.fn.editabletypes.tel = Tel;
3400}(window.jQuery));
3401
3402
3403/*
3404Number
3405*/
3406(function ($) {
3407    "use strict";
3408    
3409    var NumberInput = function (options) {
3410        this.init('number', options, NumberInput.defaults);
3411    };
3412    $.fn.editableutils.inherit(NumberInput, $.fn.editabletypes.text);
3413    $.extend(NumberInput.prototype, {
3414         render: function () {
3415            NumberInput.superclass.render.call(this);
3416            this.setAttr('min');
3417            this.setAttr('max');
3418            this.setAttr('step');
3419        },
3420        postrender: function() {
3421            if(this.$clear) {
3422                //increase right ffset  for up/down arrows
3423                this.$clear.css({right: 24});
3424                /*
3425                //can position clear button only here, when form is shown and height can be calculated
3426                var h = this.$input.outerHeight(true) || 20,
3427                    delta = (h - this.$clear.height()) / 2;
3428                
3429                //add 12px to offset right for up/down arrows    
3430                this.$clear.css({top: delta, right: delta + 16});
3431                */
3432            } 
3433        }        
3434    });     
3435    NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
3436        tpl: '<input type="number">',
3437        inputclass: 'input-mini',
3438        min: null,
3439        max: null,
3440        step: null
3441    });
3442    $.fn.editabletypes.number = NumberInput;
3443}(window.jQuery));
3444
3445
3446/*
3447Range (inherit from number)
3448*/
3449(function ($) {
3450    "use strict";
3451    
3452    var Range = function (options) {
3453        this.init('range', options, Range.defaults);
3454    };
3455    $.fn.editableutils.inherit(Range, $.fn.editabletypes.number);
3456    $.extend(Range.prototype, {
3457        render: function () {
3458            this.$input = this.$tpl.filter('input');
3459            
3460            this.setClass();
3461            this.setAttr('min');
3462            this.setAttr('max');
3463            this.setAttr('step');           
3464            
3465            this.$input.on('input', function(){
3466                $(this).siblings('output').text($(this).val()); 
3467            });  
3468        },
3469        activate: function() {
3470            this.$input.focus();
3471        }         
3472    });
3473    Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {
3474        tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>',
3475        inputclass: 'input-medium'
3476    });
3477    $.fn.editabletypes.range = Range;
3478}(window.jQuery));
3479
3480/*
3481Time
3482*/
3483(function ($) {
3484    "use strict";
3485
3486    var Time = function (options) {
3487        this.init('time', options, Time.defaults);
3488    };
3489    //inherit from abstract, as inheritance from text gives selection error.
3490    $.fn.editableutils.inherit(Time, $.fn.editabletypes.abstractinput);
3491    $.extend(Time.prototype, {
3492        render: function() {
3493           this.setClass();
3494        }        
3495    });
3496    Time.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
3497        tpl: '<input type="time">'
3498    });
3499    $.fn.editabletypes.time = Time;
3500}(window.jQuery));
3501
3502/**
3503Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.  
3504Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.  
3505Compatible **select2 version is 3.4.1**!   
3506You should manually download and include select2 distributive:  
3507
3508    <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>  
3509    <script src="select2/select2.js"></script>  
3510    
3511To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css): 
3512
3513    <link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>    
3514    
3515**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.    
3516You need initially put both `data-value` and element's text youself:    
3517
3518    <a href="#" data-type="select2" data-value="1">Text1</a>
3519    
3520    
3521@class select2
3522@extends abstractinput
3523@since 1.4.1
3524@final
3525@example
3526<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>
3527<script>
3528$(function(){
3529    //local source
3530    $('#country').editable({
3531        source: [
3532              {id: 'gb', text: 'Great Britain'},
3533              {id: 'us', text: 'United States'},
3534              {id: 'ru', text: 'Russia'}
3535           ],
3536        select2: {
3537           multiple: true
3538        }
3539    });
3540    //remote source (simple)
3541    $('#country').editable({
3542        source: '/getCountries'  
3543    });
3544    //remote source (advanced)
3545    $('#country').editable({
3546        select2: {
3547            placeholder: 'Select Country',
3548            allowClear: true,
3549            minimumInputLength: 3,
3550            id: function (item) {
3551                return item.CountryId;
3552            },
3553            ajax: {
3554                url: '/getCountries',
3555                dataType: 'json',
3556                data: function (term, page) {
3557                    return { query: term };
3558                },
3559                results: function (data, page) {
3560                    return { results: data };
3561                }
3562            },
3563            formatResult: function (item) {
3564                return item.CountryName;
3565            },
3566            formatSelection: function (item) {
3567                return item.CountryName;
3568            },
3569            initSelection: function (element, callback) {
3570                return $.get('/getCountryById', { query: element.val() }, function (data) {
3571                    callback(data);
3572                });
3573            } 
3574        }  
3575    });
3576});
3577</script>
3578**/
3579(function ($) {
3580    "use strict";
3581    
3582    var Constructor = function (options) {
3583        this.init('select2', options, Constructor.defaults);
3584
3585        options.select2 = options.select2 || {};
3586
3587        this.sourceData = null;
3588        
3589        //placeholder
3590        if(options.placeholder) {
3591            options.select2.placeholder = options.placeholder;
3592        }
3593       
3594        //if not `tags` mode, use source
3595        if(!options.select2.tags && options.source) {
3596            var source = options.source;
3597            //if source is function, call it (once!)
3598            if ($.isFunction(options.source)) {
3599                source = options.source.call(options.scope);
3600            }               
3601
3602            if (typeof source === 'string') {
3603                options.select2.ajax = options.select2.ajax || {};
3604                //some default ajax params
3605                if(!options.select2.ajax.data) {
3606                    options.select2.ajax.data = function(term) {return { query:term };};
3607                }
3608                if(!options.select2.ajax.results) {
3609                    options.select2.ajax.results = function(data) { return {results:data };};
3610                }
3611                options.select2.ajax.url = source;
3612            } else {
3613                //check format and convert x-editable format to select2 format (if needed)
3614                this.sourceData = this.convertSource(source);
3615                options.select2.data = this.sourceData;
3616            }
3617        } 
3618           
3619        //overriding objects in config (as by default jQuery extend() is not recursive)
3620        this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
3621        
3622        //detect whether it is multi-valued
3623        this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
3624        this.isRemote = ('ajax' in this.options.select2);
3625        
3626        //store function returning ID of item
3627        //should be here as used inautotext for local source
3628        this.idFunc = this.options.select2.id;
3629        if (typeof(this.idFunc) !== "function") {
3630            var idKey = this.idFunc || 'id';
3631            this.idFunc = function (e) { return e[idKey]; };
3632        }
3633        
3634        //store function that renders text in select2
3635        this.formatSelection = this.options.select2.formatSelection; 
3636        if (typeof(this.formatSelection) !== "function") {
3637            this.formatSelection = function (e) { return e.text; };
3638        }       
3639    };
3640
3641    $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
3642
3643    $.extend(Constructor.prototype, {
3644        render: function() {
3645            this.setClass();
3646            
3647            //apply select2
3648            this.$input.select2(this.options.select2);
3649
3650            //when data is loaded via ajax, we need to know when it's done to populate listData
3651            if(this.isRemote) {
3652                //listen to loaded event to populate data
3653                this.$input.on('select2-loaded', $.proxy(function(e) {
3654                    this.sourceData = e.items.results;
3655                }, this));
3656            }
3657
3658            //trigger resize of editableform to re-position container in multi-valued mode           
3659            if(this.isMultiple) {
3660               this.$input.on('change', function() {
3661                   $(this).closest('form').parent().triggerHandler('resize');
3662               }); 
3663            }
3664       },
3665       
3666       value2html: function(value, element) {
3667           var text = '', data,
3668               that = this;
3669           
3670           if(this.options.select2.tags) { //in tags mode just assign value
3671              data = value; 
3672              //data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc); 
3673           } else if(this.sourceData) {
3674              data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc); 
3675           } else {
3676              //can not get list of possible values (e.g. autotext for select2 with ajax source) 
3677           }
3678           
3679           //data may be array (when multiple values allowed)          
3680           if($.isArray(data)) {
3681               //collect selected data and show with separator
3682               text = [];
3683               $.each(data, function(k, v){
3684                   text.push(v && typeof v === 'object' ? that.formatSelection(v) : v); 
3685               });                   
3686           } else if(data) {
3687               text = that.formatSelection(data);  
3688           }
3689
3690           text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
3691
3692           $(element).text(text);
3693       },       
3694        
3695       html2value: function(html) {
3696           return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
3697       }, 
3698       
3699       value2input: function(value) {
3700           //for local source use data directly from source (to allow autotext)
3701           /*
3702           if(!this.isRemote && !this.isMultiple) {
3703               var items = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);
3704               if(items.length) {
3705                   this.$input.select2('data', items[0]);
3706                   return;
3707               }
3708           } 
3709           */
3710           
3711           //for remote source just set value, text is updated by initSelection    
3712           this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
3713           
3714           //if remote source AND no user's initSelection provided --> try to use element's text
3715           if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) {
3716               var customId = this.options.select2.id,
3717                   customText = this.options.select2.formatSelection;
3718               if(!customId && !customText) {      
3719                   var data = {id: value, text: $(this.options.scope).text()};
3720                   this.$input.select2('data', data);    
3721               }
3722           }
3723       },
3724       
3725       input2value: function() { 
3726           return this.$input.select2('val');
3727       },
3728
3729       str2value: function(str, separator) {
3730            if(typeof str !== 'string' || !this.isMultiple) {
3731                return str;
3732            }
3733            
3734            separator = separator || this.options.select2.separator || $.fn.select2.defaults.separator;
3735            
3736            var val, i, l;
3737                
3738            if (str === null || str.length < 1) {
3739                return null;
3740            }
3741            val = str.split(separator);
3742            for (i = 0, l = val.length; i < l; i = i + 1) {
3743                val[i] = $.trim(val[i]);
3744            }
3745            
3746            return val;
3747       },
3748       
3749        autosubmit: function() {
3750            this.$input.on('change', function(e, isInitial){
3751                if(!isInitial) {
3752                  $(this).closest('form').submit();
3753                }
3754            });
3755        },
3756        
3757        /*
3758        Converts source from x-editable format: {value: 1, text: "1"} to
3759        select2 format: {id: 1, text: "1"}
3760        */
3761        convertSource: function(source) {
3762            if($.isArray(source) && source.length && source[0].value !== undefined) {
3763                for(var i = 0; i<source.length; i++) {
3764                    if(source[i].value !== undefined) {
3765                        source[i].id = source[i].value;
3766                        delete source[i].value;
3767                    }
3768                }
3769            }
3770            return source;            
3771        },
3772        
3773        destroy: function() {
3774            if(this.$input.data('select2')) {
3775                this.$input.select2('destroy');
3776            }
3777        }               
3778        
3779    });      
3780
3781    Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
3782        /**
3783        @property tpl 
3784        @default <input type="hidden">
3785        **/         
3786        tpl:'<input type="hidden">',
3787        /**
3788        Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
3789        
3790        @property select2 
3791        @type object
3792        @default null
3793        **/
3794        select2: null,
3795        /**
3796        Placeholder attribute of select
3797
3798        @property placeholder 
3799        @type string
3800        @default null
3801        **/             
3802        placeholder: null,
3803        /**
3804        Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.
3805        Please note, that format is different from simple `select` input: use 'id' instead of 'value'.
3806        E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.  
3807        
3808        @property source 
3809        @type array
3810        @default null        
3811        **/
3812        source: null,
3813        /**
3814        Separator used to display tags. 
3815        
3816        @property viewseparator 
3817        @type string
3818        @default ', '        
3819        **/
3820        viewseparator: ', '        
3821    });
3822
3823    $.fn.editabletypes.select2 = Constructor;      
3824    
3825}(window.jQuery));
3826
3827/**
3828* Combodate - 1.0.4
3829* Dropdown date and time picker.
3830* Converts text input into dropdowns to pick day, month, year, hour, minute and second.
3831* Uses momentjs as datetime library http://momentjs.com.
3832* For internalization include corresponding file from https://github.com/timrwoo
3832d/moment/tree/master/lang 
3833*
3834* Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight
3835* In combodate: 
3836* 12:00 pm --> 12:00 (24-h format, midday)
3837* 12:00 am --> 00:00 (24-h format, midnight, start of day)
3838* 
3839* Differs from momentjs parse rules:
3840* 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change)
3841* 00:00 am, 12:00 am --> 00:00 (24-h format, day not change)
3842* 
3843* 
3844* Author: Vitaliy Potapov
3845* Project page: http://github.com/vitalets/combodate
3846* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
3847**/
3848(function ($) {
3849
3850    var Combodate = function (element, options) {
3851        this.$element = $(element);
3852        if(!this.$element.is('input')) {
3853            $.error('Combodate should be applied to INPUT element');
3854            return;
3855        }
3856        this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data());
3857        this.init();  
3858     };
3859
3860    Combodate.prototype = {
3861        constructor: Combodate, 
3862        init: function () {
3863            this.map = {
3864                //key   regexp    moment.method
3865                day:    ['D',    'date'], 
3866                month:  ['M',    'month'], 
3867                year:   ['Y',    'year'], 
3868                hour:   ['[Hh]', 'hours'],
3869                minute: ['m',    'minutes'], 
3870                second: ['s',    'seconds'],
3871                ampm:   ['[Aa]', ''] 
3872            };
3873            
3874            this.$widget = $('<span class="combodate"></span>').html(this.getTemplate());
3875                      
3876            this.initCombos();
3877            
3878            //update original input on change 
3879            this.$widget.on('change', 'select', $.proxy(function(){
3880                this.$element.val(this.getValue());
3881            }, this));
3882            
3883            this.$widget.find('select').css('width', 'auto');
3884                                       
3885            //hide original input and insert widget                                       
3886            this.$element.hide().after(this.$widget);
3887            
3888            //set initial value
3889            this.setValue(this.$element.val() || this.options.value);
3890        },
3891        
3892        /*
3893         Replace tokens in template with <select> elements 
3894        */         
3895        getTemplate: function() {
3896            var tpl = this.options.template;
3897
3898            //first pass
3899            $.each(this.map, function(k, v) {
3900                v = v[0]; 
3901                var r = new RegExp(v+'+'),
3902                    token = v.length > 1 ? v.substring(1, 2) : v;
3903                    
3904                tpl = tpl.replace(r, '{'+token+'}');
3905            });
3906
3907            //replace spaces with &nbsp;
3908            tpl = tpl.replace(/ /g, '&nbsp;');
3909
3910            //second pass
3911            $.each(this.map, function(k, v) {
3912                v = v[0];
3913                var token = v.length > 1 ? v.substring(1, 2) : v;
3914                    
3915                tpl = tpl.replace('{'+token+'}', '<select class="'+k+'"></select>');
3916            });   
3917
3918            return tpl;
3919        },
3920        
3921        /*
3922         Initialize combos that presents in template 
3923        */        
3924        initCombos: function() {
3925            var that = this;
3926            $.each(this.map, function(k, v) {
3927               var $c = that.$widget.find('.'+k), f, items;
3928               if($c.length) {
3929                   that['$'+k] = $c; //set properties like this.$day, this.$month etc.
3930                   f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1); //define method name to fill items, e.g `fillDays`
3931                   items = that[f](); 
3932                   that['$'+k].html(that.renderItems(items));
3933               }
3934            }); 
3935        },
3936        
3937        /*
3938         Initialize items of combos. Handles `firstItem` option 
3939        */
3940        initItems: function(key) {
3941            var values = [],
3942                relTime;
3943                
3944            if(this.options.firstItem === 'name') {
3945                //need both to support moment ver < 2 and  >= 2
3946                relTime = moment.relativeTime || moment.langData()._relativeTime; 
3947                var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];
3948                //take last entry (see momentjs lang files structure) 
3949                header = header.split(' ').reverse()[0];                
3950                values.push(['', header]);
3951            } else if(this.options.firstItem === 'empty') {
3952                values.push(['', '']);
3953            }
3954            return values;
3955        },        
3956        
3957        /*
3958        render items to string of <option> tags
3959        */
3960        renderItems: function(items) {
3961            var str = [];
3962            for(var i=0; i<items.length; i++) {
3963                str.push('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');                
3964            }
3965            return str.join("\n");
3966        },        
3967
3968        /*
3969        fill day
3970        */
3971        fillDay: function() {
3972            var items = this.initItems('d'), name, i,
3973                twoDigit = this.options.template.indexOf('DD') !== -1;
3974                
3975            for(i=1; i<=31; i++) {
3976                name = twoDigit ? this.leadZero(i) : i;
3977                items.push([i, name]);
3978            }
3979            return items;        
3980        },
3981        
3982        /*
3983        fill month
3984        */
3985        fillMonth: function() {
3986            var items = this.initItems('M'), name, i, 
3987                longNames = this.options.template.indexOf('MMMM') !== -1,
3988                shortNames = this.options.template.indexOf('MMM') !== -1,
3989                twoDigit = this.options.template.indexOf('MM') !== -1;
3990                
3991            for(i=0; i<=11; i++) {
3992                if(longNames) {
3993                    //see https://github.com/timrwood/momentjs.com/pull/36
3994                    name = moment().date(1).month(i).format('MMMM');
3995                } else if(shortNames) {
3996                    name = moment().date(1).month(i).format('MMM');
3997                } else if(twoDigit) {
3998                    name = this.leadZero(i+1);
3999                } else {
4000                    name = i+1;
4001                }
4002                items.push([i, name]);
4003            } 
4004            return items;
4005        },  
4006        
4007        /*
4008        fill year
4009        */
4010        fillYear: function() {
4011            var items = [], name, i, 
4012                longNames = this.options.template.indexOf('YYYY') !== -1;
4013           
4014            for(i=this.options.maxYear; i>=this.options.minYear; i--) {
4015                name = longNames ? i : (i+'').substring(2);
4016                items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);
4017            }
4018            
4019            items = this.initItems('y').concat(items);
4020            
4021            return items;              
4022        },    
4023        
4024        /*
4025        fill hour
4026        */
4027        fillHour: function() {
4028            var items = this.initItems('h'), name, i,
4029                h12 = this.options.template.indexOf('h') !== -1,
4030                h24 = this.options.template.indexOf('H') !== -1,
4031                twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
4032                min = h12 ? 1 : 0, 
4033                max = h12 ? 12 : 23;
4034                
4035            for(i=min; i<=max; i++) {
4036                name = twoDigit ? this.leadZero(i) : i;
4037                items.push([i, name]);
4038            } 
4039            return items;                 
4040        },    
4041        
4042        /*
4043        fill minute
4044        */
4045        fillMinute: function() {
4046            var items = this.initItems('m'), name, i,
4047                twoDigit = this.options.template.indexOf('mm') !== -1;
4048
4049            for(i=0; i<=59; i+= this.options.minuteStep) {
4050                name = twoDigit ? this.leadZero(i) : i;
4051                items.push([i, name]);
4052            }    
4053            return items;              
4054        },  
4055        
4056        /*
4057        fill second
4058        */
4059        fillSecond: function() {
4060            var items = this.initItems('s'), name, i,
4061                twoDigit = this.options.template.indexOf('ss') !== -1;
4062
4063            for(i=0; i<=59; i+= this.options.secondStep) {
4064                name = twoDigit ? this.leadZero(i) : i;
4065                items.push([i, name]);
4066            }    
4067            return items;              
4068        },  
4069        
4070        /*
4071        fill ampm
4072        */
4073        fillAmpm: function() {
4074            var ampmL = this.options.template.indexOf('a') !== -1,
4075                ampmU = this.options.template.indexOf('A') !== -1,            
4076                items = [
4077                    ['am', ampmL ? 'am' : 'AM'],
4078                    ['pm', ampmL ? 'pm' : 'PM']
4079                ];
4080            return items;                              
4081        },                                       
4082        
4083        /*
4084         Returns current date value from combos. 
4085         If format not specified - `options.format` used.
4086         If format = `null` - Moment object returned.
4087        */
4088        getValue: function(format) {
4089            var dt, values = {}, 
4090                that = this,
4091                notSelected = false;
4092                
4093            //getting selected values    
4094            $.each(this.map, function(k, v) {
4095                if(k === 'ampm') {
4096                    return;
4097                }
4098                var def = k === 'day' ? 1 : 0;
4099                  
4100                values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def; 
4101                
4102                if(isNaN(values[k])) {
4103                   notSelected = true;
4104                   return false; 
4105                }
4106            });
4107            
4108            //if at least one visible combo not selected - return empty string
4109            if(notSelected) {
4110               return '';
4111            }
4112            
4113            //convert hours 12h --> 24h 
4114            if(this.$ampm) {
4115                //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
4116                if(values.hour === 12) {
4117                    values.hour = this.$ampm.val() === 'am' ? 0 : 12;                    
4118                } else {
4119                    values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
4120                }
4121            }    
4122            
4123            dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
4124            
4125            //highlight invalid date
4126            this.highlight(dt);
4127                              
4128            format = format === undefined ? this.options.format : format;
4129            if(format === null) {
4130               return dt.isValid() ? dt : null; 
4131            } else {
4132               return dt.isValid() ? dt.format(format) : ''; 
4133            }           
4134        },
4135        
4136        setValue: function(value) {
4137            if(!value) {
4138                return;
4139            }
4140            
4141            var dt = typeof value === 'string' ? moment(value, this.options.format) : moment(value),
4142                that = this,
4143                values = {};
4144            
4145                //function to find nearest value in select options
4146                function getNearest($select, value) {
4147                    var delta = {};
4148                    $select.children('option').each(function(i, opt){
4149                        var optValue = $(opt).attr('value'),
4150                        distance;
4151
4152                        if(optValue === '') return;
4153                        distance = Math.abs(optValue - value); 
4154                        if(typeof delta.distance === 'undefined' || distance < delta.distance) {
4155                            delta = {value: optValue, distance: distance};
4156                        } 
4157                    }); 
4158                    return delta.value;
4159                }             
4160            
4161            if(dt.isValid()) {
4162                 //read values from date object
4163                 $.each(this.map, function(k, v) {
4164                     if(k === 'ampm') {
4165                         return; 
4166                     }
4167                     values[k] = dt[v[1]]();
4168                 });
4169               
4170               if(this.$ampm) {
4171                   //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
4172                   if(values.hour >= 12) {
4173                       values.ampm = 'pm';
4174                       if(values.hour > 12) {
4175                           values.hour -= 12;
4176                       }
4177                   } else {
4178                       values.ampm = 'am';
4179                       if(values.hour === 0) {
4180                           values.hour = 12;
4181                       }
4182                   } 
4183               }
4184               
4185               $.each(values, function(k, v) {
4186                   //call val() for each existing combo, e.g. this.$hour.val()
4187                   if(that['$'+k]) {
4188                       
4189                       if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) {
4190                          v = getNearest(that['$'+k], v);
4191                       }
4192                       
4193                       if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) {
4194                          v = getNearest(that['$'+k], v);
4195                       }                       
4196                       
4197                       that['$'+k].val(v);                       
4198                   }
4199               });
4200               
4201               this.$element.val(dt.format(this.options.format));
4202            }
4203        },
4204        
4205        /*
4206         highlight combos if date is invalid
4207        */
4208        highlight: function(dt) {
4209            if(!dt.isValid()) {
4210                if(this.options.errorClass) {
4211                    this.$widget.addClass(this.options.errorClass);
4212                } else {
4213                    //store original border color
4214                    if(!this.borderColor) {
4215                        this.borderColor = this.$widget.find('select').css('border-color'); 
4216                    }
4217                    this.$widget.find('select').css('border-color', 'red');
4218                } 
4219            } else {
4220                if(this.options.errorClass) {
4221                    this.$widget.removeClass(this.options.errorClass);
4222                } else {
4223                    this.$widget.find('select').css('border-color', this.borderColor);
4224                }  
4225            }
4226        },
4227        
4228        leadZero: function(v) {
4229            return v <= 9 ? '0' + v : v; 
4230        },
4231        
4232        destroy: function() {
4233            this.$widget.remove();
4234            this.$element.removeData('combodate').show();
4235        }
4236        
4237        //todo: clear method        
4238    };
4239
4240    $.fn.combodate = function ( option ) {
4241        var d, args = Array.apply(null, arguments);
4242        args.shift();
4243
4244        //getValue returns date as string / object (not jQuery object)
4245        if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) {
4246          return d.getValue.apply(d, args);
4247        }        
4248        
4249        return this.each(function () {
4250            var $this = $(this),
4251            data = $this.data('combodate'),
4252            options = typeof option == 'object' && option;
4253            if (!data) {
4254                $this.data('combodate', (data = new Combodate(this, options)));
4255            }
4256            if (typeof option == 'string' && typeof data[option] == 'function') {
4257                data[option].apply(data, args);
4258            }
4259        });
4260    };  
4261    
4262    $.fn.combodate.defaults = {
4263         //in this format value stored in original input
4264        format: 'DD-MM-YYYY HH:mm',      
4265        //in this format items in dropdowns are displayed
4266        template: 'D / MMM / YYYY   H : mm',
4267        //initial value, can be `new Date()`    
4268        value: null,                       
4269        minYear: 1970,
4270        maxYear: 2015,
4271        yearDescending: true,
4272        minuteStep: 5,
4273        secondStep: 1,
4274        firstItem: 'empty', //'name', 'empty', 'none'
4275        errorClass: null,
4276        roundTime: true //whether to round minutes and seconds if step > 1
4277    };
4278
4279}(window.jQuery));
4280/**
4281Combodate input - dropdown date and time picker.    
4282Based on [combodate](http://vitalets.github.com/combodate) plugin (included). To use it you should manually include [momentjs](http://momentjs.com).
4283
4284    <script src="js/moment.min.js"></script>
4285   
4286Allows to input:
4287
4288* only date
4289* only time 
4290* both date and time  
4291
4292Please note, that format is taken from momentjs and **not compatible** with bootstrap-datepicker / jquery UI datepicker.  
4293Internally value stored as `momentjs` object. 
4294
4295@class combodate
4296@extends abstractinput
4297@final
4298@since 1.4.0
4299@example
4300<a href="#" id="dob" data-type="combodate" data-pk="1" data-url="/post" data-value="1984-05-15" data-original-title="Select date"></a>
4301<script>
4302$(function(){
4303    $('#dob').editable({
4304        format: 'YYYY-MM-DD',    
4305        viewformat: 'DD.MM.YYYY',    
4306        template: 'D / MMMM / YYYY',    
4307        combodate: {
4308                minYear: 2000,
4309                maxYear: 2015,
4310                minuteStep: 1
4311           }
4312        }
4313    });
4314});
4315</script>
4316**/
4317
4318/*global moment*/
4319
4320(function ($) {
4321    "use strict";
4322    
4323    var Constructor = function (options) {
4324        this.init('combodate', options, Constructor.defaults);
4325        
4326        //by default viewformat equals to format
4327        if(!this.options.viewformat) {
4328            this.options.viewformat = this.options.format;
4329        }        
4330        
4331        //try parse combodate config defined as json string in data-combodate
4332        options.combodate = $.fn.editableutils.tryParseJson(options.combodate, true);
4333
4334        //overriding combodate config (as by default jQuery extend() is not recursive)
4335        this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, {
4336            format: this.options.format,
4337            template: this.options.template
4338        });
4339    };
4340
4341    $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);    
4342    
4343    $.extend(Constructor.prototype, {
4344        render: function () {
4345            this.$input.combodate(this.options.combodate);
4346            
4347            //"clear" link
4348            /*
4349            if(this.options.clear) {
4350                this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
4351                    e.preventDefault();
4352                    e.stopPropagation();
4353                    this.clear();
4354                }, this));
4355                
4356                this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));  
4357            } 
4358            */               
4359        },
4360        
4361        value2html: function(value, element) {
4362            var text = value ? value.format(this.options.viewformat) : '';
4363            $(element).text(text); 
4364        },
4365
4366        html2value: function(html) {
4367            return html ? moment(html, this.options.viewformat) : null;
4368        },   
4369        
4370        value2str: function(value) {
4371            return value ? value.format(this.options.format) : '';
4372       }, 
4373       
4374       str2value: function(str) {
4375           return str ? moment(str, this.options.format) : null;
4376       }, 
4377       
4378       value2submit: function(value) {
4379           return this.value2str(value);
4380       },                    
4381
4382       value2input: function(value) {
4383           this.$input.combodate('setValue', value);
4384       },
4385        
4386       input2value: function() { 
4387           return this.$input.combodate('getValue', null);
4388       },       
4389       
4390       activate: function() {
4391           this.$input.siblings('.combodate').find('select').eq(0).focus();
4392       },
4393       
4394       /*
4395       clear:  function() {
4396          this.$input.data('datepicker').date = null;
4397          this.$input.find('.active').removeClass('active');
4398       },
4399       */
4400       
4401       autosubmit: function() {
4402           
4403       }
4404
4405    });
4406    
4407    Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
4408        /**
4409        @property tpl 
4410        @default <input type="text">
4411        **/         
4412        tpl:'<input type="text">',
4413        /**
4414        @property inputclass 
4415        @default null
4416        **/         
4417        inputclass: null,
4418        /**
4419        Format used for sending value to server. Also applied when convert
4419ing date from <code>data-value</code> attribute.<br>
4420        See list of tokens in [momentjs docs](http://momentjs.com/docs/#/parsing/string-format)  
4421        
4422        @property format 
4423        @type string
4424        @default YYYY-MM-DD
4425        **/         
4426        format:'YYYY-MM-DD',
4427        /**
4428        Format used for displaying date. Also applied when converting date from element's text on init.   
4429        If not specified equals to `format`.
4430        
4431        @property viewformat 
4432        @type string
4433        @default null
4434        **/          
4435        viewformat: null,        
4436        /**
4437        Template used for displaying dropdowns.
4438        
4439        @property template 
4440        @type string
4441        @default D / MMM / YYYY
4442        **/          
4443        template: 'D / MMM / YYYY',  
4444        /**
4445        Configuration of combodate.
4446        Full list of options: http://vitalets.github.com/combodate/#docs
4447        
4448        @property combodate 
4449        @type object
4450        @default null
4451        **/
4452        combodate: null
4453        
4454        /*
4455        (not implemented yet)
4456        Text shown as clear date button. 
4457        If <code>false</code> clear button will not be rendered.
4458        
4459        @property clear 
4460        @type boolean|string
4461        @default 'x clear'         
4462        */
4463        //clear: '&times; clear'
4464    });   
4465
4466    $.fn.editabletypes.combodate = Constructor;
4467
4468}(window.jQuery));
4469
4470/*
4471Editableform based on jQuery UI
4472*/
4473(function ($) {
4474    "use strict";
4475    
4476    $.extend($.fn.editableform.Constructor.prototype, {
4477        initButtons: function() {
4478            var $btn = this.$form.find('.editable-buttons');
4479            $btn.append($.fn.editableform.buttons);
4480            if(this.options.showbuttons === 'bottom') {
4481                $btn.addClass('editable-buttons-bottom');
4482            }
4483                          
4484            this.$form.find('.editable-submit').button({
4485                icons: { primary: "ui-icon-check" },
4486                text: false
4487            }).removeAttr('title');
4488            this.$form.find('.editable-cancel').button({
4489                icons: { primary: "ui-icon-closethick" },
4490                text: false
4491            }).removeAttr('title');
4492        }
4493    });
4494    
4495    //error classes
4496    $.fn.editableform.errorGroupClass = null;
4497    $.fn.editableform.errorBlockClass = 'ui-state-error';
4498    
4499}(window.jQuery));
4500/**
4501* Editable jQuery UI Tooltip 
4502* ---------------------
4503* requires jquery ui 1.9.x 
4504*/
4505(function ($) {
4506    "use strict";
4507
4508    //extend methods
4509    $.extend($.fn.editableContainer.Popup.prototype, {
4510        containerName: 'tooltip',  //jQuery method, aplying the widget
4511        //object name in element's .data() 
4512        containerDataName: 'ui-tooltip', 
4513        innerCss: '.ui-tooltip-content', 
4514        
4515        //split options on containerOptions and formOptions
4516        splitOptions: function() {
4517            this.containerOptions = {};
4518            this.formOptions = {};
4519            
4520            //check that jQueryUI build contains tooltip widget
4521            if(!$.ui[this.containerName]) {
4522                $.error('Please use jQueryUI with "tooltip" widget! http://jqueryui.com/download');
4523                return;
4524            }
4525            //defaults for tooltip
4526            var cDef = $.ui[this.containerName].prototype.options;
4527            for(var k in this.options) {
4528              if(k in cDef) {
4529                 this.containerOptions[k] = this.options[k];
4530              } else {
4531                 this.formOptions[k] = this.options[k];
4532              } 
4533            }
4534        },        
4535        
4536        initContainer: function(){
4537            this.handlePlacement();
4538            $.extend(this.containerOptions, {
4539                items: '*',
4540                content: ' ',
4541                track:  false,
4542                open: $.proxy(function() {
4543                        //disable events hiding tooltip by default
4544                        this.container()._on(this.container().element, {
4545                            mouseleave: function(e){ e.stopImmediatePropagation(); },
4546                            focusout: function(e){ e.stopImmediatePropagation(); }
4547                        });  
4548                    }, this)
4549            });
4550            
4551            this.call(this.containerOptions);
4552            
4553            //disable standart triggering tooltip events
4554            this.container()._off(this.container().element, 'mouseover focusin');
4555        },         
4556        
4557        tip: function() {
4558            return this.container() ? this.container()._find(this.container().element) : null;
4559        },
4560        
4561        innerShow: function() {
4562            this.call('open');
4563            var label = this.options.title || this.$element.data( "ui-tooltip-title") || this.$element.data( "originalTitle"); 
4564            this.tip().find(this.innerCss).empty().append($('<label>').text(label));
4565        },  
4566        
4567        innerHide: function() {
4568            this.call('close'); 
4569        },
4570        
4571        innerDestroy: function() {
4572            /* tooltip destroys itself on hide */
4573        },         
4574        
4575        setPosition: function() {
4576            this.tip().position( $.extend({
4577                of: this.$element
4578            }, this.containerOptions.position ) );     
4579        },
4580        
4581        handlePlacement: function() {
4582           var pos; 
4583           switch(this.options.placement) {
4584               case 'top':
4585                      pos = {
4586                          my: "center bottom-5", 
4587                          at: "center top", 
4588                          collision: 'flipfit'
4589                      };
4590               break;
4591               case 'right':
4592                      pos = {
4593                          my: "left+5 center", 
4594                          at: "right center", 
4595                          collision: 'flipfit'
4596                      };
4597               break;
4598               case 'bottom':
4599                      pos = {
4600                          my: "center top+5", 
4601                          at: "center bottom", 
4602                          collision: 'flipfit'
4603                      };
4604               break;
4605               case 'left':
4606                      pos = {
4607                          my: "right-5 center", 
4608                          at: "left center", 
4609                          collision: 'flipfit'
4610                      };
4611               break;                                             
4612           }
4613           
4614           this.containerOptions.position = pos;
4615        }
4616                
4617    });
4618    
4619}(window.jQuery));
4620
4621/**
4622jQuery UI Datepicker.  
4623Description and examples: http://jqueryui.com/datepicker.   
4624This input is also accessible as **date** type. Do not use it together with __bootstrap-datepicker__ as both apply <code>$().datepicker()</code> method.  
4625For **i18n** you should include js file from here: https://github.com/jquery/jquery-ui/tree/master/ui/i18n.
4626
4627@class dateui
4628@extends abstractinput
4629@final
4630@example
4631<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a>
4632<script>
4633$(function(){
4634    $('#dob').editable({
4635        format: 'yyyy-mm-dd',    
4636        viewformat: 'dd/mm/yyyy',    
4637        datepicker: {
4638                firstDay: 1
4639           }
4640        }
4641    });
4642});
4643</script>
4644**/
4645(function ($) {
4646    "use strict";
4647    
4648    var DateUI = function (options) {
4649        this.init('dateui', options, DateUI.defaults);
4650        this.initPicker(options, DateUI.defaults);
4651    };
4652
4653    $.fn.editableutils.inherit(DateUI, $.fn.editabletypes.abstractinput);    
4654    
4655    $.extend(DateUI.prototype, {
4656        initPicker: function(options, defaults) {
4657            //by default viewformat equals to format
4658            if(!this.options.viewformat) {
4659                this.options.viewformat = this.options.format;
4660            }
4661            
4662            //correct formats: replace yyyy with yy (for compatibility with bootstrap datepicker)
4663            this.options.viewformat = this.options.viewformat.replace('yyyy', 'yy'); 
4664            this.options.format = this.options.format.replace('yyyy', 'yy');             
4665            
4666            //overriding datepicker config (as by default jQuery extend() is not recursive)
4667            //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
4668            this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
4669                dateFormat: this.options.viewformat
4670            });                        
4671        },
4672        
4673        render: function () {
4674            this.$input.datepicker(this.options.datepicker);
4675            
4676            //"clear" link
4677            if(this.options.clear) {
4678                this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
4679                    e.preventDefault();
4680                    e.stopPropagation();
4681                    this.clear();
4682                }, this));
4683                
4684                this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));  
4685            }              
4686        },
4687
4688        value2html: function(value, element) {
4689            var text = $.datepicker.formatDate(this.options.viewformat, value);
4690            DateUI.superclass.value2html(text, element); 
4691        },
4692
4693        html2value: function(html) {
4694           if(typeof html !== 'string') {
4695               return html;
4696           }            
4697            
4698           //if string does not match format, UI datepicker throws exception
4699           var d;
4700           try {
4701              d = $.datepicker.parseDate(this.options.viewformat, html);
4702           } catch(e) {}
4703           
4704           return d;            
4705        },   
4706        
4707        value2str: function(value) {
4708           return $.datepicker.formatDate(this.options.format, value);
4709       }, 
4710       
4711       str2value: function(str) {
4712           if(typeof str !== 'string') {
4713               return str;
4714           }
4715           
4716           //if string does not match format, UI datepicker throws exception
4717           var d;
4718           try {
4719              d = $.datepicker.parseDate(this.options.format, str);
4720           } catch(e) {}
4721           
4722           return d;
4723       }, 
4724       
4725       value2submit: function(value) { 
4726           return this.value2str(value);
4727       },                     
4728
4729       value2input: function(value) {
4730           this.$input.datepicker('setDate', value);
4731       },
4732        
4733       input2value: function() { 
4734           return this.$input.datepicker('getDate');
4735       },       
4736       
4737       activate: function() {
4738       },
4739       
4740       clear:  function() {
4741           this.$input.datepicker('setDate', null);
4742       },
4743       
4744       autosubmit: function() {
4745           this.$input.on('mouseup', 'table.ui-datepicker-calendar a.ui-state-default', function(e){
4746               var $form = $(this).closest('form');
4747               setTimeout(function() {
4748                   $form.submit();
4749               }, 200);
4750           });
4751       }
4752
4753    });
4754    
4755    DateUI.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
4756        /**
4757        @property tpl 
4758        @default <div></div>
4759        **/         
4760        tpl:'<div class="editable-date"></div>',
4761        /**
4762        @property inputclass 
4763        @default null
4764        **/         
4765        inputclass: null,
4766        /**
4767        Format used for sending value to server. Also applied when convert
4767ing date from <code>data-value</code> attribute.<br>
4768        Full list of tokens: http://docs.jquery.com/UI/Datepicker/formatDate
4769        
4770        @property format 
4771        @type string
4772        @default yyyy-mm-dd
4773        **/          
4774        format:'yyyy-mm-dd', 
4775        /**
4776        Format used for displaying date. Also applied when converting date from element's text on init.    
4777        If not specified equals to <code>format</code>
4778        
4779        @property viewformat 
4780        @type string
4781        @default null
4782        **/          
4783        viewformat: null,
4784        
4785        /**
4786        Configuration of datepicker.
4787        Full list of options: http://api.jqueryui.com/datepicker
4788        
4789        @property datepicker 
4790        @type object
4791        @default {
4792           firstDay: 0, 
4793           changeYear: true, 
4794           changeMonth: true
4795        }
4796        **/
4797        datepicker: {
4798            firstDay: 0,
4799            changeYear: true,
4800            changeMonth: true,
4801            showOtherMonths: true
4802        },
4803        /**
4804        Text shown as clear date button. 
4805        If <code>false</code> clear button will not be rendered.
4806        
4807        @property clear 
4808        @type boolean|string
4809        @default 'x clear'         
4810        **/
4811        clear: '&times; clear'        
4812    });   
4813
4814    $.fn.editabletypes.dateui = DateUI;
4815
4816}(window.jQuery));
4817
4818/**
4819jQuery UI datefield input - modification for inline mode.
4820Shows normal <input type="text"> and binds popup datepicker.  
4821Automatically shown in inline mode.
4822
4823@class dateuifield
4824@extends dateui
4825
4826@since 1.4.0
4827**/
4828(function ($) {
4829    "use strict";
4830    
4831    var DateUIField = function (options) {
4832        this.init('dateuifield', options, DateUIField.defaults);
4833        this.initPicker(options, DateUIField.defaults);
4834    };
4835
4836    $.fn.editableutils.inherit(DateUIField, $.fn.editabletypes.dateui);    
4837    
4838    $.extend(DateUIField.prototype, {
4839       render: function () {
4840          //  this.$input = this.$tpl.find('input'); 
4841            this.$input.datepicker(this.options.datepicker);
4842            $.fn.editabletypes.text.prototype.renderClear.call(this);
4843       },
4844      
4845       value2input: function(value) {
4846           this.$input.val($.datepicker.formatDate(this.options.viewformat, value));
4847       },
4848        
4849       input2value: function() { 
4850           return this.html2value(this.$input.val());
4851       },        
4852        
4853       activate: function() {
4854           $.fn.editabletypes.text.prototype.activate.call(this);
4855       },
4856       
4857       toggleClear: function() {
4858           $.fn.editabletypes.text.prototype.toggleClear.call(this);
4859       },
4860       
4861       autosubmit: function() {
4862          //reset autosubmit to empty  
4863       }
4864    });
4865    
4866    DateUIField.defaults = $.extend({}, $.fn.editabletypes.dateui.defaults, {
4867        /**
4868        @property tpl 
4869        @default <input type="text">
4870        **/         
4871        tpl: '<input type="text"/>',
4872        /**
4873        @property inputclass 
4874        @default null
4875        **/         
4876        inputclass: null,
4877        
4878        /* datepicker config */
4879        datepicker: {
4880            showOn: "button",
4881            buttonImage: "http://jqueryui.com/resources/demos/datepicker/images/calendar.gif",
4882            buttonImageOnly: true,            
4883            firstDay: 0,
4884            changeYear: true,
4885            changeMonth: true,
4886            showOtherMonths: true
4887        },
4888        
4889        /* disable clear link */ 
4890        clear: false
4891    });
4892    
4893    $.fn.editabletypes.dateuifield = DateUIField;
4894
4895}(window.jQuery));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.