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