PageSourceSearch

https://khub.net/combo?browserId=chrome&minifierType=js&languageId…be.digitalplace.communities.group.taglib.js

js khub.net collected 2026-09-24 17:22:18 UTC 245,462 bytes, 7,392 lines download raw bytes

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

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