source: pro-violet-viettel/docs/template/assets/js/uncompressed/x-editable/bootstrap-editable.js

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