x-editable-rails 0.0.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +1 -1
  3. data/README.md +31 -14
  4. data/lib/x-editable-rails.rb +5 -1
  5. data/lib/x-editable-rails/version.rb +1 -1
  6. data/lib/x-editable-rails/view_helpers.rb +20 -0
  7. data/vendor/assets/images/editable/clear.png +0 -0
  8. data/vendor/assets/images/{loading.gif → editable/loading.gif} +0 -0
  9. data/vendor/assets/javascripts/{bootstrap-editable.js → editable/bootstrap-editable.js} +3816 -1425
  10. data/vendor/assets/javascripts/{jquery-editable-poshytip.js → editable/jquery-editable-poshytip.js} +2171 -541
  11. data/vendor/assets/javascripts/{jqueryui-editable.js → editable/jqueryui-editable.js} +2080 -478
  12. data/vendor/assets/javascripts/{rails-editable.js.coffee → editable/rails.js.coffee} +3 -0
  13. data/vendor/assets/stylesheets/editable/bootstrap-editable.css +495 -0
  14. data/vendor/assets/stylesheets/{jquery-editable.css → editable/jquery-editable.css} +63 -18
  15. data/vendor/assets/stylesheets/{jqueryui-editable.css → editable/jqueryui-editable.css} +63 -18
  16. metadata +79 -66
  17. data/.gitignore +0 -19
  18. data/Gemfile +0 -4
  19. data/Rakefile +0 -1
  20. data/vendor/assets/javascripts/bootstrap-editable-inline.js +0 -3769
  21. data/vendor/assets/javascripts/bootstrap-editable-inline.min.js +0 -5
  22. data/vendor/assets/javascripts/bootstrap-editable.min.js +0 -5
  23. data/vendor/assets/javascripts/jquery-editable-inline.js +0 -2892
  24. data/vendor/assets/javascripts/jquery-editable-inline.min.js +0 -5
  25. data/vendor/assets/javascripts/jquery-editable-poshytip.min.js +0 -5
  26. data/vendor/assets/javascripts/jqueryui-editable-inline.js +0 -2916
  27. data/vendor/assets/javascripts/jqueryui-editable-inline.min.js +0 -5
  28. data/vendor/assets/javascripts/jqueryui-editable.min.js +0 -5
  29. data/vendor/assets/stylesheets/bootstrap-editable.css +0 -434
  30. data/x-editable-rails.gemspec +0 -19
@@ -1,7 +1,7 @@
1
- /*! X-editable - v1.3.0
1
+ /*! X-editable - v1.4.4
2
2
  * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
3
3
  * http://github.com/vitalets/x-editable
4
- * Copyright (c) 2012 Vitaliy Potapov; Licensed MIT */
4
+ * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
5
5
 
6
6
  /**
7
7
  Form with single input element, two buttons and two states: normal/loading.
@@ -13,38 +13,36 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
13
13
  @uses textarea
14
14
  **/
15
15
  (function ($) {
16
-
16
+ "use strict";
17
+
17
18
  var EditableForm = function (div, options) {
18
19
  this.options = $.extend({}, $.fn.editableform.defaults, options);
19
- this.$div = $(div); //div, containing form. Not form tag! Not editable-element.
20
+ this.$div = $(div); //div, containing form. Not form tag. Not editable-element.
20
21
  if(!this.options.scope) {
21
22
  this.options.scope = this;
22
23
  }
23
- this.initInput();
24
+ //nothing shown after init
24
25
  };
25
26
 
26
27
  EditableForm.prototype = {
27
28
  constructor: EditableForm,
28
29
  initInput: function() { //called once
29
- var TypeConstructor, typeOptions;
30
-
31
- //create input of specified type
32
- if(typeof $.fn.editabletypes[this.options.type] === 'function') {
33
- TypeConstructor = $.fn.editabletypes[this.options.type];
34
- typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
35
- this.input = new TypeConstructor(typeOptions);
36
- } else {
37
- $.error('Unknown type: '+ this.options.type);
38
- return;
39
- }
40
-
30
+ //take input from options (as it is created in editable-element)
31
+ this.input = this.options.input;
32
+
33
+ //set initial value
34
+ //todo: may be add check: typeof str === 'string' ?
41
35
  this.value = this.input.str2value(this.options.value);
42
36
  },
43
37
  initTemplate: function() {
44
38
  this.$form = $($.fn.editableform.template);
45
39
  },
46
40
  initButtons: function() {
47
- this.$form.find('.editable-buttons').append($.fn.editableform.buttons);
41
+ var $btn = this.$form.find('.editable-buttons');
42
+ $btn.append($.fn.editableform.buttons);
43
+ if(this.options.showbuttons === 'bottom') {
44
+ $btn.addClass('editable-buttons-bottom');
45
+ }
48
46
  },
49
47
  /**
50
48
  Renders editableform
@@ -52,47 +50,49 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
52
50
  @method render
53
51
  **/
54
52
  render: function() {
53
+ //init loader
55
54
  this.$loading = $($.fn.editableform.loading);
56
55
  this.$div.empty().append(this.$loading);
57
- this.showLoading();
58
56
 
59
57
  //init form template and buttons
60
- this.initTemplate();
58
+ this.initTemplate();
61
59
  if(this.options.showbuttons) {
62
60
  this.initButtons();
63
61
  } else {
64
62
  this.$form.find('.editable-buttons').remove();
65
63
  }
66
64
 
65
+ //show loading state
66
+ this.showLoading();
67
+
67
68
  /**
68
69
  Fired when rendering starts
69
70
  @event rendering
70
71
  @param {Object} event event object
71
72
  **/
72
73
  this.$div.triggerHandler('rendering');
74
+
75
+ //init input
76
+ this.initInput();
77
+
78
+ //append input to form
79
+ this.input.prerender();
80
+ this.$form.find('div.editable-input').append(this.input.$tpl);
73
81
 
82
+ //append form to container
83
+ this.$div.append(this.$form);
84
+
74
85
  //render input
75
86
  $.when(this.input.render())
76
87
  .then($.proxy(function () {
77
- //input
78
- this.$form.find('div.editable-input').append(this.input.$input);
79
-
80
- //automatically submit inputs when no buttons shown
88
+ //setup input to submit automatically when no buttons shown
81
89
  if(!this.options.showbuttons) {
82
90
  this.input.autosubmit();
83
91
  }
84
-
85
- //"clear" link
86
- if(this.input.$clear) {
87
- this.$form.find('div.editable-input').append($('<div class="editable-clear">').append(this.input.$clear));
88
- }
89
-
90
- //append form to container
91
- this.$div.append(this.$form);
92
92
 
93
93
  //attach 'cancel' handler
94
94
  this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
95
-
95
+
96
96
  if(this.input.error) {
97
97
  this.error(this.input.error);
98
98
  this.$form.find('.editable-submit').attr('disabled', true);
@@ -116,6 +116,11 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
116
116
  this.$div.triggerHandler('rendered');
117
117
 
118
118
  this.showForm();
119
+
120
+ //call postrender method to perform actions required visibility of form
121
+ if(this.input.postrender) {
122
+ this.input.postrender();
123
+ }
119
124
  }, this));
120
125
  },
121
126
  cancel: function() {
@@ -127,11 +132,17 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
127
132
  this.$div.triggerHandler('cancel');
128
133
  },
129
134
  showLoading: function() {
130
- var w;
135
+ var w, h;
131
136
  if(this.$form) {
132
- //set loading size equal to form
133
- this.$loading.width(this.$form.outerWidth());
134
- this.$loading.height(this.$form.outerHeight());
137
+ //set loading size equal to form
138
+ w = this.$form.outerWidth();
139
+ h = this.$form.outerHeight();
140
+ if(w) {
141
+ this.$loading.width(w);
142
+ }
143
+ if(h) {
144
+ this.$loading.height(h);
145
+ }
135
146
  this.$form.hide();
136
147
  } else {
137
148
  //stretch loading to fill container width
@@ -159,14 +170,23 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
159
170
 
160
171
  error: function(msg) {
161
172
  var $group = this.$form.find('.control-group'),
162
- $block = this.$form.find('.editable-error-block');
173
+ $block = this.$form.find('.editable-error-block'),
174
+ lines;
163
175
 
164
176
  if(msg === false) {
165
177
  $group.removeClass($.fn.editableform.errorGroupClass);
166
178
  $block.removeClass($.fn.editableform.errorBlockClass).empty().hide();
167
179
  } else {
180
+ //convert newline to <br> for more pretty error display
181
+ if(msg) {
182
+ lines = msg.split("\n");
183
+ for (var i = 0; i < lines.length; i++) {
184
+ lines[i] = $('<div>').text(lines[i]).html();
185
+ }
186
+ msg = lines.join('<br>');
187
+ }
168
188
  $group.addClass($.fn.editableform.errorGroupClass);
169
- $block.addClass($.fn.editableform.errorBlockClass).text(msg).show();
189
+ $block.addClass($.fn.editableform.errorBlockClass).html(msg).show();
170
190
  }
171
191
  },
172
192
 
@@ -218,6 +238,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
218
238
  }
219
239
 
220
240
  //if success callback returns object like {newValue: <something>} --> use that value instead of submitted
241
+ //it is usefull if you want to chnage value in url-function
221
242
  if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
222
243
  newValue = res.newValue;
223
244
  }
@@ -241,8 +262,15 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
241
262
  this.$div.triggerHandler('save', {newValue: newValue, response: response});
242
263
  }, this))
243
264
  .fail($.proxy(function(xhr) {
244
- this.error(typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!');
245
- this.showForm();
265
+ var msg;
266
+ if(typeof this.options.error === 'function') {
267
+ msg = this.options.error.call(this.options.scope, xhr, newValue);
268
+ } else {
269
+ msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';
270
+ }
271
+
272
+ this.error(msg);
273
+ this.showForm();
246
274
  }, this));
247
275
  },
248
276
 
@@ -254,7 +282,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
254
282
  this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true);
255
283
 
256
284
  var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
257
- send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk)))),
285
+ send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),
258
286
  params;
259
287
 
260
288
  if (send) { //send to server
@@ -299,10 +327,15 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
299
327
  },
300
328
 
301
329
  option: function(key, value) {
302
- this.options[key] = value;
330
+ if(key in this.options) {
331
+ this.options[key] = value;
332
+ }
333
+
303
334
  if(key === 'value') {
304
335
  this.setValue(value);
305
336
  }
337
+
338
+ //do not pass option to input as it is passed in editable-element
306
339
  },
307
340
 
308
341
  setValue: function(value, convertStr) {
@@ -311,6 +344,11 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
311
344
  } else {
312
345
  this.value = value;
313
346
  }
347
+
348
+ //if form is visible, update input
349
+ if(this.$form && this.$form.is(':visible')) {
350
+ this.input.value2input(this.value);
351
+ }
314
352
  }
315
353
  };
316
354
 
@@ -363,18 +401,25 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
363
401
  type: 'text',
364
402
  /**
365
403
  Url for submit, e.g. <code>'/post'</code>
366
- If function - it will be called instead of ajax. Function can return deferred object to run fail/done callbacks.
404
+ If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks.
367
405
 
368
406
  @property url
369
407
  @type string|function
370
408
  @default null
371
409
  @example
372
410
  url: function(params) {
411
+ var d = new $.Deferred;
373
412
  if(params.value === 'abc') {
374
- var d = new $.Deferred;
375
- return d.reject('field cannot be "abc"'); //returning error via deferred object
413
+ return d.reject('error message'); //returning error via deferred object
376
414
  } else {
377
- someModel.set(params.name, params.value); //save data in some js model
415
+ //async saving data in js model
416
+ someModel.asyncSaveMethod({
417
+ ...,
418
+ success: function(){
419
+ d.resolve();
420
+ }
421
+ });
422
+ return d.promise();
378
423
  }
379
424
  }
380
425
  **/
@@ -445,7 +490,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
445
490
  validate: null,
446
491
  /**
447
492
  Success callback. Called when value successfully sent on server and **response status = 200**.
448
- Useful to work with json response. For example, if your backend response can be <code>{success: true}</code>
493
+ Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code>
449
494
  or <code>{success: false, msg: "server error"}</code> you can check it inside this callback.
450
495
  If it returns **string** - means error occured and string is shown as error message.
451
496
  If it returns **object like** <code>{newValue: &lt;something&gt;}</code> - it overwrites value, submitted by user.
@@ -461,26 +506,45 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
461
506
  **/
462
507
  success: null,
463
508
  /**
464
- Additional options for ajax request.
509
+ Error callback. Called when request failed (response status != 200).
510
+ Usefull when you want to parse error response and display a custom message.
511
+ Must return **string** - the message to be displayed in the error block.
512
+
513
+ @property error
514
+ @type function
515
+ @default null
516
+ @since 1.4.4
517
+ @example
518
+ error: function(response, newValue) {
519
+ if(response.status === 500) {
520
+ return 'Service unavailable. Please try later.';
521
+ } else {
522
+ return response.responseText;
523
+ }
524
+ }
525
+ **/
526
+ error: null,
527
+ /**
528
+ Additional options for submit ajax request.
465
529
  List of values: http://api.jquery.com/jQuery.ajax
466
-
530
+
467
531
  @property ajaxOptions
468
532
  @type object
469
533
  @default null
470
534
  @since 1.1.1
535
+ @example
536
+ ajaxOptions: {
537
+ type: 'put',
538
+ dataType: 'json'
539
+ }
471
540
  **/
472
541
  ajaxOptions: null,
473
542
  /**
474
- Whether to show buttons or not.
475
- Form without buttons can be auto-submitted by input or by onblur = 'submit'.
476
- @example
477
- ajaxOptions: {
478
- method: 'PUT',
479
- dataType: 'xml'
480
- }
543
+ Where to show buttons: left(true)|bottom|false
544
+ Form without buttons is auto-submitted.
481
545
 
482
546
  @property showbuttons
483
- @type boolean
547
+ @type boolean|string
484
548
  @default true
485
549
  @since 1.1.1
486
550
  **/
@@ -504,7 +568,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
504
568
  @default false
505
569
  @since 1.2.0
506
570
  **/
507
- savenochange: false
571
+ savenochange: false
508
572
  };
509
573
 
510
574
  /*
@@ -530,11 +594,14 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
530
594
 
531
595
  //error class attached to editable-error-block
532
596
  $.fn.editableform.errorBlockClass = 'editable-error';
533
- }(window.jQuery));
597
+ }(window.jQuery));
598
+
534
599
  /**
535
600
  * EditableForm utilites
536
601
  */
537
602
  (function ($) {
603
+ "use strict";
604
+
538
605
  //utils
539
606
  $.fn.editableutils = {
540
607
  /**
@@ -621,19 +688,22 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
621
688
  return newObj;
622
689
  },
623
690
 
624
- /**
625
- * exclude complex objects from $.data() before pass to config
691
+ /*
692
+ exclude complex objects from $.data() before pass to config
626
693
  */
627
694
  getConfigData: function($element) {
628
695
  var data = {};
629
696
  $.each($element.data(), function(k, v) {
630
- if(typeof v !== 'object' || (v && typeof v === 'object' && v.constructor === Object)) {
697
+ if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
631
698
  data[k] = v;
632
699
  }
633
700
  });
634
701
  return data;
635
702
  },
636
703
 
704
+ /*
705
+ returns keys of object
706
+ */
637
707
  objectKeys: function(o) {
638
708
  if (Object.keys) {
639
709
  return Object.keys(o);
@@ -657,9 +727,100 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
657
727
  **/
658
728
  escape: function(str) {
659
729
  return $('<div>').text(str).html();
660
- }
730
+ },
731
+
732
+ /*
733
+ returns array items from sourceData having value property equal or inArray of 'value'
734
+ */
735
+ itemsByValue: function(value, sourceData, valueProp) {
736
+ if(!sourceData || value === null) {
737
+ return [];
738
+ }
739
+
740
+ valueProp = valueProp || 'value';
741
+
742
+ var isValArray = $.isArray(value),
743
+ result = [],
744
+ that = this;
745
+
746
+ $.each(sourceData, function(i, o) {
747
+ if(o.children) {
748
+ result = result.concat(that.itemsByValue(value, o.children, valueProp));
749
+ } else {
750
+ /*jslint eqeq: true*/
751
+ if(isValArray) {
752
+ if($.grep(value, function(v){ return v == (o && typeof o === 'object' ? o[valueProp] : o); }).length) {
753
+ result.push(o);
754
+ }
755
+ } else {
756
+ if(value == (o && typeof o === 'object' ? o[valueProp] : o)) {
757
+ result.push(o);
758
+ }
759
+ }
760
+ /*jslint eqeq: false*/
761
+ }
762
+ });
763
+
764
+ return result;
765
+ },
766
+
767
+ /*
768
+ Returns input by options: type, mode.
769
+ */
770
+ createInput: function(options) {
771
+ var TypeConstructor, typeOptions, input,
772
+ type = options.type;
773
+
774
+ //`date` is some kind of virtual type that is transformed to one of exact types
775
+ //depending on mode and core lib
776
+ if(type === 'date') {
777
+ //inline
778
+ if(options.mode === 'inline') {
779
+ if($.fn.editabletypes.datefield) {
780
+ type = 'datefield';
781
+ } else if($.fn.editabletypes.dateuifield) {
782
+ type = 'dateuifield';
783
+ }
784
+ //popup
785
+ } else {
786
+ if($.fn.editabletypes.date) {
787
+ type = 'date';
788
+ } else if($.fn.editabletypes.dateui) {
789
+ type = 'dateui';
790
+ }
791
+ }
792
+
793
+ //if type still `date` and not exist in types, replace with `combodate` that is base input
794
+ if(type === 'date' && !$.fn.editabletypes.date) {
795
+ type = 'combodate';
796
+ }
797
+ }
798
+
799
+ //`datetime` should be datetimefield in 'inline' mode
800
+ if(type === 'datetime' && options.mode === 'inline') {
801
+ type = 'datetimefield';
802
+ }
803
+
804
+ //change wysihtml5 to textarea for jquery UI and plain versions
805
+ if(type === 'wysihtml5' && !$.fn.editabletypes[type]) {
806
+ type = 'textarea';
807
+ }
808
+
809
+ //create input of specified type. Input will be used for converting value, not in form
810
+ if(typeof $.fn.editabletypes[type] === 'function') {
811
+ TypeConstructor = $.fn.editabletypes[type];
812
+ typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults));
813
+ input = new TypeConstructor(typeOptions);
814
+ return input;
815
+ } else {
816
+ $.error('Unknown type: '+ type);
817
+ return false;
818
+ }
819
+ }
820
+
661
821
  };
662
- }(window.jQuery));
822
+ }(window.jQuery));
823
+
663
824
  /**
664
825
  Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
665
826
  This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
@@ -670,20 +831,30 @@ Applied as jQuery method.
670
831
  @uses editableform
671
832
  **/
