PageSourceSearch

https://webassets.alcom.ax/bootstrap5-editable/js/bootstrap-editable.js

js alcom.ax collected 2026-09-24 09:38:39 UTC 225,161 bytes, 6,722 lines download raw bytes

1/*! X-editable - v1.5.3 
2* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
3* http://github.com/vitalets/x-editable
4* Copyright (c) 2018 Vitaliy Potapov; Licensed MIT */
5/**
6Form with single input element, two buttons and two states: normal/loading.
7Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown.
8Editableform is linked with one of input types, e.g. 'text', 'select' etc.
9
10@class editableform
11@uses text
12@uses textarea
13**/
14(function ($) {
15    "use strict";
16    
17    var EditableForm = function (div, options) {
18        this.options = $.extend({}, $.fn.editableform.defaults, options);
19        this.$div = $(div); //div, containing form. Not form tag. Not editable-element.
20        if(!this.options.scope) {
21            this.options.scope = this;
22        }
23        //nothing shown after init
24    };
25
26    EditableForm.prototype = {
27        constructor: EditableForm,
28        initInput: function() {  //called once
29            //take input from options (as it is created in editable-element)
30            this.input = this.options.input;
31            
32            //set initial value
33            //todo: may be add check: typeof str === 'string' ? 
34            this.value = this.input.str2value(this.options.value); 
35            
36            //prerender: get input.$input
37            this.input.prerender();
38        },
39        initTemplate: function() {
40            this.$form = $($.fn.editableform.template); 
41        },
42        initButtons: function() {
43            var $btn = this.$form.find('.editable-buttons');
44            $btn.append($.fn.editableform.buttons);
45            if(this.options.showbuttons === 'bottom') {
46                $btn.addClass('editable-buttons-bottom');
47            }
48        },
49        /**
50        Renders editableform
51
52        @method render
53        **/        
54        render: function() {
55            //init loader
56            this.$loading = $($.fn.editableform.loading);        
57            this.$div.empty().append(this.$loading);
58            
59            //init form template and buttons
60            this.initTemplate();
61            if(this.options.showbuttons) {
62                this.initButtons();
63            } else {
64                this.$form.find('.editable-buttons').remove();
65            }
66
67            //show loading state
68            this.showLoading();            
69            
70            //flag showing is form now saving value to server. 
71            //It is needed to wait when closing form.
72            this.isSaving = false;
73            
74            /**        
75            Fired when rendering starts
76            @event rendering 
77            @param {Object} event event object
78            **/            
79            this.$div.triggerHandler('rendering');
80            
81            //init input
82            this.initInput();
83            
84            //append input to form
85            this.$form.find('div.editable-input').append(this.input.$tpl);            
86            
87            //append form to container
88            this.$div.append(this.$form);
89            
90            //render input
91            $.when(this.input.render())
92            .then($.proxy(function () {
93                //setup input to submit automatically when no buttons shown
94                if(!this.options.showbuttons) {
95                    this.input.autosubmit(); 
96                }
97                 
98                //attach 'cancel' handler
99                this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
100                
101                if(this.input.error) {
102                    this.error(this.input.error);
103                    this.$form.find('.editable-submit').attr('disabled', true);
104                    this.input.$input.attr('disabled', true);
105                    //prevent form from submitting
106                    this.$form.submit(function(e){ e.preventDefault(); });
107                } else {
108                    this.error(false);
109                    this.input.$input.removeAttr('disabled');
110                    this.$form.find('.editable-submit').removeAttr('disabled');
111                    var value = (this.value === null || this.value === undefined || this.value === '') ? this.options.defaultValue : this.value;
112                    this.input.value2input(value);
113                    //attach submit handler
114                    this.$form.submit($.proxy(this.submit, this));
115                }
116
117                /**        
118                Fired when form is rendered
119                @event rendered
120                @param {Object} event event object
121                **/            
122                this.$div.triggerHandler('rendered');                
123
124                this.showForm();
125                
126                //call postrender method to perform actions required visibility of form
127                if(this.input.postrender) {
128                    this.input.postrender();
129                }                
130            }, this));
131        },
132        cancel: function() {   
133            /**        
134            Fired when form was cancelled by user
135            @event cancel 
136            @param {Object} event event object
137            **/              
138            this.$div.triggerHandler('cancel');
139        },
140        showLoading: function() {
141            var w, h;
142            if(this.$form) {
143                //set loading size equal to form
144                w = this.$form.outerWidth();
145                h = this.$form.outerHeight(); 
146                if(w) {
147                    this.$loading.width(w);
148                }
149                if(h) {
150                    this.$loading.height(h);
151                }
152                this.$form.hide();
153            } else {
154                //stretch loading to fill container width
155                w = this.$loading.parent().width();
156                if(w) {
157                    this.$loading.width(w);
158                }
159            }
160            this.$loading.show(); 
161        },
162
163        showForm: function(activate) {
164            this.$loading.hide();
165            this.$form.show();
166            if(activate !== false) {
167                this.input.activate(); 
168            }
169            /**        
170            Fired when form is shown
171            @event show 
172            @param {Object} event event object
173            **/                    
174            this.$div.triggerHandler('show');
175        },
176
177        error: function(msg) {
178            var $group = this.$form.find('.control-group'),
179                $block = this.$form.find('.editable-error-block'),
180                lines;
181
182            if(msg === false) {
183                $group.removeClass($.fn.editableform.errorGroupClass);
184                $block.removeClass($.fn.editableform.errorBlockClass).empty().hide(); 
185            } else {
186                //convert newline to <br> for more pretty error display
187                if(msg) {
188                    lines = (''+msg).split('\n');
189                    for (var i = 0; i < lines.length; i++) {
190                        lines[i] = $('<div>').text(lines[i]).html();
191                    }
192                    msg = lines.join('<br>');
193                }
194                $group.addClass($.fn.editableform.errorGroupClass);
195                $block.addClass($.fn.editableform.errorBlockClass).html(msg).show();
196            }
197        },
198
199        submit: function(e) {
200            e.stopPropagation();
201            e.preventDefault();
202            
203            //get new value from input
204            var newValue = this.input.input2value(); 
205
206            //validation: if validate returns string or truthy value - means error
207            //if returns object like {newValue: '...'} => submitted value is reassigned to it
208            var error = this.validate(newValue);
209            if ($.type(error) === 'object' && error.newValue !== undefined) {
210                newValue = error.newValue;
211                this.input.value2input(newValue);
212                if(typeof error.msg === 'string') {
213                    this.error(error.msg);
214                    this.showForm();
215                    return;
216                }
217            } else if (error) {
218                this.error(error);
219                this.showForm();
220                return;
221            } 
222            
223            //if value not changed --> trigger 'nochange' event and return
224            /*jslint eqeq: true*/
225            if (!this.options.savenochange && this.input.value2str(newValue) === this.input.value2str(this.value)) {
226            /*jslint eqeq: false*/                
227                /**        
228                Fired when value not changed but form is submitted. Requires savenochange = false.
229                @event nochange 
230                @param {Object} event event object
231                **/                    
232                this.$div.triggerHandler('nochange');            
233                return;
234            } 
235
236            //convert value for submitting to server
237            var submitValue = this.input.value2submit(newValue);
238            
239            this.isSaving = true;
240            
241            //sending data to server
242            $.when(this.save(submitValue))
243            .done($.proxy(function(response) {
244                this.isSaving = false;
245
246                //run success callback
247                var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
248
249                //if success callback returns false --> keep form open and do not activate input
250                if(res === false) {
251                    this.error(false);
252                    this.showForm(false);
253                    return;
254                }
255
256                //if success callback returns string -->  keep form open, show error and activate input               
257                if(typeof res === 'string') {
258                    this.error(res);
259                    this.showForm();
260                    return;
261                }
262
263                //if success callback returns object like {newValue: <something>} --> use that value instead of submitted
264                //it is useful if you want to chnage value in url-function
265                if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
266                    newValue = res.newValue;
267                }
268
269                //clear error message
270                this.error(false);   
271                this.value = newValue;
272                /**        
273                Fired when form is submitted
274                @event save 
275                @param {Object} event event object
276                @param {Object} params additional params
277                @param {mixed} params.newValue raw new value
278                @param {mixed} params.submitValue submitted value as string
279                @param {Object} params.response ajax response
280
281                @example
282                $('#form-div').on('save'), function(e, params){
283                    if(params.newValue === 'username') {...}
284                });
285                **/
286                this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response});
287            }, this))
288            .fail($.proxy(function(xhr) {
289                this.isSaving = false;
290
291                var msg;
292                if(typeof this.options.error === 'function') {
293                    msg = this.options.error.call(this.options.scope, xhr, newValue);
294                } else {
295                    msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';
296                }
297
298                this.error(msg);
299                this.showForm();
300            }, this));
301        },
302
303        save: function(submitValue) {
304            //try parse composite pk defined as json string in data-pk 
305            this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true); 
306            
307            var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
308            /*
309              send on server in following cases:
310              1. url is function
311              2. url is string AND (pk defined OR send option = always) 
312            */
313            send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),
314            params;
315
316            if (send) { //send to server
317                this.showLoading();
318
319                //standard params
320                params = {
321                    name: this.options.name || '',
322                    value: submitValue,
323                    pk: pk 
324                };
325
326                //additional params
327                if(typeof this.options.params === 'function') {
328                    params = this.options.params.call(this.options.scope, params);  
329                } else {
330                    //try parse json in single quotes (from data-params attribute)
331                    this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true);   
332                    $.extend(params, this.options.params);
333                }
334
335                if(typeof this.options.url === 'function') { //user's function
336                    return this.options.url.call(this.options.scope, params);
337                } else {  
338                    //send ajax to server and return deferred object
339                    return $.ajax($.extend({
340                        url     : this.options.url,
341                        data    : params,
342                        type    : 'POST'
343                    }, this.options.ajaxOptions));
344                }
345            }
346        }, 
347
348        validate: function (value) {
349            if (value === undefined) {
350                value = this.value;
351            }
352            if (typeof this.options.validate === 'function') {
353                return this.options.validate.call(this.options.scope, value);
354            }
355        },
356
357        option: function(key, value) {
358            if(key in this.options) {
359                this.options[key] = value;
360            }
361            
362            if(key === 'value') {
363                this.setValue(value);
364            }
365            
366            //do not pass option to input as it is passed in editable-element
367        },
368
369        setValue: function(value, convertStr) {
370            if(convertStr) {
371                this.value = this.input.str2value(value);
372            } else {
373                this.value = value;
374            }
375            
376            //if form is visible, update input
377            if(this.$form && this.$form.is(':visible')) {
378                this.input.value2input(this.value);
379            }            
380        }               
381    };
382
383    /*
384    Initialize editableform. Applied to jQuery object.
385
386    @method $().editableform(options)
387    @params {Object} options
388    @example
389    var $form = $('&lt;div&gt;').editableform({
390        type: 'text',
391        name: 'username',
392        url: '/post',
393        value: 'vitaliy'
394    });
395
396    //to display form you should call 'render' method
397    $form.editableform('render');     
398    */
399    $.fn.editableform = function (option) {
400        var args = arguments;
401        return this.each(function () {
402            var $this = $(this), 
403            data = $this.data('editableform'), 
404            options = typeof option === 'object' && option; 
405            if (!data) {
406                $this.data('editableform', (data = new EditableForm(this, options)));
407            }
408
409            if (typeof option === 'string') { //call method 
410                data[option].apply(data, Array.prototype.slice.call(args, 1));
411            } 
412        });
413    };
414
415    //keep link to constructor to allow inheritance
416    $.fn.editableform.Constructor = EditableForm;    
417
418    //defaults
419    $.fn.editableform.defaults = {
420        /* see also defaults for input */
421
422        /**
423        Type of input. Can be <code>
423text|textarea|select|date|checklist</code>
424
425        @property type 
426        @type string
427        @default 'text'
428        **/
429        type: 'text',
430        /**
431        Url for submit, e.g. <code>'/post'</code>  
432        If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks.
433
434        @property url 
435        @type string|function
436        @default null
437        @example
438        url: function(params) {
439            var d = new $.Deferred;
440            if(params.value === 'abc') {
441                return d.reject('error message'); //returning error via deferred object
442            } else {
443                //async saving data in js model
444                someModel.asyncSaveMethod({
445                   ..., 
446                   success: function(){
447                      d.resolve();
448                   }
449                }); 
450                return d.promise();
451            }
452        } 
453        **/        
454        url:null,
455        /**
456        Additional params for submit. If defined as <code>object</code> - it is **appended** to original ajax data (pk, name and value).  
457        If defined as <code>function</code> - returned object **overwrites** original ajax data.
458        @example
459        params: function(params) {
460            //originally params contain pk, name and value
461            params.a = 1;
462            return params;
463        }
464
465        @property params 
466        @type object|function
467        @default null
468        **/          
469        params:null,
470        /**
471        Name of field. Will be submitted on server. Can be taken from <code>id</code> attribute
472
473        @property name 
474        @type string
475        @default null
476        **/         
477        name: null,
478        /**
479        Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>.
480        Can be calculated dynamically via function.
481
482        @property pk 
483        @type string|object|function
484        @default null
485        **/         
486        pk: null,
487        /**
488        Initial value. If not defined - will be taken from element's content.
489        For __select__ type should be defined (as it is ID of shown text).
490
491        @property value 
492        @type string|object
493        @default null
494        **/        
495        value: null,
496        /**
497        Value that will be displayed in input if original field value is empty (`null|undefined|''`).
498
499        @property defaultValue 
500        @type string|object
501        @default null
502        @since 1.4.6
503        **/        
504        defaultValue: null,
505        /**
506        Strategy for sending data on server. Can be `auto|always|never`.
507        When 'auto' data will be sent on server **only if pk and url defined**, otherwise new value will be stored locally.
508
509        @property send 
510        @type string
511        @default 'auto'
512        **/          
513        send: 'auto', 
514        /**
515        Function for client-side validation. If returns string - means validation not passed and string showed as error.
516        Since 1.5.1 you can modify submitted value by returning object from `validate`: 
517        `{newValue: '...'}` or `{newValue: '...', msg: '...'}`
518
519        @property validate 
520        @type function
521        @default null
522        @example
523        validate: function(value) {
524            if($.trim(value) == '') {
525                return 'This field is required';
526            }
527        }
528        **/         
529        validate: null,
530        /**
531        Success callback. Called when value successfully sent on server and **response status = 200**.  
532        Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code>
533        or `{success: false, msg: "server error"}` you can check it inside this callback.  
534        If it returns **string** - means error occured and string is shown as error message.  
535        If it returns **object like** `{newValue: &lt;something&gt;}` - it overwrites value, submitted by user
536        (useful when server changes value).  
537        Otherwise newValue simply rendered into element.
538        
539        @property success 
540        @type function
541        @default null
542        @example
543        success: function(response, newValue) {
544            if(!response.success) return response.msg;
545        }
546        **/          
547        success: null,
548        /**
549        Error callback. Called when request failed (response status != 200).  
550        Usefull when you want to parse error response and display a custom message.
551        Must return **string** - the message to be displayed in the error block.
552                
553        @property error 
554        @type function
555        @default null
556        @since 1.4.4
557        @example
558        error: function(response, newValue) {
559            if(response.status === 500) {
560                return 'Service unavailable. Please try later.';
561            } else {
562                return response.responseText;
563            }
564        }
565        **/          
566        error: null,
567        /**
568        Additional options for submit ajax request.
569        List of values: http://api.jquery.com/jQuery.ajax
570        
571        @property ajaxOptions 
572        @type object
573        @default null
574        @since 1.1.1        
575        @example 
576        ajaxOptions: {
577            type: 'put',
578            dataType: 'json'
579        }        
580        **/        
581        ajaxOptions: null,
582        /**
583        Where to show buttons: left(true)|bottom|false  
584        Form without buttons is auto-submitted.
585
586        @property showbuttons 
587        @type boolean|string
588        @default true
589        @since 1.1.1
590        **/         
591        showbuttons: true,
592        /**
593        Scope for callback methods (success, validate).  
594        If <code>null</code> means editableform instance itself. 
595
596        @property scope 
597        @type DOMElement|object
598        @default null
599        @since 1.2.0
600        @private
601        **/            
602        scope: null,
603        /**
604        Whether to save or cancel value when it was not changed but form was submitted
605
606        @property savenochange 
607        @type boolean
608        @default false
609        @since 1.2.0
610        **/
611        savenochange: false
612    };   
613
614    /*
615    Note: following params could redefined in engine: bootstrap or jqueryui:
616    Classes 'control-group' and 'editable-error-block' must always present!
617    */      
618    $.fn.editableform.template = '<form class="form-inline editableform">'+
619    '<div class="control-group">' + 
620    '<div><div class="editable-input"></div><div class="editable-buttons"></div></div>'+
621    '<div class="editable-error-block"></div>' + 
622    '</div>' + 
623    '</form>';
624
625    //loading div
626    $.fn.editableform.loading = '<div class="editableform-loading"></div>';
627
628    //buttons
629    $.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+
630    '<button type="button" class="editable-cancel">cancel</button>';      
631
632    //error class attached to control-group
633    $.fn.editableform.errorGroupClass = null;  
634
635    //error class attached to editable-error-block
636    $.fn.editableform.errorBlockClass = 'editable-error';
637    
638    //engine
639    $.fn.editableform.engine = 'jquery';
640}(window.jQuery));
641
642/**
643* EditableForm utilites
644*/
645(function ($) {
646    "use strict";
647    
648    //utils
649    $.fn.editableutils = {
650        /**
651        * classic JS inheritance function
652        */  
653        inherit: function (Child, Parent) {
654            var F = function() { };
655            F.prototype = Parent.prototype;
656            Child.prototype = new F();
657            Child.prototype.constructor = Child;
658            Child.superclass = Parent.prototype;
659        },
660
661        /**
662        * set caret position in input
663        * see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
664        */        
665        setCursorPosition: function(elem, pos) {
666            if (elem.setSelectionRange) {
667                try { elem.setSelectionRange(pos, pos); } catch (e) {}
668            } else if (elem.createTextRange) {
669                var range = elem.createTextRange();
670                range.collapse(true);
671                range.moveEnd('character', pos);
672                range.moveStart('character', pos);
673                range.select();
674            }
675        },
676
677        /**
678        * function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
679        * That allows such code as: <a data-source="{'a': 'b', 'c': 'd'}">
680        * safe = true --> means no exception will be thrown
681        * for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
682        */
683        tryParseJson: function(s, safe) {
684            if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
685                if (safe) {
686                    try {
687                        /*jslint evil: true*/
688                        s = (new Function('return ' + s))();
689                        /*jslint evil: false*/
690                    } catch (e) {} finally {
691                        return s;
692                    }
693                } else {
694                    /*jslint evil: true*/
695                    s = (new Function('return ' + s))();
696                    /*jslint evil: false*/
697                }
698            }
699            return s;
700        },
701
702        /**
703        * slice object by specified keys
704        */
705        sliceObj: function(obj, keys, caseSensitive /* default: false */) {
706            var key, keyLower, newObj = {};
707
708            if (!$.isArray(keys) || !keys.length) {
709                return newObj;
710            }
711
712            for (var i = 0; i < keys.length; i++) {
713                key = keys[i];
714                if (obj.hasOwnProperty(key)) {
715                    newObj[key] = obj[key];
716                }
717
718                if(caseSensitive === true) {
719                    continue;
720                }
721
722                //when getting data-* attributes via $.data() it's converted to lowercase.
723                //details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
724                //workaround is code below.
725                keyLower = key.toLowerCase();
726                if (obj.hasOwnProperty(keyLower)) {
727                    newObj[key] = obj[keyLower];
728                }
729            }
730
731            return newObj;
732        },
733
734        /*
735        exclude complex objects from $.data() before pass to config
736        */
737        getConfigData: function($element) {
738            var data = {};
739            $.each($element[0].dataset, function(k, v) {
740                if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
741                    data[k] = v;
742                }
743            });
744            return data;
745        },
746
747        /*
748         returns keys of object
749        */
750        objectKeys: function(o) {
751            if (Object.keys) {
752                return Object.keys(o);  
753            } else {
754                if (o !== Object(o)) {
755                    throw new TypeError('Object.keys called on a non-object');
756                }
757                var k=[], p;
758                for (p in o) {
759                    if (Object.prototype.hasOwnProperty.call(o,p)) {
760                        k.push(p);
761                    }
762                }
763                return k;
764            }
765
766        },
767        
768       /**
769        method to escape html.
770       **/
771       escape: function(str) {
772           return $('<div>').text(str).html();
773       },
774       
775       /*
776        returns array items from sourceData having value property equal or inArray of 'value'
777       */
778       itemsByValue: function(value, sourceData, valueProp) {
779           if(!sourceData || value === null) {
780               return [];
781           }
782           
783           if (typeof(valueProp) !== "function") {
784               var idKey = valueProp || 'value';
785               valueProp = function (e) { return e[idKey]; };
786           }
787                      
788           var isValArray = $.isArray(value),
789           result = [], 
790           that = this;
791
792           $.each(sourceData, function(i, o) {
793               if(o.children) {
794                   result = result.concat(that.itemsByValue(value, o.children, valueProp));
795               } else {
796                   /*jslint eqeq: true*/
797                   if(isValArray) {
798                       if($.grep(value, function(v){  return v == (o && typeof o === 'object' ? valueProp(o) : o); }).length) {
799                           result.push(o); 
800                       }
801                   } else {
802                       var itemValue = (o && (typeof o === 'object')) ? valueProp(o) : o;
803                       if(value == itemValue) {
804                           result.push(o); 
805                       }
806                   }
807                   /*jslint eqeq: false*/
808               }
809           });
810           
811           return result;
812       },
813       
814       /*
815       Returns input by options: type, mode. 
816       */
817       createInput: function(options) {
818           var TypeConstructor, typeOptions, input,
819           type = options.type;
820
821           //`date` is some kind of virtual type that is transformed to one of exact types
822           //depending on mode and core lib
823           if(type === 'date') {
824               //inline
825               if(options.mode === 'inline') {
826                   if($.fn.editabletypes.datefield) {
827                       type = 'datefield';
828                   } else if($.fn.editabletypes.dateuifield) {
829                       type = 'dateuifield';
830                   }
831               //popup
832               } else {
833                   if($.fn.editabletypes.date) {
834                       type = 'date';
835                   } else if($.fn.editabletypes.dateui) {
836                       type = 'dateui';
837                   }
838               }
839               
840               //if type still `date` and not exist in types, replace with `combodate` that is base input
841               if(type === 'date' && !$.fn.editabletypes.date) {
842                   type = 'combodate';
843               } 
844           }
845           
846           //`datetime` should be datetimefield in 'inline' mode
847           if(type === 'datetime' && options.mode === 'inline') {
848             type = 'datetimefield';  
849           }           
850
851           //change wysihtml5 to textarea for jquery UI and plain versions
852           if(type === 'wysihtml5' && !$.fn.editabletypes[type]) {
853               type = 'textarea';
854           }
855
856           //create input of specified type. Input will be used for converting value, not in form
857           if(typeof $.fn.editabletypes[type] === 'function') {
858               TypeConstructor = $.fn.editabletypes[type];
859               typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults));
860               input = new TypeConstructor(typeOptions);
861               return input;
862           } else {
863               $.error('Unknown type: '+ type);
864               return false; 
865           }  
866       },
867       
868       //see http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr
869       supportsTransitions: function () {
870           var b = document.body || document.documentElement,
871               s = b.style,
872               p = 'transition',
873               v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
874               
875           if(typeof s[p] === 'string') {
876               return true; 
877           }
878
879           // Tests for vendor specific prop
880           p = p.charAt(0).toUpperCase() + p.substr(1);
881           for(var i=0; i<v.length; i++) {
882               if(typeof s[v[i] + p] === 'string') { 
883                   return true; 
884               }
885           }
886           return false;
887       }            
888       
889    };      
890}(window.jQuery));
891
892/**
893Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
894This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
895Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br>
896Applied as jQuery method.
897
898@class editableContainer
899@uses editableform
900**/
901(function ($) {
902    "use strict";
903
904    var Popup = function (element, options) {
905        this.init(element, options);
906    };
907    
908    var Inline = function (element, options) {
909        this.init(element, options);
910    };    
911
912    //methods
913    Popup.prototype = {
914        containerName: null, //method to call container on element
915        containerDataName: null, //object name in element's .data()
916        innerCss: null, //tbd in child class
917        containerClass: 'editable-container editable-popup', //css class applied to container element
918        defaults: {}, //container itself defaults
919        
920        init: function(element, options) {
921            this.$element = $(element);
922            //since 1.4.1 container do not use data-* directly as they already merged into options.
923            this.options = $.extend({}, $.fn.editableContainer.defaults, options);         
924            this.splitOptions();
925            
926            //set scope of form callbacks to element
927            this.formOptions.scope = this.$element[0]; 
928            
929            this.initContainer();
930            
931            //flag to hide container, when saving value will finish
932            this.delayedHide = false;
933
934            //bind 'destroyed' listener to destroy container when element is removed from dom
935            this.$element.on('destroyed', $.proxy(function(){
936                this.destroy();
937            }, this)); 
938            
939            //attach document handler to close containers on click / escape
940            if(!$(document).data('editable-handlers-attached')) {
941                //close all on escape
942                $(document).on('keyup.editable', function (e) {
943                    if (e.which === 27) {
944                        $('.editable-open').editableContainer('hide', 'cancel');
945                        //todo: return focus on element 
946                    }
947                });
948
949                //close containers when click outside 
950                //(mousedown could be better than click, it closes everything also on drag drop)
951                $(document).on('click.editable', function(e) {
952                    var $target = $(e.target), i,
953                        exclude_classes = ['.editable-container', 
954                                           '.ui-datepicker-header', 
955                                           '.datepicker', //in inline mode datepicker is rendered into body
956                                           '.modal-backdrop', 
957                                           '.bootstrap-wysihtml5-insert-image-modal', 
958                                           '.bootstrap-wysihtml5-insert-link-modal'
959                                           ];
960
961                    // select2 has extra body click in IE
962                    // see: https://github.com/ivaynberg/select2/issues/1058 
963                    if ($('.select2-drop-mask').is(':visible')) {
964                        return;
965                    }
966
967                    //check if element is detached. It occurs when clicking in bootstrap datepicker
968                    if (!$.contains(document.documentElement, e.target)) {
969                        return;
970                    }
971
972                    //for some reason FF 20 generates extra event (click) in select2 widget with e.target = document
973                    //we need to filter it via construction below. See https://github.com/vitalets/x-editable/issues/199
974                    //Possibly related to http://stackoverflow.com/questions/10119793/why-does-firefox-react-differently-from-webkit-and-ie-to-click-event-on-selec
975                    if($target.is(document)) {
976                        return;
977                    }
978                    
979                    //if click inside one of exclude classes --> no nothing
980                    for(i=0; i<exclude_classes.length; i++) {
981                         if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) {
982                             return;
983                         }
984                    }
985                      
986                    //close all open containers (except one - target)
987                    Popup.prototype.closeOthers(e.target);
988                });
989                
990                $(document).data('editable-handlers-attached', true);
991            }                        
992        },
993
994        //split options on containerOptions and formOptions
995        splitOptions: function() {
996            this.containerOptions = {};
997            this.formOptions = {};
998            
999            if(!$.fn[this.containerName]) {
1000                throw new Error(this.containerName + ' not found. Have you included corresponding js file?');   
1001            }
1002            
1003            //keys defined in container defaults go to container, others go to form
1004            for(var k in this.options) {
1005              if(k in this.defaults) {
1006                 this.containerOptions[k] = this.options[k];
1007              } else {
1008                 this.formOptions[k] = this.options[k];
1009              } 
1010            }
1011        },
1012        
1013        /*
1014        Returns jquery object of container
1015        @method tip()
1016        */         
1017        tip: function() {
1018            return this.container() ? this.container().$tip : null;
1019        },
1020
1021        /* returns container object */
1022        container: function() {
1023            var container;
1024            //first, try get it by `containerDataName`
1025            if(this.containerDataName) {
1026                if(container = this.$element.data(this.containerDataName)) {
1027                    return container;
1028                }
1029            }
1030            //second, try `containerName`
1031            container = this.$element.data(this.containerName);
1032            return container;
1033        },
1034
1035        /* call native method of underlying container, e.g. this.$element.popover('method') */ 
1036        call: function() {
1037            this.$element[this.containerName].apply(this.$element, arguments); 
1038        },        
1039        
1040        initContainer: function(){
1041            this.call(this.containerOptions);
1042        },
1043
1044        renderForm: function() {
1045            this.$form
1046            .editableform(this.formOptions)
1047            .on({
1048                save: $.proxy(this.save, this), //click on submit button (value changed)
1049                nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)                
1050                cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on cancel button
1051                show: $.proxy(function() {
1052                    if(this.delayedHide) {
1053                        this.hide(this.delayedHide.reason);
1054                        this.delayedHide = false;
1055                    } else {
1056                        this.setPosition();
1057                    }
1058                }, this), //re-position container every time form is shown (occurs each time after loading state)
1059                rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
1060                resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed 
1061                rendered: $.proxy(function(){
1062                    /**        
1063                    Fired when container is shown and form is rendered (for select will wait for loading dropdown options).  
1064                    **Note:** Bootstrap popover has own `shown` event that now cannot be separated from x-editable's one.
1065                    The workaround is to check `arguments.length` that is always `2` for x-editable.                     
1066                    
1067                    @event shown 
1068                    @param {Object} event event object
1069                    @example
1070                    $('#username').on('shown', function(e, editable) {
1071                        editable.input.$input.val('overwriting value of input..');
1072                    });                     
1073                    **/                      
1074                    /*
1075                     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.  
1076                    */
1077                    this.$element.triggerHandler('shown', $(this.options.scope).data('editable')); 
1078                }, this) 
1079            })
1080            .editableform('render');
1081        },        
1082
1083        /**
1084        Shows container with form
1085        @method show()
1086        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
1087        **/
1088        /* Note: poshytip owerwrites this method totally! */          
1089        show: function (closeAll) {
1090            this.$element.addClass('editable-open');
1091            if(closeAll !== false) {
1092                //close all open containers (except this)
1093                this.closeOthers(this.$element[0]);  
1094            }
1095            
1096            //show container itself
1097            this.innerShow();
1098            this.tip().addClass(this.containerClass);
1099
1100            /*
1101            Currently, form is re-rendered on every show. 
1102            The main reason is that we dont know, what will container do with content when closed:
1103            remove(), detach() or just hide() - it depends on container.
1104            
1105            Detaching form itself before hide and re-insert before show is good solution, 
1106            but visually it looks ugly --> container changes size before hide.  
1107            */             
1108            
1109            //if form already exist - delete previous data 
1110            if(this.$form) {
1111                //todo: destroy prev data!
1112                //this.$form.destroy();
1113            }
1114
1115            this.$form = $('<div>');
1116            
1117            //insert form into container body
1118            if(this.tip().is(this.innerCss)) {
1119                //for inline container
1120                this.tip().append(this.$form); 
1121            } else {
1122                this.tip().find(this.innerCss).append(this.$form);
1123            } 
1124            
1125            //render form
1126            this.renderForm();
1127        },
1128
1129        /**
1130        Hides container with form
1131        @method hide()
1132        @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
1133        **/         
1134        hide: function(reason) {  
1135            if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
1136                return;
1137            }
1138            
1139            //if form is saving value, schedule hide
1140            if(this.$form.data('editableform').isSaving) {
1141                this.delayedHide = {reason: reason};
1142                return;    
1143            } else {
1144                this.delayedHide = false;
1145            }
1146
1147            this.$element.removeClass('editable-open');   
1148            this.innerHide();
1149
1150            /**
1151            Fired when container was hidden. It occurs on both save or cancel.  
1152            **Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.
1153            The workaround is to check `arguments.length` that is always `2` for x-editable. 
1154
1155            @event hidden 
1156            @param {object} event event object
1157            @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|manual</code>
1158            @example
1159            $('#username').on('hidden', function(e, reason) {
1160                if(reason === 'save' || reason === 'cancel') {
1161                    //auto-open next editable
1162                    $(this).closest('tr').next().find('.editable').editable('show');
1163                } 
1164            });
1165            **/
1166            this.$element.triggerHandler('hidden', reason || 'manual');   
1167        },
1168
1169        /* internal show method. To be overwritten in child classes */
1170        innerShow: function () {
1171             
1172        },        
1173
1174        /* internal hide method. To be overwritten in child classes */
1175        innerHide: function () {
1176
1177        },
1178        
1179        /**
1180        Toggles container visibility (show / hide)
1181        @method toggle()
1182        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
1183        **/          
1184        toggle: function(closeAll) {
1185            if(this.container() && this.tip() && this.tip().is(':visible')) {
1186                this.hide();
1187            } else {
1188                this.show(closeAll);
1189            } 
1190        },
1191
1192        /*
1193        Updates the position of container when content changed.
1194        @method setPosition()
1195        */       
1196        setPosition: function() {
1197            //tbd in child class
1198        },
1199
1200        save: function(e, params) {
1201            /**        
1202            Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
1203            
1204            @event save 
1205            @param {Object} event event object
1206            @param {Object} params additional params
1207            @param {mixed} params.newValue submitted value
1208            @param {Object} params.response ajax response
1209            @example
1210            $('#username').on('save', function(e, params) {
1211                //assuming server response: '{success: true}'
1212                var pk = $(this).data('editableContainer').options.pk;
1213                if(params.response && params.response.success) {
1214                    alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
1215                } else {
1216                    alert('error!'); 
1217                } 
1218            });
1219            **/             
1220            this.$element.triggerHandler('save', params);
1221            
1222            //hide must be after trigger, as saving value may require methods of plugin, applied to input
1223            this.hide('save');
1224        },
1225
1226        /**
1227        Sets new option
1228        
1229        @method option(key, value)
1230        @param {string} key 
1231        @param {mixed} value 
1232        **/         
1233        option: function(key, value) {
1234            this.options[key] = value;
1235            if(key in this.containerOptions) {
1236                this.containerOptions[key] = value;
1237                this.setContainerOption(key, value); 
1238            } else {
1239                this.formOptions[key] = value;
1240                if(this.$form) {
1241                    this.$form.editableform('option', key, value);  
1242                }
1243            }
1244        },
1245        
1246        setContainerOption: function(key, value) {
1247            this.call('option', key, value);
1248        },
1249
1250        /**
1251        Destroys the container instance
1252        @method destroy()
1253        **/        
1254        destroy: function() {
1255            this.hide();
1256            this.innerDestroy();
1257            this.$element.off('destroyed');
1258            this.$element.removeData('editableContainer');
1259        },
1260        
1261        /* to be overwritten in child classes */
1262        innerDestroy: function() {
1263            
1264        }, 
1265        
1266        /*
1267        Closes other containers except one related to passed element. 
1268        Other containers can be cancelled or submitted (depends on onblur option)
1269        */
1270        closeOthers: function(element) {
1271            $('.editable-open').each(function(i, el){
1272                //do nothing with passed element and it's children
1273                if(el === element || $(el).find(element).length) {
1274                    return;
1275                }
1276
1277                //otherwise cancel or submit all open containers 
1278                var $el = $(el),
1279                ec = $el.data('editableContainer');
1280
1281                if(!ec) {
1282                    return;  
1283                }
1284                
1285                if(ec.options.onblur === 'cancel') {
1286                    $el.data('editableContainer').hide('onblur');
1287                } else if(ec.options.onblur === 'submit') {
1288                    $el.data('editableContainer').tip().find('form').submit();
1289                }
1290            });
1291
1292        },
1293        
1294        /**
1295        Activates input of visible container (e.g. set focus)
1296        @method activate()
1297        **/         
1298        activate: function() {
1299            if(this.tip && this.tip().is(':visible') && this.$form) {
1300               this.$form.data('editableform').input.activate(); 
1301            }
1302        } 
1303
1304    };
1305
1306    /**
1307    jQuery method to initialize editableContainer.
1308    
1309    @method $().editableContainer(options)
1310    @params {Object} options
1311    @example
1312    $('#edit').editableContainer({
1313        type: 'text',
1314        url: '/post',
1315        pk: 1,
1316        value: 'hello'
1317    });
1318    **/  
1319    $.fn.editableContainer = function (option) {
1320        var args = arguments;
1321        return this.each(function () {
1322            var $this = $(this),
1323            dataKey = 'editableContainer', 
1324            data = $this.data(dataKey),
1325            options = typeof option === 'object' && option,
1326            Constructor = (options.mode === 'inline') ? Inline : Popup;             
1327
1328            if (!data) {
1329                $this.data(dataKey, (data = new Constructor(this, options)));
1330            }
1331
1332            if (typeof option === 'string') { //call method 
1333                data[option].apply(data, Array.prototype.slice.call(args, 1));
1334            }            
1335        });
1336    };     
1337
1338    //store constructors
1339    $.fn.editableContainer.Popup = Popup;
1340    $.fn.editableContainer.Inline = Inline;
1341
1342    //defaults
1343    $.fn.editableContainer.defaults = {
1344        /**
1345        Initial value of form input
1346
1347        @property value 
1348        @type mixed
1349        @default null
1350        @private
1351        **/        
1352        value: null,
1353        /**
1354        Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container.
1355
1356        @property placement 
1357        @type string
1358        @default 'top'
1359        **/        
1360        placement: 'top',
1361        /**
1362        Whether to hide container on save/cancel.
1363
1364        @property autohide 
1365        @type boolean
1366        @default true
1367        @private 
1368        **/        
1369        autohide: true,
1370        /**
1371        Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>.  
1372        Setting <code>ignore</code> allows to have several containers open. 
1373
1374        @property onblur 
1375        @type string
1376        @default 'cancel'
1377        @since 1.1.1
1378        **/        
1379        onblur: 'cancel',
1380        
1381        /**
1382        Animation speed (inline mode only)
1383        @property anim 
1384        @type string
1385        @default false
1386        **/        
1387        anim: false,
1388        
1389        /**
1390        Mode of editable, can be `popup` or `inline` 
1391        
1392        @property mode 
1393        @type string         
1394        @default 'popup'
1395        @since 1.4.0        
1396        **/        
1397        mode: 'popup'        
1398    };
1399
1400    /* 
1401    * workaround to have 'destroyed' event to destroy popover when element is destroyed
1402    * see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
1403    */
1404    jQuery.event.special.destroyed = {
1405        remove: function(o) {
1406            if (o.handler) {
1407                o.handler();
1408            }
1409        }
1410    };    
1411
1412}(window.jQuery));
1413
1414/**
1415* Editable Inline 
1416* ---------------------
1417*/
1418(function ($) {
1419    "use strict";
1420    
1421    //copy prototype from EditableContainer
1422    //extend methods
1423    $.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, {
1424        containerName: 'editableform',
1425        innerCss: '.editable-inline',
1426        containerClass: 'editable-container editable-inline', //css class applied to container element
1427                 
1428        initContainer: function(){
1429            //container is <span> element
1430            this.$tip = $('<span></span>');
1431            
1432            //convert anim to miliseconds (int)
1433            if(!this.options.anim) {
1434                this.options.anim = 0;
1435            }         
1436        },
1437        
1438        splitOptions: function() {
1439            //all options are passed to form
1440            this.containerOptions = {};
1441            this.formOptions = this.options;
1442        },
1443        
1444        tip: function() {
1445           return this.$tip; 
1446        },
1447        
1448        innerShow: function () {
1449            this.$element.hide();
1450            this.tip().insertAfter(this.$element).show();
1451        }, 
1452        
1453        innerHide: function () {
1454            this.$tip.hide(this.options.anim, $.proxy(function() {
1455                this.$element.show();
1456                this.innerDestroy();
1457            }, this)); 
1458        },
1459        
1460        innerDestroy: function() {
1461            if(this.tip()) {
1462                this.tip().empty().remove();
1463            }
1464        } 
1465    });
1466
1467}(window.jQuery));
1468/**
1469Makes editable any HTML element on the page. Applied as jQuery method.
1470
1471@class editable
1472@uses editableContainer
1473**/
1474(function ($) {
1475    "use strict";
1476
1477    var Editable = function (element, options) {
1478        this.$element = $(element);
1479        //data-* has more priority over js options: because dynamically created elements may change data-* 
1480        this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element));  
1481        if(this.options.selector) {
1482            this.initLive();
1483        } else {
1484            this.init();
1485        }
1486        
1487        //check for transition support
1488        if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
1489            this.options.highlight = false;
1490        }
1491    };
1492
1493    Editable.prototype = {
1494        constructor: Editable, 
1495        init: function () {
1496            var isValueByText = false, 
1497                doAutotext, finalize;
1498
1499            //name
1500            this.options.name = this.options.name || this.$element.attr('id');
1501             
1502            //create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select)
1503            //also we set scope option to have access to element inside input specific callbacks (e. g. source as function)
1504            this.options.scope = this.$element[0]; 
1505            this.input = $.fn.editableutils.createInput(this.options);
1506            if(!this.input) {
1507                return; 
1508            }            
1509
1510            //set value from settings or by element's text
1511            if (this.options.value === undefined || this.options.value === null) {
1512                this.value = this.input.html2value($.trim(this.$element.html()));
1513                isValueByText = true;
1514            } else {
1515                /*
1516                  value can be string when received from 'data-value' attribute
1517                  for complext objects value can be set as json string in data-value attribute, 
1518                  e.g. data-value="{city: 'Moscow', street: 'Lenina'}"
1519                */
1520                this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true); 
1521                if(typeof this.options.value === 'string') {
1522                    this.value = this.input.str2value(this.options.value);
1523                } else {
1524                    this.value = this.options.value;
1525                }
1526            }
1527            
1528            //add 'editable' class to every editable element
1529            this.$element.addClass('editable');
1530            
1531            //specifically for "textarea" add class .editable-pre-wrapped to keep linebreaks
1532            if(this.input.type === 'textarea') {
1533                this.$element.addClass('editable-pre-wrapped');
1534            }
1535            
1536            //attach handler activating editable. In disabled mode it just prevent default action (useful for links)
1537            if(this.options.toggle !== 'manual') {
1538                this.$element.addClass('editable-click');
1539                this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
1540                    //prevent following link if editable enabled
1541                    if(!this.options.disabled) {
1542                        e.preventDefault();
1543                    }
1544                    
1545                    //stop propagation not required because in document click handler it checks event target
1546                    //e.stopPropagation();
1547                    
1548                    if(this.options.toggle === 'mouseenter') {
1549                        //for hover only show container
1550                        this.show();
1551                    } else {
1552                        //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
1553                        var closeAll = (this.options.toggle !== 'click');
1554                        this.toggle(closeAll);
1555                    }
1556                }, this));
1557            } else {
1558                this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
1559            }
1560            
1561            //if display is function it's far more convinient to have autotext = always to render correctly on init
1562            //see https://github.com/vitalets/x-editable-yii/issues/34
1563            if(typeof this.options.display === 'function') {
1564                this.options.autotext = 'always';
1565            }
1566            
1567            //check conditions for autotext:
1568            switch(this.options.autotext) {
1569              case 'always':
1570               doAutotext = true;
1571              break;
1572              case 'auto':
1573                //if element text is empty and value is defined and value not generated by text --> run autotext
1574                doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText;
1575              break;
1576              default:
1577               doAutotext = false;
1578            }
1579
1580            //depending on autotext run render() or just finilize init
1581            $.when(doAutotext ? this.render() : true).then($.proxy(function() {
1582                if(this.options.disabled) {
1583                    this.disable();
1584                } else {
1585                    this.enable(); 
1586                }
1587               /**        
1588               Fired when element was initialized by `$().editable()` method. 
1589               Please note that you should setup `init` handler **before** applying `editable`. 
1590                              
1591               @event init 
1592               @param {Object} event event object
1593               @param {Object} editable editable instance (as here it cannot accessed via data('editable'))
1594               @since 1.2.0
1595               @example
1596               $('#username').on('init', function(e, editable) {
1597                   alert('initialized ' + editable.options.name);
1598               });
1599               $('#username').editable();
1600               **/                  
1601                this.$element.triggerHandler('init', this);
1602            }, this));
1603        },
1604
1605        /*
1606         Initializes parent element for live editables 
1607        */
1608        initLive: function() {
1609           //store selector 
1610           var selector = this.options.selector;
1611           //modify options for child elements
1612           this.options.selector = false; 
1613           this.options.autotext = 'never';
1614           //listen toggle events
1615           this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){
1616               var $target = $(e.target).closest(selector);
1617               if(!$target.data('editable')) {
1618                   //if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user)
1619                   //see https://github.com/vitalets/x-editable/issues/137 
1620                   if($target.hasClass(this.options.emptyclass)) {
1621                      $target.empty();
1622                   }
1623                   $target.editable(this.options).trigger(e);
1624               }
1625           }, this)); 
1626        },
1627        
1628        /*
1629        Renders value into element's text.
1630        Can call custom display method from options.
1631        Can return deferred object.
1632        @method render()
1633        @param {mixed} response server response (if exist) to pass into display function
1634        */          
1635        render: function(response) {
1636            //do not display anything
1637            if(this.options.display === false) {
1638                return;
1639            }
1640            
1641            //if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded
1642            if(this.input.value2htmlFinal) {
1643                return this.input.value2html(this.value, this.$element[0], this.options.display, response); 
1644            //if display method defined --> use it    
1645            } else if(typeof this.options.display === 'function') {
1646                return this.options.display.call(this.$element[0], this.value, response);
1647            //else use input's original value2html() method    
1648            } else {
1649                return this.input.value2html(this.value, this.$element[0]); 
1650            }
1651        },
1652        
1653        /**
1654        Enables editable
1655        @method enable()
1656        **/          
1657        enable: function() {
1658            this.options.disabled = false;
1659            this.$element.removeClass('editable-disabled');
1660            this.handleEmpty(this.isEmpty);
1661            if(this.options.toggle !== 'manual') {
1662                if(this.$element.attr('tabindex') === '-1') {    
1663                    this.$element.removeAttr('tabindex');                                
1664                }
1665            }
1666        },
1667        
1668        /**
1669        Disables editable
1670        @method disable()
1671        **/         
1672        disable: function() {
1673            this.options.disabled = true; 
1674            this.hide();           
1675            this.$element.addClass('editable-disabled');
1676            this.handleEmpty(this.isEmpty);
1677            //do not stop focus on this element
1678            this.$element.attr('tabindex', -1);                
1679        },
1680        
1681        /**
1682        Toggles enabled / disabled state of editable element
1683        @method toggleDisabled()
1684        **/         
1685        toggleDisabled: function() {
1686            if(this.options.disabled) {
1687                this.enable();
1688            } else { 
1689                this.disable(); 
1690            }
1691        },  
1692        
1693        /**
1694        Sets new option
1695        
1696        @method option(key, value)
1697        @param {string|object} key option name or object with several options
1698        @param {mixed} value option new value
1699        @example
1700        $('.editable').editable('option', 'pk', 2);
1701        **/          
1702        option: function(key, value) {
1703            //set option(s) by object
1704            if(key && typeof key === 'object') {
1705               $.each(key, $.proxy(function(k, v){
1706                  this.option($.trim(k), v); 
1707               }, this)); 
1708               return;
1709            }
1710
1711            //set option by string             
1712            this.options[key] = value;                          
1713            
1714            //disabled
1715            if(key === 'disabled') {
1716               return value ? this.disable() : this.enable();
1717            } 
1718            
1719            //value
1720            if(key === 'value') {
1721                this.setValue(value);
1722            }
1723            
1724            //transfer new option to container! 
1725            if(this.container) {
1726                this.container.option(key, value);  
1727            }
1728             
1729            //pass option to input directly (as it points to the same in form)
1730            if(this.input.option) {
1731                this.input.option(key, value);
1732            }
1733            
1734        },              
1735        
1736        /*
1737        * set emptytext if element is empty
1738        */
1739        handleEmpty: function (isEmpty) {
1740            //do not handle empty if we do not display anything
1741            if(this.options.display === false) {
1742                return;
1743            }
1744
1745            /* 
1746            isEmpty may be set directly as param of method.
1747            It is required when we enable/disable field and can't rely on content 
1748            as node content is text: "Empty" that is not empty %)
1749            */
1750            if(isEmpty !== undefined) { 
1751                this.isEmpty = isEmpty;
1752            } else {
1753                //detect empty
1754                //for some inputs we need more smart check
1755                //e.g. wysihtml5 may have <br>, <p></p>, <img>
1756                if(typeof(this.input.isEmpty) === 'function') {
1757                    this.isEmpty = this.input.isEmpty(this.$element);                    
1758                } else {
1759                    this.isEmpty = $.trim(this.$element.html()) === '';
1760                }
1761            }           
1762            
1763            //emptytext shown only for enabled
1764            if(!this.options.disabled) {
1765                if (this.isEmpty) {
1766                    this.$element.html(this.options.emptytext);
1767                    if(this.options.emptyclass) {
1768                        this.$element.addClass(this.options.emptyclass);
1769                    }
1770                } else if(this.options.emptyclass) {
1771                    this.$element.removeClass(this.options.emptyclass);
1772                }
1773            } else {
1774                //below required if element disable property was changed
1775                if(this.isEmpty) {
1776                    this.$element.empty();
1777                    if(this.options.emptyclass) {
1778                        this.$element.removeClass(this.options.emptyclass);
1779                    }
1780                }
1781            }
1782        },        
1783        
1784        /**
1785        Shows container with form
1786        @method show()
1787        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
1788        **/  
1789        show: function (closeAll) {
1790            if(this.options.disabled) {
1791                return;
1792            }
1793            
1794            //init editableContainer: popover, tooltip, inline, etc..
1795            if(!this.container) {
1796                var containerOptions = $.extend({}, this.options, {
1797                    value: this.value,
1798                    input: this.input //pass input to form (as it is already created)
1799                });
1800                this.$element.editableContainer(containerOptions);
1801                //listen `save` event 
1802                this.$element.on("save.internal", $.proxy(this.save, this));
1803                this.container = this.$element.data('editableContainer'); 
1804            } else if(this.container.tip().is(':visible')) {
1805                return;
1806            }      
1807            
1808            //show container
1809            this.container.show(closeAll);
1810        },
1811        
1812        /**
1813        Hides container with form
1814        @method hide()
1815        **/       
1816        hide: function () {   
1817            if(this.container) {  
1818                this.container.hide();
1819            }
1820        },
1821        
1822        /**
1823        Toggles container visibility (show / hide)
1824        @method toggle()
1825        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
1826        **/  
1827        toggle: function(closeAll) {
1828            if(this.container && this.container.tip().is(':visible')) {
1829                this.hide();
1830            } else {
1831                this.show(closeAll);
1832            }
1833        },
1834        
1835        /*
1836        * called when form was submitted
1837        */          
1838        save: function(e, params) {
1839            //mark element with unsaved class if needed
1840            if(this.options.unsavedclass) {
1841                /*
1842                 Add unsaved css to element if:
1843                  - url is not user's function 
1844                  - value was not sent to server
1845                  - params.response === undefined, that means data was not sent
1846                  - value changed 
1847                */
1848                var sent = false;
1849                sent = sent || typeof this.options.url === 'function';
1850                sent = sent || this.options.display === false; 
1851                sent = sent || params.response !== undefined; 
1852                sent = sent || (this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(params.newValue)); 
1853                
1854                if(sent) {
1855                    this.$element.removeClass(this.options.unsavedclass); 
1856                } else {
1857                    this.$element.addClass(this.options.unsavedclass);                    
1858                }
1859            }
1860            
1861            //highlight when saving
1862            if(this.options.highlight) {
1863                var $e = this.$element,
1864                    bgColor = $e.css('background-color');
1865                    
1866                $e.css('background-color', this.options.highlight);
1867                setTimeout(function(){
1868                    if(bgColor === 'transparent') {
1869                        bgColor = ''; 
1870                    }
1871                    $e.css('background-color', bgColor);
1872                    $e.addClass('editable-bg-transition');
1873                    setTimeout(function(){
1874                       $e.removeClass('editable-bg-transition');  
1875                    }, 1700);
1876                }, 10);
1877            }
1878            
1879            //set new value
1880            this.setValue(params.newValue, false, params.response);
1881            
1882            /**        
1883            Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
1884            
1885            @event save 
1886            @param {Object} event event object
1887            @param {Object} params additional params
1888            @param {mixed} params.newValue submitted value
1889            @param {Object} params.response ajax response
1890            @example
1891            $('#username').on('save', function(e, params) {
1892                alert('Saved value: ' + params.newValue);
1893            });
1894            **/
1895            //event itself is triggered by editableContainer. Description here is only for documentation              
1896        },
1897
1898        validate: function () {
1899            if (typeof this.options.validate === 'function') {
1900                return this.options.validate.call(this, this.value);
1901            }
1902        },
1903        
1904        /**
1905        Sets new value of editable
1906        @method setValue(value, convertStr)
1907        @param {mixed} value new value 
1908        @param {boolean} convertStr whether to convert value from string to internal format
1909        **/         
1910        setValue: function(value, convertStr, response) {
1911            if(convertStr) {
1912                this.value = this.input.str2value(value);
1913            } else {
1914                this.value = value;
1915            }
1916            if(this.container) {
1917                this.container.option('value', this.value);
1918            }
1919            $.when(this.render(response))
1920            .then($.proxy(function() {
1921                this.handleEmpty();
1922            }, this));
1923        },
1924        
1925        /**
1926        Activates input of visible container (e.g. set focus)
1927        @method activate()
1928        **/         
1929        activate: function() {
1930            if(this.container) {
1931               this.container.activate(); 
1932            }
1933        },
1934        
1935        /**
1936        Removes editable feature from element
1937        @method destroy()
1938        **/        
1939        destroy: function() {
1940            this.disable();
1941            
1942            if(this.container) {
1943               this.container.destroy(); 
1944            }
1945            
1946            this.input.destroy();
1947
1948            if(this.options.toggle !== 'manual') {
1949                this.$element.removeClass('editable-click');
1950                this.$element.off(this.options.toggle + '.editable');
1951            } 
1952            
1953            this.$element.off("save.internal");
1954            
1955            this.$element.removeClass('editable editable-open editable-disabled');
1956            this.$element.removeData('editable');
1957        }        
1958    };
1959
1960    /* EDITABLE PLUGIN DEFINITION
1961    * ======================= */
1962
1963    /**
1964    jQuery method to initialize editable element.
1965    
1966    @method $().editable(options)
1967    @params {Object} options
1968    @example
1969    $('#username').editable({
1970        type: 'text',
1971        url: '/post',
1972        pk: 1
1973    });
1974    **/
1975    $.fn.editable = function (option) {
1976        //special API methods returning non-jquery object
1977        var result = {}, args = arguments, datakey = 'editable';
1978        switch (option) {
1979            /**
1980            Runs client-side validation for all matched editables
1981            
1982            @method validate()
1983            @returns {Object} validation errors map
1984            @example
1985            $('#username, #fullname').editable('validate');
1986            // possible result:
1987            {
1988              username: "username is required",
1989              fullname: "fullname should be minimum 3 letters length"
1990            }
1991            **/
1992            case 'validate':
1993                this.each(function () {
1994                    var $this = $(this), data = $this.data(datakey), error;
1995                    if (data && (error = data.validate())) {
1996                        result[data.options.name] = error;
1997                    }
1998                });
1999            return result;
2000
2001            /**
2002            Returns current values of editable elements.   
2003            Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.    
2004            If value of some editable is `null` or `undefined` it is excluded from result object.
2005            When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object.   
2006             
2007            @method getValue()
2008            @param {bool} isSingle whether to return just value of single element
2009            @returns {Object} object of element names and values
2010            @example
2011            $('#username, #fullname').editable('getValue');
2012            //result:
2013            {
2014            username: "superuser",
2015            fullname: "John"
2016            }
2017            //isSingle = true
2018            $('#username').editable('getValue', true);
2019            //result "superuser" 
2020            **/
2021            case 'getValue':
2022                if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
2023                    result = this.eq(0).data(datakey).value;
2024                } else {
2025                    this.each(function () {
2026                        var $this = $(this), data = $this.data(datakey);
2027                        if (data && data.value !== undefined && data.value !== null) {
2028                            result[data.options.name] = data.input.value2submit(data.value);
2029                        }
2030                    });
2031                }
2032            return result;
2033
2034            /**
2035            This method collects values from several editable elements and submit them all to server.   
2036            Internally it runs client-side validation for all fields and submits only in case of success.  
2037            See <a href="#newrecord">creating new records</a> for details.  
2038            Since 1.5.1 `submit` can be applied to single element to send data programmatically. In that case
2039            `url`, `success` and `error` is taken from initial options and you can just call `$('#username').editable('submit')`. 
2040            
2041            @method submit(options)
2042            @param {object} options 
2043            @param {object} options.url url to submit data 
2044            @param {object} options.data additional data to submit
2045            @param {object} options.ajaxOptions additional ajax options
2046            @param {function} options.error(obj) error handler 
2047            @param {function} options.success(obj,config) success handler
2048            @returns {Object} jQuery object
2049            **/
2050            case 'submit':  //collects value, validate and submit to server for creating new record
2051                var config = arguments[1] || {},
2052                $elems = this,
2053                errors = this.editable('validate');
2054
2055                // validation ok
2056                if($.isEmptyObject(errors)) {
2057                    var ajaxOptions = {};
2058                                                      
2059                    // for single element use url, success etc from options
2060                    if($elems.length === 1) {
2061                        var editable = $elems.data('editable');
2062                        //standard params
2063                        var params = {
2064                            name: editable.options.name || '',
2065                            value: editable.input.value2submit(editable.value),
2066                            pk: (typeof editable.options.pk === 'function') ? 
2067                                editable.options.pk.call(editable.options.scope) : 
2068                                editable.options.pk 
2069                        };
2070
2071                        //additional params
2072                        if(typeof editable.options.params === 'function') {
2073                            params = editable.options.params.call(editable.options.scope, params);  
2074                        } else {
2075                            //try parse json in single quotes (from data-params attribute)
2076                            editable.options.params = $.fn.editableutils.tryParseJson(editable.options.params, true);   
2077                            $.extend(params, editable.options.params);
2078                        }
2079
2080                        ajaxOptions = {
2081                            url: editable.options.url,
2082                            data: params,
2083                            type: 'POST'  
2084                        };
2085                        
2086                        // use success / error from options 
2087                        config.success = config.success || editable.options.success;
2088                        config.error = config.error || editable.options.error;
2089                        
2090                    // multiple elements
2091                    } else {
2092                        var values = this.editable('getValue'); 
2093                        
2094                        ajaxOptions = {
2095                            url: config.url,
2096                            data: values, 
2097                            type: 'POST'
2098                        };                        
2099                    }                    
2100
2101                    // ajax success callabck (response 200 OK)
2102                    ajaxOptions.success = typeof config.success === 'function' ? function(response) {
2103                            config.success.call($elems, response, config);
2104                        } : $.noop;
2105                                  
2106                    // ajax error callabck
2107                    ajaxOptions.error = typeof config.error === 'function' ? function() {
2108                             config.error.apply($elems, arguments);
2109                        } : $.noop;
2110                       
2111                    // extend ajaxOptions    
2112                    if(config.ajaxOptions) { 
2113                        $.extend(ajaxOptions, config.ajaxOptions);
2114                    }
2115                    
2116                    // extra data 
2117                    if(config.data) {
2118                        $.extend(ajaxOptions.data, config.data);
2119                    }                     
2120                    
2121                    // perform ajax request
2122                    $.ajax(ajaxOptions);
2123                } else { //client-side validation error
2124                    if(typeof config.error === 'function') {
2125                        config.error.call($elems, errors);
2126                    }
2127                }
2128            return this;
2129        }
2130
2131        //return jquery object
2132        return this.each(function () {
2133            var $this = $(this), 
2134                data = $this.data(datakey), 
2135                options = typeof option === 'object' && option;
2136
2137            //for delegated targets do not store `editable` object for element
2138            //it's allows several different selectors.
2139            //see: https://github.com/vitalets/x-editable/issues/312    
2140            if(options && options.selector) {
2141                data = new Editable(this, options);
2142                return; 
2143            }    
2144            
2145            if (!data) {
2146                $this.data(datakey, (data = new Editable(this, options)));
2147            }
2148
2149            if (typeof option === 'string') { //call method 
2150                data[option].apply(data, Array.prototype.slice.call(args, 1));
2151            } 
2152        });
2153    };    
2154            
2155
2156    $.fn.editable.defaults = {
2157        /**
2158        Type of input. Can be <code>
2158text|textarea|select|date|checklist</code> and more
2159
2160        @property type 
2161        @type string
2162        @default 'text'
2163        **/
2164        type: 'text',        
2165        /**
2166        Sets disabled state of editable
2167
2168        @property disabled 
2169        @type boolean
2170        @default false
2171        **/         
2172        disabled: false,
2173        /**
2174        How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>.   
2175        When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable.    
2176        **Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element, 
2177        you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document.
2178        
2179        @example
2180        $('#edit-button').click(function(e) {
2181            e.stopPropagation();
2182            $('#username').editable('toggle');
2183        });
2184
2185        @property toggle 
2186        @type string
2187        @default 'click'
2188        **/          
2189        toggle: 'click',
2190        /**
2191        Text shown when element is empty.
2192
2193        @property emptytext 
2194        @type string
2195        @default 'Empty'
2196        **/         
2197        emptytext: 'Empty',
2198        /**
2199        Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date.
2200        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>.  
2201        <code>auto</code> - text will be automatically set only if element is empty.  
2202        <code>always|never</code> - always(never) try to set element's text.
2203
2204        @property autotext 
2205        @type string
2206        @default 'auto'
2207        **/          
2208        autotext: 'auto', 
2209        /**
2210        Initial value of input. If not set, taken from element's text.  
2211        Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option).  
2212        For example, to display currency sign:
2213        @example
2214        <a id="price" data-type="text" data-value="100"></a>
2215        <script>
2216        $('#price').editable({
2217            ...
2218            display: function(value) {
2219              $(this).text(value + '$');
2220            } 
2221        }) 
2222        </script>
2223                
2224        @property value 
2225        @type mixed
2226        @default element's text
2227        **/
2228        value: null,
2229        /**
2230        Callback to perform custom displaying of value in element's text.  
2231        If `null`, default input's display used.  
2232        If `false`, no displaying methods will be called, element's text will never change.  
2233        Runs under element's scope.  
2234        _**Parameters:**_  
2235        
2236        * `value` current value to be displayed
2237        * `response` server response (if display called after ajax submit), since 1.4.0
2238         
2239        For _inputs with source_ (select, checklist) parameters are different:  
2240          
2241        * `value` current value to be displayed
2242        * `sourceData` array of items for current input (e.g. dropdown items) 
2243        * `response` server response (if display called after ajax submit), since 1.4.0
2244                  
2245        To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`.
2246        
2247        @property display 
2248        @type function|boolean
2249        @default null
2250        @since 1.2.0
2251        @example
2252        display: function(value, sourceData) {
2253           //display checklist as comma-separated values
2254           var html = [],
2255               checked = $.fn.editableutils.itemsByValue(value, sourceData);
2256               
2257           if(checked.length) {
2258               $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
2259               $(this).html(html.join(', '));
2260           } else {
2261               $(this).empty(); 
2262           }
2263        }
2264        **/          
2265        display: null,
2266        /**
2267        Css class applied when editable text is empty.
2268
2269        @property emptyclass 
2270        @type string
2271        @since 1.4.1        
2272        @default editable-empty
2273        **/        
2274        emptyclass: 'editable-empty',
2275        /**
2276        Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`).  
2277        You may set it to `null` if you work with editables locally and submit them together.  
2278
2279        @property unsavedclass 
2280        @type string
2281        @since 1.4.1        
2282        @default editable-unsaved
2283        **/        
2284        unsavedclass: 'editable-unsaved',
2285        /**
2286        If selector is provided, editable will be delegated to the specified targets.  
2287        Usefull for dynamically generated DOM elements.  
2288        **Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options, 
2289        as they actually become editable only after first click.  
2290        You should manually set class `editable-click` to these elements.  
2291        Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element:
2292
2293        @property selector 
2294        @type string
2295        @since 1.4.1        
2296        @default null
2297        @example
2298        <div id="user">
2299          <!-- empty -->
2300          <a href="#" data-name="username" data-type="text" class="editable-click editable-empty" data-value="" title="Username">Empty</a>
2301          <!-- non-empty -->
2302          <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" class="editable-click" title="Group">Operator</a>
2303        </div>     
2304        
2305        <script>
2306        $('#user').editable({
2307            selector: 'a',
2308            url: '/post',
2309            pk: 1
2310        });
2311        </script>
2312        **/         
2313        selector: null,
2314        /**
2315        Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers.
2316        
2317        @property highlight 
2318        @type string|boolean
2319        @since 1.4.5        
2320        @default #FFFF80 
2321        **/
2322        highlight: '#FFFF80'
2323    };
2324    
2325}(window.jQuery));
2326
2327/**
2328AbstractInput - base class for all editable inputs.
2329It defines interface to be implemented by any input type.
2330To create your own input you can inherit from this class.
2331
2332@class abstractinput
2333**/
2334(function ($) {
2335    "use strict";
2336
2337    //types
2338    $.fn.editabletypes = {};
2339
2340    var AbstractInput = function () { };
2341
2342    AbstractInput.prototype = {
2343       /**
2344        Initializes input
2345
2346        @method init() 
2347        **/
2348       init: function(type, options, defaults) {
2349           this.type = type;
2350           this.options = $.extend({}, defaults, options);
2351       },
2352
2353       /*
2354       this method called before render to init $tpl that is inserted in DOM
2355       */
2356       prerender: function() {
2357           this.$tpl = $(this.options.tpl); //whole tpl as jquery object    
2358           this.$input = this.$tpl;         //control itself, can be changed in render method
2359           this.$clear = null;              //clear button
2360           this.error = null;               //error message, if input cannot be rendered           
2361       },
2362       
2363       /**
2364        Renders input from tpl. Can return jQuery deferred object.
2365        Can be overwritten in child objects
2366
2367        @method render()
2368       **/
2369       render: function() {
2370
2371       }, 
2372
2373       /**
2374        Sets element's html by value. 
2375
2376        @method value2html(value, element)
2377        @param {mixed} value
2378        @param {DOMElement} element
2379       **/
2380       value2html: function(value, element) {
2381           $(element)[this.options.escape ? 'text' : 'html']($.trim(value));
2382       },
2383
2384       /**
2385        Converts element's html to value
2386
2387        @method html2value(html)
2388        @param {string} html
2389        @returns {mixed}
2390       **/
2391       html2value: function(html) {
2392           return $('<div>').html(html).text();
2393       },
2394
2395       /**
2396        Converts value to string (for internal compare). For submitting to server used value2submit().
2397
2398        @method value2str(value) 
2399        @param {mixed} value
2400        @returns {string}
2401       **/
2402       value2str: function(value) {
2403           return String(value);
2404       }, 
2405
2406       /**
2407        Converts string received from server into value. Usually from `data-value` attribute.
2408
2409        @method str2value(str)
2410        @param {string} str
2411        @returns {mixed}
2412       **/
2413       str2value: function(str) {
2414           return str;
2415       }, 
2416       
2417       /**
2418        Converts value for submitting to server. Result can be string or object.
2419
2420        @method value2submit(value) 
2421        @param {mixed} value
2422        @returns {mixed}
2423       **/
2424       value2submit: function(value) {
2425           return value;
2426       },
2427
2428       /**
2429        Sets value of input.
2430
2431        @method value2input(value) 
2432        @param {mixed} value
2433       **/
2434       value2input: function(value) {
2435           this.$input.val(value);
2436       },
2437
2438       /**
2439        Returns value of input. Value can be object (e.g. datepicker)
2440
2441        @method input2value() 
2442       **/
2443       input2value: function() { 
2444           return this.$input.val();
2445       }, 
2446
2447       /**
2448        Activates input. For text it sets focus.
2449
2450        @method activate() 
2451       **/
2452       activate: function() {
2453           if(this.$input.is(':visible')) {
2454               this.$input.focus();
2455           }
2456       },
2457
2458       /**
2459        Creates input.
2460
2461        @method clear() 
2462       **/        
2463       clear: function() {
2464           this.$input.val(null);
2465       },
2466
2467       /**
2468        method to escape html.
2469       **/
2470       escape: function(str) {
2471           return $('<div>').text(str).html();
2472       },
2473       
2474       /**
2475        attach handler to automatically submit form when value changed (useful when buttons not shown)
2476       **/
2477       autosubmit: function() {
2478        
2479       },
2480       
2481       /**
2482       Additional actions when destroying element 
2483       **/
2484       destroy: function() {
2485       },
2486
2487       // -------- helper functions --------
2488       setClass: function() {          
2489           if(this.options.inputclass) {
2490               this.$input.addClass(this.options.inputclass); 
2491           } 
2492       },
2493
2494       setAttr: function(attr) {
2495           if (this.options[attr] !== undefined && this.options[attr] !== null) {
2496               this.$input.attr(attr, this.options[attr]);
2497           } 
2498       },
2499       
2500       option: function(key, value) {
2501            this.options[key] = value;
2502       }
2503       
2504    };
2505        
2506    AbstractInput.defaults = {  
2507        /**
2508        HTML template of input. Normally you should not change it.
2509
2510        @property tpl 
2511        @type string
2512        @default ''
2513        **/   
2514        tpl: '',
2515        /**
2516        CSS class automatically applied to input
2517        
2518        @property inputclass 
2519        @type string
2520        @default null
2521        **/         
2522        inputclass: null,
2523        
2524        /**
2525        If `true` - html will be escaped in content of element via $.text() method.  
2526        If `false` - html will not be escaped, $.html() used.  
2527        When you use own `display` function, this option obviosly has no effect.
2528        
2529        @property escape 
2530        @type boolean
2531        @since 1.5.0
2532        @default true
2533        **/         
2534        escape: true,
2535                
2536        //scope for external methods (e.g. source defined as function)
2537        //for internal use only
2538        scope: null,
2539        
2540        //need to re-declare showbuttons here to get it's value from common config (passed only options existing in defaults)
2541        showbuttons: true 
2542    };
2543    
2544    $.extend($.fn.editabletypes, {abstractinput: AbstractInput});
2545        
2546}(window.jQuery));
2547
2548/**
2549List - abstract class for inputs that have source option loaded from js array or via ajax
2550
2551@class list
2552@extends abstractinput
2553**/
2554(function ($) {
2555    "use strict";
2556    
2557    var List = function (options) {
2558       
2559    };
2560
2561    $.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput);
2562
2563    $.extend(List.prototype, {
2564        render: function () {
2565            var deferred = $.Deferred();
2566
2567            this.error = null;
2568            this.onSourceReady(function () {
2569                this.renderList();
2570                deferred.resolve();
2571            }, function () {
2572                this.error = this.options.sourceError;
2573                deferred.resolve();
2574            });
2575
2576            return deferred.promise();
2577        },
2578
2579        html2value: function (html) {
2580            return null; //can't set value by text
2581        },
2582        
2583        value2html: function (value, element, display, response) {
2584            var deferred = $.Deferred(),
2585                success = function () {
2586                    if(typeof display === 'function') {
2587                        //custom display method
2588                        display.call(element, value, this.sourceData, response); 
2589                    } else {
2590                        this.value2htmlFinal(value, element);
2591                    }
2592                    deferred.resolve();
2593               };
2594            
2595            //for null value just call success without loading source
2596            if(value === null) {
2597               success.call(this);   
2598            } else {
2599               this.onSourceReady(success, function () { deferred.resolve(); });
2600            }
2601
2602            return deferred.promise();
2603        },  
2604
2605        // ------------- additional functions ------------
2606
2607        onSourceReady: function (success, error) {
2608            //run source if it function
2609            var source;
2610            if ($.isFunction(this.options.source)) {
2611                source = this.options.source.call(this.options.scope);
2612                this.sourceData = null;
2613                //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed
2614            } else {
2615                source = this.options.source;
2616            }            
2617            
2618            //if allready loaded just call success
2619            if(this.options.sourceCache && $.isArray(this.sourceData)) {
2620                success.call(this);
2621                return; 
2622            }
2623
2624            //try parse json in single quotes (for double quotes jquery does automatically)
2625            try {
2626                source = $.fn.editableutils.tryParseJson(source, false);
2627            } catch (e) {
2628                error.call(this);
2629                return;
2630            }
2631
2632            //loading from url
2633            if (typeof source === 'string') {
2634                //try to get sourceData from cache
2635                if(this.options.sourceCache) {
2636                    var cacheID = source,
2637                    cache;
2638
2639                    if (!$(document).data(cacheID)) {
2640                        $(document).data(cacheID, {});
2641                    }
2642                    cache = $(document).data(cacheID);
2643
2644                    //check for cached data
2645                    if (cache.loading === false && cache.sourceData) { //take source from cache
2646                        this.sourceData = cache.sourceData;
2647                        this.doPrepend();
2648                        success.call(this);
2649                        return;
2650                    } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
2651                        cache.callbacks.push($.proxy(function () {
2652                            this.sourceData = cache.sourceData;
2653                            this.doPrepend();
2654                            success.call(this);
2655                        }, this));
2656
2657                        //also collecting error callbacks
2658                        cache.err_callbacks.push($.proxy(error, this));
2659                        return;
2660                    } else { //no cache yet, activate it
2661                        cache.loading = true;
2662                        cache.callbacks = [];
2663                        cache.err_callbacks = [];
2664                    }
2665                }
2666                
2667                //ajaxOptions for source. Can be overwritten bt options.sourceOptions
2668                var ajaxOptions = $.extend({
2669                    url: source,
2670                    type: 'get',
2671                    cache: false,
2672                    dataType: 'json',
2673                    success: $.proxy(function (data) {
2674                        if(cache) {
2675                            cache.loading = false;
2676                        }
2677                        this.sourceData = this.makeArray(data);
2678                        if($.isArray(this.sourceData)) {
2679                            if(cache) {
2680                                //store result in cache
2681                                cache.sourceData = this.sourceData;
2682                                //run success callbacks for other fields waiting for this source
2683                                $.each(cache.callbacks, function () { this.call(); }); 
2684                            }
2685                            this.doPrepend();
2686                            success.call(this);
2687                        } else {
2688                            error.call(this);
2689                            if(cache) {
2690                                //run error callbacks for other fields waiting for this source
2691                                $.each(cache.err_callbacks, function () { this.call(); }); 
2692                            }
2693                        }
2694                    }, this),
2695                    error: $.proxy(function () {
2696                        error.call(this);
2697                        if(cache) {
2698                             cache.loading = false;
2699                             //run error callbacks for other fields
2700                             $.each(cache.err_callbacks, function () { this.call(); }); 
2701                        }
2702                    }, this)
2703                }, this.options.sourceOptions);
2704                
2705                //loading sourceData from server
2706                $.ajax(ajaxOptions);
2707                
2708            } else { //options as json/array
2709                this.sourceData = this.makeArray(source);
2710                    
2711                if($.isArray(this.sourceData)) {
2712                    this.doPrepend();
2713                    success.call(this);   
2714                } else {
2715                    error.call(this);
2716                }
2717            }
2718        },
2719
2720        doPrepend: function () {
2721            if(this.options.prepend === null || this.options.prepend === undefined) {
2722                return;  
2723            }
2724            
2725            if(!$.isArray(this.prependData)) {
2726                //run prepend if it is function (once)
2727                if ($.isFunction(this.options.prepend)) {
2728                    this.options.prepend = this.options.prepend.call(this.options.scope);
2729                }
2730              
2731                //try parse json in single quotes
2732                this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
2733                
2734                //convert prepend from string to object
2735                if (typeof this.options.prepend === 'string') {
2736                    this.options.prepend = {'': this.options.prepend};
2737                }
2738                
2739                this.prependData = this.makeArray(this.options.prepend);
2740            }
2741
2742            if($.isArray(this.prependData) && $.isArray(this.sourceData)) {
2743                this.sourceData = this.prependData.concat(this.sourceData);
2744            }
2745        },
2746
2747        /*
2748         renders input list
2749        */
2750        renderList: function() {
2751            // this method should be overwritten in child class
2752        },
2753       
2754         /*
2755         set element's html by value
2756        */
2757        value2htmlFinal: function(value, element) {
2758            // this method should be overwritten in child class
2759        },        
2760
2761        /**
2762        * convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}]
2763        */
2764        makeArray: function(data) {
2765            var count, obj, result = [], item, iterateItem;
2766            if(!data || typeof data === 'string') {
2767                return null; 
2768            }
2769
2770            if($.isArray(data)) { //array
2771                /* 
2772                   function to iterate inside item of array if item is object.
2773                   Caclulates count of keys in item and store in obj. 
2774                */
2775                iterateItem = function (k, v) {
2776                    obj = {value: k, text: v};
2777                    if(count++ >= 2) {
2778                        return false;// exit from `each` if item has more than one key.
2779                    }
2780                };
2781            
2782                for(var i = 0; i < data.length; i++) {
2783                    item = data[i]; 
2784                    if(typeof item === 'object') {
2785                        count = 0; //count of keys inside item
2786                        $.each(item, iterateItem);
2787                        //case: [{val1: 'text1'}, {val2: 'text2} ...]
2788                        if(count === 1) { 
2789                            result.push(obj); 
2790                            //case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...]
2791                        } else if(count > 1) {
2792                            //removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text')
2793                            if(item.children) {
2794                                item.children = this.makeArray(item.children);   
2795                            }
2796                            result.push(item);
2797                        }
2798                    } else {
2799                        //case: ['text1', 'text2' ...]
2800                        result.push({value: item, text: item}); 
2801                    }
2802                }
2803            } else {  //case: {val1: 'text1', val2: 'text2, ...}
2804                $.each(data, function (k, v) {
2805                    result.push({value: k, text: v});
2806                });  
2807            }
2808            return result;
2809        },
2810        
2811        option: function(key, value) {
2812            this.options[key] = value;
2813            if(key === 'source') {
2814                this.sourceData = null;
2815            }
2816            if(key === 'prepend') {
2817                this.prependData = null;
2818            }            
2819        }        
2820
2821    });      
2822
2823    List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
2824        /**
2825        Source data for list.  
2826        If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`  
2827        For compability, object format is also supported: `{"1": "text1", "2"
2827: "text2" ...}` but it does not guarantee elements order.
2828        
2829        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.
2830          
2831        If **function**, it should return data in format above (since 1.4.0).
2832        
2833        Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only):  
2834        @example
2835        [
2836          {text: "group1", children: [
2837            {value: 1, text: "text1"}, 
2838            {value: 2, text: "text2"}
2839          ]}, 
2840          ...
2841        ] 
2842
2843		
2844        @property source 
2845        @type string | array | object | function
2846        @default null
2847        **/         
2848        source: null, 
2849        /**
2850        Data automatically prepended to the beginning of dropdown list.
2851        
2852        @property prepend 
2853        @type string | array | object | function
2854        @default false
2855        **/         
2856        prepend: false,
2857        /**
2858        Error message when list cannot be loaded (e.g. ajax error)
2859        
2860        @property sourceError 
2861        @type string
2862        @default Error when loading list
2863        **/          
2864        sourceError: 'Error when loading list',
2865        /**
2866        if `true` and source is **string url** - results will be cached for fields with the same source.    
2867        Usefull for editable column in grid to prevent extra requests.
2868        
2869        @property sourceCache 
2870        @type boolean
2871        @default true
2872        @since 1.2.0
2873        **/        
2874        sourceCache: true,
2875        /**
2876        Additional ajax options to be used in $.ajax() when loading list from server.
2877        Useful to send extra parameters or change request method.
2878        
2879        @property sourceOptions 
2880        @type object|function
2881        @default null
2882        @since 1.5.0
2883        @example
2884        sourceOptions: {
2885            data: {param: 123},
2886            type: 'post'
2887        }
2888        
2889        **/        
2890        sourceOptions: null
2891    });
2892
2893    $.fn.editabletypes.list = List;      
2894
2895}(window.jQuery));
2896
2897/**
2898Text input
2899
2900@class text
2901@extends abstractinput
2902@final
2903@example
2904<a href="#" id="username" data-type="text" data-pk="1">awesome</a>
2905<script>
2906$(function(){
2907    $('#username').editable({
2908        url: '/post',
2909        title: 'Enter username'
2910    });
2911});
2912</script>
2913**/
2914(function ($) {
2915    "use strict";
2916
2917    var Text = function (options) {
2918        this.init('text', options, Text.defaults);
2919    };
2920
2921    $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
2922
2923    $.extend(Text.prototype, {
2924        render: function() {
2925           this.renderClear();
2926           this.setClass();
2927           this.setAttr('placeholder');
2928        },
2929
2930        activate: function() {
2931            if(this.$input.is(':visible')) {
2932                this.$input.focus();
2933//                if (this.$input.is('input,textarea') && !this.$input.is('[type="checkbox"],[type="range"],[type="number"],[type="email"]')) {
2934                if (this.$input.is('input,textarea') && !this.$input.is('[type="checkbox"],[type="range"]')) {
2935                    $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
2936                }
2937
2938                if(this.toggleClear) {
2939                    this.toggleClear();
2940                }
2941            }
2942        },
2943
2944        //render clear button
2945        renderClear:  function() {
2946           if (this.options.clear) {
2947               this.$clear = $('<span class="editable-clear-x"></span>');
2948               this.$input.after(this.$clear)
2949                          .css('padding-right', 24)
2950                          .keyup($.proxy(function(e) {
2951                              //arrows, enter, tab, etc
2952                              if(~$.inArray(e.keyCode, [40,38,9,13,27])) {
2953                                return;
2954                              }
2955
2956                              clearTimeout(this.t);
2957                              var that = this;
2958                              this.t = setTimeout(function() {
2959                                that.toggleClear(e);
2960                              }, 100);
2961
2962                          }, this))
2963                          .parent().css('position', 'relative');
2964
2965               this.$clear.click($.proxy(this.clear, this));
2966           }
2967        },
2968
2969        postrender: function() {
2970            /*
2971            //now `clear` is positioned via css
2972            if(this.$clear) {
2973                //can position clear button only here, when form is shown and height can be calculated
2974//                var h = this.$input.outerHeight(true) || 20,
2975                var h = this.$clear.parent().height(),
2976                    delta = (h - this.$clear.height()) / 2;
2977
2978                //this.$clear.css({bottom: delta, right: delta});
2979            }
2980            */
2981        },
2982
2983        //show / hide clear button
2984        toggleClear: function(e) {
2985            if(!this.$clear) {
2986                return;
2987            }
2988
2989            var len = this.$input.val().length,
2990                visible = this.$clear.is(':visible');
2991
2992            if(len && !visible) {
2993                this.$clear.show();
2994            }
2995
2996            if(!len && visible) {
2997                this.$clear.hide();
2998            }
2999        },
3000
3001        clear: function() {
3002           this.$clear.hide();
3003           this.$input.val('').focus();
3004        }
3005    });
3006
3007    Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
3008        /**
3009        @property tpl
3010        @default <input type="text">
3011        **/
3012        tpl: '<input type="text">',
3013        /**
3014        Placeholder attribute of input. Shown when input is empty.
3015
3016        @property placeholder
3017        @type string
3018        @default null
3019        **/
3020        placeholder: null,
3021
3022        /**
3023        Whether to show `clear` button
3024
3025        @property clear
3026        @type boolean
3027        @default true
3028        **/
3029        clear: true
3030    });
3031
3032    $.fn.editabletypes.text = Text;
3033
3034}(window.jQuery));
3035
3036/**
3037Textarea input
3038
3039@class textarea
3040@extends abstractinput
3041@final
3042@example
3043<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a>
3044<script>
3045$(function(){
3046    $('#comments').editable({
3047        url: '/post',
3048        title: 'Enter comments',
3049        rows: 10
3050    });
3051});
3052</script>
3053**/
3054(function ($) {
3055    "use strict";
3056    
3057    var Textarea = function (options) {
3058        this.init('textarea', options, Textarea.defaults);
3059    };
3060
3061    $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
3062
3063    $.extend(Textarea.prototype, {
3064        render: function () {
3065            this.setClass();
3066            this.setAttr('placeholder');
3067            this.setAttr('rows');                        
3068            
3069            //ctrl + enter
3070            this.$input.keydown(function (e) {
3071                if (e.ctrlKey && e.which === 13) {
3072                    $(this).closest('form').submit();
3073                }
3074            });
3075        },
3076        
3077       //using `white-space: pre-wrap` solves \n  <--> BR conversion very elegant!
3078       /* 
3079       value2html: function(value, element) {
3080            var html = '', lines;
3081            if(value) {
3082                lines = value.split("\n");
3083                for (var i = 0; i < lines.length; i++) {
3084                    lines[i] = $('<div>').text(lines[i]).html();
3085                }
3086                html = lines.join('<br>');
3087            }
3088            $(element).html(html);
3089        },
3090       
3091        html2value: function(html) {
3092            if(!html) {
3093                return '';
3094            }
3095
3096            var regex = new RegExp(String.fromCharCode(10), 'g');
3097            var lines = html.split(/<br\s*\/?>/i);
3098            for (var i = 0; i < lines.length; i++) {
3099                var text = $('<div>').html(lines[i]).text();
3100
3101                // Remove newline characters (\n) to avoid them being converted by value2html() method
3102                // thus adding extra <br> tags
3103                text = text.replace(regex, '');
3104
3105                lines[i] = text;
3106            }
3107            return lines.join("\n");
3108        },
3109         */
3110        activate: function() {
3111            $.fn.editabletypes.text.prototype.activate.call(this);
3112        }
3113    });
3114
3115    Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
3116        /**
3117        @property tpl
3118        @default <textarea></textarea>
3119        **/
3120        tpl:'<textarea></textarea>',
3121        /**
3122        @property inputclass
3123        @default input-large
3124        **/
3125        inputclass: 'input-large',
3126        /**
3127        Placeholder attribute of input. Shown when input is empty.
3128
3129        @property placeholder
3130        @type string
3131        @default null
3132        **/
3133        placeholder: null,
3134        /**
3135        Number of rows in textarea
3136
3137        @property rows
3138        @type integer
3139        @default 7
3140        **/        
3141        rows: 7        
3142    });
3143
3144    $.fn.editabletypes.textarea = Textarea;
3145
3146}(window.jQuery));
3147
3148/**
3149Select (dropdown)
3150
3151@class select
3152@extends list
3153@final
3154@example
3155<a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-title="Select status"></a>
3156<script>
3157$(function(){
3158    $('#status').editable({
3159        value: 2,    
3160        source: [
3161              {value: 1, text: 'Active'},
3162              {value: 2, text: 'Blocked'},
3163              {value: 3, text: 'Deleted'}
3164           ]
3165    });
3166});
3167</script>
3168**/
3169(function ($) {
3170    "use strict";
3171    
3172    var Select = function (options) {
3173        this.init('select', options, Select.defaults);
3174    };
3175
3176    $.fn.editableutils.inherit(Select, $.fn.editabletypes.list);
3177
3178    $.extend(Select.prototype, {
3179        renderList: function() {
3180            this.$input.empty();
3181            var escape = this.options.escape;
3182
3183            var fillItems = function($el, data) {
3184                var attr;
3185                if($.isArray(data)) {
3186                    for(var i=0; i<data.length; i++) {
3187                        attr = {};
3188                        if(data[i].children) {
3189                            attr.label = data[i].text;
3190                            $el.append(fillItems($('<optgroup>', attr), data[i].children)); 
3191                        } else {
3192                            attr.value = data[i].value;
3193                            if(data[i].disabled) {
3194                                attr.disabled = true;
3195                            }
3196                            var $option = $('<option>', attr);
3197                            $option[escape ? 'text' : 'html'](data[i].text);
3198                            $el.append($option);
3199                        }
3200                    }
3201                }
3202                return $el;
3203            };        
3204
3205            fillItems(this.$input, this.sourceData);
3206            
3207            this.setClass();
3208            
3209            //enter submit
3210            this.$input.on('keydown.editable', function (e) {
3211                if (e.which === 13) {
3212                    $(this).closest('form').submit();
3213                }
3214            });            
3215        },
3216       
3217        value2htmlFinal: function(value, element) {
3218            var text = '', 
3219                items = $.fn.editableutils.itemsByValue(value, this.sourceData);
3220                
3221            if(items.length) {
3222                text = items[0].text;
3223            }
3224            
3225            //$(element).text(text);
3226            $.fn.editabletypes.abstractinput.prototype.value2html.call(this, text, element);
3227        },
3228        
3229        autosubmit: function() {
3230            this.$input.off('keydown.editable').on('change.editable', function(){
3231                $(this).closest('form').submit();
3232            });
3233        }
3234    });      
3235
3236    Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
3237        /**
3238        @property tpl 
3239        @default <select></select>
3240        **/         
3241        tpl:'<select></select>'
3242    });
3243
3244    $.fn.editabletypes.select = Select;      
3245
3246}(window.jQuery));
3247
3248/**
3249List of checkboxes. 
3250Internally value stored as javascript array of values.
3251
3252@class checklist
3253@extends list
3254@final
3255@example
3256<a href="#" id="options" data-type="checklist" data-pk="1" data-url="/post" data-title="Select options"></a>
3257<script>
3258$(function(){
3259    $('#options').editable({
3260        value: [2, 3],    
3261        source: [
3262              {value: 1, text: 'option1'},
3263              {value: 2, text: 'option2'},
3264              {value: 3, text: 'option3'}
3265           ]
3266    });
3267});
3268</script>
3269**/
3270(function ($) {
3271    "use strict";
3272    
3273    var Checklist = function (options) {
3274        this.init('checklist', options, Checklist.defaults);
3275    };
3276
3277    $.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);
3278
3279    $.extend(Checklist.prototype, {
3280        renderList: function() {
3281            var $label, $div;
3282            
3283            this.$tpl.empty();
3284            
3285            if(!$.isArray(this.sourceData)) {
3286                return;
3287            }
3288
3289            for(var i=0; i<this.sourceData.length; i++) {
3290                $label = $('<label>').append($('<input>', {
3291                                           type: 'checkbox',
3292                                           value: this.sourceData[i].value 
3293                                     }));
3294                var $option = $('<span>');
3295                $option[this.options.escape ? 'text' : 'html'](' '+this.sourceData[i].text);
3296                $label.append($option);
3297
3298                $('<div>').append($label).appendTo(this.$tpl);
3299            }
3300
3301            this.$input = this.$tpl.find('input[type="checkbox"]');
3302            this.setClass();
3303        },
3304       
3305       value2str: function(value) {
3306           return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : '';
3307       },  
3308       
3309       //parse separated string
3310        str2value: function(str) {
3311           var reg, value = null;
3312           if(typeof str === 'string' && str.length) {
3313               reg = new RegExp('\\s*'+$.trim(this.options.separator)+'\\s*');
3314               value = str.split(reg);
3315           } else if($.isArray(str)) {
3316               value = str; 
3317           } else {
3318               value = [str];
3319           }
3320           return value;
3321        },       
3322       
3323       //set checked on required checkboxes
3324       value2input: function(value) {
3325            this.$input.prop('checked', false);
3326            if($.isArray(value) && value.length) {
3327               this.$input.each(function(i, el) {
3328                   var $el = $(el);
3329                   // cannot use $.inArray as it performs strict comparison
3330                   $.each(value, function(j, val){
3331                       /*jslint eqeq: true*/
3332                       if($el.val() == val) {
3333                       /*jslint eqeq: false*/                           
3334                           $el.prop('checked', true);
3335                       }
3336                   });
3337               }); 
3338            }  
3339        },  
3340        
3341       input2value: function() { 
3342           var checked = [];
3343           this.$input.filter(':checked').each(function(i, el) {
3344               checked.push($(el).val());
3345           });
3346           return checked;
3347       },            
3348          
3349       //collect text of checked boxes
3350        value2htmlFinal: function(value, element) {
3351           var html = [],
3352               checked = $.fn.editableutils.itemsByValue(value, this.sourceData),
3353               escape = this.options.escape;
3354               
3355           if(checked.length) {
3356               $.each(checked, function(i, v) {
3357                   var text = escape ? $.fn.editableutils.escape(v.text) : v.text; 
3358                   html.push(text); 
3359               });
3360               $(element).html(html.join('<br>'));
3361           } else {
3362               $(element).empty(); 
3363           }
3364        },
3365        
3366       activate: function() {
3367           this.$input.first().focus();
3368       },
3369       
3370       autosubmit: function() {
3371           this.$input.on('keydown', function(e){
3372               if (e.which === 13) {
3373                   $(this).closest('form').submit();
3374               }
3375           });
3376       }
3377    });      
3378
3379    Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
3380        /**
3381        @property tpl 
3382        @default <div></div>
3383        **/         
3384        tpl:'<div class="editable-checklist"></div>',
3385        
3386        /**
3387        @property inputclass 
3388        @type string
3389        @default null
3390        **/         
3391        inputclass: null,        
3392        
3393        /**
3394        Separator of values when reading from `data-value` attribute
3395
3396        @property separator 
3397        @type string
3398        @default ','
3399        **/         
3400        separator: ','
3401    });
3402
3403    $.fn.editabletypes.checklist = Checklist;      
3404
3405}(window.jQuery));
3406
3407/**
3408HTML5 input types.
3409Following types are supported:
3410
3411* password
3412* email
3413* url
3414* tel
3415* number
3416* range
3417* time
3418
3419Learn more about html5 inputs:  
3420http://www.w3.org/wiki/HTML5_form_additions  
3421To check browser compatibility please see:  
3422https://developer.mozilla.org/en-US/docs/HTML/Element/Input
3423            
3424@class html5types 
3425@extends text
3426@final
3427@since 1.3.0
3428@example
3429<a href="#" id="email" data-type="email" data-pk="1">[email protected]</a>
3430<script>
3431$(function(){
3432    $('#email').editable({
3433        url: '/post',
3434        title: 'Enter email'
3435    });
3436});
3437</script>
3438**/
3439
3440/**
3441@property tpl 
3442@default depends on type
3443**/ 
3444
3445/*
3446Password
3447*/
3448(function ($) {
3449    "use strict";
3450    
3451    var Password = function (options) {
3452        this.init('password', options, Password.defaults);
3453    };
3454    $.fn.editableutils.inherit(Password, $.fn.editabletypes.text);
3455    $.extend(Password.prototype, {
3456       //do not display password, show '[hidden]' instead
3457       value2html: function(value, element) {
3458           if(value) {
3459               $(element).text('[hidden]');
3460           } else {
3461               $(element).empty(); 
3462           }
3463       },
3464       //as password not displayed, should not set value by html
3465       html2value: function(html) {
3466           return null;
3467       }       
3468    });    
3469    Password.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
3470        tpl: '<input type="password">'
3471    });
3472    $.fn.editabletypes.password = Password;
3473}(window.jQuery));
3474
3475
3476/*
3477Email
3478*/
3479(function ($) {
3480    "use strict";
3481    
3482    var Email = function (options) {
3483        this.init('email', options, Email.defaults);
3484    };
3485    $.fn.editableutils.inherit(Email, $.fn.editabletypes.text);
3486    Email.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
3487        tpl: '<input type="email">'
3488    });
3489    $.fn.editabletypes.email = Email;
3490}(window.jQuery));
3491
3492
3493/*
3494Url
3495*/
3496(function ($) {
3497    "use strict";
3498    
3499    var Url = function (options) {
3500        this.init('url', options, Url.defaults);
3501    };
3502    $.fn.editableutils.inherit(Url, $.fn.editabletypes.text);
3503    Url.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
3504        tpl: '<input type="url">'
3505    });
3506    $.fn.editabletypes.url = Url;
3507}(window.jQuery));
3508
3509
3510/*
3511Tel
3512*/
3513(function ($) {
3514    "use strict";
3515    
3516    var Tel = function (options) {
3517        this.init('tel', options, Tel.defaults);
3518    };
3519    $.fn.editableutils.inherit(Tel, $.fn.editabletypes.text);
3520    Tel.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
3521        tpl: '<input type="tel">'
3522    });
3523    $.fn.editabletypes.tel = Tel;
3524}(window.jQuery));
3525
3526
3527/*
3528Number
3529*/
3530(function ($) {
3531    "use strict";
3532    
3533    var NumberInput = function (options) {
3534        this.init('number', options, NumberInput.defaults);
3535    };
3536    $.fn.editableutils.inherit(NumberInput, $.fn.editabletypes.text);
3537    $.extend(NumberInput.prototype, {
3538         render: function () {
3539            NumberInput.superclass.render.call(this);
3540            this.setAttr('min');
3541            this.setAttr('max');
3542            this.setAttr('step');
3543        },
3544        postrender: function() {
3545            if(this.$clear) {
3546                //increase right ffset  for up/down arrows
3547                this.$clear.css({right: 24});
3548                /*
3549                //can position clear button only here, when form is shown and height can be calculated
3550                var h = this.$input.outerHeight(true) || 20,
3551                    delta = (h - this.$clear.height()) / 2;
3552                
3553                //add 12px to offset right for up/down arrows    
3554                this.$clear.css({top: delta, right: delta + 16});
3555                */
3556            } 
3557        }        
3558    });     
3559    NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
3560        tpl: '<input type="number">',
3561        inputclass: 'input-mini',
3562        min: null,
3563        max: null,
3564        step: null
3565    });
3566    $.fn.editabletypes.number = NumberInput;
3567}(window.jQuery));
3568
3569
3570/*
3571Range (inherit from number)
3572*/
3573(function ($) {
3574    "use strict";
3575    
3576    var Range = function (options) {
3577        this.init('range', options, Range.defaults);
3578    };
3579    $.fn.editableutils.inherit(Range, $.fn.editabletypes.number);
3580    $.extend(Range.prototype, {
3581        render: function () {
3582            this.$input = this.$tpl.filter('input');
3583            
3584            this.setClass();
3585            this.setAttr('min');
3586            this.setAttr('max');
3587            this.setAttr('step');           
3588            
3589            this.$input.on('input', function(){
3590                $(this).siblings('output').text($(this).val()); 
3591            });  
3592        },
3593        activate: function() {
3594            this.$input.focus();
3595        }         
3596    });
3597    Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {
3598        tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>',
3599        inputclass: 'input-medium'
3600    });
3601    $.fn.editabletypes.range = Range;
3602}(window.jQuery));
3603
3604/*
3605Time
3606*/
3607(function ($) {
3608    "use strict";
3609
3610    var Time = function (options) {
3611        this.init('time', options, Time.defaults);
3612    };
3613    //inherit from abstract, as inheritance from text gives selection error.
3614    $.fn.editableutils.inherit(Time, $.fn.editabletypes.abstractinput);
3615    $.extend(Time.prototype, {
3616        render: function() {
3617           this.setClass();
3618        }        
3619    });
3620    Time.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
3621        tpl: '<input type="time">'
3622    });
3623    $.fn.editabletypes.time = Time;
3624}(window.jQuery));
3625
3626/**
3627Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.  
3628Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.  
3629 
3630You should manually download and include select2 distributive:  
3631
3632    <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>  
3633    <script src="select2/select2.js"></script>  
3634    
3635To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css): 
3636
3637    <link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>    
3638    
3639**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.    
3640You need initially put both `data-value` and element's text youself:    
3641
3642    <a href="#" data-type="select2" data-value="1">Text1</a>
3643    
3644    
3645@class select2
3646@extends abstractinput
3647@since 1.4.1
3648@final
3649@example
3650<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>
3651<script>
3652$(function(){
3653    //local source
3654    $('#country').editable({
3655        source: [
3656              {id: 'gb', text: 'Great Britain'},
3657              {id: 'us', text: 'United States'},
3658              {id: 'ru', text: 'Russia'}
3659           ],
3660        select2: {
3661           multiple: true
3662        }
3663    });
3664    //remote source (simple)
3665    $('#country').editable({
3666        source: '/getCountries',
3667        select2: {
3668            placeholder: 'Select Country',
3669            minimumInputLength: 1
3670        }
3671    });
3672    //remote source (advanced)
3673    $('#country').editable({
3674        select2: {
3675            placeholder: 'Select Country',
3676            allowClear: true,
3677            minimumInputLength: 3,
3678            id: function (item) {
3679                return item.CountryId;
3680            },
3681            ajax: {
3682                url: '/getCountries',
3683                dataType: 'json',
3684                data: function (term, page) {
3685                    return { query: term };
3686                },
3687                results: function (data, page) {
3688                    return { results: data };
3689                }
3690            },
3691            formatResult: function (item) {
3692                return item.CountryName;
3693            },
3694            formatSelection: function (item) {
3695                return item.CountryName;
3696            },
3697            initSelection: function (element, callback) {
3698                return $.get('/getCountryById', { query: element.val() }, function (data) {
3699                    callback(data);
3700                });
3701            } 
3702        }  
3703    });
3704});
3705</script>
3706**/
3707(function ($) {
3708    "use strict";
3709    
3710    var Constructor = function (options) {
3711        this.init('select2', options, Constructor.defaults);
3712
3713        options.select2 = options.select2 || {};
3714
3715        this.sourceData = null;
3716
3717        //placeholder
3718        if(options.placeholder) {
3719            options.select2.placeholder = options.placeholder;
3720        }
3721       
3722        //if not `tags` mode, use source
3723        if(!options.select2.tags && options.source) {
3724            var source = options.source;
3725            //if source is function, call it (once!)
3726            if ($.isFunction(options.source)) {
3727                source = options.source.call(options.scope);
3728            }               
3729
3730            if (typeof source === 'string') {
3731                options.select2.ajax = options.select2.ajax || {};
3732                //some default ajax params
3733                if(!options.select2.ajax.data) {
3734                    options.select2.ajax.data = function(term) {return { query:term };};
3735                }
3736                if(!options.select2.ajax.results) {
3737                    options.select2.ajax.results = function(data) { return {results:data };};
3738                }
3739                options.select2.ajax.url = source;
3740            } else {
3741                //check format and convert x-editable format to select2 format (if needed)
3742                this.sourceData = this.convertSource(source);
3743                options.select2.data = this.sourceData;
3744            }
3745        } 
3746
3747        //overriding objects in config (as by default jQuery extend() is not recursive)
3748        this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
3749
3750        //detect whether it is multi-valued
3751        this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
3752        this.isRemote = ('ajax' in this.options.select2);
3753
3754        //store function returning ID of item
3755        //should be here as used inautotext for local source
3756        this.idFunc = this.options.select2.id;
3757        if (typeof(this.idFunc) !== "function") {
3758            var idKey = this.idFunc || 'id';
3759            this.idFunc = function (e) { return e[idKey]; };
3760        }
3761
3762        //store function that renders text in select2
3763        this.formatSelection = this.options.select2.formatSelection;
3764        if (typeof(this.formatSelection) !== "function") {
3765            this.formatSelection = function (e) { return e.text; };
3766        }
3767    };
3768
3769    $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
3770
3771    $.extend(Constructor.prototype, {
3772        render: function() {
3773            this.setClass();
3774
3775            //can not apply select2 here as it calls initSelection 
3776            //over input that does not have correct value yet.
3777            //apply select2 only in value2input
3778            //this.$input.select2(this.options.select2);
3779
3780            //when data is loaded via ajax, we need to know when it's done to populate listData
3781            if(this.isRemote) {
3782                //listen to loaded event to populate data
3783                this.$input.on('select2-loaded', $.proxy(function(e) {
3784                    this.sourceData = e.items.results;
3785                }, this));
3786            }
3787
3788            //trigger resize of editableform to re-position container in multi-valued mode
3789            if(this.isMultiple) {
3790               this.$input.on('change', function() {
3791                   $(this).closest('form').parent().triggerHandler('resize');
3792               });
3793            }
3794       },
3795
3796       value2html: function(value, element) {
3797           var text = '', data,
3798               that = this;
3799
3800           if(this.options.select2.tags) { //in tags mode just assign value
3801              data = value; 
3802              //data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
3803           } else if(this.sourceData) {
3804              data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc); 
3805           } else {
3806              //can not get list of possible values 
3807              //(e.g. autotext for select2 with ajax source)
3808           }
3809
3810           //data may be array (when multiple values allowed)
3811           if($.isArray(data)) {
3812               //collect selected data and show with separator
3813               text = [];
3814               $.each(data, function(k, v){
3815                   text.push(v && typeof v === 'object' ? that.formatSelection(v) : v);
3816               });
3817           } else if(data) {
3818               text = that.formatSelection(data);
3819           }
3820
3821           text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
3822
3823           //$(element).text(text);
3824           Constructor.superclass.value2html.call(this, text, element); 
3825       },
3826
3827       html2value: function(html) {
3828           return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
3829       },
3830
3831       value2input: function(value) {
3832           // if value array => join it anyway
3833           if($.isArray(value)) {
3834              value = value.join(this.getSeparator());
3835           }
3836
3837           //for remote source just set value, text is updated by initSelection
3838           if(!this.$input.data('select2')) {
3839               this.$input.val(value);
3840               this.$input.select2(this.options.select2);
3841           } else {
3842               //second argument needed to separate initial change from user's click (for autosubmit)   
3843               this.$input.val(value).trigger('change', true); 
3844
3845               //Uncaught Error: cannot call val() if initSelection() is not defined
3846               //this.$input.select2('val', value);
3847           }
3848
3849           // if defined remote source AND no multiple mode AND no user's initSelection provided --> 
3850           // we should somehow get text for provided id.
3851           // The solution is to use element's text as text for that id (exclude empty)
3852           if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) {
3853               // customId and customText are methods to extract `id` and `text` from data object
3854               // we can use this workaround only if user did not define these methods
3855               // otherwise we cant construct data object
3856               var customId = this.options.select2.id,
3857                   customText = this.options.select2.formatSelection;
3858
3859               if(!customId && !customText) {
3860                   var $el = $(this.options.scope);
3861                   if (!$el.data('editable').isEmpty) {
3862                       var data = {id: value, text: $el.text()};
3863                       this.$input.select2('data', data); 
3864                   }
3865               }
3866           }
3867       },
3868       
3869       input2value: function() { 
3870           return this.$input.select2('val');
3871       },
3872
3873       str2value: function(str, separator) {
3874            if(typeof str !== 'string' || !this.isMultiple) {
3875                return str;
3876            }
3877
3878            separator = separator || this.getSeparator();
3879
3880            var val, i, l;
3881
3882            if (str === null || str.length < 1) {
3883                return null;
3884            }
3885            val = str.split(separator);
3886            for (i = 0, l = val.length; i < l; i = i + 1) {
3887                val[i] = $.trim(val[i]);
3888            }
3889
3890            return val;
3891       },
3892
3893        autosubmit: function() {
3894            this.$input.on('change', function(e, isInitial){
3895                if(!isInitial) {
3896                  $(this).closest('form').submit();
3897                }
3898            });
3899        },
3900
3901        getSeparator: function() {
3902            return this.options.select2.separator || $.fn.select2.defaults.separator;
3903        },
3904
3905        /*
3906        Converts source from x-editable format: {value: 1, text: "1"} to
3907        select2 format: {id: 1, text: "1"}
3908        */
3909        convertSource: function(source) {
3910            if($.isArray(source) && source.length && source[0].value !== undefined) {
3911                for(var i = 0; i<source.length; i++) {
3912                    if(source[i].value !== undefined) {
3913                        source[i].id = source[i].value;
3914                        delete source[i].value;
3915                    }
3916                }
3917            }
3918            return source;
3919        },
3920        
3921        activate: function() {
3922            this.$input.select2('open');
3923        },
3924        
3925        destroy: function() {
3926            if(this.$input) {
3927                if(this.$input.data('select2')) {
3928                    this.$input.select2('destroy');
3929                }
3930            }
3931        }
3932        
3933    });
3934
3935    Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
3936        /**
3937        @property tpl 
3938        @default <input type="hidden">
3939        **/
3940        tpl:'<input type="hidden">',
3941        /**
3942        Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
3943
3944        @property select2 
3945        @type object
3946        @default null
3947        **/
3948        select2: null,
3949        /**
3950        Placeholder attribute of select
3951
3952        @property placeholder 
3953        @type string
3954        @default null
3955        **/
3956        placeholder: null,
3957        /**
3958        Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.
3959        Please note, that format is different from simple `select` input: use 'id' instead of 'value'.
3960        E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.
3961
3962        @property source 
3963        @type array|string|function
3964        @default null        
3965        **/
3966        source: null,
3967        /**
3968        Separator used to display tags.
3969
3970        @property viewseparator 
3971        @type string
3972        @default ', '        
3973        **/
3974        viewseparator: ', '
3975    });
3976
3977    $.fn.editabletypes.select2 = Constructor;
3978
3979}(window.jQuery));
3980
3981/**
3982* Combodate - 1.1.0
3983* Dropdown date and time picker.
3984* Converts text input into dropdowns to pick day, month, year, hour, minute and second.
3985* Uses momentjs as datetime library http://momentjs.com.
3986* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang
3987*
3988* Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight
3989* In combodate:
3990* 12:00 pm --> 12:00 (24-h format, midday)
3991* 12:00 am --> 00:00 (24-h format, midnight, start of day)
3992*
3993* Differs from momentjs parse rules:
3994* 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change)
3995* 00:00 am, 12:00 am --> 00:00 (24-h format, day not change)
3996*
3997*
3998* Author: Vitaliy Potapov
3999* Project page: http://github.com/vitalets/combodate
4000* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
4001**/
4002(function ($) {
4003
4004    var Combodate = function (element, options) {
4005        this.$element = $(element);
4006        if(!this.$element.is('input')) {
4007            $.error('Combodate should be applied to INPUT element');
4008            return;
4009        }
4010        this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data());
4011        this.init();
4012     };
4013
4014    Combodate.prototype = {
4015        constructor: Combodate,
4016        init: function () {
4017            this.map = {
4018                //key   regexp    moment.method
4019                day:    ['D',    'date'],
4020                month:  ['M',    'month'],
4021                year:   ['Y',    'year'],
4022                hour:   ['[Hh]', 'hours'],
4023                minute: ['m',    'minutes'],
4024                second: ['s',    'seconds'],
4025                ampm:   ['[Aa]', '']
4026            };
4027
4028            this.$widget = $('<span class="combodate"></span>').html(this.getTemplate());
4029
4030            this.initCombos();
4031
4032            // internal momentjs instance
4033            this.datetime = null;
4034
4035            //update original input on change
4036            this.$widget.on('change', 'select', $.proxy(function(e) {
4037                this.$element.val(this.getValue()).change();
4038                // update days count if month or year changes
4039                if (this.options.smartDays) {
4040                    if ($(e.target).is('.month') || $(e.target).is('.year')) {
4041                        this.fillCombo('day');
4042                    }
4043                }
4044            }, this));
4045
4046            this.$widget.find('select').css('width', 'auto');
4047
4048            // hide original input and insert widget
4049            this.$element.hide().after(this.$widget);
4050
4051            // set initial value
4052            this.setValue(this.$element.val() || this.options.value);
4053        },
4054
4055        /*
4056         Replace tokens in template with <select> elements
4057        */
4058        getTemplate: function() {
4059            var tpl = this.options.template;
4060            var inputDisabled = this.$element.prop('disabled');
4061            var customClass = this.options.customClass;
4062
4063            //first pass
4064            $.each(this.map, function(k, v) {
4065                v = v[0];
4066                var r = new RegExp(v+'+'),
4067                    token = v.length > 1 ? v.substring(1, 2) : v;
4068
4069                tpl = tpl.replace(r, '{'+token+'}');
4070            });
4071
4072            //replace spaces with &nbsp;
4073            tpl = tpl.replace(/ /g, '&nbsp;');
4074
4075            //second pass
4076            $.each(this.map, function(k, v) {
4077                v = v[0];
4078                var token = v.length > 1 ? v.substring(1, 2) : v;
4079
4080                tpl = tpl.replace('{'+token+'}', '<select class="'+k+' '+customClass +'"'+
4081                     (inputDisabled ? ' disabled="disabled"' : '')+'></select>');
4082            });
4083
4084            return tpl;
4085        },
4086
4087        /*
4088         Initialize combos that presents in template
4089        */
4090        initCombos: function() {
4091            for (var k in this.map) {
4092                var $c = this.$widget.find('.'+k);
4093                // set properties like this.$day, this.$month etc.
4094                this['$'+k] = $c.length ? $c : null;
4095                // fill with items
4096                this.fillCombo(k);
4097            }
4098        },
4099
4100        /*
4101         Fill combo with items
4102        */
4103        fillCombo: function(k) {
4104            var $combo = this['$'+k];
4105            if (!$combo) {
4106                return;
4107            }
4108
4109            // define method name to fill items, e.g `fillDays`
4110            var f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1);
4111            var items = this[f]();
4112            var value = $combo.val();
4113
4114            $combo.empty();
4115            for(var i=0; i<items.length; i++) {
4116                $combo.append('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');
4117            }
4118
4119            $combo.val(value);
4120        },
4121
4122        /*
4123         Initialize items of combos. Handles `firstItem` option
4124        */
4125        fillCommon: function(key) {
4126            var values = [],
4127                relTime;
4128
4129            if(this.options.firstItem === 'name') {
4130                //need both to support moment ver < 2 and  >= 2
4131                if (moment.localeData) {
4132                    relTime = moment.localeData()._relativeTime;
4133                } else {
4134                    relTime = moment.relativeTime || moment.langData()._relativeTime;
4135                }
4136                var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];
4137                //take last entry (see momentjs lang files structure)
4138                header = header.split(' ').reverse()[0];
4139                values.push(['', header]);
4140            } else if(this.options.firstItem === 'empty') {
4141                values.push(['', '']);
4142            }
4143            return values;
4144        },
4145
4146
4147        /*
4148        fill day
4149        */
4150        fillDay: function() {
4151            var items = this.fillCommon('d'), name, i,
4152                twoDigit = this.options.template.indexOf('DD') !== -1,
4153                daysCount = 31;
4154
4155            // detect days count (depends on month and year)
4156            // originally https://github.com/vitalets/combodate/pull/7
4157            if (this.options.smartDays && this.$month && this.$year) {
4158                var month = parseInt(this.$month.val(), 10);
4159                var year = parseInt(this.$year.val(), 10);
4160
4161                if (!isNaN(month) && !isNaN(year)) {
4162                    daysCount = moment([year, month]).daysInMonth();
4163                }
4164            }
4165
4166            for (i = 1; i <= daysCount; i++) {
4167                name = twoDigit ? this.leadZero(i) : i;
4168                items.push([i, name]);
4169            }
4170            return items;
4171        },
4172
4173        /*
4174        fill month
4175        */
4176        fillMonth: function() {
4177            var items = this.fillCommon('M'), name, i,
4178                longNamesNum = this.options.template.indexOf('MMMMMM') !== -1,
4179                shortNamesNum = this.options.template.indexOf('MMMMM') !== -1,
4180                longNames = this.options.template.indexOf('MMMM') !== -1,
4181                shortNames = this.options.template.indexOf('MMM') !== -1,
4182                twoDigit = this.options.template.indexOf('MM') !== -1;
4183
4184            for(i=0; i<=11; i++) {
4185                if (longNamesNum) {
4186                    name = moment().date(1).month(i).format('MM - MMMM');
4187                } else if (shortNamesNum) {
4188                    name = moment().date(1).month(i).format('MM - MMM');
4189                } else if(longNames) {
4190                    //see https://github.com/timrwood/momentjs.com/pull/36
4191                    name = moment().date(1).month(i).format('MMMM');
4192                } else if(shortNames) {
4193                    name = moment().date(1).month(i).format('MMM');
4194                } else if(twoDigit) {
4195                    name = this.leadZero(i+1);
4196                } else {
4197                    name = i+1;
4198                }
4199                items.push([i, name]);
4200            }
4201            return items;
4202        },
4203
4204        /*
4205        fill year
4206        */
4207        fillYear: function() {
4208            var items = [], name, i,
4209                longNames = this.options.template.indexOf('YYYY') !== -1;
4210
4211            for(i=this.options.maxYear; i>=this.options.minYear; i--) {
4212                name = longNames ? i : (i+'').substring(2);
4213                items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);
4214            }
4215
4216            items = this.fillCommon('y').concat(items);
4217
4218            return items;
4219        },
4220
4221        /*
4222        fill hour
4223        */
4224        fillHour: function() {
4225            var items = this.fillCommon('h'), name, i,
4226                h12 = this.options.template.indexOf('h') !== -1,
4227                h24 = this.options.template.indexOf('H') !== -1,
4228                twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
4229                min = h12 ? 1 : 0,
4230                max = h12 ? 12 : 23;
4231
4232            for(i=min; i<=max; i++) {
4233                name = twoDigit ? this.leadZero(i) : i;
4234                items.push([i, name]);
4235            }
4236            return items;
4237        },
4238
4239        /*
4240        fill minute
4241        */
4242        fillMinute: function() {
4243            var items = this.fillCommon('m'), name, i,
4244                twoDigit = this.options.template.indexOf('mm') !== -1;
4245
4246            for(i=0; i<=59; i+= this.options.minuteStep) {
4247                name = twoDigit ? this.leadZero(i) : i;
4248                items.push([i, name]);
4249            }
4250            return items;
4251        },
4252
4253        /*
4254        fill second
4255        */
4256        fillSecond: function() {
4257            var items = this.fillCommon('s'), name, i,
4258                twoDigit = this.options.template.indexOf('ss') !== -1;
4259
4260            for(i=0; i<=59; i+= this.options.secondStep) {
4261                name = twoDigit ? this.leadZero(i) : i;
4262                items.push([i, name]);
4263            }
4264            return items;
4265        },
4266
4267        /*
4268        fill ampm
4269        */
4270        fillAmpm: function() {
4271            var ampmL = this.options.template.indexOf('a') !== -1,
4272                ampmU = this.options.template.indexOf('A') !== -1,
4273                items = [
4274                    ['am', ampmL ? 'am' : 'AM'],
4275                    ['pm', ampmL ? 'pm' : 'PM']
4276                ];
4277            return items;
4278        },
4279
4280        /*
4281         Returns current date value from combos.
4282         If format not specified - `options.format` used.
4283         If format = `null` - Moment object returned.
4284        */
4285        getValue: function(format) {
4286            var dt, values = {},
4287                that = this,
4288                notSelected = false;
4289
4290            //getting selected values
4291            $.each(this.map, function(k, v) {
4292                if(k === 'ampm') {
4293                    return;
4294                }
4295
4296                // if combo exists, use it's value, otherwise use default
4297                if (that['$'+k]) {
4298                    values[k] = parseInt(that['$'+k].val(), 10);
4299                } else {
4300                    var defaultValue;
4301                    if (that.datetime) {
4302                        defaultValue = that.datetime[v[1]]();
4303                    } else {
4304                        defaultValue = k === 'day' ? 1 : 0;
4305                    }
4306                    values[k] = defaultValue;
4307                }
4308
4309                if(isNaN(values[k])) {
4310                   notSelected = true;
4311                   return false;
4312                }
4313            });
4314
4315            //if at least one visible combo not selected - return empty string
4316            if(notSelected) {
4317               return '';
4318            }
4319
4320            //convert hours 12h --> 24h
4321            if(this.$ampm) {
4322                //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
4323                if(values.hour === 12) {
4324                    values.hour = this.$ampm.val() === 'am' ? 0 : 12;
4325                } else {
4326                    values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
4327                }
4328            }
4329
4330            dt = moment([
4331                values.year,
4332                values.month,
4333                values.day,
4334                values.hour,
4335                values.minute,
4336                values.second
4337            ]);
4338
4339            //highlight invalid date
4340            this.highlight(dt);
4341
4342            format = format === undefined ? this.options.format : format;
4343            if(format === null) {
4344               return dt.isValid() ? dt : null;
4345            } else {
4346               return dt.isValid() ? dt.format(format) : '';
4347            }
4348        },
4349
4350        setValue: function(value) {
4351            if(!value) {
4352                return;
4353            }
4354
4355            // parse in strict mode (third param `true`)
4356            var dt = typeof value === 'string' ? moment(value, this.options.format, true) : moment(value),
4357                that = this,
4358                values = {};
4359
4360            //function to find nearest value in select options
4361            function getNearest($select, value) {
4362                var delta = {};
4363                $select.children('option').each(function(i, opt){
4364                    var optValue = $(opt).attr('value'),
4365                    distance;
4366
4367                    if(optValue === '') return;
4368                    distance = Math.abs(optValue - value);
4369                    if(typeof delta.distance === 'undefined' || distance < delta.distance) {
4370                        delta = {value: optValue, distance: distance};
4371                    }
4372                });
4373                return delta.value;
4374            }
4375
4376            if(dt.isValid()) {
4377                //read values from date object
4378                $.each(this.map, function(k, v) {
4379                    if(k === 'ampm') {
4380                       return;
4381                    }
4382                    values[k] = dt[v[1]]();
4383                });
4384
4385                if(this.$ampm) {
4386                    //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
4387                    if(values.hour >= 12) {
4388                        values.ampm = 'pm';
4389                        if(values.hour > 12) {
4390                            values.hour -= 12;
4391                        }
4392                    } else {
4393                        values.ampm = 'am';
4394                        if(values.hour === 0) {
4395                            values.hour = 12;
4396                        }
4397                    }
4398                }
4399
4400                $.each(values, function(k, v) {
4401                    //call val() for each existing combo, e.g. this.$hour.val()
4402                    if(that['$'+k]) {
4403
4404                        if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) {
4405                           v = getNearest(that['$'+k], v);
4406                        }
4407
4408                        if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) {
4409                           v = getNearest(that['$'+k], v);
4410                        }
4411
4412                        that['$'+k].val(v);
4413                    }
4414                });
4415
4416                // update days count
4417                if (this.options.smartDays) {
4418                    this.fillCombo('day');
4419                }
4420
4421                this.$element.val(dt.format(this.options.format)).change();
4422                this.datetime = dt;
4423            } else {
4424                this.datetime = null;
4425            }
4426        },
4427
4428        /*
4429         highlight combos if date is invalid
4430        */
4431        highlight: function(dt) {
4432            if(!dt.isValid()) {
4433                if(this.options.errorClass) {
4434                    this.$widget.addClass(this.options.errorClass);
4435                } else {
4436                    //store original border color
4437                    if(!this.borderColor) {
4438                        this.borderColor = this.$widget.find('select').css('border-color');
4439                    }
4440                    this.$widget.find('select').css('border-color', 'red');
4441                }
4442            } else {
4443                if(this.options.errorClass) {
4444                    this.$widget.removeClass(this.options.errorClass);
4445                } else {
4446                    this.$widget.find('select').css('border-color', this.borderColor);
4447                }
4448            }
4449        },
4450
4451        leadZero: function(v) {
4452            return v <= 9 ? '0' + v : v;
4453        },
4454
4455        destroy: function() {
4456            this.$widget.remove();
4457            this.$element.removeData('combodate').show();
4458        }
4459
4460        //todo: clear method
4461    };
4462
4463    $.fn.combodate = function ( option ) {
4464        var d, args = Array.apply(null, arguments);
4465        args.shift();
4466
4467        //getValue returns date as string / object (not jQuery object)
4468        if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) {
4469          return d.getValue.apply(d, args);
4470        }
4471
4472        return this.each(function () {
4473            var $this = $(this),
4474            data = $this.data('combodate'),
4475            options = typeof option == 'object' && option;
4476            if (!data) {
4477                $this.data('combodate', (data = new Combodate(this, options)));
4478            }
4479            if (typeof option == 'string' && typeof data[option] == 'function') {
4480                data[option].apply(data, args);
4481            }
4482        });
4483    };
4484
4485    $.fn.combodate.defaults = {
4486         //in this format value stored in original input
4487        format: 'DD-MM-YYYY HH:mm',
4488        //in this format items in dropdowns are displayed
4489        template: 'D / MMM / YYYY   H : mm',
4490        //initial value, can be `new Date()`
4491        value: null,
4492        minYear: 1970,
4493        maxYear: new Date().getFullYear(),
4494        yearDescending: true,
4495        minuteStep: 5,
4496        secondStep: 1,
4497        firstItem: 'empty', //'name', 'empty', 'none'
4498        errorClass: null,
4499        customClass: '',
4500        roundTime: true, // whether to round minutes and seconds if step > 1
4501        smartDays: false // whether days in combo depend on selected month: 31, 30, 28
4502    };
4503
4504}(window.jQuery));
4505
4506/**
4507Combodate input - dropdown date and time picker.    
4508Based on [combodate](http://vitalets.github.com/combodate) plugin (included). To use it you should manually include [momentjs](http://momentjs.com).
4509
4510    <script src="js/moment.min.js"></script>
4511   
4512Allows to input:
4513
4514* only date
4515* only time 
4516* both date and time  
4517
4518Please note, that format is taken from momentjs and **not compatible** with bootstrap-datepicker / jquery UI datepicker.  
4519Internally value stored as `momentjs` object. 
4520
4521@class combodate
4522@extends abstractinput
4523@final
4524@since 1.4.0
4525@example
4526<a href="#" id="dob" data-type="combodate" data-pk="1" data-url="/post" data-value="1984-05-15" data-title="Select date"></a>
4527<script>
4528$(function(){
4529    $('#dob').editable({
4530        format: 'YYYY-MM-DD',    
4531        viewformat: 'DD.MM.YYYY',    
4532        template: 'D / MMMM / YYYY',    
4533        combodate: {
4534                minYear: 2000,
4535                maxYear: new Date().getFullYear(),
4536                minuteStep: 1
4537           }
4538        }
4539    });
4540});
4541</script>
4542**/
4543
4544/*global moment*/
4545
4546(function ($) {
4547    "use strict";
4548    
4549    var Constructor = function (options) {
4550        this.init('combodate', options, Constructor.defaults);
4551        
4552        //by default viewformat equals to format
4553        if(!this.options.viewformat) {
4554            this.options.viewformat = this.options.format;
4555        }        
4556        
4557        //try parse combodate config defined as json string in data-combodate
4558        options.combodate = $.fn.editableutils.tryParseJson(options.combodate, true);
4559
4560        //overriding combodate config (as by default jQuery extend() is not recursive)
4561        this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, {
4562            format: this.options.format,
4563            template: this.options.template
4564        });
4565    };
4566
4567    $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);    
4568    
4569    $.extend(Constructor.prototype, {
4570        render: function () {
4571            this.$input.combodate(this.options.combodate);
4572                    
4573            if($.fn.editableform.engine === 'bs3') {
4574                this.$input.siblings().find('select').addClass('form-control');
4575            }
4576            
4577            if(this.options.inputclass) {
4578                this.$input.siblings().find('select').addClass(this.options.inputclass);
4579            }            
4580            //"clear" link
4581            /*
4582            if(this.options.clear) {
4583                this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
4584                    e.preventDefault();
4585                    e.stopPropagation();
4586                    this.clear();
4587                }, this));
4588                
4589                this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));  
4590            } 
4591            */               
4592        },
4593        
4594        value2html: function(value, element) {
4595            var text = value ? value.format(this.options.viewformat) : '';
4596            //$(element).text(text);
4597            Constructor.superclass.value2html.call(this, text, element);  
4598        },
4599
4600        html2value: function(html) {
4601            return html ? moment(html, this.options.viewformat) : null;
4602        },   
4603        
4604        value2str: function(value) {
4605            return value ? value.format(this.options.format) : '';
4606       }, 
4607       
4608       str2value: function(str) {
4609           return str ? moment(str, this.options.format) : null;
4610       }, 
4611       
4612       value2submit: function(value) {
4613           return this.value2str(value);
4614       },                    
4615
4616       value2input: function(value) {
4617           this.$input.combodate('setValue', value);
4618       },
4619        
4620       input2value: function() { 
4621           return this.$input.combodate('getValue', null);
4622       },       
4623       
4624       activate: function() {
4625           this.$input.siblings('.combodate').find('select').eq(0).focus();
4626       },
4627       
4628       /*
4629       clear:  function() {
4630          this.$input.data('datepicker').date = null;
4631          this.$input.find('.active').removeClass('active');
4632       },
4633       */
4634       
4635       autosubmit: function() {
4636           
4637       }
4638
4639    });
4640    
4641    Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
4642        /**
4643        @property tpl 
4644        @default <input type="text">
4645        **/         
4646        tpl:'<input type="text">',
4647        /**
4648        @property inputclass 
4649        @default null
4650        **/         
4651        inputclass: null,
4652        /**
4653        Format used for sending value to server. Also applied when convert
4653ing date from <code>data-value</code> attribute.<br>
4654        See list of tokens in [momentjs docs](http://momentjs.com/docs/#/parsing/string-format)  
4655        
4656        @property format 
4657        @type string
4658        @default YYYY-MM-DD
4659        **/         
4660        format:'YYYY-MM-DD',
4661        /**
4662        Format used for displaying date. Also applied when converting date from element's text on init.   
4663        If not specified equals to `format`.
4664        
4665        @property viewformat 
4666        @type string
4667        @default null
4668        **/          
4669        viewformat: null,        
4670        /**
4671        Template used for displaying dropdowns.
4672        
4673        @property template 
4674        @type string
4675        @default D / MMM / YYYY
4676        **/          
4677        template: 'D / MMM / YYYY',  
4678        /**
4679        Configuration of combodate.
4680        Full list of options: http://vitalets.github.com/combodate/#docs
4681        
4682        @property combodate 
4683        @type object
4684        @default null
4685        **/
4686        combodate: null
4687        
4688        /*
4689        (not implemented yet)
4690        Text shown as clear date button. 
4691        If <code>false</code> clear button will not be rendered.
4692        
4693        @property clear 
4694        @type boolean|string
4695        @default 'x clear'         
4696        */
4697        //clear: '&times; clear'
4698    });   
4699
4700    $.fn.editabletypes.combodate = Constructor;
4701
4702}(window.jQuery));
4703
4704/*
4705Editableform based on Twitter Bootstrap 3
4706*/
4707(function ($) {
4708    "use strict";
4709    
4710    //store parent methods
4711    var pInitInput = $.fn.editableform.Constructor.prototype.initInput;
4712    
4713    $.extend($.fn.editableform.Constructor.prototype, {
4714        initTemplate: function() {
4715            this.$form = $($.fn.editableform.template); 
4716            this.$form.find('.control-group').addClass('form-group');
4717            this.$form.find('.editable-error-block').addClass('help-block');
4718        },
4719        initInput: function() {  
4720            pInitInput.apply(this);
4721
4722            var emptyInputClass = this.input.options.inputclass === null || this.input.options.inputclass === false;
4723            var defaultClass = 'form-control-sm';
4724            
4725            //bs3 add `form-control` class to standard inputs
4726            var stdtypes = 'text,select,textarea,password,email,url,tel,number,range,time,typeaheadjs'.split(','); 
4727            if(~$.inArray(this.input.type, stdtypes)) {
4728                this.input.$input.addClass('form-control');
4729                if(emptyInputClass) {
4730                    this.input.options.inputclass = defaultClass;
4731                    this.input.$input.addClass(defaultClass);
4732                }
4733            }             
4734        
4735            //apply size class also to buttons (to fit size of control)
4736            var $btn = this.$form.find('.editable-buttons');
4737            var classes = emptyInputClass ? [defaultClass] : this.input.options.inputclass.split(' ');
4738            for(var i=0; i<classes.length; i++) {
4739                // `btn-sm` is default now
4740                /*
4741                if(classes[i].toLowerCase() === 'input-sm') { 
4742                    $btn.find('button').addClass('btn-sm');  
4743                }
4744                */
4745                if(classes[i].toLowerCase() === 'input-lg') {
4746                    $btn.find('button').removeClass('btn-sm').addClass('btn-lg'); 
4747                }
4748            }
4749        }
4750    });    
4751    
4752    //buttons
4753    $.fn.editableform.buttons = 
4754      '<button type="submit" class="btn btn-primary btn-sm editable-submit">'+
4755        '<i class="fa fa-check" aria-hidden="true"></i>'+
4756      '</button>'+
4757      '<button type="button" class="btn btn-default btn-sm editable-cancel">'+
4758        '<i class="fa fa-times" aria-hidden="true"></i>'+
4759      '</button>';         
4760    
4761    //error classes
4762    $.fn.editableform.errorGroupClass = 'has-error';
4763    $.fn.editableform.errorBlockClass = null;  
4764    //engine
4765    $.fn.editableform.engine = 'bs4';  
4766}(window.jQuery));
4767/**
4768* Editable Popover for Bootstrap 4 based on Popper.js
4769* ---------------------
4770* requires bootstrap-popover.js
4771*/
4772(function ($) {
4773    "use strict";
4774    
4775    //extend methods
4776    $.extend($.fn.editableContainer.Popup.prototype, {
4777        containerName: 'popover',
4778        containerDataName: 'bs.popover',
4779        innerCss: '.popover-body',
4780	defaults: bootstrap.Popover.Default,
4781        
4782        initContainer: function(){
4783            $.extend(this.containerOptions, {
4784                trigger: 'manual',
4785                selector: false,
4786                content: ' ',
4787                template: this.defaults.template
4788            });
4789            
4790            //as template property is used in inputs, hide it from popover
4791            var t;
4792            if(this.$element.data('template')) {
4793                t = this.$element.data('template');
4794                this.$element.removeData('template');
4795            }
4796            
4797            this.call(this.containerOptions);
4798            
4799            if(t) {
4800                //restore data('template')
4801                this.$element.data('template', t);
4802            }
4803        },
4804        
4805        /* show */
4806        innerShow: function () {
4807            this.call('show');
4808        },
4809        
4810        /* hide */
4811        innerHide: function () {
4812            this.call('hide');
4813        },
4814        
4815        /* destroy */
4816        innerDestroy: function() {
4817            this.call('dispose');
4818        },
4819        
4820        setContainerOption: function(key, value) {
4821            this.container().options[key] = value;
4822        },
4823        
4824        setPosition: function () {
4825            (function() {}).call(this.container());
4826        },
4827        
4828        tip: function() {
4829            return this.container() ? $(this.container().tip) : null;
4830        }
4831    });
4832    
4833}(window.jQuery));
4834
4835/* =========================================================
4836 * bootstrap-datepicker.js
4837 * http://www.eyecon.ro/bootstrap-datepicker
4838 * =========================================================
4839 * Copyright 2012 Stefan Petre
4840 * Improvements by Andrew Rowls
4841 *
4842 * Licensed under the Apache License, Version 2.0 (the "License");
4843 * you may not use this file except in compliance with the License.
4844 * You may obtain a copy of the License at
4845 *
4846 * http://www.apache.org/licenses/LICENSE-2.0
4847 *
4848 * Unless required by applicable law or agreed to in writing, software
4849 * distributed under the License is distributed on an "AS IS" BASIS,
4850 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4851 * See the License for the specific language governing permissions and
4852 * limitations under the License.
4853 * ========================================================= */
4854
4855(function( $ ) {
4856
4857	function UTCDate(){
4858		return new Date(Date.UTC.apply(Date, arguments));
4859	}
4860	function UTCToday(){
4861		var today = new Date();
4862		return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
4863	}
4864
4865	// Picker object
4866
4867	var Datepicker = function(element, options) {
4868		var that = this;
4869
4870		this._process_options(options);
4871
4872		this.element = $(element);
4873		this.isInline = false;
4874		this.isInput = this.element.is('input');
4875		this.component = this.element.is('.date') ? this.element.find('.add-on, .btn') : false;
4876		this.hasInput = this.component && this.element.find('input').length;
4877		if(this.component && this.component.length === 0)
4878			this.component = false;
4879
4880		this.picker = $(DPGlobal.template);
4881		this._buildEvents();
4882		this._attachEvents();
4883
4884		if(this.isInline) {
4885			this.picker.addClass('datepicker-inline').appendTo(this.element);
4886		} else {
4887			this.picker.addClass('datepicker-dropdown dropdown-menu');
4888		}
4889
4890		if (this.o.rtl){
4891			this.picker.addClass('datepicker-rtl');
4892			this.picker.find('.prev i, .next i')
4893						.toggleClass('icon-arrow-left icon-arrow-right');
4894		}
4895
4896
4897		this.viewMode = this.o.startView;
4898
4899		if (this.o.calendarWeeks)
4900			this.picker.find('tfoot th.today')
4901						.attr('colspan', function(i, val){
4902							return parseInt(val) + 1;
4903						});
4904
4905		this._allow_update = false;
4906
4907		this.setStartDate(this.o.startDate);
4908		this.setEndDate(this.o.endDate);
4909		this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled);
4910
4911		this.fillDow();
4912		this.fillMonths();
4913
4914		this._allow_update = true;
4915
4916		this.update();
4917		this.showMode();
4918
4919		if(this.isInline) {
4920			this.show();
4921		}
4922	};
4923
4924	Datepicker.prototype = {
4925		constructor: Datepicker,
4926
4927		_process_options: function(opts){
4928			// Store raw options for reference
4929			this._o = $.extend({}, this._o, opts);
4930			// Processed options
4931			var o = this.o = $.extend({}, this._o);
4932
4933			// Check if "de-DE" style date is available, if not language should
4934			// fallback to 2 letter code eg "de"
4935			var lang = o.language;
4936			if (!dates[lang]) {
4937				lang = lang.split('-')[0];
4938				if (!dates[lang])
4939					lang = defaults.language;
4940			}
4941			o.language = lang;
4942
4943			switch(o.startView){
4944				case 2:
4945				case 'decade':
4946					o.startView = 2;
4947					break;
4948				case 1:
4949				case 'year':
4950					o.startView = 1;
4951					break;
4952				default:
4953					o.startView = 0;
4954			}
4955
4956			switch (o.minViewMode) {
4957				case 1:
4958				case 'months':
4959					o.minViewMode = 1;
4960					break;
4961				case 2:
4962				case 'years':
4963					o.minViewMode = 2;
4964					break;
4965				default:
4966					o.minViewMode = 0;
4967			}
4968
4969			o.startView = Math.max(o.startView, o.minViewMode);
4970
4971			o.weekStart %= 7;
4972			o.weekEnd = ((o.weekStart + 6) % 7);
4973
4974			var format = DPGlobal.parseFormat(o.format)
4975			if (o.startDate !== -Infinity) {
4976				o.startDate = DPGlobal.parseDate(o.startDate, format, o.language);
4977			}
4978			if (o.endDate !== Infinity) {
4979				o.endDate = DPGlobal.parseDate(o.endDate, format, o.language);
4980			}
4981
4982			o.daysOfWeekDisabled = o.daysOfWeekDisabled||[];
4983			if (!$.isArray(o.daysOfWeekDisabled))
4984				o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/);
4985			o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function (d) {
4986				return parseInt(d, 10);
4987			});
4988		},
4989		_events: [],
4990		_secondaryEvents: [],
4991		_applyEvents: function(evs){
4992			for (var i=0, el, ev; i<evs.length; i++){
4993				el = evs[i][0];
4994				ev = evs[i][1];
4995				el.on(ev);
4996			}
4997		},
4998		_unapplyEvents: function(evs){
4999			for (var i=0, el, ev; i<evs.length; i++){
5000				el = evs[i][0];
5001				ev = evs[i][1];
5002				el.off(ev);
5003			}
5004		},
5005		_buildEvents: function(){
5006			if (this.isInput) { // single input
5007				this._events = [
5008					[this.element, {
5009						focus: $.proxy(this.show, this),
5010						keyup: $.proxy(this.update, this),
5011						keydown: $.proxy(this.keydown, this)
5012					}]
5013				];
5014			}
5015			else if (this.component && this.hasInput){ // component: input + button
5016				this._events = [
5017					// For components that are not readonly, allow keyboard nav
5018					[this.element.find('input'), {
5019						focus: $.proxy(this.show, this),
5020						keyup: $.proxy(this.update, this),
5021						keydown: $.proxy(this.keydown, this)
5022					}],
5023					[this.component, {
5024						click: $.proxy(this.show, this)
5025					}]
5026				];
5027			}
5028			else if (this.element.is('div')) {  // inline datepicker
5029				this.isInline = true;
5030			}
5031			else {
5032				this._events = [
5033					[this.element, {
5034						click: $.proxy(this.show, this)
5035					}]
5036				];
5037			}
5038
5039			this._secondaryEvents = [
5040				[this.picker, {
5041					click: $.proxy(this.click, this)
5042				}],
5043				[$(window), {
5044					resize: $.proxy(this.place, this)
5045				}],
5046				[$(document), {
5047					mousedown: $.proxy(function (e) {
5048						// Clicked outside the datepicker, hide it
5049						if (!(
5050							this.element.is(e.target) ||
5051							this.element.find(e.target).size() ||
5052							this.picker.is(e.target) ||
5053							this.picker.find(e.target).size()
5054						)) {
5055							this.hide();
5056						}
5057					}, this)
5058				}]
5059			];
5060		},
5061		_attachEvents: function(){
5062			this._detachEvents();
5063			this._applyEvents(this._events);
5064		},
5065		_detachEvents: function(){
5066			this._unapplyEvents(this._events);
5067		},
5068		_attachSecondaryEvents: function(){
5069			this._detachSecondaryEvents();
5070			this._applyEvents(this._secondaryEvents);
5071		},
5072		_detachSecondaryEvents: function(){
5073			this._unapplyEvents(this._secondaryEvents);
5074		},
5075		_trigger: function(event, altdate){
5076			var date = altdate || this.date,
5077				local_date = new Date(date.getTime() + (date.getTimezoneOffset()*60000));
5078
5079			this.element.trigger({
5080				type: event,
5081				date: local_date,
5082				format: $.proxy(function(altformat){
5083					var format = altformat || this.o.format;
5084					return DPGlobal.formatDate(date, format, this.o.language);
5085				}, this)
5086			});
5087		},
5088
5089		show: function(e) {
5090			if (!this.isInline)
5091				this.picker.appendTo('body');
5092			this.picker.show();
5093			this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
5094			this.place();
5095			this._attachSecondaryEvents();
5096			if (e) {
5097				e.preventDefault();
5098			}
5099			this._trigger('show');
5100		},
5101
5102		hide: function(e){
5103			if(this.isInline) return;
5104			if (!this.picker.is(':visible')) return;
5105			this.picker.hide().detach();
5106			this._detachSecondaryEvents();
5107			this.viewMode = this.o.startView;
5108			this.showMode();
5109
5110			if (
5111				this.o.forceParse &&
5112				(
5113					this.isInput && this.element.val() ||
5114					this.hasInput && this.element.find('input').val()
5115				)
5116			)
5117				this.setValue();
5118			this._trigger('hide');
5119		},
5120
5121		remove: function() {
5122			this.hide();
5123			this._detachEvents();
5124			this._detachSecondaryEvents();
5125			this.picker.remove();
5126			delete this.element.data().datepicker;
5127			if (!this.isInput) {
5128				delete this.element.data().date;
5129			}
5130		},
5131
5132		getDate: function() {
5133			var d = this.getUTCDate();
5134			return new Date(d.getTime() + (d.getTimezoneOffset()*60000));
5135		},
5136
5137		getUTCDate: function() {
5138			return this.date;
5139		},
5140
5141		setDate: function(d) {
5142			this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));
5143		},
5144
5145		setUTCDate: function(d) {
5146			this.date = d;
5147			this.setValue();
5148		},
5149
5150		setValue: function() {
5151			var formatted = this.getFormattedDate();
5152			if (!this.isInput) {
5153				if (this.component){
5154					this.element.find('input').val(formatted);
5155				}
5156			} else {
5157				this.element.val(formatted);
5158			}
5159		},
5160
5161		getFormattedDate: function(format) {
5162			if (format === undefined)
5163				format = this.o.format;
5164			return DPGlobal.formatDate(this.date, format, this.o.language);
5165		},
5166
5167		setStartDate: function(startDate){
5168			this._process_options({startDate: startDate});
5169			this.update();
5170			this.updateNavArrows();
5171		},
5172
5173		setEndDate: function(endDate){
5174			this._process_options({endDate: endDate});
5175			this.update();
5176			this.updateNavArrows();
5177		},
5178
5179		setDaysOfWeekDisabled: function(daysOfWeekDisabled){
5180			this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
5181			this.update();
5182			this.updateNavArrows();
5183		},
5184
5185		place: function(){
5186						if(this.isInline) return;
5187			var zIndex = parseInt(this.element.parents().filter(function() {
5188							return $(this).css('z-index') != 'auto';
5189						}).first().css('z-index'))+10;
5190			var offset = this.component ? this.component.parent().offset() : this.element.offset();
5191			var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(true);
5192			this.picker.css({
5193				top: offset.top + height,
5194				left: offset.left,
5195				zIndex: zIndex
5196			});
5197		},
5198
5199		_allow_update: true,
5200		update: function(){
5201			if (!this._allow_update) return;
5202
5203			var date, fromArgs = false;
5204			if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
5205				date = arguments[0];
5206				fromArgs = true;
5207			} else {
5208				date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();
5209				delete this.element.data().date;
5210			}
5211
5212			this.date = DPGlobal.parseDate(date, this.o.format, this.o.language);
5213
5214			if(fromArgs) this.setValue();
5215
5216			if (this.date < this.o.startDate) {
5217				this.viewDate = new Date(this.o.startDate);
5218			} else if (this.date > this.o.endDate) {
5219				this.viewDate = new Date(this.o.endDate);
5220			} else {
5221				this.viewDate = new Date(this.date);
5222			}
5223			this.fill();
5224		},
5225
5226		fillDow: function(){
5227			var dowCnt = this.o.weekStart,
5228			html = '<tr>';
5229			if(this.o.calendarWeeks){
5230				var cell = '<th class="cw">&nbsp;</th>';
5231				html += cell;
5232				this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
5233			}
5234			while (dowCnt < this.o.weekStart + 7) {
5235				html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';
5236			}
5237			html += '</tr>';
5238			this.picker.find('.datepicker-days thead').append(html);
5239		},
5240
5241		fillMonths: function(){
5242			var html = '',
5243			i = 0;
5244			while (i < 12) {
5245				html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>';
5246			}
5247			this.picker.find('.datepicker-months td').html(html);
5248		},
5249
5250		setRange: function(range){
5251			if (!range || !range.length)
5252				delete this.range;
5253			else
5254				this.range = $.map(range, function(d){ return d.valueOf(); });
5255			this.fill();
5256		},
5257
5258		getClassNames: function(date){
5259			var cls = [],
5260				year = this.viewDate.getUTCFullYear(),
5261				month = this.viewDate.getUTCMonth(),
5262				currentDate = this.date.valueOf(),
5263				today = new Date();
5264			if (date.getUTCFullYear() < year || (date.getUTCFullYear() == year && date.getUTCMonth() < month)) {
5265				cls.push('old');
5266			} else if (date.getUTCFullYear() > year || (date.getUTCFullYear() == year && date.getUTCMonth() > month)) {
5267				cls.push('new');
5268			}
5269			// Compare internal UTC date with local today, not UTC today
5270			if (this.o.todayHighlight &&
5271				date.getUTCFullYear() == today.getFullYear() &&
5272				date.getUTCMonth() == today.getMonth() &&
5273				date.getUTCDate() == today.getDate()) {
5274				cls.push('today');
5275			}
5276			if (currentDate && date.valueOf() == currentDate) {
5277				cls.push('active');
5278			}
5279			if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate ||
5280				$.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1) {
5281				cls.push('disabled');
5282			}
5283			if (this.range){
5284				if (date > this.range[0] && date < this.range[this.range.length-1]){
5285					cls.push('range');
5286				}
5287				if ($.inArray(date.valueOf(), this.range) != -1){
5288					cls.push('selected');
5289				}
5290			}
5291			return cls;
5292		},
5293
5294		fill: function() {
5295			var d = new Date(this.viewDate),
5296				year = d.getUTCFullYear(),
5297				month = d.getUTCMonth(),
5298				startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
5299				startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
5300				endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
5301				endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
5302				currentDate = this.date && this.date.valueOf(),
5303				tooltip;
5304			this.picker.find('.datepicker-days thead th.datepicker-switch')
5305						.text(dates[this.o.language].months[month]+' '+year);
5306			this.picker.find('tfoot th.today')
5307						.text(dates[this.o.language].today)
5308						.toggle(this.o.todayBtn !== false);
5309			this.picker.find('tfoot th.clear')
5310						.text(dates[this.o.language].clear)
5311						.toggle(this.o.clearBtn !== false);
5312			this.updateNavArrows();
5313			this.fillMonths();
5314			var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
5315				day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
5316			prevMonth.setUTCDate(day);
5317			prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);
5318			var nextMonth = new Date(prevMonth);
5319			nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
5320			nextMonth = nextMonth.valueOf();
5321			var html = [];
5322			var clsName;
5323			while(prevMonth.valueOf() < nextMonth) {
5324				if (prevMonth.getUTCDay() == this.o.weekStart) {
5325					html.push('<tr>');
5326					if(this.o.calendarWeeks){
5327						// ISO 8601: First week contains first thursday.
5328						// ISO also states week starts on Monday, but we can be more abstract here.
5329						var
5330							// Start of current week: based on weekstart/current date
5331							ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
5332							// Thursday of this week
5333							th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
5334							// First Thursday of year, year from thursday
5335							yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),
5336							// Calendar week: ms between thursdays, div ms per day, div 7 days
5337							calWeek =  (th - yth) / 864e5 / 7 + 1;
5338						html.push('<td class="cw">'+ calWeek +'</td>');
5339
5340					}
5341				}
5342				clsName = this.getClassNames(prevMonth);
5343				clsName.push('day');
5344
5345				var before = this.o.beforeShowDay(prevMonth);
5346				if (before === undefined)
5347					before = {};
5348				else if (typeof(before) === 'boolean')
5349					before = {enabled: before};
5350				else if (typeof(before) === 'string')
5351					before = {classes: before};
5352				if (before.enabled === false)
5353					clsName.push('disabled');
5354				if (before.classes)
5355					clsName = clsName.concat(before.classes.split(/\s+/));
5356				if (before.tooltip)
5357					tooltip = before.tooltip;
5358
5359				clsName = $.unique(clsName);
5360				html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>');
5361				if (prevMonth.getUTCDay() == this.o.weekEnd) {
5362					html.push('</tr>');
5363				}
5364				prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
5365			}
5366			this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
5367			var currentYear = this.date && this.date.getUTCFullYear();
5368
5369			var months = this.picker.find('.datepicker-months')
5370						.find('th:eq(1)')
5371							.text(year)
5372							.end()
5373						.find('span').removeClass('active');
5374			if (currentYear && currentYear == year) {
5375				months.eq(this.date.getUTCMonth()).addClass('active');
5376			}
5377			if (year < startYear || year > endYear) {
5378				months.addClass('disabled');
5379			}
5380			if (year == startYear) {
5381				months.slice(0, startMonth).addClass('disabled');
5382			}
5383			if (year == endYear) {
5384				months.slice(endMonth+1).addClass('disabled');
5385			}
5386
5387			html = '';
5388			year = parseInt(year/10, 10) * 10;
5389			var yearCont = this.picker.find('.datepicker-years')
5390								.find('th:eq(1)')
5391									.text(year + '-' + (year + 9))
5392									.end()
5393								.find('td');
5394			year -= 1;
5395			for (var i = -1; i < 11; i++) {
5396				html += '<span class="year'+(i == -1 ? ' old' : i == 10 ? ' new' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
5397				year += 1;
5398			}
5399			yearCont.html(html);
5400		},
5401
5402		updateNavArrows: function() {
5403			if (!this._allow_update) return;
5404
5405			var d = new Date(this.viewDate),
5406				year = d.getUTCFullYear(),
5407				month = d.getUTCMonth();
5408			switch (this.viewMode) {
5409				case 0:
5410					if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()) {
5411						this.picker.find('.prev').css({visibility: 'hidden'});
5412					} else {
5413						this.picker.find('.prev').css({visibility: 'visible'});
5414					}
5415					if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()) {
5416						this.picker.find('.next').css({visibility: 'hidden'});
5417					} else {
5418						this.picker.find('.next').css({visibility: 'visible'});
5419					}
5420					break;
5421				case 1:
5422				case 2:
5423					if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()) {
5424						this.picker.find('.prev').css({visibility: 'hidden'});
5425					} else {
5426						this.picker.find('.prev').css({visibility: 'visible'});
5427					}
5428					if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()) {
5429						this.picker.find('.next').css({visibility: 'hidden'});
5430					} else {
5431						this.picker.find('.next').css({visibility: 'visible'});
5432					}
5433					break;
5434			}
5435		},
5436
5437		click: function(e) {
5438			e.preventDefault();
5439			var target = $(e.target).closest('span, td, th');
5440			if (target.length == 1) {
5441				switch(target[0].nodeName.toLowerCase()) {
5442					case 'th':
5443						switch(target[0].className) {
5444							case 'datepicker-switch':
5445								this.showMode(1);
5446								break;
5447							case 'prev':
5448							case 'next':
5449								var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
5450								switch(this.viewMode){
5451									case 0:
5452										this.viewDate = this.moveMonth(this.viewDate, dir);
5453										break;
5454									case 1:
5455									case 2:
5456										this.viewDate = this.moveYear(this.viewDate, dir);
5457										break;
5458								}
5459								this.fill();
5460								break;
5461							case 'today':
5462								var date = new Date();
5463								date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
5464
5465								this.showMode(-2);
5466								var which = this.o.todayBtn == 'linked' ? null : 'view';
5467								this._setDate(date, which);
5468								break;
5469							case 'clear':
5470								var element;
5471								if (this.isInput)
5472									element = this.element;
5473								else if (this.component)
5474									element = this.element.find('input');
5475								if (element)
5476									element.val("").change();
5477								this._trigger('changeDate');
5478								this.update();
5479								if (this.o.autoclose)
5480									this.hide();
5481								break;
5482						}
5483						break;
5484					case 'span':
5485						if (!target.is('.disabled')) {
5486							this.viewDate.setUTCDate(1);
5487							if (target.is('.month')) {
5488								var day = 1;
5489								var month = target.parent().find('span').index(target);
5490								var year = this.viewDate.getUTCFullYear();
5491								this.viewDate.setUTCMonth(month);
5492								this._trigger('changeMonth', this.viewDate);
5493								if (this.o.minViewMode === 1) {
5494									this._setDate(UTCDate(year, month, day,0,0,0,0));
5495								}
5496							} else {
5497								var year = parseInt(target.text(), 10)||0;
5498								var day = 1;
5499								var month = 0;
5500								this.viewDate.setUTCFullYear(year);
5501								this._trigger('changeYear', this.viewDate);
5502								if (this.o.minViewMode === 2) {
5503									this._setDate(UTCDate(year, month, day,0,0,0,0));
5504								}
5505							}
5506							this.showMode(-1);
5507							this.fill();
5508						}
5509						break;
5510					case 'td':
5511						if (target.is('.day') && !target.is('.disabled')){
5512							var day = parseInt(target.text(), 10)||1;
5513							var year = this.viewDate.getUTCFullYear(),
5514								month = this.viewDate.getUTCMonth();
5515							if (target.is('.old')) {
5516								if (month === 0) {
5517									month = 11;
5518									year -= 1;
5519								} else {
5520									month -= 1;
5521								}
5522							} else if (target.is('.new')) {
5523								if (month == 11) {
5524									month = 0;
5525									year += 1;
5526								} else {
5527									month += 1;
5528								}
5529							}
5530							this._setDate(UTCDate(year, month, day,0,0,0,0));
5531						}
5532						break;
5533				}
5534			}
5535		},
5536
5537		_setDate: function(date, which){
5538			if (!which || which == 'date')
5539				this.date = new Date(date);
5540			if (!which || which  == 'view')
5541				this.viewDate = new Date(date);
5542			this.fill();
5543			this.setValue();
5544			this._trigger('changeDate');
5545			var element;
5546			if (this.isInput) {
5547				element = this.element;
5548			} else if (this.component){
5549				element = this.element.find('input');
5550			}
5551			if (element) {
5552				element.change();
5553				if (this.o.autoclose && (!which || which == 'date')) {
5554					this.hide();
5555				}
5556			}
5557		},
5558
5559		moveMonth: function(date, dir){
5560			if (!dir) return date;
5561			var new_date = new Date(date.valueOf()),
5562				day = new_date.getUTCDate(),
5563				month = new_date.getUTCMonth(),
5564				mag = Math.abs(dir),
5565				new_month, test;
5566			dir = dir > 0 ? 1 : -1;
5567			if (mag == 1){
5568				test = dir == -1
5569					// If going back one month, make sure month is not current month
5570					// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
5571					? function(){ return new_date.getUTCMonth() == month; }
5572					// If going forward one month, make sure month is as expected
5573					// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
5574					: function(){ return new_date.getUTCMonth() != new_month; };
5575				new_month = month + dir;
5576				new_date.setUTCMonth(new_month);
5577				// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
5578				if (new_month < 0 || new_month > 11)
5579					new_month = (new_month + 12) % 12;
5580			} else {
5581				// For magnitudes >1, move one month at a time...
5582				for (var i=0; i<mag; i++)
5583					// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
5584					new_date = this.moveMonth(new_date, dir);
5585				// ...then reset the day, keeping it in the new month
5586				new_month = new_date.getUTCMonth();
5587				new_date.setUTCDate(day);
5588				test = function(){ return new_month != new_date.getUTCMonth(); };
5589			}
5590			// Common date-resetting loop -- if date is beyond end of month, make it
5591			// end of month
5592			while (test()){
5593				new_date.setUTCDate(--day);
5594				new_date.setUTCMonth(new_month);
5595			}
5596			return new_date;
5597		},
5598
5599		moveYear: function(date, dir){
5600			return this.moveMonth(date, dir*12);
5601		},
5602
5603		dateWithinRange: function(date){
5604			return date >= this.o.startDate && date <= this.o.endDate;
5605		},
5606
5607		keydown: function(e){
5608			if (this.picker.is(':not(:visible)')){
5609				if (e.keyCode == 27) // allow escape to hide and re-show picker
5610					this.show();
5611				return;
5612			}
5613			var dateChanged = false,
5614				dir, day, month,
5615				newDate, newViewDate;
5616			switch(e.keyCode){
5617				case 27: // escape
5618					this.hide();
5619					e.preventDefault();
5620					break;
5621				case 37: // left
5622				case 39: // right
5623					if (!this.o.keyboardNavigation) break;
5624					dir = e.keyCode == 37 ? -1 : 1;
5625					if (e.ctrlKey){
5626						newDate = this.moveYear(this.date, dir);
5627						newViewDate = this.moveYear(this.viewDate, dir);
5628					} else if (e.shiftKey){
5629						newDate = this.moveMonth(this.date, dir);
5630						newViewDate = this.moveMonth(this.viewDate, dir);
5631					} else {
5632						newDate = new Date(this.date);
5633						newDate.setUTCDate(this.date.getUTCDate() + dir);
5634						newViewDate = new Date(this.viewDate);
5635						newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
5636					}
5637					if (this.dateWithinRange(newDate)){
5638						this.date = newDate;
5639						this.viewDate = newViewDate;
5640						this.setValue();
5641						this.update();
5642						e.preventDefault();
5643						dateChanged = true;
5644					}
5645					break;
5646				case 38: // up
5647				case 40: // down
5648					if (!this.o.keyboardNavigation) break;
5649					dir = e.keyCode == 38 ? -1 : 1;
5650					if (e.ctrlKey){
5651						newDate = this.moveYear(this.date, dir);
5652						newViewDate = this.moveYear(this.viewDate, dir);
5653					} else if (e.shiftKey){
5654						newDate = this.moveMonth(this.date, dir);
5655						newViewDate = this.moveMonth(this.viewDate, dir);
5656					} else {
5657						newDate = new Date(this.date);
5658						newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
5659						newViewDate = new Date(this.viewDate);
5660						newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
5661					}
5662					if (this.dateWithinRange(newDate)){
5663						this.date = newDate;
5664						this.viewDate = newViewDate;
5665						this.setValue();
5666						this.update();
5667						e.preventDefault();
5668						dateChanged = true;
5669					}
5670					break;
5671				case 13: // enter
5672					this.hide();
5673					e.preventDefault();
5674					break;
5675				case 9: // tab
5676					this.hide();
5677					break;
5678			}
5679			if (dateChanged){
5680				this._trigger('changeDate');
5681				var element;
5682				if (this.isInput) {
5683					element = this.element;
5684				} else if (this.component){
5685					element = this.element.find('input');
5686				}
5687				if (element) {
5688					element.change();
5689				}
5690			}
5691		},
5692
5693		showMode: function(dir) {
5694			if (dir) {
5695				this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir));
5696			}
5697			/*
5698				vitalets: fixing bug of very special conditions:
5699				jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
5700				Method show() does not set display css correctly and datepicker is not shown.
5701				Changed to .css('display', 'block') solve the problem.
5702				See https://github.com/vitalets/x-editable/issues/37
5703
5704				In jquery 1.7.2+ everything works fine.
5705			*/
5706			//this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
5707			this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
5708			this.updateNavArrows();
5709		}
5710	};
5711
5712	var DateRangePicker = function(element, options){
5713		this.element = $(element);
5714		this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; });
5715		delete options.inputs;
5716
5717		$(this.inputs)
5718			.datepicker(options)
5719			.bind('changeDate', $.proxy(this.dateUpdated, this));
5720
5721		this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); });
5722		this.updateDates();
5723	};
5724	DateRangePicker.prototype = {
5725		updateDates: function(){
5726			this.dates = $.map(this.pickers, function(i){ return i.date; });
5727			this.updateRanges();
5728		},
5729		updateRanges: function(){
5730			var range = $.map(this.dates, function(d){ return d.valueOf(); });
5731			$.each(this.pickers, function(i, p){
5732				p.setRange(range);
5733			});
5734		},
5735		dateUpdated: function(e){
5736			var dp = $(e.target).data('datepicker'),
5737				new_date = dp.getUTCDate(),
5738				i = $.inArray(e.target, this.inputs),
5739				l = this.inputs.length;
5740			if (i == -1) return;
5741
5742			if (new_date < this.dates[i]){
5743				// Date being moved earlier/left
5744				while (i>=0 && new_date < this.dates[i]){
5745					this.pickers[i--].setUTCDate(new_date);
5746				}
5747			}
5748			else if (new_date > this.dates[i]){
5749				// Date being moved later/right
5750				while (i<l && new_date > this.dates[i]){
5751					this.pickers[i++].setUTCDate(new_date);
5752				}
5753			}
5754			this.updateDates();
5755		},
5756		remove: function(){
5757			$.map(this.pickers, function(p){ p.remove(); });
5758			delete this.element.data().datepicker;
5759		}
5760	};
5761
5762	function opts_from_el(el, prefix){
5763		// Derive options from element data-attrs
5764		var data = $(el).data(),
5765			out = {}, inkey,
5766			replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'),
5767			prefix = new RegExp('^' + prefix.toLowerCase());
5768		for (var key in data)
5769			if (prefix.test(key)){
5770				inkey = key.replace(replace, function(_,a){ return a.toLowerCase(); });
5771				out[inkey] = data[key];
5772			}
5773		return out;
5774	}
5775
5776	function opts_from_locale(lang){
5777		// Derive options from locale plugins
5778		var out = {};
5779		// Check if "de-DE" style date is available, if not language should
5780		// fallback to 2 letter code eg "de"
5781		if (!dates[lang]) {
5782			lang = lang.split('-')[0]
5783			if (!dates[lang])
5784				return;
5785		}
5786		var d = dates[lang];
5787		$.each(locale_opts, function(i,k){
5788			if (k in d)
5789				out[k] = d[k];
5790		});
5791		return out;
5792	}
5793
5794	var old = $.fn.datepicker;
5795	var datepicker = $.fn.datepicker = function ( option ) {
5796		var args = Array.apply(null, arguments);
5797		args.shift();
5798		var internal_return,
5799			this_return;
5800		this.each(function () {
5801			var $this = $(this),
5802				data = $this.data('datepicker'),
5803				options = typeof option == 'object' && option;
5804			if (!data) {
5805				var elopts = opts_from_el(this, 'date'),
5806					// Preliminary otions
5807					xopts = $.extend({}, defaults, elopts, options),
5808					locopts = opts_from_locale(xopts.language),
5809					// Options priority: js args, data-attrs, locales, defaults
5810					opts = $.extend({}, defaults, locopts, elopts, options);
5811				if ($this.is('.input-daterange') || opts.inputs){
5812					var ropts = {
5813						inputs: opts.inputs || $this.find('input').toArray()
5814					};
5815					$this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts))));
5816				}
5817				else{
5818					$this.data('datepicker', (data = new Datepicker(this, opts)));
5819				}
5820			}
5821			if (typeof option == 'string' && typeof data[option] == 'function') {
5822				internal_return = data[option].apply(data, args);
5823				if (internal_return !== undefined)
5824					return false;
5825			}
5826		});
5827		if (internal_return !== undefined)
5828			return internal_return;
5829		else
5830			return this;
5831	};
5832
5833	var defaults = $.fn.datepicker.defaults = {
5834		autoclose: false,
5835		beforeShowDay: $.noop,
5836		calendarWeeks: false,
5837		clearBtn: false,
5838		daysOfWeekDisabled: [],
5839		endDate: Infinity,
5840		forceParse: true,
5841		format: 'mm/dd/yyyy',
5842		keyboardNavigation: true,
5843		language: 'en',
5844		minViewMode: 0,
5845		rtl: false,
5846		startDate: -Infinity,
5847		startView: 0,
5848		todayBtn: false,
5849		todayHighlight: false,
5850		weekStart: 0
5851	};
5852	var locale_opts = $.fn.datepicker.locale_opts = [
5853		'format',
5854		'rtl',
5855		'weekStart'
5856	];
5857	$.fn.datepicker.Constructor = Datepicker;
5858	var dates = $.fn.datepicker.dates = {
5859		en: {
5860			days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
5861			daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
5862			daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
5863			months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
5864			monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
5865			today: "Today",
5866			clear: "Clear"
5867		}
5868	};
5869
5870	var DPGlobal = {
5871		modes: [
5872			{
5873				clsName: 'days',
5874				navFnc: 'Month',
5875				navStep: 1
5876			},
5877			{
5878				clsName: 'months',
5879				navFnc: 'FullYear',
5880				navStep: 1
5881			},
5882			{
5883				clsName: 'years',
5884				navFnc: 'FullYear',
5885				navStep: 10
5886		}],
5887		isLeapYear: function (year) {
5888			return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
5889		},
5890		getDaysInMonth: function (year, month) {
5891			return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
5892		},
5893		validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
5894		nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
5895		parseFormat: function(format){
5896			// IE treats \0 as a string end in inputs (truncating the value),
5897			// so it's a bad format delimiter, anyway
5898			var separators = format.replace(this.validParts, '\0').split('\0'),
5899				parts = format.match(this.validParts);
5900			if (!separators || !separators.length || !parts || parts.length === 0){
5901				throw new Error("Invalid date format.");
5902			}
5903			return {separators: separators, parts: parts};
5904		},
5905		parseDate: function(date, format, language) {
5906			if (date instanceof Date) return date;
5907			if (typeof format === 'string')
5908				format = DPGlobal.parseFormat(format);
5909			if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {
5910				var part_re = /([\-+]\d+)([dmwy])/,
5911					parts = date.match(/([\-+]\d+)([dmwy])/g),
5912					part, dir;
5913				date = new Date();
5914				for (var i=0; i<parts.length; i++) {
5915					part = part_re.exec(parts[i]);
5916					dir = parseInt(part[1]);
5917					switch(part[2]){
5918						case 'd':
5919							date.setUTCDate(date.getUTCDate() + dir);
5920							break;
5921						case 'm':
5922							date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
5923							break;
5924						case 'w':
5925							date.setUTCDate(date.getUTCDate() + dir * 7);
5926							break;
5927						case 'y':
5928							date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
5929							break;
5930					}
5931				}
5932				return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
5933			}
5934			var parts = date && date.match(this.nonpunctuation) || [],
5935				date = new Date(),
5936				parsed = {},
5937				setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
5938				setters_map = {
5939					yyyy: function(d,v){ return d.setUTCFullYear(v); },
5940					yy: function(d,v){ return d.setUTCFullYear(2000+v); },
5941					m: function(d,v){
5942						v -= 1;
5943						while (v<0) v += 12;
5944						v %= 12;
5945						d.setUTCMonth(v);
5946						while (d.getUTCMonth() != v)
5947							d.setUTCDate(d.getUTCDate()-1);
5948						return d;
5949					},
5950					d: function(d,v){ return d.setUTCDate(v); }
5951				},
5952				val, filtered, part;
5953			setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
5954			setters_map['dd'] = setters_map['d'];
5955			date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
5956			var fparts = format.parts.slice();
5957			// Remove noop parts
5958			if (parts.length != fparts.length) {
5959				fparts = $(fparts).filter(function(i,p){
5960					return $.inArray(p, setters_order) !== -1;
5961				}).toArray();
5962			}
5963			// Process remainder
5964			if (parts.length == fparts.length) {
5965				for (var i=0, cnt = fparts.length; i < cnt; i++) {
5966					val = parseInt(parts[i], 10);
5967					part = fparts[i];
5968					if (isNaN(val)) {
5969						switch(part) {
5970							case 'MM':
5971								filtered = $(dates[language].months).filter(function(){
5972									var m = this.slice(0, parts[i].length),
5973										p = parts[i].slice(0, m.length);
5974									return m == p;
5975								});
5976								val = $.inArray(filtered[0], dates[language].months) + 1;
5977								break;
5978							case 'M':
5979								filtered = $(dates[language].monthsShort).filter(function(){
5980									var m = this.slice(0, parts[i].length),
5981										p = parts[i].slice(0, m.length);
5982									return m == p;
5983								});
5984								val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
5985								break;
5986						}
5987					}
5988					parsed[part] = val;
5989				}
5990				for (var i=0, s; i<setters_order.length; i++){
5991					s = setters_order[i];
5992					if (s in parsed && !isNaN(parsed[s]))
5993						setters_map[s](date, parsed[s]);
5994				}
5995			}
5996			return date;
5997		},
5998		formatDate: function(date, format, language){
5999			if (typeof format === 'string')
6000				format = DPGlobal.parseFormat(format);
6001			var val = {
6002				d: date.getUTCDate(),
6003				D: dates[language].daysShort[date.getUTCDay()],
6004				DD: dates[language].days[date.getUTCDay()],
6005				m: date.getUTCMonth() + 1,
6006				M: dates[language].monthsShort[date.getUTCMonth()],
6007				MM: dates[language].months[date.getUTCMonth()],
6008				yy: date.getUTCFullYear().toString().substring(2),
6009				yyyy: date.getUTCFullYear()
6010			};
6011			val.dd = (val.d < 10 ? '0' : '') + val.d;
6012			val.mm = (val.m < 10 ? '0' : '') + val.m;
6013			var date = [],
6014				seps = $.extend([], format.separators);
6015			for (var i=0, cnt = format.parts.length; i <= cnt; i++) {
6016				if (seps.length)
6017					date.push(seps.shift());
6018				date.push(val[format.parts[i]]);
6019			}
6020			return date.join('');
6021		},
6022		headTemplate: '<thead>'+
6023							'<tr>'+
6024								'<th class="prev"><i class="icon-arrow-left"/></th>'+
6025								'<th colspan="5" class="datepicker-switch"></th>'+
6026								'<th class="next"><i class="icon-arrow-right"/></th>'+
6027							'</tr>'+
6028						'</thead>',
6029		contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
6030		footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>'
6031	};
6032	DPGlobal.template = '<div class="datepicker">'+
6033							'<div class="datepicker-days">'+
6034								'<table class=" table-condensed">'+
6035									DPGlobal.headTemplate+
6036									'<tbody></tbody>'+
6037									DPGlobal.footTemplate+
6038								'</table>'+
6039							'</div>'+
6040							'<div class="datepicker-months">'+
6041								'<table class="table-condensed">'+
6042									DPGlobal.headTemplate+
6043									DPGlobal.contTemplate+
6044									DPGlobal.footTemplate+
6045								'</table>'+
6046							'</div>'+
6047							'<div class="datepicker-years">'+
6048								'<table class="table-condensed">'+
6049									DPGlobal.headTemplate+
6050									DPGlobal.contTemplate+
6051									DPGlobal.footTemplate+
6052								'</table>'+
6053							'</div>'+
6054						'</div>';
6055
6056	$.fn.datepicker.DPGlobal = DPGlobal;
6057
6058
6059	/* DATEPICKER NO CONFLICT
6060	* =================== */
6061
6062	$.fn.datepicker.noConflict = function(){
6063		$.fn.datepicker = old;
6064		return this;
6065	};
6066
6067
6068	/* DATEPICKER DATA-API
6069	* ================== */
6070
6071	$(document).on(
6072		'focus.datepicker.data-api click.datepicker.data-api',
6073		'[data-provide="datepicker"]',
6074		function(e){
6075			var $this = $(this);
6076			if ($this.data('datepicker')) return;
6077			e.preventDefault();
6078			// component click requires us to explicitly show it
6079			datepicker.call($this, 'show');
6080		}
6081	);
6082	$(function(){
6083		//$('[data-provide="datepicker-inline"]').datepicker();
6084        //vit: changed to support noConflict()
6085        datepicker.call($('[data-provide="datepicker-inline"]'));
6086	});
6087
6088}( window.jQuery ));
6089
6090/**
6091Bootstrap-datepicker.  
6092Description and examples: https://github.com/eternicode/bootstrap-datepicker.  
6093For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
6094and set `language` option.  
6095Since 1.4.0 date has different appearance in **popup** and **inline** modes. 
6096
6097@class date
6098@extends abstractinput
6099@final
6100@example
6101<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-title="Select date">15/05/1984</a>
6102<script>
6103$(function(){
6104    $('#dob').editable({
6105        format: 'yyyy-mm-dd',    
6106        viewformat: 'dd/mm/yyyy',    
6107        datepicker: {
6108                weekStart: 1
6109           }
6110        }
6111    });
6112});
6113</script>
6114**/
6115(function ($) {
6116    "use strict";
6117    
6118    //store bootstrap-datepicker as bdateicker to exclude conflict with jQuery UI one
6119    $.fn.bdatepicker = $.fn.datepicker.noConflict();
6120    if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name
6121        $.fn.datepicker = $.fn.bdatepicker;    
6122    }    
6123    
6124    var Date = function (options) {
6125        this.init('date', options, Date.defaults);
6126        this.initPicker(options, Date.defaults);
6127    };
6128
6129    $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);    
6130    
6131    $.extend(Date.prototype, {
6132        initPicker: function(options, defaults) {
6133            //'format' is set directly from settings or data-* attributes
6134
6135            //by default viewformat equals to format
6136            if(!this.options.viewformat) {
6137                this.options.viewformat = this.options.format;
6138            }
6139            
6140            //try parse datepicker config defined as json string in data-datepicker
6141            options.datepicker = $.fn.editableutils.tryParseJson(options.datepicker, true);
6142            
6143            //overriding datepicker config (as by default jQuery extend() is not recursive)
6144            //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
6145            this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
6146                format: this.options.viewformat
6147            });
6148            
6149            //language
6150            this.options.datepicker.language = this.options.datepicker.language || 'en'; 
6151
6152            //store DPglobal
6153            this.dpg = $.fn.bdatepicker.DPGlobal; 
6154
6155            //store parsed formats
6156            this.parsedFormat = this.dpg.parseFormat(this.options.format);
6157            this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);            
6158        },
6159        
6160        render: function () {
6161            this.$input.bdatepicker(this.options.datepicker);
6162            
6163            //"clear" link
6164            if(this.options.clear) {
6165                this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
6166                    e.preventDefault();
6167                    e.stopPropagation();
6168                    this.clear();
6169                }, this));
6170                
6171                this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));  
6172            }                
6173        },
6174        
6175        value2html: function(value, element) {
6176           var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
6177           Date.superclass.value2html.call(this, text, element); 
6178        },
6179
6180        html2value: function(html) {
6181            return this.parseDate(html, this.parsedViewFormat);
6182        },   
6183
6184        value2str: function(value) {
6185            return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
6186        }, 
6187
6188        str2value: function(str) {
6189            return this.parseDate(str, this.parsedFormat);
6190        }, 
6191
6192        value2submit: function(value) {
6193            return this.value2str(value);
6194        },                    
6195
6196        value2input: function(value) {
6197            this.$input.bdatepicker('update', value);
6198        },
6199
6200        input2value: function() { 
6201            return this.$input.data('datepicker').date;
6202        },       
6203
6204        activate: function() {
6205        },
6206
6207        clear:  function() {
6208            this.$input.data('datepicker').date = null;
6209            this.$input.find('.active').removeClass('active');
6210            if(!this.options.showbuttons) {
6211                this.$input.closest('form').submit(); 
6212            }
6213        },
6214
6215        autosubmit: function() {
6216            this.$input.on('mouseup', '.day', function(e){
6217                if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) {
6218                    return;
6219                }
6220                var $form = $(this).closest('form');
6221                setTimeout(function() {
6222                    $form.submit();
6223                }, 200);
6224            });
6225           //changedate is not suitable as it triggered when showing datepicker. see #149
6226           /*
6227           this.$input.on('changeDate', function(e){
6228               var $form = $(this).closest('form');
6229               setTimeout(function() {
6230                   $form.submit();
6231               }, 200);
6232           });
6233           */
6234       },
6235       
6236       /*
6237        For incorrect date bootstrap-datepicker returns current date that is not suitable
6238        for datefield.
6239        This function returns null for incorrect date.  
6240       */
6241       parseDate: function(str, format) {
6242           var date = null, formattedBack;
6243           if(str) {
6244               date = this.dpg.parseDate(str, format, this.options.datepicker.language);
6245               if(typeof str === 'string') {
6246                   formattedBack = this.dpg.formatDate(date, format, this.options.datepicker.language);
6247                   if(str !== formattedBack) {
6248                       date = null;
6249                   }
6250               }
6251           }
6252           return date;
6253       }
6254
6255    });
6256
6257    Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
6258        /**
6259        @property tpl 
6260        @default <div></div>
6261        **/         
6262        tpl:'<div class="editable-date well"></div>',
6263        /**
6264        @property inputclass 
6265        @default null
6266        **/
6267        inputclass: null,
6268        /**
6269        Format used for sending value to server. Also applied when convert
6269ing date from <code>data-value</code> attribute.<br>
6270        Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>  
6271
6272        @property format 
6273        @type string
6274        @default yyyy-mm-dd
6275        **/
6276        format:'yyyy-mm-dd',
6277        /**
6278        Format used for displaying date. Also applied when converting date from element's text on init.   
6279        If not specified equals to <code>format</code>
6280
6281        @property viewformat 
6282        @type string
6283        @default null
6284        **/
6285        viewformat: null,
6286        /**
6287        Configuration of datepicker.
6288        Full list of options: http://bootstrap-datepicker.readthedocs.org/en/latest/options.html
6289
6290        @property datepicker 
6291        @type object
6292        @default {
6293            weekStart: 0,
6294            startView: 0,
6295            minViewMode: 0,
6296            autoclose: false
6297        }
6298        **/
6299        datepicker:{
6300            weekStart: 0,
6301            startView: 0,
6302            minViewMode: 0,
6303            autoclose: false
6304        },
6305        /**
6306        Text shown as clear date button. 
6307        If <code>false</code> clear button will not be rendered.
6308
6309        @property clear 
6310        @type boolean|string
6311        @default 'x clear'
6312        **/
6313        clear: '&times; clear'
6314    });
6315
6316    $.fn.editabletypes.date = Date;
6317
6318}(window.jQuery));
6319
6320/**
6321Bootstrap datefield input - modification for inline mode.
6322Shows normal <input type="text"> and binds popup datepicker.
6323Automatically shown in inline mode.
6324
6325@class datefield
6326@extends date
6327
6328@since 1.4.0
6329**/
6330(function ($) {
6331    "use strict";
6332
6333    var DateField = function (options) {
6334        this.init('datefield', options, DateField.defaults);
6335        this.initPicker(options, DateField.defaults);
6336    };
6337
6338    $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
6339
6340    $.extend(DateField.prototype, {
6341        render: function () {
6342            this.$input = this.$tpl.find('input');
6343            this.setClass();
6344            this.setAttr('placeholder');
6345
6346            //bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js)
6347            this.$input.bdatepicker(this.options.datepicker);
6348
6349            //need to disable original event handlers
6350            this.$input.off('focus keydown');
6351
6352            //update value of datepicker
6353            this.$input.keyup($.proxy(function(){
6354               this.$tpl.removeData('date');
6355               this.$tpl.bdatepicker('update');
6356            }, this));
6357
6358        },
6359
6360       value2input: function(value) {
6361           this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');
6362           this.$tpl.bdatepicker('update');
6363       },
6364
6365       input2value: function() {
6366           return this.html2value(this.$input.val());
6367       },
6368
6369       activate: function() {
6370           $.fn.editabletypes.text.prototype.activate.call(this);
6371       },
6372
6373       autosubmit: function() {
6374         //reset autosubmit to empty
6375       }
6376    });
6377
6378    DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {
6379        /**
6380        @property tpl
6381        **/
6382        tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
6383        /**
6384        @property inputclass
6385        @default 'input-small'
6386        **/
6387        inputclass: 'input-small',
6388
6389        /* datepicker config */
6390        datepicker: {
6391            weekStart: 0,
6392            startView: 0,
6393            minViewMode: 0,
6394            autoclose: true
6395        }
6396    });
6397
6398    $.fn.editabletypes.datefield = DateField;
6399
6400}(window.jQuery));
6401
6402/**
6403Bootstrap-datetimepicker.  
6404Based on [smalot bootstrap-datetimepicker plugin](https://github.com/smalot/bootstrap-datetimepicker). 
6405Before usage you should manually include dependent js and css:
6406
6407    <link href="css/datetimepicker.css" rel="stylesheet" type="text/css"></link> 
6408    <script src="js/bootstrap-datetimepicker.js"></script>
6409
6410For **i18n** you should include js file from here: https://github.com/smalot/bootstrap-datetimepicker/tree/master/js/locales
6411and set `language` option.  
6412
6413@class datetime
6414@extends abstractinput
6415@final
6416@since 1.4.4
6417@example
6418<a href="#" id="last_seen" data-type="datetime" data-pk="1" data-url="/post" title="Select date & time">15/03/2013 12:45</a>
6419<script>
6420$(function(){
6421    $('#last_seen').editable({
6422        format: 'yyyy-mm-dd hh:ii',    
6423        viewformat: 'dd/mm/yyyy hh:ii',    
6424        datetimepicker: {
6425                weekStart: 1
6426           }
6427        }
6428    });
6429});
6430</script>
6431**/
6432(function ($) {
6433    "use strict";
6434
6435    var DateTime = function (options) {
6436        this.init('datetime', options, DateTime.defaults);
6437        this.initPicker(options, DateTime.defaults);
6438    };
6439
6440    $.fn.editableutils.inherit(DateTime, $.fn.editabletypes.abstractinput);
6441
6442    $.extend(DateTime.prototype, {
6443        initPicker: function(options, defaults) {
6444            //'format' is set directly from settings or data-* attributes
6445
6446            //by default viewformat equals to format
6447            if(!this.options.viewformat) {
6448                this.options.viewformat = this.options.format;
6449            }
6450            
6451            //try parse datetimepicker config defined as json string in data-datetimepicker
6452            options.datetimepicker = $.fn.editableutils.tryParseJson(options.datetimepicker, true);
6453
6454            //overriding datetimepicker config (as by default jQuery extend() is not recursive)
6455            //since 1.4 datetimepicker internally uses viewformat instead of format. Format is for submit only
6456            this.options.datetimepicker = $.extend({}, defaults.datetimepicker, options.datetimepicker, {
6457                format: this.options.viewformat
6458            });
6459
6460            //language
6461            this.options.datetimepicker.language = this.options.datetimepicker.language || 'en'; 
6462
6463            //store DPglobal
6464            this.dpg = $.fn.datetimepicker.DPGlobal; 
6465
6466            //store parsed formats
6467            this.parsedFormat = this.dpg.parseFormat(this.options.format, this.options.formatType);
6468            this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat, this.options.formatType);
6469        },
6470
6471        render: function () {
6472            this.$input.datetimepicker(this.options.datetimepicker);
6473
6474            //adjust container position when viewMode changes
6475            //see https://github.com/smalot/bootstrap-datetimepicker/pull/80
6476            this.$input.on('changeMode', function(e) {
6477                var f = $(this).closest('form').parent();
6478                //timeout here, otherwise container changes position before form has new size
6479                setTimeout(function(){
6480                    f.triggerHandler('resize');
6481                }, 0);
6482            });
6483
6484            //"clear" link
6485            if(this.options.clear) {
6486                this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
6487                    e.preventDefault();
6488                    e.stopPropagation();
6489                    this.clear();
6490                }, this));
6491
6492                this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));  
6493            }
6494        },
6495
6496        value2html: function(value, element) {
6497            //formatDate works with UTCDate!
6498            var text = value ? this.dpg.formatDate(this.toUTC(value), this.parsedViewFormat, this.options.datetimepicker.language, this.options.formatType) : '';
6499            if(element) {
6500                DateTime.superclass.value2html.call(this, text, element);
6501            } else {
6502                return text;
6503            }
6504        },
6505
6506        html2value: function(html) {
6507            //parseDate return utc date!
6508            var value = this.parseDate(html, this.parsedViewFormat); 
6509            return value ? this.fromUTC(value) : null;
6510        },
6511
6512        value2str: function(value) {
6513            //formatDate works with UTCDate!
6514            return value ? this.dpg.formatDate(this.toUTC(value), this.parsedFormat, this.options.datetimepicker.language, this.options.formatType) : '';
6515       },
6516
6517       str2value: function(str) {
6518           //parseDate return utc date!
6519           var value = this.parseDate(str, this.parsedFormat);
6520           return value ? this.fromUTC(value) : null;
6521       },
6522
6523       value2submit: function(value) {
6524           return this.value2str(value);
6525       },
6526
6527       value2input: function(value) {
6528           if(value) {
6529             this.$input.data('datetimepicker').setDate(value);
6530           }
6531       },
6532
6533       input2value: function() { 
6534           //date may be cleared, in that case getDate() triggers error
6535           var dt = this.$input.data('datetimepicker');
6536           return dt.date ? dt.getDate() : null;
6537       },
6538
6539       activate: function() {
6540       },
6541
6542       clear: function() {
6543          this.$input.data('datetimepicker').date = null;
6544          this.$input.find('.active').removeClass('active');
6545          if(!this.options.showbuttons) {
6546             this.$input.closest('form').submit(); 
6547          }          
6548       },
6549
6550       autosubmit: function() {
6551           this.$input.on('mouseup', '.minute', function(e){
6552               var $form = $(this).closest('form');
6553               setTimeout(function() {
6554                   $form.submit();
6555               }, 200);
6556           });
6557       },
6558
6559       //convert date from local to utc
6560       toUTC: function(value) {
6561         return value ? new Date(value.valueOf() - value.getTimezoneOffset() * 60000) : value;  
6562       },
6563
6564       //convert date from utc to local
6565       fromUTC: function(value) {
6566         return value ? new Date(value.valueOf() + value.getTimezoneOffset() * 60000) : value;  
6567       },
6568
6569       /*
6570        For incorrect date bootstrap-datetimepicker returns current date that is not suitable
6571        for datetimefield.
6572        This function returns null for incorrect date.  
6573       */
6574       parseDate: function(str, format) {
6575           var date = null, formattedBack;
6576           if(str) {
6577               date = this.dpg.parseDate(str, format, this.options.datetimepicker.language, this.options.formatType);
6578               if(typeof str === 'string') {
6579                   formattedBack = this.dpg.formatDate(date, format, this.options.datetimepicker.language, this.options.formatType);
6580                   if(str !== formattedBack) {
6581                       date = null;
6582                   } 
6583               }
6584           }
6585           return date;
6586       }
6587
6588    });
6589
6590    DateTime.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
6591        /**
6592        @property tpl 
6593        @default <div></div>
6594        **/         
6595        tpl:'<div class="editable-date well"></div>',
6596        /**
6597        @property inputclass 
6598        @default null
6599        **/
6600        inputclass: null,
6601        /**
6602        Format used for sending value to server. Also applied when convert
6602ing date from <code>data-value</code> attribute.<br>
6603        Possible tokens are: <code>d, dd, m, mm, yy, yyyy, h, i</code>  
6604        
6605        @property format 
6606        @type string
6607        @default yyyy-mm-dd hh:ii
6608        **/         
6609        format:'yyyy-mm-dd hh:ii',
6610        formatType:'standard',
6611        /**
6612        Format used for displaying date. Also applied when converting date from element's text on init.   
6613        If not specified equals to <code>format</code>
6614        
6615        @property viewformat 
6616        @type string
6617        @default null
6618        **/
6619        viewformat: null,
6620        /**
6621        Configuration of datetimepicker.
6622        Full list of options: https://github.com/smalot/bootstrap-datetimepicker
6623
6624        @property datetimepicker 
6625        @type object
6626        @default { }
6627        **/
6628        datetimepicker:{
6629            todayHighlight: false,
6630            autoclose: false
6631        },
6632        /**
6633        Text shown as clear date button. 
6634        If <code>false</code> clear button will not be rendered.
6635
6636        @property clear 
6637        @type boolean|string
6638        @default 'x clear'
6639        **/
6640        clear: '&times; clear'
6641    });
6642
6643    $.fn.editabletypes.datetime = DateTime;
6644
6645}(window.jQuery));
6646/**
6647Bootstrap datetimefield input - datetime input for inline mode.
6648Shows normal <input type="text"> and binds popup datetimepicker.  
6649Automatically shown in inline mode.
6650
6651@class datetimefield
6652@extends datetime
6653
6654**/
6655(function ($) {
6656    "use strict";
6657    
6658    var DateTimeField = function (options) {
6659        this.init('datetimefield', options, DateTimeField.defaults);
6660        this.initPicker(options, DateTimeField.defaults);
6661    };
6662
6663    $.fn.editableutils.inherit(DateTimeField, $.fn.editabletypes.datetime);
6664    
6665    $.extend(DateTimeField.prototype, {
6666        render: function () {
6667            this.$input = this.$tpl.find('input');
6668            this.setClass();
6669            this.setAttr('placeholder');
6670            
6671            this.$tpl.datetimepicker(this.options.datetimepicker);
6672            
6673            //need to disable original event handlers
6674            this.$input.off('focus keydown');
6675            
6676            //update value of datepicker
6677            this.$input.keyup($.proxy(function(){
6678               this.$tpl.removeData('date');
6679               this.$tpl.datetimepicker('update');
6680            }, this));
6681            
6682        },   
6683      
6684       value2input: function(value) {
6685           this.$input.val(this.value2html(value));
6686           this.$tpl.datetimepicker('update');
6687       },
6688        
6689       input2value: function() { 
6690           return this.html2value(this.$input.val());
6691       },              
6692        
6693       activate: function() {
6694           $.fn.editabletypes.text.prototype.activate.call(this);
6695       },
6696       
6697       autosubmit: function() {
6698         //reset autosubmit to empty  
6699       }
6700    });
6701    
6702    DateTimeField.defaults = $.extend({}, $.fn.editabletypes.datetime.defaults, {
6703        /**
6704        @property tpl 
6705        **/         
6706        tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
6707        /**
6708        @property inputclass 
6709        @default 'input-medium'
6710        **/         
6711        inputclass: 'input-medium',
6712        
6713        /* datetimepicker config */
6714        datetimepicker:{
6715            todayHighlight: false,
6716            autoclose: true
6717        }
6718    });
6719    
6720    $.fn.editabletypes.datetimefield = DateTimeField;
6721
6722}(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.