Subversion Repositories wpShopGermany4

Rev

Details | Last modification | View Log | RSS feed

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