672
833
  (function ($) {
834
+ "use strict";
673
835
 
674
- var EditableContainer = function (element, options) {
836
+ var Popup = function (element, options) {
675
837
  this.init(element, options);
676
838
  };
839
+
840
+ var Inline = function (element, options) {
841
+ this.init(element, options);
842
+ };
677
843
 
678
844
  //methods
679
- EditableContainer.prototype = {
845
+ Popup.prototype = {
680
846
  containerName: null, //tbd in child class
681
847
  innerCss: null, //tbd in child class
848
+ containerClass: 'editable-container editable-popup', //css class applied to container element
682
849
  init: function(element, options) {
683
850
  this.$element = $(element);
684
- //todo: what is in priority: data or js?
685
- this.options = $.extend({}, $.fn.editableContainer.defaults, $.fn.editableutils.getConfigData(this.$element), options);
851
+ //since 1.4.1 container do not use data-* directly as they already merged into options.
852
+ this.options = $.extend({}, $.fn.editableContainer.defaults, options);
686
853
  this.splitOptions();
854
+
855
+ //set scope of form callbacks to element
856
+ this.formOptions.scope = this.$element[0];
857
+
687
858
  this.initContainer();
688
859
 
689
860
  //bind 'destroyed' listener to destroy container when element is removed from dom
@@ -691,7 +862,7 @@ Applied as jQuery method.
691
862
  this.destroy();
692
863
  }, this));
693
864
 
694
- //attach document handlers (once)
865
+ //attach document handler to close containers on click / escape
695
866
  if(!$(document).data('editable-handlers-attached')) {
696
867
  //close all on escape
697
868
  $(document).on('keyup.editable', function (e) {
@@ -701,17 +872,39 @@ Applied as jQuery method.
701
872
  }
702
873
  });
703
874
 
704
- //close containers when click outside
875
+ //close containers when click outside
876
+ //(mousedown could be better than click, it closes everything also on drag drop)
705
877
  $(document).on('click.editable', function(e) {
706
- var $target = $(e.target);
878
+ var $target = $(e.target), i,
879
+ exclude_classes = ['.editable-container',
880
+ '.ui-datepicker-header',
881
+ '.datepicker', //in inline mode datepicker is rendered into body
882
+ '.modal-backdrop',
883
+ '.bootstrap-wysihtml5-insert-image-modal',
884
+ '.bootstrap-wysihtml5-insert-link-modal'
885
+ ];
707
886
 
708
- //if click inside some editableContainer --> no nothing
709
- if($target.is('.editable-container') || $target.parents('.editable-container').length || $target.parents('.ui-datepicker-header').length) {
710
- return;
711
- } else {
712
- //close all open containers (except one)
713
- EditableContainer.prototype.closeOthers(e.target);
887
+ //check if element is detached. It occurs when clicking in bootstrap datepicker
888
+ if (!$.contains(document.documentElement, e.target)) {
889
+ return;
890
+ }
891
+
892
+ //for some reason FF 20 generates extra event (click) in select2 widget with e.target = document
893
+ //we need to filter it via construction below. See https://github.com/vitalets/x-editable/issues/199
894
+ //Possibly related to http://stackoverflow.com/questions/10119793/why-does-firefox-react-differently-from-webkit-and-ie-to-click-event-on-selec
895
+ if($target.is(document)) {
896
+ return;
897
+ }
898
+
899
+ //if click inside one of exclude classes --> no nothing
900
+ for(i=0; i<exclude_classes.length; i++) {
901
+ if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) {
902
+ return;
903
+ }
714
904
  }
905
+
906
+ //close all open containers (except one - target)
907
+ Popup.prototype.closeOthers(e.target);
715
908
  });
716
909
 
717
910
  $(document).data('editable-handlers-attached', true);
@@ -722,7 +915,13 @@ Applied as jQuery method.
722
915
  splitOptions: function() {
723
916
  this.containerOptions = {};
724
917
  this.formOptions = {};
918
+
919
+ if(!$.fn[this.containerName]) {
920
+ throw new Error(this.containerName + ' not found. Have you included corresponding js file?');
921
+ }
922
+
725
923
  var cDef = $.fn[this.containerName].defaults;
924
+ //keys defined in container defaults go to container, others go to form
726
925
  for(var k in this.options) {
727
926
  if(k in cDef) {
728
927
  this.containerOptions[k] = this.options[k];
@@ -732,59 +931,66 @@ Applied as jQuery method.
732
931
  }
733
932
  },
734
933
 
934
+ /*
935
+ Returns jquery object of container
936
+ @method tip()
937
+ */
938
+ tip: function() {
939
+ return this.container() ? this.container().$tip : null;
940
+ },
941
+
942
+ /* returns container object */
943
+ container: function() {
944
+ return this.$element.data(this.containerDataName || this.containerName);
945
+ },
946
+
947
+ /* call native method of underlying container, e.g. this.$element.popover('method') */
948
+ call: function() {
949
+ this.$element[this.containerName].apply(this.$element, arguments);
950
+ },
951
+
735
952
  initContainer: function(){
736
953
  this.call(this.containerOptions);
737
954
  },
738
955
 
739
- initForm: function() {
740
- this.formOptions.scope = this.$element[0]; //set scope of form callbacks to element
741
- this.$form = $('<div>')
956
+ renderForm: function() {
957
+ this.$form
742
958
  .editableform(this.formOptions)
743
959
  .on({
744
- save: $.proxy(this.save, this),
745
- cancel: $.proxy(function(){ this.hide('cancel'); }, this),
746
- nochange: $.proxy(function(){ this.hide('nochange'); }, this),
960
+ save: $.proxy(this.save, this), //click on submit button (value changed)
961
+ nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)
962
+ cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button
747
963
  show: $.proxy(this.setPosition, this), //re-position container every time form is shown (occurs each time after loading state)
748
964
  rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
965
+ resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
749
966
  rendered: $.proxy(function(){
750
967
  /**
751
- Fired when container is shown and form is rendered (for select will wait for loading dropdown options)
968
+ Fired when container is shown and form is rendered (for select will wait for loading dropdown options).
969
+ **Note:** Bootstrap popover has own `shown` event that now cannot be separated from x-editable's one.
970
+ The workaround is to check `arguments.length` that is always `2` for x-editable.
752
971
 
753
972
  @event shown
754
973
  @param {Object} event event object
755
974
  @example
756
- $('#username').on('shown', function() {
757
- var $tip = $(this).data('editableContainer').tip();
758
- $tip.find('input').val('overwriting value of input..');
975
+ $('#username').on('shown', function(e, editable) {
976
+ editable.input.$input.val('overwriting value of input..');
759
977
  });
760
978
  **/
761
- this.$element.triggerHandler('shown');
979
+ /*
980
+ 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.
981
+ */
982
+ this.$element.triggerHandler('shown', this);
762
983
  }, this)
763
- });
764
- return this.$form;
984
+ })
985
+ .editableform('render');
765
986
  },
766
987
 
767
- /*
768
- Returns jquery object of container
769
- @method tip()
770
- */
771
- tip: function() {
772
- return this.container().$tip;
773
- },
774
-
775
- container: function() {
776
- return this.$element.data(this.containerName);
777
- },
778
-
779
- call: function() {
780
- this.$element[this.containerName].apply(this.$element, arguments);
781
- },
782
-
783
988
  /**
784
989
  Shows container with form
785
990
  @method show()
786
991
  @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
787
- **/
992
+ **/
993
+ /* Note: poshytip owerwrites this method totally! */
788
994
  show: function (closeAll) {
789
995
  this.$element.addClass('editable-open');
790
996
  if(closeAll !== false) {
@@ -792,16 +998,37 @@ Applied as jQuery method.
792
998
  this.closeOthers(this.$element[0]);
793
999
  }
794
1000
 
1001
+ //show container itself
795
1002
  this.innerShow();
796
- },
797
-
798
- /* internal show method. To be overwritten in child classes */
799
- innerShow: function () {
800
- this.call('show');
801
- this.tip().addClass('editable-container');
802
- this.initForm();
803
- this.tip().find(this.innerCss).empty().append(this.$form);
804
- this.$form.editableform('render');
1003
+ this.tip().addClass(this.containerClass);
1004
+
1005
+ /*
1006
+ Currently, form is re-rendered on every show.
1007
+ The main reason is that we dont know, what container will do with content when closed:
1008
+ remove(), detach() or just hide().
1009
+
1010
+ Detaching form itself before hide and re-insert before show is good solution,
1011
+ but visually it looks ugly, as container changes size before hide.
1012
+ */
1013
+
1014
+ //if form already exist - delete previous data
1015
+ if(this.$form) {
1016
+ //todo: destroy prev data!
1017
+ //this.$form.destroy();
1018
+ }
1019
+
1020
+ this.$form = $('<div>');
1021
+
1022
+ //insert form into container body
1023
+ if(this.tip().is(this.innerCss)) {
1024
+ //for inline container
1025
+ this.tip().append(this.$form);
1026
+ } else {
1027
+ this.tip().find(this.innerCss).append(this.$form);
1028
+ }
1029
+
1030
+ //render form
1031
+ this.renderForm();
805
1032
  },
806
1033
 
807
1034
  /**
@@ -813,14 +1040,18 @@ Applied as jQuery method.
813
1040
  if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
814
1041
  return;
815
1042
  }
1043
+
816
1044
  this.$element.removeClass('editable-open');
817
1045
  this.innerHide();
1046
+
818
1047
  /**
819
- Fired when container was hidden. It occurs on both save or cancel.
1048
+ Fired when container was hidden. It occurs on both save or cancel.
1049
+ **Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.
1050
+ The workaround is to check `arguments.length` that is always `2` for x-editable.
820
1051
 
821
1052
  @event hidden
822
1053
  @param {object} event event object
823
- @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
1054
+ @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|manual</code>
824
1055
  @example
825
1056
  $('#username').on('hidden', function(e, reason) {
826
1057
  if(reason === 'save' || reason === 'cancel') {
@@ -829,12 +1060,17 @@ Applied as jQuery method.
829
1060
  }
830
1061
  });
831
1062
  **/
832
- this.$element.triggerHandler('hidden', reason);
1063
+ this.$element.triggerHandler('hidden', reason || 'manual');
833
1064
  },
834
1065
 
1066
+ /* internal show method. To be overwritten in child classes */
1067
+ innerShow: function () {
1068
+
1069
+ },
1070
+
835
1071
  /* internal hide method. To be overwritten in child classes */
836
1072
  innerHide: function () {
837
- this.call('hide');
1073
+
838
1074
  },
839
1075
 
840
1076
  /**
@@ -843,7 +1079,7 @@ Applied as jQuery method.
843
1079
  @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
844
1080
  **/
845
1081
  toggle: function(closeAll) {
846
- if(this.tip && this.tip().is(':visible')) {
1082
+ if(this.container() && this.tip() && this.tip().is(':visible')) {
847
1083
  this.hide();
848
1084
  } else {
849
1085
  this.show(closeAll);
@@ -859,7 +1095,6 @@ Applied as jQuery method.
859
1095
  },
860
1096
 
861
1097
  save: function(e, params) {
862
- this.hide('save');
863
1098
  /**
864
1099
  Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
865
1100
 
@@ -880,6 +1115,9 @@ Applied as jQuery method.
880
1115
  });
881
1116
  **/
882
1117
  this.$element.triggerHandler('save', params);
1118
+
1119
+ //hide must be after trigger, as saving value may require methods od plugin, applied to input
1120
+ this.hide('save');
883
1121
  },
884
1122
 
885
1123
  /**
@@ -911,9 +1149,17 @@ Applied as jQuery method.
911
1149
  @method destroy()
912
1150
  **/
913
1151
  destroy: function() {
914
- this.call('destroy');
1152
+ this.hide();
1153
+ this.innerDestroy();
1154
+ this.$element.off('destroyed');
1155
+ this.$element.removeData('editableContainer');
915
1156
  },
916
1157
 
1158
+ /* to be overwritten in child classes */
1159
+ innerDestroy: function() {
1160
+
1161
+ },
1162
+
917
1163
  /*
918
1164
  Closes other containers except one related to passed element.
919
1165
  Other containers can be cancelled or submitted (depends on onblur option)
@@ -972,11 +1218,12 @@ Applied as jQuery method.
972
1218
  return this.each(function () {
973
1219
  var $this = $(this),
974
1220
  dataKey = 'editableContainer',
975
- data = $this.data(dataKey),
976
- options = typeof option === 'object' && option;
1221
+ data = $this.data(dataKey),
1222
+ options = typeof option === 'object' && option,
1223
+ Constructor = (options.mode === 'inline') ? Inline : Popup;
977
1224
 
978
1225
  if (!data) {
979
- $this.data(dataKey, (data = new EditableContainer(this, options)));
1226
+ $this.data(dataKey, (data = new Constructor(this, options)));
980
1227
  }
981
1228
 
982
1229
  if (typeof option === 'string') { //call method
@@ -985,8 +1232,9 @@ Applied as jQuery method.
985
1232
  });
986
1233
  };
987
1234
 
988
- //store constructor
989
- $.fn.editableContainer.Constructor = EditableContainer;
1235
+ //store constructors
1236
+ $.fn.editableContainer.Popup = Popup;
1237
+ $.fn.editableContainer.Inline = Inline;
990
1238
 
991
1239
  //defaults
992
1240
  $.fn.editableContainer.defaults = {
@@ -1025,7 +1273,25 @@ Applied as jQuery method.
1025
1273
  @default 'cancel'
1026
1274
  @since 1.1.1
1027
1275
  **/
1028
- onblur: 'cancel'
1276
+ onblur: 'cancel',
1277
+
1278
+ /**
1279
+ Animation speed (inline mode)
1280
+ @property anim
1281
+ @type string
1282
+ @default false
1283
+ **/
1284
+ anim: false,
1285
+
1286
+ /**
1287
+ Mode of editable, can be `popup` or `inline`
1288
+
1289
+ @property mode
1290
+ @type string
1291
+ @default 'popup'
1292
+ @since 1.4.0
1293
+ **/
1294
+ mode: 'popup'
1029
1295
  };
1030
1296
 
1031
1297
  /*
@@ -1042,6 +1308,60 @@ Applied as jQuery method.
1042
1308
 
1043
1309
  }(window.jQuery));
1044
1310
 
1311
+ /**
1312
+ * Editable Inline
1313
+ * ---------------------
1314
+ */
1315
+ (function ($) {
1316
+ "use strict";
1317
+
1318
+ //copy prototype from EditableContainer
1319
+ //extend methods
1320
+ $.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, {
1321
+ containerName: 'editableform',
1322
+ innerCss: '.editable-inline',
1323
+ containerClass: 'editable-container editable-inline', //css class applied to container element
1324
+
1325
+ initContainer: function(){
1326
+ //container is <span> element
1327
+ this.$tip = $('<span></span>');
1328
+
1329
+ //convert anim to miliseconds (int)
1330
+ if(!this.options.anim) {
1331
+ this.options.anim = 0;
1332
+ }
1333
+ },
1334
+
1335
+ splitOptions: function() {
1336
+ //all options are passed to form
1337
+ this.containerOptions = {};
1338
+ this.formOptions = this.options;
1339
+ },
1340
+
1341
+ tip: function() {
1342
+ return this.$tip;
1343
+ },
1344
+
1345
+ innerShow: function () {
1346
+ this.$element.hide();
1347
+ this.tip().insertAfter(this.$element).show();
1348
+ },
1349
+
1350
+ innerHide: function () {
1351
+ this.$tip.hide(this.options.anim, $.proxy(function() {
1352
+ this.$element.show();
1353
+ this.innerDestroy();
1354
+ }, this));
1355
+ },
1356
+
1357
+ innerDestroy: function() {
1358
+ if(this.tip()) {
1359
+ this.tip().empty().remove();
1360
+ }
1361
+ }
1362
+ });
1363
+
1364
+ }(window.jQuery));
1045
1365
  /**
1046
1366
  Makes editable any HTML element on the page. Applied as jQuery method.
1047
1367
 
@@ -1049,37 +1369,33 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1049
1369
  @uses editableContainer
1050
1370
  **/
1051
1371
  (function ($) {
1372
+ "use strict";
1052
1373
 
1053
1374
  var Editable = function (element, options) {
1054
1375
  this.$element = $(element);
1055
- this.options = $.extend({}, $.fn.editable.defaults, $.fn.editableutils.getConfigData(this.$element), options);
1056
- this.init();
1376
+ //data-* has more priority over js options: because dynamically created elements may change data-*
1377
+ this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element));
1378
+ if(this.options.selector) {
1379
+ this.initLive();
1380
+ } else {
1381
+ this.init();
1382
+ }
1057
1383
  };
1058
1384
 
1059
1385
  Editable.prototype = {
1060
1386
  constructor: Editable,
1061
1387
  init: function () {
1062
- var TypeConstructor,
1063
- isValueByText = false,
1064
- doAutotext,
1065
- finalize;
1066
-
1067
- //editableContainer must be defined
1068
- if(!$.fn.editableContainer) {
1069
- $.error('You must define $.fn.editableContainer via including corresponding file (e.g. editable-popover.js)');
1070
- return;
1071
- }
1072
-
1388
+ var isValueByText = false,
1389
+ doAutotext, finalize;
1390
+
1073
1391
  //name
1074
1392
  this.options.name = this.options.name || this.$element.attr('id');
1075
1393
 
1076
- //create input of specified type. Input will be used for converting value, not in form
1077
- if(typeof $.fn.editabletypes[this.options.type] === 'function') {
1078
- TypeConstructor = $.fn.editabletypes[this.options.type];
1079
- this.typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
1080
- this.input = new TypeConstructor(this.typeOptions);
1081
- } else {
1082
- $.error('Unknown type: '+ this.options.type);
1394
+ //create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select)
1395
+ //also we set scope option to have access to element inside input specific callbacks (e. g. source as function)
1396
+ this.options.scope = this.$element[0];
1397
+ this.input = $.fn.editableutils.createInput(this.options);
1398
+ if(!this.input) {
1083
1399
  return;
1084
1400
  }
1085
1401
 
@@ -1108,8 +1424,10 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1108
1424
  if(this.options.toggle !== 'manual') {
1109
1425
  this.$element.addClass('editable-click');
1110
1426
  this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
1427
+ //prevent following link
1111
1428
  e.preventDefault();
1112
- //stop propagation not required anymore because in document click handler it checks event target
1429
+
1430
+ //stop propagation not required because in document click handler it checks event target
1113
1431
  //e.stopPropagation();
1114
1432
 
1115
1433
  if(this.options.toggle === 'mouseenter') {
@@ -1126,9 +1444,19 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1126
1444
  }
1127
1445
 
1128
1446
  //check conditions for autotext:
1129
- //if value was generated by text or value is empty, no sense to run autotext
1130
- doAutotext = !isValueByText && this.value !== null && this.value !== undefined;
1131
- doAutotext &= (this.options.autotext === 'always') || (this.options.autotext === 'auto' && !this.$element.text().length);
1447
+ switch(this.options.autotext) {
1448
+ case 'always':
1449
+ doAutotext = true;
1450
+ break;
1451
+ case 'auto':
1452
+ //if element text is empty and value is defined and value not generated by text --> run autotext
1453
+ doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText;
1454
+ break;
1455
+ default:
1456
+ doAutotext = false;
1457
+ }
1458
+
1459
+ //depending on autotext run render() or just finilize init
1132
1460
  $.when(doAutotext ? this.render() : true).then($.proxy(function() {
1133
1461
  if(this.options.disabled) {
1134
1462
  this.disable();
@@ -1136,34 +1464,65 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1136
1464
  this.enable();
1137
1465
  }
1138
1466
  /**
1139
- Fired when element was initialized by editable method.
1467
+ Fired when element was initialized by `$().editable()` method.
1468
+ Please note that you should setup `init` handler **before** applying `editable`.
1140
1469
 
1141
1470
  @event init
1142
1471
  @param {Object} event event object
1143
- @param {Object} editable editable instance
1472
+ @param {Object} editable editable instance (as here it cannot accessed via data('editable'))
1144
1473
  @since 1.2.0
1474
+ @example
1475
+ $('#username').on('init', function(e, editable) {
1476
+ alert('initialized ' + editable.options.name);
1477
+ });
1478
+ $('#username').editable();
1145
1479
  **/
1146
1480
  this.$element.triggerHandler('init', this);
1147
1481
  }, this));
1148
1482
  },
1149
1483
 
1484
+ /*
1485
+ Initializes parent element for live editables
1486
+ */
1487
+ initLive: function() {
1488
+ //store selector
1489
+ var selector = this.options.selector;
1490
+ //modify options for child elements
1491
+ this.options.selector = false;
1492
+ this.options.autotext = 'never';
1493
+ //listen toggle events
1494
+ this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){
1495
+ var $target = $(e.target);
1496
+ if(!$target.data('editable')) {
1497
+ //if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user)
1498
+ //see https://github.com/vitalets/x-editable/issues/137
1499
+ if($target.hasClass(this.options.emptyclass)) {
1500
+ $target.empty();
1501
+ }
1502
+ $target.editable(this.options).trigger(e);
1503
+ }
1504
+ }, this));
1505
+ },
1506
+
1150
1507
  /*
1151
1508
  Renders value into element's text.
1152
1509
  Can call custom display method from options.
1153
1510
  Can return deferred object.
1154
1511
  @method render()
1512
+ @param {mixed} response server response (if exist) to pass into display function
1155
1513
  */
1156
- render: function() {
1514
+ render: function(response) {
1157
1515
  //do not display anything
1158
1516
  if(this.options.display === false) {
1159
1517
  return;
1160
1518
  }
1161
- //if it is input with source, we pass callback in third param to be called when source is loaded
1162
- if(this.input.options.hasOwnProperty('source')) {
1163
- return this.input.value2html(this.value, this.$element[0], this.options.display);
1519
+
1520
+ //if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded
1521
+ if(this.input.value2htmlFinal) {
1522
+ return this.input.value2html(this.value, this.$element[0], this.options.display, response);
1164
1523
  //if display method defined --> use it
1165
1524
  } else if(typeof this.options.display === 'function') {
1166
- return this.options.display.call(this.$element[0], this.value);
1525
+ return this.options.display.call(this.$element[0], this.value, response);
1167
1526
  //else use input's original value2html() method
1168
1527
  } else {
1169
1528
  return this.input.value2html(this.value, this.$element[0]);
@@ -1177,7 +1536,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1177
1536
  enable: function() {
1178
1537
  this.options.disabled = false;
1179
1538
  this.$element.removeClass('editable-disabled');
1180
- this.handleEmpty();
1539
+ this.handleEmpty(this.isEmpty);
1181
1540
  if(this.options.toggle !== 'manual') {
1182
1541
  if(this.$element.attr('tabindex') === '-1') {
1183
1542
  this.$element.removeAttr('tabindex');
@@ -1193,7 +1552,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1193
1552
  this.options.disabled = true;
1194
1553
  this.hide();
1195
1554
  this.$element.addClass('editable-disabled');
1196
- this.handleEmpty();
1555
+ this.handleEmpty(this.isEmpty);
1197
1556
  //do not stop focus on this element
1198
1557
  this.$element.attr('tabindex', -1);
1199
1558
  },
@@ -1233,12 +1592,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1233
1592
 
1234
1593
  //disabled
1235
1594
  if(key === 'disabled') {
1236
- if(value) {
1237
- this.disable();
1238
- } else {
1239
- this.enable();
1240
- }
1241
- return;
1595
+ return value ? this.disable() : this.enable();
1242
1596
  }
1243
1597
 
1244
1598
  //value
@@ -1250,30 +1604,42 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1250
1604
  if(this.container) {
1251
1605
  this.container.option(key, value);
1252
1606
  }
1607
+
1608
+ //pass option to input directly (as it points to the same in form)
1609
+ if(this.input.option) {
1610
+ this.input.option(key, value);
1611
+ }
1612
+
1253
1613
  },
1254
1614
 
1255
1615
  /*
1256
- * set emptytext if element is empty (reverse: remove emptytext if needed)
1616
+ * set emptytext if element is empty
1257
1617
  */
1258
- handleEmpty: function () {
1618
+ handleEmpty: function (isEmpty) {
1259
1619
  //do not handle empty if we do not display anything
1260
1620
  if(this.options.display === false) {
1261
1621
  return;
1262
1622
  }
1623
+
1624
+ this.isEmpty = isEmpty !== undefined ? isEmpty : $.trim(this.$element.text()) === '';
1263
1625
 
1264
- var emptyClass = 'editable-empty';
1265
1626
  //emptytext shown only for enabled
1266
1627
  if(!this.options.disabled) {
1267
- if ($.trim(this.$element.text()) === '') {
1268
- this.$element.addClass(emptyClass).text(this.options.emptytext);
1269
- } else {
1270
- this.$element.removeClass(emptyClass);
1628
+ if (this.isEmpty) {
1629
+ this.$element.text(this.options.emptytext);
1630
+ if(this.options.emptyclass) {
1631
+ this.$element.addClass(this.options.emptyclass);
1632
+ }
1633
+ } else if(this.options.emptyclass) {
1634
+ this.$element.removeClass(this.options.emptyclass);
1271
1635
  }
1272
1636
  } else {
1273
1637
  //below required if element disable property was changed
1274
- if(this.$element.hasClass(emptyClass)) {
1638
+ if(this.isEmpty) {
1275
1639
  this.$element.empty();
1276
- this.$element.removeClass(emptyClass);
1640
+ if(this.options.emptyclass) {
1641
+ this.$element.removeClass(this.options.emptyclass);
1642
+ }
1277
1643
  }
1278
1644
  }
1279
1645
  },
@@ -1291,9 +1657,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1291
1657
  //init editableContainer: popover, tooltip, inline, etc..
1292
1658
  if(!this.container) {
1293
1659
  var containerOptions = $.extend({}, this.options, {
1294
- value: this.value
1660
+ value: this.value,
1661
+ input: this.input //pass input to form (as it is already created)
1295
1662
  });
1296
1663
  this.$element.editableContainer(containerOptions);
1664
+ //listen `save` event
1297
1665
  this.$element.on("save.internal", $.proxy(this.save, this));
1298
1666
  this.container = this.$element.data('editableContainer');
1299
1667
  } else if(this.container.tip().is(':visible')) {
@@ -1331,15 +1699,30 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1331
1699
  * called when form was submitted
1332
1700
  */
1333
1701
  save: function(e, params) {
1334
- //if url is not user's function and value was not sent to server and value changed --> mark element with unsaved css.
1335
- if(typeof this.options.url !== 'function' && this.options.display !== false && params.response === undefined && this.input.value2str(this.value) !== this.input.value2str(params.newValue)) {
1336
- this.$element.addClass('editable-unsaved');
1337
- } else {
1338
- this.$element.removeClass('editable-unsaved');
1702
+ //mark element with unsaved class if needed
1703
+ if(this.options.unsavedclass) {
1704
+ /*
1705
+ Add unsaved css to element if:
1706
+ - url is not user's function
1707
+ - value was not sent to server
1708
+ - params.response === undefined, that means data was not sent
1709
+ - value changed
1710
+ */
1711
+ var sent = false;
1712
+ sent = sent || typeof this.options.url === 'function';
1713
+ sent = sent || this.options.display === false;
1714
+ sent = sent || params.response !== undefined;
1715
+ sent = sent || (this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(params.newValue));
1716
+
1717
+ if(sent) {
1718
+ this.$element.removeClass(this.options.unsavedclass);
1719
+ } else {
1720
+ this.$element.addClass(this.options.unsavedclass);
1721
+ }
1339
1722
  }
1340
1723
 
1341
- // this.hide();
1342
- this.setValue(params.newValue);
1724
+ //set new value
1725
+ this.setValue(params.newValue, false, params.response);
1343
1726
 
1344
1727
  /**
1345
1728
  Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
@@ -1351,13 +1734,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1351
1734
  @param {Object} params.response ajax response
1352
1735
  @example
1353
1736
  $('#username').on('save', function(e, params) {
1354
- //assuming server response: '{success: true}'
1355
- var pk = $(this).data('editable').options.pk;
1356
- if(params.response && params.response.success) {
1357
- alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
1358
- } else {
1359
- alert('error!');
1360
- }
1737
+ alert('Saved value: ' + params.newValue);
1361
1738
  });
1362
1739
  **/
1363
1740
  //event itself is triggered by editableContainer. Description here is only for documentation
@@ -1375,7 +1752,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1375
1752
  @param {mixed} value new value
1376
1753
  @param {boolean} convertStr whether to convert value from string to internal format
1377
1754
  **/
1378
- setValue: function(value, convertStr) {
1755
+ setValue: function(value, convertStr, response) {
1379
1756
  if(convertStr) {
1380
1757
  this.value = this.input.str2value(value);
1381
1758
  } else {
@@ -1384,7 +1761,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1384
1761
  if(this.container) {
1385
1762
  this.container.option('value', this.value);
1386
1763
  }
1387
- $.when(this.render())
1764
+ $.when(this.render(response))
1388
1765
  .then($.proxy(function() {
1389
1766
  this.handleEmpty();
1390
1767
  }, this));
@@ -1398,13 +1775,35 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1398
1775
  if(this.container) {
1399
1776
  this.container.activate();
1400
1777
  }
1401
- }
1402
- };
1403
-
1404
- /* EDITABLE PLUGIN DEFINITION
1405
- * ======================= */
1778
+ },
1779
+
1780
+ /**
1781
+ Removes editable feature from element
1782
+ @method destroy()
1783
+ **/
1784
+ destroy: function() {
1785
+ this.disable();
1786
+
1787
+ if(this.container) {
1788
+ this.container.destroy();
1789
+ }
1406
1790
 
1407
- /**
1791
+ if(this.options.toggle !== 'manual') {
1792
+ this.$element.removeClass('editable-click');
1793
+ this.$element.off(this.options.toggle + '.editable');
1794
+ }
1795
+
1796
+ this.$element.off("save.internal");
1797
+
1798
+ this.$element.removeClass('editable editable-open editable-disabled');
1799
+ this.$element.removeData('editable');
1800
+ }
1801
+ };
1802
+
1803
+ /* EDITABLE PLUGIN DEFINITION
1804
+ * ======================= */
1805
+
1806
+ /**
1408
1807
  jQuery method to initialize editable element.
1409
1808
 
1410
1809
  @method $().editable(options)
@@ -1415,7 +1814,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1415
1814
  url: '/post',
1416
1815
  pk: 1
1417
1816
  });
1418
- **/
1817
+ **/
1419
1818
  $.fn.editable = function (option) {
1420
1819
  //special API methods returning non-jquery object
1421
1820
  var result = {}, args = arguments, datakey = 'editable';
@@ -1432,7 +1831,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1432
1831
  username: "username is required",
1433
1832
  fullname: "fullname should be minimum 3 letters length"
1434
1833
  }
1435
- **/
1834
+ **/
1436
1835
  case 'validate':
1437
1836
  this.each(function () {
1438
1837
  var $this = $(this), data = $this.data(datakey), error;
@@ -1443,17 +1842,20 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1443
1842
  return result;
1444
1843
 
1445
1844
  /**
1446
- Returns current values of editable elements. If value is <code>null</code> or <code>undefined</code> it will not be returned
1845
+ Returns current values of editable elements.
1846
+ Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.
1847
+ If value of some editable is `null` or `undefined` it is excluded from result object.
1848
+
1447
1849
  @method getValue()
1448
1850
  @returns {Object} object of element names and values
1449
1851
  @example
1450
- $('#username, #fullname').editable('validate');
1852
+ $('#username, #fullname').editable('getValue');
1451
1853
  // possible result:
1452
1854
  {
1453
1855
  username: "superuser",
1454
1856
  fullname: "John"
1455
1857
  }
1456
- **/
1858
+ **/
1457
1859
  case 'getValue':
1458
1860
  this.each(function () {
1459
1861
  var $this = $(this), data = $this.data(datakey);
@@ -1472,11 +1874,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1472
1874
  @param {object} options
1473
1875
  @param {object} options.url url to submit data
1474
1876
  @param {object} options.data additional data to submit
1475
- @param {object} options.ajaxOptions additional ajax options
1877
+ @param {object} options.ajaxOptions additional ajax options
1476
1878
  @param {function} options.error(obj) error handler
1477
1879
  @param {function} options.success(obj,config) success handler
1478
1880
  @returns {Object} jQuery object
1479
- **/
1881
+ **/
1480
1882
  case 'submit': //collects value, validate and submit to server for creating new record
1481
1883
  var config = arguments[1] || {},
1482
1884
  $elems = this,
@@ -1584,8 +1986,20 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1584
1986
  **/
1585
1987
  autotext: 'auto',
1586
1988
  /**
1587
- Initial value of input. Taken from <code>data-value</code> or element's text.
1588
-
1989
+ Initial value of input. If not set, taken from element's text.
1990
+ Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option).
1991
+ For example, to display currency sign:
1992
+ @example
1993
+ <a id="price" data-type="text" data-value="100"></a>
1994
+ <script>
1995
+ $('#price').editable({
1996
+ ...
1997
+ display: function(value) {
1998
+ $(this).text(value + '$');
1999
+ }
2000
+ })
2001
+ </script>
2002
+
1589
2003
  @property value
1590
2004
  @type mixed
1591
2005
  @default element's text
@@ -1593,10 +2007,21 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1593
2007
  value: null,
1594
2008
  /**
1595
2009
  Callback to perform custom displaying of value in element's text.
1596
- If <code>null</code>, default input's value2html() will be called.
1597
- If <code>false</code>, no displaying methods will be called, element's text will no change.
2010
+ If `null`, default input's display used.
2011
+ If `false`, no displaying methods will be called, element's text will never change.
1598
2012
  Runs under element's scope.
1599
- Second parameter __sourceData__ is passed for inputs with source (select, checklist).
2013
+ _**Parameters:**_
2014
+
2015
+ * `value` current value to be displayed
2016
+ * `response` server response (if display called after ajax submit), since 1.4.0
2017
+
2018
+ For _inputs with source_ (select, checklist) parameters are different:
2019
+
2020
+ * `value` current value to be displayed
2021
+ * `sourceData` array of items for current input (e.g. dropdown items)
2022
+ * `response` server response (if display called after ajax submit), since 1.4.0
2023
+
2024
+ To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`.
1600
2025
 
1601
2026
  @property display
1602
2027
  @type function|boolean
@@ -1604,11 +2029,67 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1604
2029
  @since 1.2.0
1605
2030
  @example
1606
2031
  display: function(value, sourceData) {
1607
- var escapedValue = $('<div>').text(value).html();
1608
- $(this).html('<b>'+escapedValue+'</b>');
2032
+ //display checklist as comma-separated values
2033
+ var html = [],
2034
+ checked = $.fn.editableutils.itemsByValue(value, sourceData);
2035
+
2036
+ if(checked.length) {
2037
+ $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
2038
+ $(this).html(html.join(', '));
2039
+ } else {
2040
+ $(this).empty();
2041
+ }
1609
2042
  }
1610
2043
  **/
1611
- display: null
2044
+ display: null,
2045
+ /**
2046
+ Css class applied when editable text is empty.
2047
+
2048
+ @property emptyclass
2049
+ @type string
2050
+ @since 1.4.1
2051
+ @default editable-empty
2052
+ **/
2053
+ emptyclass: 'editable-empty',
2054
+ /**
2055
+ Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`).
2056
+ You may set it to `null` if you work with editables locally and submit them together.
2057
+
2058
+ @property unsavedclass
2059
+ @type string
2060
+ @since 1.4.1
2061
+ @default editable-unsaved
2062
+ **/
2063
+ unsavedclass: 'editable-unsaved',
2064
+ /**
2065
+ If selector is provided, editable will be delegated to the specified targets.
2066
+ Usefull for dynamically generated DOM elements.
2067
+ **Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options,
2068
+ as they actually become editable only after first click.
2069
+ You should manually set class `editable-click` to these elements.
2070
+ Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element:
2071
+
2072
+ @property selector
2073
+ @type string
2074
+ @since 1.4.1
2075
+ @default null
2076
+ @example
2077
+ <div id="user">
2078
+ <!-- empty -->
2079
+ <a href="#" data-name="username" data-type="text" class="editable-click editable-empty" data-value="" title="Username">Empty</a>
2080
+ <!-- non-empty -->
2081
+ <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" class="editable-click" title="Group">Operator</a>
2082
+ </div>
2083
+
2084
+ <script>
2085
+ $('#user').editable({
2086
+ selector: 'a',
2087
+ url: '/post',
2088
+ pk: 1
2089
+ });
2090
+ </script>
2091
+ **/
2092
+ selector: null
1612
2093
  };
1613
2094
 
1614
2095
  }(window.jQuery));
@@ -1621,7 +2102,8 @@ To create your own input you can inherit from this class.
1621
2102
  @class abstractinput
1622
2103
  **/
1623
2104
  (function ($) {
1624
-
2105
+ "use strict";
2106
+
1625
2107
  //types
1626
2108
  $.fn.editabletypes = {};
1627
2109
 
@@ -1635,26 +2117,27 @@ To create your own input you can inherit from this class.
1635
2117
  **/
1636
2118
  init: function(type, options, defaults) {
1637
2119
  this.type = type;
1638
- this.options = $.extend({}, defaults, options);
1639
- this.$input = null;
1640
- this.$clear = null;
1641
- this.error = null;
2120
+ this.options = $.extend({}, defaults, options);
2121
+ },
2122
+
2123
+ /*
2124
+ this method called before render to init $tpl that is inserted in DOM
2125
+ */
2126
+ prerender: function() {
2127
+ this.$tpl = $(this.options.tpl); //whole tpl as jquery object
2128
+ this.$input = this.$tpl; //control itself, can be changed in render method
2129
+ this.$clear = null; //clear button
2130
+ this.error = null; //error message, if input cannot be rendered
1642
2131
  },
1643
2132
 
1644
2133
  /**
1645
2134
  Renders input from tpl. Can return jQuery deferred object.
2135
+ Can be overwritten in child objects
1646
2136
 
1647
2137
  @method render()
1648
2138
  **/
1649
2139
  render: function() {
1650
- this.$input = $(this.options.tpl);
1651
- if(this.options.inputclass) {
1652
- this.$input.addClass(this.options.inputclass);
1653
- }
1654
-
1655
- if (this.options.placeholder) {
1656
- this.$input.attr('placeholder', this.options.placeholder);
1657
- }
2140
+
1658
2141
  },
1659
2142
 
1660
2143
  /**
@@ -1691,7 +2174,7 @@ To create your own input you can inherit from this class.
1691
2174
  },
1692
2175
 
1693
2176
  /**
1694
- Converts string received from server into value.
2177
+ Converts string received from server into value. Usually from `data-value` attribute.
1695
2178
 
1696
2179
  @method str2value(str)
1697
2180
  @param {string} str
@@ -1702,7 +2185,7 @@ To create your own input you can inherit from this class.
1702
2185
  },
1703
2186
 
1704
2187
  /**
1705
- Converts value for submitting to server
2188
+ Converts value for submitting to server. Result can be string or object.
1706
2189
 
1707
2190
  @method value2submit(value)
1708
2191
  @param {mixed} value
@@ -1763,7 +2246,25 @@ To create your own input you can inherit from this class.
1763
2246
  **/
1764
2247
  autosubmit: function() {
1765
2248
 
2249
+ },
2250
+
2251
+ // -------- helper functions --------
2252
+ setClass: function() {
2253
+ if(this.options.inputclass) {
2254
+ this.$input.addClass(this.options.inputclass);
2255
+ }
2256
+ },
2257
+
2258
+ setAttr: function(attr) {
2259
+ if (this.options[attr] !== undefined && this.options[attr] !== null) {
2260
+ this.$input.attr(attr, this.options[attr]);
2261
+ }
2262
+ },
2263
+
2264
+ option: function(key, value) {
2265
+ this.options[key] = value;
1766
2266
  }
2267
+
1767
2268
  };
1768
2269
 
1769
2270
  AbstractInput.defaults = {
@@ -1783,14 +2284,12 @@ To create your own input you can inherit from this class.
1783
2284
  @default input-medium
1784
2285
  **/
1785
2286
  inputclass: 'input-medium',
1786
- /**
1787
- Name attribute of input
1788
-
1789
- @property name
1790
- @type string
1791
- @default null
1792
- **/
1793
- name: null
2287
+ //scope for external methods (e.g. source defined as function)
2288
+ //for internal use only
2289
+ scope: null,
2290
+
2291
+ //need to re-declare showbuttons here to get it's value from common config (passed only options existing in defaults)
2292
+ showbuttons: true
1794
2293
  };
1795
2294
 
1796
2295
  $.extend($.fn.editabletypes, {abstractinput: AbstractInput});
@@ -1804,7 +2303,8 @@ List - abstract class for inputs that have source option loaded from js array or
1804
2303
  @extends abstractinput
1805
2304
  **/
1806
2305
  (function ($) {
1807
-
2306
+ "use strict";
2307
+
1808
2308
  var List = function (options) {
1809
2309
 
1810
2310
  };
@@ -1813,11 +2313,9 @@ List - abstract class for inputs that have source option loaded from js array or
1813
2313
 
1814
2314
  $.extend(List.prototype, {
1815
2315
  render: function () {
1816
- List.superclass.render.call(this);
1817
2316
  var deferred = $.Deferred();
2317
+
1818
2318
  this.error = null;
1819
- this.sourceData = null;
1820
- this.prependData = null;
1821
2319
  this.onSourceReady(function () {
1822
2320
  this.renderList();
1823
2321
  deferred.resolve();
@@ -1833,20 +2331,24 @@ List - abstract class for inputs that have source option loaded from js array or
1833
2331
  return null; //can't set value by text
1834
2332
  },
1835
2333
 
1836
- value2html: function (value, element, display) {
1837
- var deferred = $.Deferred();
1838
- this.onSourceReady(function () {
1839
- if(typeof display === 'function') {
1840
- //custom display method
1841
- display.call(element, value, this.sourceData);
1842
- } else {
1843
- this.value2htmlFinal(value, element);
1844
- }
1845
- deferred.resolve();
1846
- }, function () {
1847
- //do nothing with element
1848
- deferred.resolve();
1849
- });
2334
+ value2html: function (value, element, display, response) {
2335
+ var deferred = $.Deferred(),
2336
+ success = function () {
2337
+ if(typeof display === 'function') {
2338
+ //custom display method
2339
+ display.call(element, value, this.sourceData, response);
2340
+ } else {
2341
+ this.value2htmlFinal(value, element);
2342
+ }
2343
+ deferred.resolve();
2344
+ };
2345
+
2346
+ //for null value just call success without loading source
2347
+ if(value === null) {
2348
+ success.call(this);
2349
+ } else {
2350
+ this.onSourceReady(success, function () { deferred.resolve(); });
2351
+ }
1850
2352
 
1851
2353
  return deferred.promise();
1852
2354
  },
@@ -1867,12 +2369,19 @@ List - abstract class for inputs that have source option loaded from js array or
1867
2369
  error.call(this);
1868
2370
  return;
1869
2371
  }
2372
+
2373
+ var source = this.options.source;
2374
+
2375
+ //run source if it function
2376
+ if ($.isFunction(source)) {
2377
+ source = source.call(this.options.scope);
2378
+ }
1870
2379
 
1871
2380
  //loading from url
1872
- if (typeof this.options.source === 'string') {
2381
+ if (typeof source === 'string') {
1873
2382
  //try to get from cache
1874
2383
  if(this.options.sourceCache) {
1875
- var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''),
2384
+ var cacheID = source,
1876
2385
  cache;
1877
2386
 
1878
2387
  if (!$(document).data(cacheID)) {
@@ -1883,11 +2392,13 @@ List - abstract class for inputs that have source option loaded from js array or
1883
2392
  //check for cached data
1884
2393
  if (cache.loading === false && cache.sourceData) { //take source from cache
1885
2394
  this.sourceData = cache.sourceData;
2395
+ this.doPrepend();
1886
2396
  success.call(this);
1887
2397
  return;
1888
2398
  } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
1889
2399
  cache.callbacks.push($.proxy(function () {
1890
2400
  this.sourceData = cache.sourceData;
2401
+ this.doPrepend();
1891
2402
  success.call(this);
1892
2403
  }, this));
1893
2404
 
@@ -1903,10 +2414,9 @@ List - abstract class for inputs that have source option loaded from js array or
1903
2414
 
1904
2415
  //loading sourceData from server
1905
2416
  $.ajax({
1906
- url: this.options.source,
2417
+ url: source,
1907
2418
  type: 'get',
1908
2419
  cache: false,
1909
- data: this.options.name ? {name: this.options.name} : {},
1910
2420
  dataType: 'json',
1911
2421
  success: $.proxy(function (data) {
1912
2422
  if(cache) {
@@ -1914,17 +2424,19 @@ List - abstract class for inputs that have source option loaded from js array or
1914
2424
  }
1915
2425
  this.sourceData = this.makeArray(data);
1916
2426
  if($.isArray(this.sourceData)) {
1917
- this.doPrepend();
1918
- success.call(this);
1919
2427
  if(cache) {
1920
2428
  //store result in cache
1921
2429
  cache.sourceData = this.sourceData;
1922
- $.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields
2430
+ //run success callbacks for other fields waiting for this source
2431
+ $.each(cache.callbacks, function () { this.call(); });
1923
2432
  }
2433
+ this.doPrepend();
2434
+ success.call(this);
1924
2435
  } else {
1925
2436
  error.call(this);
1926
2437
  if(cache) {
1927
- $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
2438
+ //run error callbacks for other fields waiting for this source
2439
+ $.each(cache.err_callbacks, function () { this.call(); });
1928
2440
  }
1929
2441
  }
1930
2442
  }, this),
@@ -1938,7 +2450,8 @@ List - abstract class for inputs that have source option loaded from js array or
1938
2450
  }, this)
1939
2451
  });
1940
2452
  } else { //options as json/array
1941
- this.sourceData = this.makeArray(this.options.source);
2453
+ this.sourceData = this.makeArray(source);
2454
+
1942
2455
  if($.isArray(this.sourceData)) {
1943
2456
  this.doPrepend();
1944
2457
  success.call(this);
@@ -1954,11 +2467,19 @@ List - abstract class for inputs that have source option loaded from js array or
1954
2467
  }
1955
2468
 
1956
2469
  if(!$.isArray(this.prependData)) {
2470
+ //run prepend if it is function (once)
2471
+ if ($.isFunction(this.options.prepend)) {
2472
+ this.options.prepend = this.options.prepend.call(this.options.scope);
2473
+ }
2474
+
1957
2475
  //try parse json in single quotes
1958
2476
  this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
2477
+
2478
+ //convert prepend from string to object
1959
2479
  if (typeof this.options.prepend === 'string') {
1960
2480
  this.options.prepend = {'': this.options.prepend};
1961
- }
2481
+ }
2482
+
1962
2483
  this.prependData = this.makeArray(this.options.prepend);
1963
2484
  }
1964
2485
 
@@ -1985,35 +2506,45 @@ List - abstract class for inputs that have source option loaded from js array or
1985
2506
  * convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}]
1986
2507
  */
1987
2508
  makeArray: function(data) {
1988
- var count, obj, result = [], iterateEl;
2509
+ var count, obj, result = [], item, iterateItem;
1989
2510
  if(!data || typeof data === 'string') {
1990
2511
  return null;
1991
2512
  }
1992
2513
 
1993
2514
  if($.isArray(data)) { //array
1994
- iterateEl = function (k, v) {
2515
+ /*
2516
+ function to iterate inside item of array if item is object.
2517
+ Caclulates count of keys in item and store in obj.
2518
+ */
2519
+ iterateItem = function (k, v) {
1995
2520
  obj = {value: k, text: v};
1996
2521
  if(count++ >= 2) {
1997
- return false;// exit each if object has more than one value
2522
+ return false;// exit from `each` if item has more than one key.
1998
2523
  }
1999
2524
  };
2000
2525
 
2001
2526
  for(var i = 0; i < data.length; i++) {
2002
- if(typeof data[i] === 'object') {
2003
- count = 0;
2004
- $.each(data[i], iterateEl);
2005
- if(count === 1) {
2527
+ item = data[i];
2528
+ if(typeof item === 'object') {
2529
+ count = 0; //count of keys inside item
2530
+ $.each(item, iterateItem);
2531
+ //case: [{val1: 'text1'}, {val2: 'text2} ...]
2532
+ if(count === 1) {
2006
2533
  result.push(obj);
2007
- } else if(count > 1 && data[i].hasOwnProperty('value') && data[i].hasOwnProperty('text')) {
2008
- result.push(data[i]);
2009
- } else {
2010
- //data contains incorrect objects
2534
+ //case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...]
2535
+ } else if(count > 1) {
2536
+ //removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text')
2537
+ if(item.children) {
2538
+ item.children = this.makeArray(item.children);
2539
+ }
2540
+ result.push(item);
2011
2541
  }
2012
2542
  } else {
2013
- result.push({value: data[i], text: data[i]});
2543
+ //case: ['text1', 'text2' ...]
2544
+ result.push({value: item, text: item});
2014
2545
  }
2015
2546
  }
2016
- } else { //object
2547
+ } else { //case: {val1: 'text1', val2: 'text2, ...}
2017
2548
  $.each(data, function (k, v) {
2018
2549
  result.push({value: k, text: v});
2019
2550
  });
@@ -2021,41 +2552,45 @@ List - abstract class for inputs that have source option loaded from js array or
2021
2552
  return result;
2022
2553
  },
2023
2554
 
2024
- //search for item by particular value
2025
- itemByVal: function(val) {
2026
- if($.isArray(this.sourceData)) {
2027
- for(var i=0; i<this.sourceData.length; i++){
2028
- /*jshint eqeqeq: false*/
2029
- if(this.sourceData[i].value == val) {
2030
- /*jshint eqeqeq: true*/
2031
- return this.sourceData[i];
2032
- }
2033
- }
2555
+ option: function(key, value) {
2556
+ this.options[key] = value;
2557
+ if(key === 'source') {
2558
+ this.sourceData = null;
2034
2559
  }
2560
+ if(key === 'prepend') {
2561
+ this.prependData = null;
2562
+ }
2035
2563
  }
2036
2564
 
2037
2565
  });
2038
2566
 
2039
2567
  List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
2040
2568
  /**
2041
- Source data for list. If string - considered ajax url to load items. Otherwise should be an array.
2042
- Array format is: <code>[{value: 1, text: "text"}, {...}]</code><br>
2043
- For compability it also supports format <code>{value1: "text1", value2: "text2" ...}</code> but it does not guarantee elements order.
2044
- If source is **string**, results will be cached for fields with the same source and name. See also <code>sourceCache</code> option.
2569
+ Source data for list.
2570
+ If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`
2571
+ For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order.
2572
+
2573
+ 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.
2574
+
2575
+ If **function**, it should return data in format above (since 1.4.0).
2045
2576
 
2577
+ Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only).
2578
+ `[{text: "group1", children: [{value: 1, text: "text1"}, {value: 2, text: "text2"}]}, ...]`
2579
+
2580
+
2046
2581
  @property source
2047
- @type string|array|object
2582
+ @type string | array | object | function
2048
2583
  @default null
2049
2584
  **/
2050
- source:null,
2585
+ source: null,
2051
2586
  /**
2052
2587
  Data automatically prepended to the beginning of dropdown list.
2053
2588
 
2054
2589
  @property prepend
2055
- @type string|array|object
2590
+ @type string | array | object | function
2056
2591
  @default false
2057
2592
  **/
2058
- prepend:false,
2593
+ prepend: false,
2059
2594
  /**
2060
2595
  Error message when list cannot be loaded (e.g. ajax error)
2061
2596
 
@@ -2065,8 +2600,8 @@ List - abstract class for inputs that have source option loaded from js array or
2065
2600
  **/
2066
2601
  sourceError: 'Error when loading list',
2067
2602
  /**
2068
- if <code>true</code> and source is **string url** - results will be cached for fields with the same source and name.
2069
- Usefull for editable grids.
2603
+ if <code>true</code> and source is **string url** - results will be cached for fields with the same source.
2604
+ Usefull for editable column in grid to prevent extra requests.
2070
2605
 
2071
2606
  @property sourceCache
2072
2607
  @type boolean
@@ -2078,7 +2613,8 @@ List - abstract class for inputs that have source option loaded from js array or
2078
2613
 
2079
2614
  $.fn.editabletypes.list = List;
2080
2615
 
2081
- }(window.jQuery));
2616
+ }(window.jQuery));
2617
+
2082
2618
  /**
2083
2619
  Text input
2084
2620
 
@@ -2097,6 +2633,8 @@ $(function(){
2097
2633
  </script>
2098
2634
  **/
2099
2635
  (function ($) {
2636
+ "use strict";
2637
+
2100
2638
  var Text = function (options) {
2101
2639
  this.init('text', options, Text.defaults);
2102
2640
  };
@@ -2104,12 +2642,83 @@ $(function(){
2104
2642
  $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
2105
2643
 
2106
2644
  $.extend(Text.prototype, {
2645
+ render: function() {
2646
+ this.renderClear();
2647
+ this.setClass();
2648
+ this.setAttr('placeholder');
2649
+ },
2650
+
2107
2651
  activate: function() {
2108
2652
  if(this.$input.is(':visible')) {
2109
2653
  this.$input.focus();
2110
2654
  $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
2655
+ if(this.toggleClear) {
2656
+ this.toggleClear();
2657
+ }
2658
+ }
2659
+ },
2660
+
2661
+ //render clear button
2662
+ renderClear: function() {
2663
+ if (this.options.clear) {
2664
+ this.$clear = $('<span class="editable-clear-x"></span>');
2665
+ this.$input.after(this.$clear)
2666
+ .css('padding-right', 24)
2667
+ .keyup($.proxy(function(e) {
2668
+ //arrows, enter, tab, etc
2669
+ if(~$.inArray(e.keyCode, [40,38,9,13,27])) {
2670
+ return;
2671
+ }
2672
+
2673
+ clearTimeout(this.t);
2674
+ var that = this;
2675
+ this.t = setTimeout(function() {
2676
+ that.toggleClear(e);
2677
+ }, 100);
2678
+
2679
+ }, this))
2680
+ .parent().css('position', 'relative');
2681
+
2682
+ this.$clear.click($.proxy(this.clear, this));
2683
+ }
2684
+ },
2685
+
2686
+ postrender: function() {
2687
+ /*
2688
+ //now `clear` is positioned via css
2689
+ if(this.$clear) {
2690
+ //can position clear button only here, when form is shown and height can be calculated
2691
+ // var h = this.$input.outerHeight(true) || 20,
2692
+ var h = this.$clear.parent().height(),
2693
+ delta = (h - this.$clear.height()) / 2;
2694
+
2695
+ //this.$clear.css({bottom: delta, right: delta});
2696
+ }
2697
+ */
2698
+ },
2699
+
2700
+ //show / hide clear button
2701
+ toggleClear: function(e) {
2702
+ if(!this.$clear) {
2703
+ return;
2111
2704
  }
2112
- }
2705
+
2706
+ var len = this.$input.val().length,
2707
+ visible = this.$clear.is(':visible');
2708
+
2709
+ if(len && !visible) {
2710
+ this.$clear.show();
2711
+ }
2712
+
2713
+ if(!len && visible) {
2714
+ this.$clear.hide();
2715
+ }
2716
+ },
2717
+
2718
+ clear: function() {
2719
+ this.$clear.hide();
2720
+ this.$input.val('').focus();
2721
+ }
2113
2722
  });
2114
2723
 
2115
2724
  Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
@@ -2125,7 +2734,16 @@ $(function(){
2125
2734
  @type string
2126
2735
  @default null
2127
2736
  **/
2128
- placeholder: null
2737
+ placeholder: null,
2738
+
2739
+ /**
2740
+ Whether to show `clear` button
2741
+
2742
+ @property clear
2743
+ @type boolean
2744
+ @default true
2745
+ **/
2746
+ clear: true
2129
2747
  });
2130
2748
 
2131
2749
  $.fn.editabletypes.text = Text;
@@ -2144,13 +2762,15 @@ Textarea input
2144
2762
  $(function(){
2145
2763
  $('#comments').editable({
2146
2764
  url: '/post',
2147
- title: 'Enter comments'
2765
+ title: 'Enter comments',
2766
+ rows: 10
2148
2767
  });
2149
2768
  });
2150
2769
  </script>
2151
2770
  **/
2152
2771
  (function ($) {
2153
-
2772
+ "use strict";
2773
+
2154
2774
  var Textarea = function (options) {
2155
2775
  this.init('textarea', options, Textarea.defaults);
2156
2776
  };
@@ -2159,8 +2779,10 @@ $(function(){
2159
2779
 
2160
2780
  $.extend(Textarea.prototype, {
2161
2781
  render: function () {
2162
- Textarea.superclass.render.call(this);
2163
-
2782
+ this.setClass();
2783
+ this.setAttr('placeholder');
2784
+ this.setAttr('rows');
2785
+
2164
2786
  //ctrl + enter
2165
2787
  this.$input.keydown(function (e) {
2166
2788
  if (e.ctrlKey && e.which === 13) {
@@ -2185,43 +2807,56 @@ $(function(){
2185
2807
  if(!html) {
2186
2808
  return '';
2187
2809
  }
2810
+
2811
+ var regex = new RegExp(String.fromCharCode(10), 'g');
2188
2812
  var lines = html.split(/<br\s*\/?>/i);
2189
2813
  for (var i = 0; i < lines.length; i++) {
2190
- lines[i] = $('<div>').html(lines[i]).text();
2814
+ var text = $('<div>').html(lines[i]).text();
2815
+
2816
+ // Remove newline characters (\n) to avoid them being converted by value2html() method
2817
+ // thus adding extra <br> tags
2818
+ text = text.replace(regex, '');
2819
+
2820
+ lines[i] = text;
2191
2821
  }
2192
- return lines.join("\n");
2193
- },
2822
+ return lines.join("\n");
2823
+ },
2194
2824
 
2195
2825
  activate: function() {
2196
- if(this.$input.is(':visible')) {
2197
- $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
2198
- this.$input.focus();
2199
- }
2200
- }
2826
+ $.fn.editabletypes.text.prototype.activate.call(this);
2827
+ }
2201
2828
  });
2202
2829
 
2203
2830
  Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
2204
2831
  /**
2205
- @property tpl
2832
+ @property tpl
2206
2833
  @default <textarea></textarea>
2207
- **/
2834
+ **/
2208
2835
  tpl:'<textarea></textarea>',
2209
2836
  /**
2210
- @property inputclass
2837
+ @property inputclass
2211
2838
  @default input-large
2212
- **/
2839
+ **/
2213
2840
  inputclass: 'input-large',
2214
2841
  /**
2215
2842
  Placeholder attribute of input. Shown when input is empty.
2216
2843
 
2217
- @property placeholder
2844
+ @property placeholder
2218
2845
  @type string
2219
2846
  @default null
2220
- **/
2221
- placeholder: null
2847
+ **/
2848
+ placeholder: null,
2849
+ /**
2850
+ Number of rows in textarea
2851
+
2852
+ @property rows
2853
+ @type integer
2854
+ @default 7
2855
+ **/
2856
+ rows: 7
2222
2857
  });
2223
2858
 
2224
- $.fn.editabletypes.textarea = Textarea;
2859
+ $.fn.editabletypes.textarea = Textarea;
2225
2860
 
2226
2861
  }(window.jQuery));
2227
2862
 
@@ -2242,13 +2877,13 @@ $(function(){
2242
2877
  {value: 2, text: 'Blocked'},
2243
2878
  {value: 3, text: 'Deleted'}
2244
2879
  ]
2245
- }
2246
2880
  });
2247
2881
  });
2248
2882
  </script>
2249
2883
  **/
2250
2884
  (function ($) {
2251
-
2885
+ "use strict";
2886
+
2252
2887
  var Select = function (options) {
2253
2888
  this.init('select', options, Select.defaults);
2254
2889
  };
@@ -2257,13 +2892,24 @@ $(function(){
2257
2892
 
2258
2893
  $.extend(Select.prototype, {
2259
2894
  renderList: function() {
2260
- if(!$.isArray(this.sourceData)) {
2261
- return;
2262
- }
2895
+ this.$input.empty();
2263
2896
 
2264
- for(var i=0; i<this.sourceData.length; i++) {
2265
- this.$input.append($('<option>', {value: this.sourceData[i].value}).text(this.sourceData[i].text));
2266
- }
2897
+ var fillItems = function($el, data) {
2898
+ if($.isArray(data)) {
2899
+ for(var i=0; i<data.length; i++) {
2900
+ if(data[i].children) {
2901
+ $el.append(fillItems($('<optgroup>', {label: data[i].text}), data[i].children));
2902
+ } else {
2903
+ $el.append($('<option>', {value: data[i].value}).text(data[i].text));
2904
+ }
2905
+ }
2906
+ }
2907
+ return $el;
2908
+ };
2909
+
2910
+ fillItems(this.$input, this.sourceData);
2911
+
2912
+ this.setClass();
2267
2913
 
2268
2914
  //enter submit
2269
2915
  this.$input.on('keydown.editable', function (e) {
@@ -2274,11 +2920,14 @@ $(function(){
2274
2920
  },
2275
2921
 
2276
2922
  value2htmlFinal: function(value, element) {
2277
- var text = '', item = this.itemByVal(value);
2278
- if(item) {
2279
- text = item.text;
2923
+ var text = '',
2924
+ items = $.fn.editableutils.itemsByValue(value, this.sourceData);
2925
+
2926
+ if(items.length) {
2927
+ text = items[0].text;
2280
2928
  }
2281
- Select.superclass.constructor.superclass.value2html(text, element);
2929
+
2930
+ $(element).text(text);
2282
2931
  },
2283
2932
 
2284
2933
  autosubmit: function() {
@@ -2298,7 +2947,8 @@ $(function(){
2298
2947
 
2299
2948
  $.fn.editabletypes.select = Select;
2300
2949
 
2301
- }(window.jQuery));
2950
+ }(window.jQuery));
2951
+
2302
2952
  /**
2303
2953
  List of checkboxes.
2304
2954
  Internally value stored as javascript array of values.
@@ -2317,13 +2967,13 @@ $(function(){
2317
2967
  {value: 2, text: 'option2'},
2318
2968
  {value: 3, text: 'option3'}
2319
2969
  ]
2320
- }
2321
2970
  });
2322
2971
  });
2323
2972
  </script>
2324
2973
  **/
2325
2974
  (function ($) {
2326
-
2975
+ "use strict";
2976
+
2327
2977
  var Checklist = function (options) {
2328
2978
  this.init('checklist', options, Checklist.defaults);
2329
2979
  };
@@ -2333,6 +2983,9 @@ $(function(){
2333
2983
  $.extend(Checklist.prototype, {
2334
2984
  renderList: function() {
2335
2985
  var $label, $div;
2986
+
2987
+ this.$tpl.empty();
2988
+
2336
2989
  if(!$.isArray(this.sourceData)) {
2337
2990
  return;
2338
2991
  }
@@ -2340,13 +2993,15 @@ $(function(){
2340
2993
  for(var i=0; i<this.sourceData.length; i++) {
2341
2994
  $label = $('<label>').append($('<input>', {
2342
2995
  type: 'checkbox',
2343
- value: this.sourceData[i].value,
2344
- name: this.options.name
2996
+ value: this.sourceData[i].value
2345
2997
  }))
2346
2998
  .append($('<span>').text(' '+this.sourceData[i].text));
2347
2999
 
2348
- $('<div>').append($label).appendTo(this.$input);
3000
+ $('<div>').append($label).appendTo(this.$tpl);
2349
3001
  }
3002
+
3003
+ this.$input = this.$tpl.find('input[type="checkbox"]');
3004
+ this.setClass();
2350
3005
  },
2351
3006
 
2352
3007
  value2str: function(value) {
@@ -2361,23 +3016,24 @@ $(function(){
2361
3016
  value = str.split(reg);
2362
3017
  } else if($.isArray(str)) {
2363
3018
  value = str;
3019
+ } else {
3020
+ value = [str];
2364
3021
  }
2365
3022
  return value;
2366
3023
  },
2367
3024
 
2368
3025
  //set checked on required checkboxes
2369
3026
  value2input: function(value) {
2370
- var $checks = this.$input.find('input[type="checkbox"]');
2371
- $checks.removeAttr('checked');
3027
+ this.$input.prop('checked', false);
2372
3028
  if($.isArray(value) && value.length) {
2373
- $checks.each(function(i, el) {
3029
+ this.$input.each(function(i, el) {
2374
3030
  var $el = $(el);
2375
3031
  // cannot use $.inArray as it performs strict comparison
2376
3032
  $.each(value, function(j, val){
2377
3033
  /*jslint eqeq: true*/
2378
3034
  if($el.val() == val) {
2379
3035
  /*jslint eqeq: false*/
2380
- $el.attr('checked', 'checked');
3036
+ $el.prop('checked', true);
2381
3037
  }
2382
3038
  });
2383
3039
  });
@@ -2386,7 +3042,7 @@ $(function(){
2386
3042
 
2387
3043
  input2value: function() {
2388
3044
  var checked = [];
2389
- this.$input.find('input:checked').each(function(i, el) {
3045
+ this.$input.filter(':checked').each(function(i, el) {
2390
3046
  checked.push($(el).val());
2391
3047
  });
2392
3048
  return checked;
@@ -2395,11 +3051,8 @@ $(function(){
2395
3051
  //collect text of checked boxes
2396
3052
  value2htmlFinal: function(value, element) {
2397
3053
  var html = [],
2398
- /*jslint eqeq: true*/
2399
- checked = $.grep(this.sourceData, function(o){
2400
- return $.grep(value, function(v){ return v == o.value; }).length;
2401
- });
2402
- /*jslint eqeq: false*/
3054
+ checked = $.fn.editableutils.itemsByValue(value, this.sourceData);
3055
+
2403
3056
  if(checked.length) {
2404
3057
  $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
2405
3058
  $(element).html(html.join('<br>'));
@@ -2409,11 +3062,11 @@ $(function(){
2409
3062
  },
2410
3063
 
2411
3064
  activate: function() {
2412
- this.$input.find('input[type="checkbox"]').first().focus();
3065
+ this.$input.first().focus();
2413
3066
  },
2414
3067
 
2415
3068
  autosubmit: function() {
2416
- this.$input.find('input[type="checkbox"]').on('keydown', function(e){
3069
+ this.$input.on('keydown', function(e){
2417
3070
  if (e.which === 13) {
2418
3071
  $(this).closest('form').submit();
2419
3072
  }
@@ -2426,21 +3079,21 @@ $(function(){
2426
3079
  @property tpl
2427
3080
  @default <div></div>
2428
3081
  **/
2429
- tpl:'<div></div>',
3082
+ tpl:'<div class="editable-checklist"></div>',
2430
3083
 
2431
3084
  /**
2432
3085
  @property inputclass
2433
3086
  @type string
2434
- @default editable-checklist
3087
+ @default null
2435
3088
  **/
2436
- inputclass: 'editable-checklist',
3089
+ inputclass: null,
2437
3090
 
2438
3091
  /**
2439
- Separator of values when reading from 'data-value' string
3092
+ Separator of values when reading from `data-value` attribute
2440
3093
 
2441
3094
  @property separator
2442
3095
  @type string
2443
- @default ', '
3096
+ @default ','
2444
3097
  **/
2445
3098
  separator: ','
2446
3099
  });
@@ -2490,6 +3143,8 @@ $(function(){
2490
3143
  Password
2491
3144
  */
2492
3145
  (function ($) {
3146
+ "use strict";
3147
+
2493
3148
  var Password = function (options) {
2494
3149
  this.init('password', options, Password.defaults);
2495
3150
  };
@@ -2519,6 +3174,8 @@ Password
2519
3174
  Email
2520
3175
  */
2521
3176
  (function ($) {
3177
+ "use strict";
3178
+
2522
3179
  var Email = function (options) {
2523
3180
  this.init('email', options, Email.defaults);
2524
3181
  };
@@ -2534,6 +3191,8 @@ Email
2534
3191
  Url
2535
3192
  */
2536
3193
  (function ($) {
3194
+ "use strict";
3195
+
2537
3196
  var Url = function (options) {
2538
3197
  this.init('url', options, Url.defaults);
2539
3198
  };
@@ -2549,6 +3208,8 @@ Url
2549
3208
  Tel
2550
3209
  */
2551
3210
  (function ($) {
3211
+ "use strict";
3212
+
2552
3213
  var Tel = function (options) {
2553
3214
  this.init('tel', options, Tel.defaults);
2554
3215
  };
@@ -2564,6 +3225,8 @@ Tel
2564
3225
  Number
2565
3226
  */
2566
3227
  (function ($) {
3228
+ "use strict";
3229
+
2567
3230
  var NumberInput = function (options) {
2568
3231
  this.init('number', options, NumberInput.defaults);
2569
3232
  };
@@ -2571,19 +3234,24 @@ Number
2571
3234
  $.extend(NumberInput.prototype, {
2572
3235
  render: function () {
2573
3236
  NumberInput.superclass.render.call(this);
2574
-
2575
- if (this.options.min !== null) {
2576
- this.$input.attr('min', this.options.min);
2577
- }
2578
-
2579
- if (this.options.max !== null) {
2580
- this.$input.attr('max', this.options.max);
3237
+ this.setAttr('min');
3238
+ this.setAttr('max');
3239
+ this.setAttr('step');
3240
+ },
3241
+ postrender: function() {
3242
+ if(this.$clear) {
3243
+ //increase right ffset for up/down arrows
3244
+ this.$clear.css({right: 24});
3245
+ /*
3246
+ //can position clear button only here, when form is shown and height can be calculated
3247
+ var h = this.$input.outerHeight(true) || 20,
3248
+ delta = (h - this.$clear.height()) / 2;
3249
+
3250
+ //add 12px to offset right for up/down arrows
3251
+ this.$clear.css({top: delta, right: delta + 16});
3252
+ */
2581
3253
  }
2582
-
2583
- if (this.options.step !== null) {
2584
- this.$input.attr('step', this.options.step);
2585
- }
2586
- }
3254
+ }
2587
3255
  });
2588
3256
  NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
2589
3257
  tpl: '<input type="number">',
@@ -2600,35 +3268,27 @@ Number
2600
3268
  Range (inherit from number)
2601
3269
  */
2602
3270
  (function ($) {
3271
+ "use strict";
3272
+
2603
3273
  var Range = function (options) {
2604
3274
  this.init('range', options, Range.defaults);
2605
3275
  };
2606
3276
  $.fn.editableutils.inherit(Range, $.fn.editabletypes.number);
2607
3277
  $.extend(Range.prototype, {
2608
3278
  render: function () {
2609
- this.$input = $(this.options.tpl);
2610
- var $slider = this.$input.filter('input');
2611
- if(this.options.inputclass) {
2612
- $slider.addClass(this.options.inputclass);
2613
- }
2614
- if (this.options.min !== null) {
2615
- $slider.attr('min', this.options.min);
2616
- }
3279
+ this.$input = this.$tpl.filter('input');
2617
3280
 
2618
- if (this.options.max !== null) {
2619
- $slider.attr('max', this.options.max);
2620
- }
2621
-
2622
- if (this.options.step !== null) {
2623
- $slider.attr('step', this.options.step);
2624
- }
3281
+ this.setClass();
3282
+ this.setAttr('min');
3283
+ this.setAttr('max');
3284
+ this.setAttr('step');
2625
3285
 
2626
- $slider.on('input', function(){
3286
+ this.$input.on('input', function(){
2627
3287
  $(this).siblings('output').text($(this).val());
2628
3288
  });
2629
3289
  },
2630
3290
  activate: function() {
2631
- this.$input.filter('input').focus();
3291
+ this.$input.focus();
2632
3292
  }
2633
3293
  });
2634
3294
  Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {
@@ -2638,41 +3298,927 @@ Range (inherit from number)
2638
3298
  $.fn.editabletypes.range = Range;
2639
3299
  }(window.jQuery));
2640
3300
  /**
2641
- * Editable Poshytip
2642
- * ---------------------
2643
- * requires jquery.poshytip.js
2644
- */
3301
+ Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
3302
+ Please see [original docs](http://ivaynberg.github.com/select2) for detailed description and options.
3303
+ You should manually include select2 distributive:
3304
+
3305
+ <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
3306
+ <script src="select2/select2.js"></script>
3307
+
3308
+ For make it **Bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
3309
+
3310
+ <link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>
3311
+
3312
+ **Note:** currently `ajax` source for select2 is not supported, as it's not possible to load it in closed select2 state.
3313
+ The solution is to load source manually and assign statically.
3314
+
3315
+ @class select2
3316
+ @extends abstractinput
3317
+ @since 1.4.1
3318
+ @final
3319
+ @example
3320
+ <a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-original-title="Select country"></a>
3321
+ <script>
3322
+ $(function(){
3323
+ $('#country').editable({
3324
+ source: [
3325
+ {id: 'gb', text: 'Great Britain'},
3326
+ {id: 'us', text: 'United States'},
3327
+ {id: 'ru', text: 'Russia'}
3328
+ ],
3329
+ select2: {
3330
+ multiple: true
3331
+ }
3332
+ });
3333
+ });
3334
+ </script>
3335
+ **/
2645
3336
  (function ($) {
3337
+ "use strict";
2646
3338
 
2647
- //extend methods
2648
- $.extend($.fn.editableContainer.Constructor.prototype, {
2649
- containerName: 'poshytip',
2650
- innerCss: 'div.tip-inner',
2651
-
2652
- initContainer: function(){
2653
- this.handlePlacement();
2654
-
2655
- $.extend(this.containerOptions, {
2656
- showOn: 'none',
2657
- content: '',
2658
- alignTo: 'target'
2659
- });
2660
-
2661
- this.call(this.containerOptions);
2662
-
2663
- var $content = $('<div>')
2664
- .append($('<label>').text(this.options.title || this.$element.data( "title") || this.$element.data( "originalTitle")))
2665
- .append(this.initForm());
2666
-
2667
- this.call('update', $content);
2668
- },
3339
+ var Constructor = function (options) {
3340
+ this.init('select2', options, Constructor.defaults);
3341
+
3342
+ options.select2 = options.select2 || {};
2669
3343
 
2670
- innerShow: function () {
2671
- this.$form.editableform('render');
3344
+ var that = this,
3345
+ mixin = { //mixin to select2 options
3346
+ placeholder: options.placeholder
3347
+ };
3348
+
3349
+ //detect whether it is multi-valued
3350
+ this.isMultiple = options.select2.tags || options.select2.multiple;
3351
+
3352
+ //if not `tags` mode, we need define initSelection to set data from source
3353
+ if(!options.select2.tags) {
3354
+ if(options.source) {
3355
+ mixin.data = options.source;
3356
+ }
3357
+
3358
+ //this function can be defaulted in seletc2. See https://github.com/ivaynberg/select2/issues/710
3359
+ mixin.initSelection = function (element, callback) {
3360
+ //temp: try update results
3361
+ /*
3362
+ if(options.select2 && options.select2.ajax) {
3363
+ console.log('attached');
3364
+ var original = $(element).data('select2').postprocessResults;
3365
+ console.log(original);
3366
+ $(element).data('select2').postprocessResults = function(data, initial) {
3367
+ console.log('postprocess');
3368
+ // this.element.triggerHandler('loaded', [data]);
3369
+ original.apply(this, arguments);
3370
+ }
3371
+
3372
+ // $(element).on('loaded', function(){console.log('loaded');});
3373
+ $(element).data('select2').updateResults(true);
3374
+ }
3375
+ */
3376
+
3377
+ var val = that.str2value(element.val()),
3378
+ data = $.fn.editableutils.itemsByValue(val, mixin.data, 'id');
3379
+
3380
+ //for single-valued mode should not use array. Take first element instead.
3381
+ if($.isArray(data) && data.length && !that.isMultiple) {
3382
+ data = data[0];
3383
+ }
3384
+
3385
+ callback(data);
3386
+ };
3387
+ }
3388
+
3389
+ //overriding objects in config (as by default jQuery extend() is not recursive)
3390
+ this.options.select2 = $.extend({}, Constructor.defaults.select2, mixin, options.select2);
3391
+ };
3392
+
3393
+ $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
3394
+
3395
+ $.extend(Constructor.prototype, {
3396
+ render: function() {
3397
+ this.setClass();
3398
+ //apply select2
3399
+ this.$input.select2(this.options.select2);
3400
+
3401
+ //when data is loaded via ajax, we need to know when it's done
3402
+ if('ajax' in this.options.select2) {
3403
+ /*
3404
+ console.log('attached');
3405
+ var original = this.$input.data('select2').postprocessResults;
3406
+ this.$input.data('select2').postprocessResults = function(data, initial) {
3407
+ this.element.triggerHandler('loaded', [data]);
3408
+ original.apply(this, arguments);
3409
+ }
3410
+ */
3411
+ }
3412
+
3413
+
3414
+ //trigger resize of editableform to re-position container in multi-valued mode
3415
+ if(this.isMultiple) {
3416
+ this.$input.on('change', function() {
3417
+ $(this).closest('form').parent().triggerHandler('resize');
3418
+ });
3419
+ }
3420
+ },
3421
+
3422
+ value2html: function(value, element) {
3423
+ var text = '', data;
3424
+ if(this.$input) { //called when submitting form and select2 already exists
3425
+ data = this.$input.select2('data');
3426
+ } else { //on init (autotext)
3427
+ //here select2 instance not created yet and data may be even not loaded.
3428
+ //we can check data/tags property of select config and if exist lookup text
3429
+ if(this.options.select2.tags) {
3430
+ data = value;
3431
+ } else if(this.options.select2.data) {
3432
+ data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id');
3433
+ } else {
3434
+ //if('ajax' in this.options.select2) {
3435
+ }
3436
+ }
3437
+
3438
+ if($.isArray(data)) {
3439
+ //collect selected data and show with separator
3440
+ text = [];
3441
+ $.each(data, function(k, v){
3442
+ text.push(v && typeof v === 'object' ? v.text : v);
3443
+ });
3444
+ } else if(data) {
3445
+ text = data.text;
3446
+ }
3447
+
3448
+ text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
3449
+
3450
+ $(element).text(text);
3451
+ },
3452
+
3453
+ html2value: function(html) {
3454
+ return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
3455
+ },
3456
+
3457
+ value2input: function(value) {
3458
+ this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
3459
+ },
3460
+
3461
+ input2value: function() {
3462
+ return this.$input.select2('val');
3463
+ },
3464
+
3465
+ str2value: function(str, separator) {
3466
+ if(typeof str !== 'string' || !this.isMultiple) {
3467
+ return str;
3468
+ }
3469
+
3470
+ separator = separator || this.options.select2.separator || $.fn.select2.defaults.separator;
3471
+
3472
+ var val, i, l;
3473
+
3474
+ if (str === null || str.length < 1) {
3475
+ return null;
3476
+ }
3477
+ val = str.split(separator);
3478
+ for (i = 0, l = val.length; i < l; i = i + 1) {
3479
+ val[i] = $.trim(val[i]);
3480
+ }
3481
+
3482
+ return val;
3483
+ },
3484
+
3485
+ autosubmit: function() {
3486
+ this.$input.on('change', function(e, isInitial){
3487
+ if(!isInitial) {
3488
+ $(this).closest('form').submit();
3489
+ }
3490
+ });
3491
+ }
3492
+
3493
+ });
3494
+
3495
+ Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
3496
+ /**
3497
+ @property tpl
3498
+ @default <input type="hidden">
3499
+ **/
3500
+ tpl:'<input type="hidden">',
3501
+ /**
3502
+ Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
3503
+
3504
+ @property select2
3505
+ @type object
3506
+ @default null
3507
+ **/
3508
+ select2: null,
3509
+ /**
3510
+ Placeholder attribute of select
3511
+
3512
+ @property placeholder
3513
+ @type string
3514
+ @default null
3515
+ **/
3516
+ placeholder: null,
3517
+ /**
3518
+ Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.
3519
+ Please note, that format is different from simple `select` input: use 'id' instead of 'value'.
3520
+ E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.
3521
+
3522
+ @property source
3523
+ @type array
3524
+ @default null
3525
+ **/
3526
+ source: null,
3527
+ /**
3528
+ Separator used to display tags.
3529
+
3530
+ @property viewseparator
3531
+ @type string
3532
+ @default ', '
3533
+ **/
3534
+ viewseparator: ', '
3535
+ });
3536
+
3537
+ $.fn.editabletypes.select2 = Constructor;
3538
+
3539
+ }(window.jQuery));
3540
+
3541
+ /**
3542
+ * Combodate - 1.0.3
3543
+ * Dropdown date and time picker.
3544
+ * Converts text input into dropdowns to pick day, month, year, hour, minute and second.
3545
+ * Uses momentjs as datetime library http://momentjs.com.
3546
+ * For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang
3547
+ *
3548
+ * Author: Vitaliy Potapov
3549
+ * Project page: http://github.com/vitalets/combodate
3550
+ * Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
3551
+ **/
3552
+ (function ($) {
3553
+
3554
+ var Combodate = function (element, options) {
3555
+ this.$element = $(element);
3556
+ if(!this.$element.is('input')) {
3557
+ $.error('Combodate should be applied to INPUT element');
3558
+ return;
3559
+ }
3560
+ this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data());
3561
+ this.init();
3562
+ };
3563
+
3564
+ Combodate.prototype = {
3565
+ constructor: Combodate,
3566
+ init: function () {
3567
+ this.map = {
3568
+ //key regexp moment.method
3569
+ day: ['D', 'date'],
3570
+ month: ['M', 'month'],
3571
+ year: ['Y', 'year'],
3572
+ hour: ['[Hh]', 'hours'],
3573
+ minute: ['m', 'minutes'],
3574
+ second: ['s', 'seconds'],
3575
+ ampm: ['[Aa]', '']
3576
+ };
3577
+
3578
+ this.$widget = $('<span class="combodate"></span>').html(this.getTemplate());
3579
+
3580
+ this.initCombos();
3581
+
3582
+ //update original input on change
3583
+ this.$widget.on('change', 'select', $.proxy(function(){
3584
+ this.$element.val(this.getValue());
3585
+ }, this));
3586
+
3587
+ this.$widget.find('select').css('width', 'auto');
3588
+
3589
+ //hide original input and insert widget
3590
+ this.$element.hide().after(this.$widget);
3591
+
3592
+ //set initial value
3593
+ this.setValue(this.$element.val() || this.options.value);
3594
+ },
3595
+
3596
+ /*
3597
+ Replace tokens in template with <select> elements
3598
+ */
3599
+ getTemplate: function() {
3600
+ var tpl = this.options.template;
3601
+
3602
+ //first pass
3603
+ $.each(this.map, function(k, v) {
3604
+ v = v[0];
3605
+ var r = new RegExp(v+'+'),
3606
+ token = v.length > 1 ? v.substring(1, 2) : v;
3607
+
3608
+ tpl = tpl.replace(r, '{'+token+'}');
3609
+ });
3610
+
3611
+ //replace spaces with &nbsp;
3612
+ tpl = tpl.replace(/ /g, '&nbsp;');
3613
+
3614
+ //second pass
3615
+ $.each(this.map, function(k, v) {
3616
+ v = v[0];
3617
+ var token = v.length > 1 ? v.substring(1, 2) : v;
3618
+
3619
+ tpl = tpl.replace('{'+token+'}', '<select class="'+k+'"></select>');
3620
+ });
3621
+
3622
+ return tpl;
3623
+ },
3624
+
3625
+ /*
3626
+ Initialize combos that presents in template
3627
+ */
3628
+ initCombos: function() {
3629
+ var that = this;
3630
+ $.each(this.map, function(k, v) {
3631
+ var $c = that.$widget.find('.'+k), f, items;
3632
+ if($c.length) {
3633
+ that['$'+k] = $c; //set properties like this.$day, this.$month etc.
3634
+ f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1); //define method name to fill items, e.g `fillDays`
3635
+ items = that[f]();
3636
+ that['$'+k].html(that.renderItems(items));
3637
+ }
3638
+ });
3639
+ },
3640
+
3641
+ /*
3642
+ Initialize items of combos. Handles `firstItem` option
3643
+ */
3644
+ initItems: function(key) {
3645
+ var values = [],
3646
+ relTime;
3647
+
3648
+ if(this.options.firstItem === 'name') {
3649
+ //need both to support moment ver < 2 and >= 2
3650
+ relTime = moment.relativeTime || moment.langData()._relativeTime;
3651
+ var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];
3652
+ //take last entry (see momentjs lang files structure)
3653
+ header = header.split(' ').reverse()[0];
3654
+ values.push(['', header]);
3655
+ } else if(this.options.firstItem === 'empty') {
3656
+ values.push(['', '']);
3657
+ }
3658
+ return values;
3659
+ },
3660
+
3661
+ /*
3662
+ render items to string of <option> tags
3663
+ */
3664
+ renderItems: function(items) {
3665
+ var str = [];
3666
+ for(var i=0; i<items.length; i++) {
3667
+ str.push('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');
3668
+ }
3669
+ return str.join("\n");
3670
+ },
3671
+
3672
+ /*
3673
+ fill day
3674
+ */
3675
+ fillDay: function() {
3676
+ var items = this.initItems('d'), name, i,
3677
+ twoDigit = this.options.template.indexOf('DD') !== -1;
3678
+
3679
+ for(i=1; i<=31; i++) {
3680
+ name = twoDigit ? this.leadZero(i) : i;
3681
+ items.push([i, name]);
3682
+ }
3683
+ return items;
3684
+ },
3685
+
3686
+ /*
3687
+ fill month
3688
+ */
3689
+ fillMonth: function() {
3690
+ var items = this.initItems('M'), name, i,
3691
+ longNames = this.options.template.indexOf('MMMM') !== -1,
3692
+ shortNames = this.options.template.indexOf('MMM') !== -1,
3693
+ twoDigit = this.options.template.indexOf('MM') !== -1;
3694
+
3695
+ for(i=0; i<=11; i++) {
3696
+ if(longNames) {
3697
+ name = moment().month(i).format('MMMM');
3698
+ } else if(shortNames) {
3699
+ name = moment().month(i).format('MMM');
3700
+ } else if(twoDigit) {
3701
+ name = this.leadZero(i+1);
3702
+ } else {
3703
+ name = i+1;
3704
+ }
3705
+ items.push([i, name]);
3706
+ }
3707
+ return items;
3708
+ },
3709
+
3710
+ /*
3711
+ fill year
3712
+ */
3713
+ fillYear: function() {
3714
+ var items = [], name, i,
3715
+ longNames = this.options.template.indexOf('YYYY') !== -1;
3716
+
3717
+ for(i=this.options.maxYear; i>=this.options.minYear; i--) {
3718
+ name = longNames ? i : (i+'').substring(2);
3719
+ items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);
3720
+ }
3721
+
3722
+ items = this.initItems('y').concat(items);
3723
+
3724
+ return items;
3725
+ },
3726
+
3727
+ /*
3728
+ fill hour
3729
+ */
3730
+ fillHour: function() {
3731
+ var items = this.initItems('h'), name, i,
3732
+ h12 = this.options.template.indexOf('h') !== -1,
3733
+ h24 = this.options.template.indexOf('H') !== -1,
3734
+ twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
3735
+ max = h12 ? 12 : 23;
3736
+
3737
+ for(i=0; i<=max; i++) {
3738
+ name = twoDigit ? this.leadZero(i) : i;
3739
+ items.push([i, name]);
3740
+ }
3741
+ return items;
3742
+ },
3743
+
3744
+ /*
3745
+ fill minute
3746
+ */
3747
+ fillMinute: function() {
3748
+ var items = this.initItems('m'), name, i,
3749
+ twoDigit = this.options.template.indexOf('mm') !== -1;
3750
+
3751
+ for(i=0; i<=59; i+= this.options.minuteStep) {
3752
+ name = twoDigit ? this.leadZero(i) : i;
3753
+ items.push([i, name]);
3754
+ }
3755
+ return items;
3756
+ },
3757
+
3758
+ /*
3759
+ fill second
3760
+ */
3761
+ fillSecond: function() {
3762
+ var items = this.initItems('s'), name, i,
3763
+ twoDigit = this.options.template.indexOf('ss') !== -1;
3764
+
3765
+ for(i=0; i<=59; i+= this.options.secondStep) {
3766
+ name = twoDigit ? this.leadZero(i) : i;
3767
+ items.push([i, name]);
3768
+ }
3769
+ return items;
3770
+ },
3771
+
3772
+ /*
3773
+ fill ampm
3774
+ */
3775
+ fillAmpm: function() {
3776
+ var ampmL = this.options.template.indexOf('a') !== -1,
3777
+ ampmU = this.options.template.indexOf('A') !== -1,
3778
+ items = [
3779
+ ['am', ampmL ? 'am' : 'AM'],
3780
+ ['pm', ampmL ? 'pm' : 'PM']
3781
+ ];
3782
+ return items;
3783
+ },
3784
+
3785
+ /*
3786
+ Returns current date value.
3787
+ If format not specified - `options.format` used.
3788
+ If format = `null` - Moment object returned.
3789
+ */
3790
+ getValue: function(format) {
3791
+ var dt, values = {},
3792
+ that = this,
3793
+ notSelected = false;
3794
+
3795
+ //getting selected values
3796
+ $.each(this.map, function(k, v) {
3797
+ if(k === 'ampm') {
3798
+ return;
3799
+ }
3800
+ var def = k === 'day' ? 1 : 0;
3801
+
3802
+ values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def;
3803
+
3804
+ if(isNaN(values[k])) {
3805
+ notSelected = true;
3806
+ return false;
3807
+ }
3808
+ });
3809
+
3810
+ //if at least one visible combo not selected - return empty string
3811
+ if(notSelected) {
3812
+ return '';
3813
+ }
3814
+
3815
+ //convert hours if 12h format
3816
+ if(this.$ampm) {
3817
+ values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
3818
+ if(values.hour === 24) {
3819
+ values.hour = 0;
3820
+ }
3821
+ }
3822
+
3823
+ dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
3824
+
3825
+ //highlight invalid date
3826
+ this.highlight(dt);
3827
+
3828
+ format = format === undefined ? this.options.format : format;
3829
+ if(format === null) {
3830
+ return dt.isValid() ? dt : null;
3831
+ } else {
3832
+ return dt.isValid() ? dt.format(format) : '';
3833
+ }
3834
+ },
3835
+
3836
+ setValue: function(value) {
3837
+ if(!value) {
3838
+ return;
3839
+ }
3840
+
3841
+ var dt = typeof value === 'string' ? moment(value, this.options.format) : moment(value),
3842
+ that = this,
3843
+ values = {};
3844
+
3845
+ //function to find nearest value in select options
3846
+ function getNearest($select, value) {
3847
+ var delta = {};
3848
+ $select.children('option').each(function(i, opt){
3849
+ var optValue = $(opt).attr('value'),
3850
+ distance;
3851
+
3852
+ if(optValue === '') return;
3853
+ distance = Math.abs(optValue - value);
3854
+ if(typeof delta.distance === 'undefined' || distance < delta.distance) {
3855
+ delta = {value: optValue, distance: distance};
3856
+ }
3857
+ });
3858
+ return delta.value;
3859
+ }
3860
+
3861
+ if(dt.isValid()) {
3862
+ //read values from date object
3863
+ $.each(this.map, function(k, v) {
3864
+ if(k === 'ampm') {
3865
+ return;
3866
+ }
3867
+ values[k] = dt[v[1]]();
3868
+ });
3869
+
3870
+ if(this.$ampm) {
3871
+ if(values.hour > 12) {
3872
+ values.hour -= 12;
3873
+ values.ampm = 'pm';
3874
+ } else {
3875
+ values.ampm = 'am';
3876
+ }
3877
+ }
3878
+
3879
+ $.each(values, function(k, v) {
3880
+ //call val() for each existing combo, e.g. this.$hour.val()
3881
+ if(that['$'+k]) {
3882
+
3883
+ if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) {
3884
+ v = getNearest(that['$'+k], v);
3885
+ }
3886
+
3887
+ if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) {
3888
+ v = getNearest(that['$'+k], v);
3889
+ }
3890
+
3891
+ that['$'+k].val(v);
3892
+ }
3893
+ });
3894
+
3895
+ this.$element.val(dt.format(this.options.format));
3896
+ }
3897
+ },
3898
+
3899
+ /*
3900
+ highlight combos if date is invalid
3901
+ */
3902
+ highlight: function(dt) {
3903
+ if(!dt.isValid()) {
3904
+ if(this.options.errorClass) {
3905
+ this.$widget.addClass(this.options.errorClass);
3906
+ } else {
3907
+ //store original border color
3908
+ if(!this.borderColor) {
3909
+ this.borderColor = this.$widget.find('select').css('border-color');
3910
+ }
3911
+ this.$widget.find('select').css('border-color', 'red');
3912
+ }
3913
+ } else {
3914
+ if(this.options.errorClass) {
3915
+ this.$widget.removeClass(this.options.errorClass);
3916
+ } else {
3917
+ this.$widget.find('select').css('border-color', this.borderColor);
3918
+ }
3919
+ }
3920
+ },
3921
+
3922
+ leadZero: function(v) {
3923
+ return v <= 9 ? '0' + v : v;
3924
+ },
3925
+
3926
+ destroy: function() {
3927
+ this.$widget.remove();
3928
+ this.$element.removeData('combodate').show();
3929
+ }
3930
+
3931
+ //todo: clear method
3932
+ };
3933
+
3934
+ $.fn.combodate = function ( option ) {
3935
+ var d, args = Array.apply(null, arguments);
3936
+ args.shift();
3937
+
3938
+ //getValue returns date as string / object (not jQuery object)
3939
+ if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) {
3940
+ return d.getValue.apply(d, args);
3941
+ }
3942
+
3943
+ return this.each(function () {
3944
+ var $this = $(this),
3945
+ data = $this.data('combodate'),
3946
+ options = typeof option == 'object' && option;
3947
+ if (!data) {
3948
+ $this.data('combodate', (data = new Combodate(this, options)));
3949
+ }
3950
+ if (typeof option == 'string' && typeof data[option] == 'function') {
3951
+ data[option].apply(data, args);
3952
+ }
3953
+ });
3954
+ };
3955
+
3956
+ $.fn.combodate.defaults = {
3957
+ //in this format value stored in original input
3958
+ format: 'DD-MM-YYYY HH:mm',
3959
+ //in this format items in dropdowns are displayed
3960
+ template: 'D / MMM / YYYY H : mm',
3961
+ //initial value, can be `new Date()`
3962
+ value: null,
3963
+ minYear: 1970,
3964
+ maxYear: 2015,
3965
+ yearDescending: true,
3966
+ minuteStep: 5,
3967
+ secondStep: 1,
3968
+ firstItem: 'empty', //'name', 'empty', 'none'
3969
+ errorClass: null,
3970
+ roundTime: true //whether to round minutes and seconds if step > 1
3971
+ };
3972
+
3973
+ }(window.jQuery));
3974
+ /**
3975
+ Combodate input - dropdown date and time picker.
3976
+ Based on [combodate](http://vitalets.github.com/combodate) plugin (included). To use it you should manually include [momentjs](http://momentjs.com).
3977
+
3978
+ <script src="js/moment.min.js"></script>
3979
+
3980
+ Allows to input:
3981
+
3982
+ * only date
3983
+ * only time
3984
+ * both date and time
3985
+
3986
+ Please note, that format is taken from momentjs and **not compatible** with bootstrap-datepicker / jquery UI datepicker.
3987
+ Internally value stored as `momentjs` object.
3988
+
3989
+ @class combodate
3990
+ @extends abstractinput
3991
+ @final
3992
+ @since 1.4.0
3993
+ @example
3994
+ <a href="#" id="dob" data-type="combodate" data-pk="1" data-url="/post" data-value="1984-05-15" data-original-title="Select date"></a>
3995
+ <script>
3996
+ $(function(){
3997
+ $('#dob').editable({
3998
+ format: 'YYYY-MM-DD',
3999
+ viewformat: 'DD.MM.YYYY',
4000
+ template: 'D / MMMM / YYYY',
4001
+ combodate: {
4002
+ minYear: 2000,
4003
+ maxYear: 2015,
4004
+ minuteStep: 1
4005
+ }
4006
+ }
4007
+ });
4008
+ });
4009
+ </script>
4010
+ **/
4011
+
4012
+ /*global moment*/
4013
+
4014
+ (function ($) {
4015
+ "use strict";
4016
+
4017
+ var Constructor = function (options) {
4018
+ this.init('combodate', options, Constructor.defaults);
4019
+
4020
+ //by default viewformat equals to format
4021
+ if(!this.options.viewformat) {
4022
+ this.options.viewformat = this.options.format;
4023
+ }
4024
+
4025
+ //try parse combodate config defined as json string in data-combodate
4026
+ options.combodate = $.fn.editableutils.tryParseJson(options.combodate, true);
4027
+
4028
+ //overriding combodate config (as by default jQuery extend() is not recursive)
4029
+ this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, {
4030
+ format: this.options.format,
4031
+ template: this.options.template
4032
+ });
4033
+ };
4034
+
4035
+ $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
4036
+
4037
+ $.extend(Constructor.prototype, {
4038
+ render: function () {
4039
+ this.$input.combodate(this.options.combodate);
4040
+
4041
+ //"clear" link
4042
+ /*
4043
+ if(this.options.clear) {
4044
+ this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
4045
+ e.preventDefault();
4046
+ e.stopPropagation();
4047
+ this.clear();
4048
+ }, this));
4049
+
4050
+ this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
4051
+ }
4052
+ */
4053
+ },
4054
+
4055
+ value2html: function(value, element) {
4056
+ var text = value ? value.format(this.options.viewformat) : '';
4057
+ $(element).text(text);
4058
+ },
4059
+
4060
+ html2value: function(html) {
4061
+ return html ? moment(html, this.options.viewformat) : null;
4062
+ },
4063
+
4064
+ value2str: function(value) {
4065
+ return value ? value.format(this.options.format) : '';
4066
+ },
4067
+
4068
+ str2value: function(str) {
4069
+ return str ? moment(str, this.options.format) : null;
4070
+ },
4071
+
4072
+ value2submit: function(value) {
4073
+ return this.value2str(value);
4074
+ },
4075
+
4076
+ value2input: function(value) {
4077
+ this.$input.combodate('setValue', value);
4078
+ },
4079
+
4080
+ input2value: function() {
4081
+ return this.$input.combodate('getValue', null);
4082
+ },
4083
+
4084
+ activate: function() {
4085
+ this.$input.siblings('.combodate').find('select').eq(0).focus();
4086
+ },
4087
+
4088
+ /*
4089
+ clear: function() {
4090
+ this.$input.data('datepicker').date = null;
4091
+ this.$input.find('.active').removeClass('active');
4092
+ },
4093
+ */
4094
+
4095
+ autosubmit: function() {
4096
+
4097
+ }
4098
+
4099
+ });
4100
+
4101
+ Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
4102
+ /**
4103
+ @property tpl
4104
+ @default <input type="text">
4105
+ **/
4106
+ tpl:'<input type="text">',
4107
+ /**
4108
+ @property inputclass
4109
+ @default null
4110
+ **/
4111
+ inputclass: null,
4112
+ /**
4113
+ Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
4114
+ See list of tokens in [momentjs docs](http://momentjs.com/docs/#/parsing/string-format)
4115
+
4116
+ @property format
4117
+ @type string
4118
+ @default YYYY-MM-DD
4119
+ **/
4120
+ format:'YYYY-MM-DD',
4121
+ /**
4122
+ Format used for displaying date. Also applied when converting date from element's text on init.
4123
+ If not specified equals to `format`.
4124
+
4125
+ @property viewformat
4126
+ @type string
4127
+ @default null
4128
+ **/
4129
+ viewformat: null,
4130
+ /**
4131
+ Template used for displaying dropdowns.
4132
+
4133
+ @property template
4134
+ @type string
4135
+ @default D / MMM / YYYY
4136
+ **/
4137
+ template: 'D / MMM / YYYY',
4138
+ /**
4139
+ Configuration of combodate.
4140
+ Full list of options: http://vitalets.github.com/combodate/#docs
4141
+
4142
+ @property combodate
4143
+ @type object
4144
+ @default null
4145
+ **/
4146
+ combodate: null
4147
+
4148
+ /*
4149
+ (not implemented yet)
4150
+ Text shown as clear date button.
4151
+ If <code>false</code> clear button will not be rendered.
4152
+
4153
+ @property clear
4154
+ @type boolean|string
4155
+ @default 'x clear'
4156
+ */
4157
+ //clear: '&times; clear'
4158
+ });
4159
+
4160
+ $.fn.editabletypes.combodate = Constructor;
4161
+
4162
+ }(window.jQuery));
4163
+
4164
+ /**
4165
+ * Editable Poshytip
4166
+ * ---------------------
4167
+ * requires jquery.poshytip.js
4168
+ */
4169
+ (function ($) {
4170
+ "use strict";
4171
+
4172
+ //extend methods
4173
+ $.extend($.fn.editableContainer.Popup.prototype, {
4174
+ containerName: 'poshytip',
4175
+ innerCss: 'div.tip-inner',
4176
+
4177
+ initContainer: function(){
4178
+ this.handlePlacement();
4179
+
4180
+ $.extend(this.containerOptions, {
4181
+ showOn: 'none',
4182
+ content: '',
4183
+ alignTo: 'target'
4184
+ });
4185
+
4186
+ this.call(this.containerOptions);
4187
+ },
4188
+
4189
+ /*
4190
+ Overwrite totally show() method as poshytip requires content is set before show
4191
+ */
4192
+ show: function (closeAll) {
4193
+ this.$element.addClass('editable-open');
4194
+ if(closeAll !== false) {
4195
+ //close all open containers (except this)
4196
+ this.closeOthers(this.$element[0]);
4197
+ }
4198
+
4199
+ //render form
4200
+ this.$form = $('<div>');
4201
+ this.renderForm();
4202
+
4203
+ var $label = $('<label>').text(this.options.title || this.$element.data( "title") || this.$element.data( "originalTitle")),
4204
+ $content = $('<div>').append($label).append(this.$form);
4205
+
4206
+ this.call('update', $content);
2672
4207
  this.call('show');
4208
+
2673
4209
  this.tip().addClass('editable-container');
2674
4210
  this.$form.data('editableform').input.activate();
2675
- },
4211
+ },
4212
+
4213
+ /* hide */
4214
+ innerHide: function () {
4215
+ this.call('hide');
4216
+ },
4217
+
4218
+ /* destroy */
4219
+ innerDestroy: function() {
4220
+ this.call('destroy');
4221
+ },
2676
4222
 
2677
4223
  setPosition: function() {
2678
4224
  this.container().refresh(false);
@@ -2723,106 +4269,109 @@ Range (inherit from number)
2723
4269
  * see https://github.com/vadikom/poshytip/issues/7
2724
4270
  */
2725
4271
  /*jshint eqeqeq:false, curly: false*/
2726
- var tips = [],
2727
- reBgImage = /^url\(["']?([^"'\)]*)["']?\);?$/i,
2728
- rePNG = /\.png$/i,
2729
- ie6 = $.browser.msie && $.browser.version == 6;
2730
-
2731
- $.Poshytip.prototype.refresh = function(async) {
2732
- if (this.disabled)
2733
- return;
2734
-
2735
- var currPos;
2736
- if (async) {
2737
- if (!this.$tip.data('active'))
2738
- return;
2739
- // save current position as we will need to animate
2740
- currPos = {left: this.$tip.css('left'), top: this.$tip.css('top')};
2741
- }
2742
-
2743
- // reset position to avoid text wrapping, etc.
2744
- this.$tip.css({left: 0, top: 0}).appendTo(document.body);
2745
-
2746
- // save default opacity
2747
- if (this.opacity === undefined)
2748
- this.opacity = this.$tip.css('opacity');
2749
-
2750
- // check for images - this code is here (i.e. executed each time we show the tip and not on init) due to some browser inconsistencies
2751
- var bgImage = this.$tip.css('background-image').match(reBgImage),
2752
- arrow = this.$arrow.css('background-image').match(reBgImage);
2753
-
2754
- if (bgImage) {
2755
- var bgImagePNG = rePNG.test(bgImage[1]);
2756
- // fallback to background-color/padding/border in IE6 if a PNG is used
2757
- if (ie6 && bgImagePNG) {
2758
- this.$tip.css('background-image', 'none');
2759
- this.$inner.css({margin: 0, border: 0, padding: 0});
2760
- bgImage = bgImagePNG = false;
2761
- } else {
2762
- this.$tip.prepend('<table class="fallback" border="0" cellpadding="0" cellspacing="0"><tr><td class="tip-top tip-bg-image" colspan="2"><span></span></td><td class="tip-right tip-bg-image" rowspan="2"><span></span></td></tr><tr><td class="tip-left tip-bg-image" rowspan="2"><span></span></td><td></td></tr><tr><td class="tip-bottom tip-bg-image" colspan="2"><span></span></td></tr></table>')
2763
- .css({border: 0, padding: 0, 'background-image': 'none', 'background-color': 'transparent'})
2764
- .find('.tip-bg-image').css('background-image', 'url("' + bgImage[1] +'")').end()
2765
- .find('td').eq(3).append(this.$inner);
2766
- }
2767
- // disable fade effect in IE due to Alpha filter + translucent PNG issue
2768
- if (bgImagePNG && !$.support.opacity)
2769
- this.opts.fade = false;
2770
- }
2771
- // IE arrow fixes
2772
- if (arrow && !$.support.opacity) {
2773
- // disable arrow in IE6 if using a PNG
2774
- if (ie6 && rePNG.test(arrow[1])) {
2775
- arrow = false;
2776
- this.$arrow.css('background-image', 'none');
2777
- }
2778
- // disable fade effect in IE due to Alpha filter + translucent PNG issue
2779
- this.opts.fade = false;
2780
- }
2781
-
2782
- var $table = this.$tip.find('table.fallback');
2783
- if (ie6) {
2784
- // fix min/max-width in IE6
2785
- this.$tip[0].style.width = '';
2786
- $table.width('auto').find('td').eq(3).width('auto');
2787
- var tipW = this.$tip.width(),
2788
- minW = parseInt(this.$tip.css('min-width'), 10),
2789
- maxW = parseInt(this.$tip.css('max-width'), 10);
2790
- if (!isNaN(minW) && tipW < minW)
2791
- tipW = minW;
2792
- else if (!isNaN(maxW) && tipW > maxW)
2793
- tipW = maxW;
2794
- this.$tip.add($table).width(tipW).eq(0).find('td').eq(3).width('100%');
2795
- } else if ($table[0]) {
2796
- // fix the table width if we are using a background image
2797
- // IE9, FF4 use float numbers for width/height so use getComputedStyle for them to avoid text wrapping
2798
- // for details look at: http://vadikom.com/dailies/offsetwidth-offsetheight-useless-in-ie9-firefox4/
2799
- $table.width('auto').find('td').eq(3).width('auto').end().end().width(document.defaultView && document.defaultView.getComputedStyle && parseFloat(document.defaultView.getComputedStyle(this.$tip[0], null).width) || this.$tip.width()).find('td').eq(3).width('100%');
2800
- }
2801
- this.tipOuterW = this.$tip.outerWidth();
2802
- this.tipOuterH = this.$tip.outerHeight();
2803
-
2804
- this.calcPos();
2805
-
2806
- // position and show the arrow image
2807
- if (arrow && this.pos.arrow) {
2808
- this.$arrow[0].className = 'tip-arrow tip-arrow-' + this.pos.arrow;
2809
- this.$arrow.css('visibility', 'inherit');
2810
- }
2811
-
2812
- if (async) {
2813
- this.asyncAnimating = true;
2814
- var self = this;
2815
- this.$tip.css(currPos).animate({left: this.pos.l, top: this.pos.t}, 200, function() { self.asyncAnimating = false; });
2816
- } else {
2817
- this.$tip.css({left: this.pos.l, top: this.pos.t});
2818
- }
2819
- };
4272
+ if($.Poshytip) { //need this check, because in inline mode poshytip may not be loaded!
4273
+ var tips = [],
4274
+ reBgImage = /^url\(["']?([^"'\)]*)["']?\);?$/i,
4275
+ rePNG = /\.png$/i,
4276
+ ie6 = !!window.createPopup && document.documentElement.currentStyle.minWidth == 'undefined';
4277
+
4278
+ $.Poshytip.prototype.refresh = function(async) {
4279
+ if (this.disabled)
4280
+ return;
4281
+
4282
+ var currPos;
4283
+ if (async) {
4284
+ if (!this.$tip.data('active'))
4285
+ return;
4286
+ // save current position as we will need to animate
4287
+ currPos = {left: this.$tip.css('left'), top: this.$tip.css('top')};
4288
+ }
4289
+
4290
+ // reset position to avoid text wrapping, etc.
4291
+ this.$tip.css({left: 0, top: 0}).appendTo(document.body);
4292
+
4293
+ // save default opacity
4294
+ if (this.opacity === undefined)
4295
+ this.opacity = this.$tip.css('opacity');
4296
+
4297
+ // check for images - this code is here (i.e. executed each time we show the tip and not on init) due to some browser inconsistencies
4298
+ var bgImage = this.$tip.css('background-image').match(reBgImage),
4299
+ arrow = this.$arrow.css('background-image').match(reBgImage);
4300
+
4301
+ if (bgImage) {
4302
+ var bgImagePNG = rePNG.test(bgImage[1]);
4303
+ // fallback to background-color/padding/border in IE6 if a PNG is used
4304
+ if (ie6 && bgImagePNG) {
4305
+ this.$tip.css('background-image', 'none');
4306
+ this.$inner.css({margin: 0, border: 0, padding: 0});
4307
+ bgImage = bgImagePNG = false;
4308
+ } else {
4309
+ this.$tip.prepend('<table class="fallback" border="0" cellpadding="0" cellspacing="0"><tr><td class="tip-top tip-bg-image" colspan="2"><span></span></td><td class="tip-right tip-bg-image" rowspan="2"><span></span></td></tr><tr><td class="tip-left tip-bg-image" rowspan="2"><span></span></td><td></td></tr><tr><td class="tip-bottom tip-bg-image" colspan="2"><span></span></td></tr></table>')
4310
+ .css({border: 0, padding: 0, 'background-image': 'none', 'background-color': 'transparent'})
4311
+ .find('.tip-bg-image').css('background-image', 'url("' + bgImage[1] +'")').end()
4312
+ .find('td').eq(3).append(this.$inner);
4313
+ }
4314
+ // disable fade effect in IE due to Alpha filter + translucent PNG issue
4315
+ if (bgImagePNG && !$.support.opacity)
4316
+ this.opts.fade = false;
4317
+ }
4318
+ // IE arrow fixes
4319
+ if (arrow && !$.support.opacity) {
4320
+ // disable arrow in IE6 if using a PNG
4321
+ if (ie6 && rePNG.test(arrow[1])) {
4322
+ arrow = false;
4323
+ this.$arrow.css('background-image', 'none');
4324
+ }
4325
+ // disable fade effect in IE due to Alpha filter + translucent PNG issue
4326
+ this.opts.fade = false;
4327
+ }
4328
+
4329
+ var $table = this.$tip.find('table.fallback');
4330
+ if (ie6) {
4331
+ // fix min/max-width in IE6
4332
+ this.$tip[0].style.width = '';
4333
+ $table.width('auto').find('td').eq(3).width('auto');
4334
+ var tipW = this.$tip.width(),
4335
+ minW = parseInt(this.$tip.css('min-width'), 10),
4336
+ maxW = parseInt(this.$tip.css('max-width'), 10);
4337
+ if (!isNaN(minW) && tipW < minW)
4338
+ tipW = minW;
4339
+ else if (!isNaN(maxW) && tipW > maxW)
4340
+ tipW = maxW;
4341
+ this.$tip.add($table).width(tipW).eq(0).find('td').eq(3).width('100%');
4342
+ } else if ($table[0]) {
4343
+ // fix the table width if we are using a background image
4344
+ // IE9, FF4 use float numbers for width/height so use getComputedStyle for them to avoid text wrapping
4345
+ // for details look at: http://vadikom.com/dailies/offsetwidth-offsetheight-useless-in-ie9-firefox4/
4346
+ $table.width('auto').find('td').eq(3).width('auto').end().end().width(document.defaultView && document.defaultView.getComputedStyle && parseFloat(document.defaultView.getComputedStyle(this.$tip[0], null).width) || this.$tip.width()).find('td').eq(3).width('100%');
4347
+ }
4348
+ this.tipOuterW = this.$tip.outerWidth();
4349
+ this.tipOuterH = this.$tip.outerHeight();
4350
+
4351
+ this.calcPos();
4352
+
4353
+ // position and show the arrow image
4354
+ if (arrow && this.pos.arrow) {
4355
+ this.$arrow[0].className = 'tip-arrow tip-arrow-' + this.pos.arrow;
4356
+ this.$arrow.css('visibility', 'inherit');
4357
+ }
4358
+
4359
+ if (async) {
4360
+ this.asyncAnimating = true;
4361
+ var self = this;
4362
+ this.$tip.css(currPos).animate({left: this.pos.l, top: this.pos.t}, 200, function() { self.asyncAnimating = false; });
4363
+ } else {
4364
+ this.$tip.css({left: this.pos.l, top: this.pos.t});
4365
+ }
4366
+ };
4367
+ }
2820
4368
  /*jshinteqeqeq: true, curly: true*/
2821
4369
  }(window.jQuery));
2822
4370
  /**
2823
4371
  jQuery UI Datepicker.
2824
4372
  Description and examples: http://jqueryui.com/datepicker.
2825
- This input is also accessible as **date** type. Do not use it together with __bootstrap-datepicker__ as both apply <code>$().datepicker()</code> method.
4373
+ This input is also accessible as **date** type. Do not use it together with __bootstrap-datepicker__ as both apply <code>$().datepicker()</code> method.
4374
+ For **i18n** you should include js file from here: https://github.com/jquery/jquery-ui/tree/master/ui/i18n.
2826
4375
 
2827
4376
  @class dateui
2828
4377
  @extends abstractinput
@@ -2843,44 +4392,46 @@ $(function(){
2843
4392
  </script>
2844
4393
  **/
2845
4394
  (function ($) {
2846
-
4395
+ "use strict";
4396
+
2847
4397
  var DateUI = function (options) {
2848
4398
  this.init('dateui', options, DateUI.defaults);
2849
-
2850
- //set popular options directly from settings or data-* attributes
2851
- var directOptions = $.fn.editableutils.sliceObj(this.options, ['format']);
2852
-
2853
- //overriding datepicker config (as by default jQuery extend() is not recursive)
2854
- this.options.datepicker = $.extend({}, DateUI.defaults.datepicker, directOptions, options.datepicker);
2855
-
2856
- //by default viewformat equals to format
2857
- if(!this.options.viewformat) {
2858
- this.options.viewformat = this.options.datepicker.format;
2859
- }
2860
-
2861
- //correct formats: replace yyyy with yy
2862
- this.options.viewformat = this.options.viewformat.replace('yyyy', 'yy');
2863
- this.options.datepicker.format = this.options.datepicker.format.replace('yyyy', 'yy');
2864
-
2865
- //copy format to dateFormat (dateFormat option required for ui datepicker).
2866
- //This allows common option 'format' for all datepickers
2867
- this.options.datepicker.dateFormat = this.options.datepicker.format;
4399
+ this.initPicker(options, DateUI.defaults);
2868
4400
  };
2869
4401
 
2870
4402
  $.fn.editableutils.inherit(DateUI, $.fn.editabletypes.abstractinput);
2871
4403
 
2872
4404
  $.extend(DateUI.prototype, {
4405
+ initPicker: function(options, defaults) {
4406
+ //by default viewformat equals to format
4407
+ if(!this.options.viewformat) {
4408
+ this.options.viewformat = this.options.format;
4409
+ }
4410
+
4411
+ //correct formats: replace yyyy with yy (for compatibility with bootstrap datepicker)
4412
+ this.options.viewformat = this.options.viewformat.replace('yyyy', 'yy');
4413
+ this.options.format = this.options.format.replace('yyyy', 'yy');
4414
+
4415
+ //overriding datepicker config (as by default jQuery extend() is not recursive)
4416
+ //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
4417
+ this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
4418
+ dateFormat: this.options.viewformat
4419
+ });
4420
+ },
4421
+
2873
4422
  render: function () {
2874
- DateUI.superclass.render.call(this);
2875
4423
  this.$input.datepicker(this.options.datepicker);
2876
4424
 
4425
+ //"clear" link
2877
4426
  if(this.options.clear) {
2878
4427
  this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
2879
4428
  e.preventDefault();
2880
4429
  e.stopPropagation();
2881
4430
  this.clear();
2882
4431
  }, this));
2883
- }
4432
+
4433
+ this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
4434
+ }
2884
4435
  },
2885
4436
 
2886
4437
  value2html: function(value, element) {
@@ -2903,7 +4454,7 @@ $(function(){
2903
4454
  },
2904
4455
 
2905
4456
  value2str: function(value) {
2906
- return $.datepicker.formatDate(this.options.datepicker.dateFormat, value);
4457
+ return $.datepicker.formatDate(this.options.format, value);
2907
4458
  },
2908
4459
 
2909
4460
  str2value: function(str) {
@@ -2914,13 +4465,13 @@ $(function(){
2914
4465
  //if string does not match format, UI datepicker throws exception
2915
4466
  var d;
2916
4467
  try {
2917
- d = $.datepicker.parseDate(this.options.datepicker.dateFormat, str);
4468
+ d = $.datepicker.parseDate(this.options.format, str);
2918
4469
  } catch(e) {}
2919
4470
 
2920
4471
  return d;
2921
4472
  },
2922
4473
 
2923
- value2submit: function(value) {
4474
+ value2submit: function(value) {
2924
4475
  return this.value2str(value);
2925
4476
  },
2926
4477
 
@@ -2955,12 +4506,12 @@ $(function(){
2955
4506
  @property tpl
2956
4507
  @default <div></div>
2957
4508
  **/
2958
- tpl:'<div></div>',
4509
+ tpl:'<div class="editable-date"></div>',
2959
4510
  /**
2960
4511
  @property inputclass
2961
- @default 'editable-date'
4512
+ @default null
2962
4513
  **/
2963
- inputclass: 'editable-date',
4514
+ inputclass: null,
2964
4515
  /**
2965
4516
  Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
2966
4517
  Full list of tokens: http://docs.jquery.com/UI/Datepicker/formatDate
@@ -2995,7 +4546,8 @@ $(function(){
2995
4546
  datepicker: {
2996
4547
  firstDay: 0,
2997
4548
  changeYear: true,
2998
- changeMonth: true
4549
+ changeMonth: true,
4550
+ showOtherMonths: true
2999
4551
  },
3000
4552
  /**
3001
4553
  Text shown as clear date button.
@@ -3009,6 +4561,84 @@ $(function(){
3009
4561
  });
3010
4562
 
3011
4563
  $.fn.editabletypes.dateui = DateUI;
3012
- $.fn.editabletypes.date = DateUI;
3013
4564
 
3014
4565
  }(window.jQuery));
4566
+
4567
+ /**
4568
+ jQuery UI datefield input - modification for inline mode.
4569
+ Shows normal <input type="text"> and binds popup datepicker.
4570
+ Automatically shown in inline mode.
4571
+
4572
+ @class dateuifield
4573
+ @extends dateui
4574
+
4575
+ @since 1.4.0
4576
+ **/
4577
+ (function ($) {
4578
+ "use strict";
4579
+
4580
+ var DateUIField = function (options) {
4581
+ this.init('dateuifield', options, DateUIField.defaults);
4582
+ this.initPicker(options, DateUIField.defaults);
4583
+ };
4584
+
4585
+ $.fn.editableutils.inherit(DateUIField, $.fn.editabletypes.dateui);
4586
+
4587
+ $.extend(DateUIField.prototype, {
4588
+ render: function () {
4589
+ // this.$input = this.$tpl.find('input');
4590
+ this.$input.datepicker(this.options.datepicker);
4591
+ $.fn.editabletypes.text.prototype.renderClear.call(this);
4592
+ },
4593
+
4594
+ value2input: function(value) {
4595
+ this.$input.val($.datepicker.formatDate(this.options.viewformat, value));
4596
+ },
4597
+
4598
+ input2value: function() {
4599
+ return this.html2value(this.$input.val());
4600
+ },
4601
+
4602
+ activate: function() {
4603
+ $.fn.editabletypes.text.prototype.activate.call(this);
4604
+ },
4605
+
4606
+ toggleClear: function() {
4607
+ $.fn.editabletypes.text.prototype.toggleClear.call(this);
4608
+ },
4609
+
4610
+ autosubmit: function() {
4611
+ //reset autosubmit to empty
4612
+ }
4613
+ });
4614
+
4615
+ DateUIField.defaults = $.extend({}, $.fn.editabletypes.dateui.defaults, {
4616
+ /**
4617
+ @property tpl
4618
+ @default <input type="text">
4619
+ **/
4620
+ tpl: '<input type="text"/>',
4621
+ /**
4622
+ @property inputclass
4623
+ @default null
4624
+ **/
4625
+ inputclass: null,
4626
+
4627
+ /* datepicker config */
4628
+ datepicker: {
4629
+ showOn: "button",
4630
+ buttonImage: "http://jqueryui.com/resources/demos/datepicker/images/calendar.gif",
4631
+ buttonImageOnly: true,
4632
+ firstDay: 0,
4633
+ changeYear: true,
4634
+ changeMonth: true,
4635
+ showOtherMonths: true
4636
+ },
4637
+
4638
+ /* disable clear link */
4639
+ clear: false
4640
+ });
4641
+
4642
+ $.fn.editabletypes.dateuifield = DateUIField;
4643
+
4644
+ }(window.jQuery));