x-editable-rails 0.0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,7 +1775,29 @@ 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
- }
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
+ }
1790
+
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
+ }
1402
1801
  };
1403
1802
 
1404
1803
  /* EDITABLE PLUGIN DEFINITION
@@ -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, {
@@ -2637,54 +3297,926 @@ Range (inherit from number)
2637
3297
  });
2638
3298
  $.fn.editabletypes.range = Range;
2639
3299
  }(window.jQuery));
2640
- /*
2641
- Editableform based on jQuery UI
2642
- */
2643
- (function ($) {
3300
+ /**
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>
2644
3307
 
2645
- $.extend($.fn.editableform.Constructor.prototype, {
2646
- initButtons: function() {
2647
- this.$form.find('.editable-buttons').append($.fn.editableform.buttons);
2648
- this.$form.find('.editable-submit').button({
2649
- icons: { primary: "ui-icon-check" },
2650
- text: false
2651
- }).removeAttr('title');
2652
- this.$form.find('.editable-cancel').button({
2653
- icons: { primary: "ui-icon-closethick" },
2654
- text: false
2655
- }).removeAttr('title');
2656
- }
2657
- });
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>
2658
3311
 
2659
- //error classes
2660
- $.fn.editableform.errorGroupClass = null;
2661
- $.fn.editableform.errorBlockClass = 'ui-state-error';
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.
2662
3314
 
2663
- }(window.jQuery));
2664
- /**
2665
- * Editable jQuery UI Tooltip
2666
- * ---------------------
2667
- * requires jquery ui 1.9.x
2668
- */
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
+ **/
2669
3336
  (function ($) {
3337
+ "use strict";
2670
3338
 
2671
- //extend methods
2672
- $.extend($.fn.editableContainer.Constructor.prototype, {
2673
- containerName: 'tooltip',
2674
- innerCss: '.ui-tooltip-content',
3339
+ var Constructor = function (options) {
3340
+ this.init('select2', options, Constructor.defaults);
3341
+
3342
+ options.select2 = options.select2 || {};
2675
3343
 
2676
- //split options on containerOptions and formOptions
2677
- splitOptions: function() {
2678
- this.containerOptions = {};
2679
- this.formOptions = {};
2680
- //defaults for tooltip
2681
- var cDef = $.ui[this.containerName].prototype.options;
2682
- for(var k in this.options) {
2683
- if(k in cDef) {
2684
- this.containerOptions[k] = this.options[k];
2685
- } else {
2686
- this.formOptions[k] = this.options[k];
2687
- }
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
+ Editableform based on jQuery UI
4166
+ */
4167
+ (function ($) {
4168
+ "use strict";
4169
+
4170
+ $.extend($.fn.editableform.Constructor.prototype, {
4171
+ initButtons: function() {
4172
+ var $btn = this.$form.find('.editable-buttons');
4173
+ $btn.append($.fn.editableform.buttons);
4174
+ if(this.options.showbuttons === 'bottom') {
4175
+ $btn.addClass('editable-buttons-bottom');
4176
+ }
4177
+
4178
+ this.$form.find('.editable-submit').button({
4179
+ icons: { primary: "ui-icon-check" },
4180
+ text: false
4181
+ }).removeAttr('title');
4182
+ this.$form.find('.editable-cancel').button({
4183
+ icons: { primary: "ui-icon-closethick" },
4184
+ text: false
4185
+ }).removeAttr('title');
4186
+ }
4187
+ });
4188
+
4189
+ //error classes
4190
+ $.fn.editableform.errorGroupClass = null;
4191
+ $.fn.editableform.errorBlockClass = 'ui-state-error';
4192
+
4193
+ }(window.jQuery));
4194
+ /**
4195
+ * Editable jQuery UI Tooltip
4196
+ * ---------------------
4197
+ * requires jquery ui 1.9.x
4198
+ */
4199
+ (function ($) {
4200
+ "use strict";
4201
+
4202
+ //extend methods
4203
+ $.extend($.fn.editableContainer.Popup.prototype, {
4204
+ containerName: 'tooltip', //jQuery method, aplying the widget
4205
+ containerDataName: 'uiTooltip', //object name in elements .data() (e.g. uiTooltip for tooltip)
4206
+ innerCss: '.ui-tooltip-content',
4207
+
4208
+ //split options on containerOptions and formOptions
4209
+ splitOptions: function() {
4210
+ this.containerOptions = {};
4211
+ this.formOptions = {};
4212
+ //defaults for tooltip
4213
+ var cDef = $.ui[this.containerName].prototype.options;
4214
+ for(var k in this.options) {
4215
+ if(k in cDef) {
4216
+ this.containerOptions[k] = this.options[k];
4217
+ } else {
4218
+ this.formOptions[k] = this.options[k];
4219
+ }
2688
4220
  }
2689
4221
  },
2690
4222
 
@@ -2710,25 +4242,23 @@ Editableform based on jQuery UI
2710
4242
  },
2711
4243
 
2712
4244
  tip: function() {
2713
- return this.container()._find(this.container().element);
4245
+ return this.container() ? this.container()._find(this.container().element) : null;
2714
4246
  },
2715
4247
 
2716
4248
  innerShow: function() {
2717
4249
  this.call('open');
2718
- this.tip().addClass('editable-container');
2719
-
2720
- this.initForm();
2721
- this.tip().find(this.innerCss)
2722
- .empty()
2723
- .append($('<label>').text(this.options.title || this.$element.data( "ui-tooltip-title") || this.$element.data( "originalTitle")))
2724
- .append(this.$form);
2725
- this.$form.editableform('render');
4250
+ var label = this.options.title || this.$element.data( "ui-tooltip-title") || this.$element.data( "originalTitle");
4251
+ this.tip().find(this.innerCss).empty().append($('<label>').text(label));
2726
4252
  },
2727
4253
 
2728
4254
  innerHide: function() {
2729
4255
  this.call('close');
2730
4256
  },
2731
4257
 
4258
+ innerDestroy: function() {
4259
+ /* tooltip destroys itself on hide */
4260
+ },
4261
+
2732
4262
  setPosition: function() {
2733
4263
  this.tip().position( $.extend({
2734
4264
  of: this.$element
@@ -2765,26 +4295,17 @@ Editableform based on jQuery UI
2765
4295
  }
2766
4296
 
2767
4297
  this.containerOptions.position = pos;
2768
- },
2769
-
2770
- destroy: function() {
2771
- //jqueryui tooltip destroys itself
2772
- }
2773
- });
2774
-
2775
- //defaults
2776
- /*
2777
- $.fn.editableContainer.defaults = $.extend({}, $.fn.tooltip.defaults, $.fn.editableContainer.defaults, {
2778
- items: '*',
2779
- content: ' ',
4298
+ }
4299
+
2780
4300
  });
2781
- */
2782
4301
 
2783
- }(window.jQuery));
4302
+ }(window.jQuery));
4303
+
2784
4304
  /**
2785
4305
  jQuery UI Datepicker.
2786
4306
  Description and examples: http://jqueryui.com/datepicker.
2787
- This input is also accessible as **date** type. Do not use it together with __bootstrap-datepicker__ as both apply <code>$().datepicker()</code> method.
4307
+ This input is also accessible as **date** type. Do not use it together with __bootstrap-datepicker__ as both apply <code>$().datepicker()</code> method.
4308
+ For **i18n** you should include js file from here: https://github.com/jquery/jquery-ui/tree/master/ui/i18n.
2788
4309
 
2789
4310
  @class dateui
2790
4311
  @extends abstractinput
@@ -2805,44 +4326,46 @@ $(function(){
2805
4326
  </script>
2806
4327
  **/
2807
4328
  (function ($) {
2808
-
4329
+ "use strict";
4330
+
2809
4331
  var DateUI = function (options) {
2810
4332
  this.init('dateui', options, DateUI.defaults);
2811
-
2812
- //set popular options directly from settings or data-* attributes
2813
- var directOptions = $.fn.editableutils.sliceObj(this.options, ['format']);
2814
-
2815
- //overriding datepicker config (as by default jQuery extend() is not recursive)
2816
- this.options.datepicker = $.extend({}, DateUI.defaults.datepicker, directOptions, options.datepicker);
2817
-
2818
- //by default viewformat equals to format
2819
- if(!this.options.viewformat) {
2820
- this.options.viewformat = this.options.datepicker.format;
2821
- }
2822
-
2823
- //correct formats: replace yyyy with yy
2824
- this.options.viewformat = this.options.viewformat.replace('yyyy', 'yy');
2825
- this.options.datepicker.format = this.options.datepicker.format.replace('yyyy', 'yy');
2826
-
2827
- //copy format to dateFormat (dateFormat option required for ui datepicker).
2828
- //This allows common option 'format' for all datepickers
2829
- this.options.datepicker.dateFormat = this.options.datepicker.format;
4333
+ this.initPicker(options, DateUI.defaults);
2830
4334
  };
2831
4335
 
2832
4336
  $.fn.editableutils.inherit(DateUI, $.fn.editabletypes.abstractinput);
2833
4337
 
2834
4338
  $.extend(DateUI.prototype, {
4339
+ initPicker: function(options, defaults) {
4340
+ //by default viewformat equals to format
4341
+ if(!this.options.viewformat) {
4342
+ this.options.viewformat = this.options.format;
4343
+ }
4344
+
4345
+ //correct formats: replace yyyy with yy (for compatibility with bootstrap datepicker)
4346
+ this.options.viewformat = this.options.viewformat.replace('yyyy', 'yy');
4347
+ this.options.format = this.options.format.replace('yyyy', 'yy');
4348
+
4349
+ //overriding datepicker config (as by default jQuery extend() is not recursive)
4350
+ //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
4351
+ this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
4352
+ dateFormat: this.options.viewformat
4353
+ });
4354
+ },
4355
+
2835
4356
  render: function () {
2836
- DateUI.superclass.render.call(this);
2837
4357
  this.$input.datepicker(this.options.datepicker);
2838
4358
 
4359
+ //"clear" link
2839
4360
  if(this.options.clear) {
2840
4361
  this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
2841
4362
  e.preventDefault();
2842
4363
  e.stopPropagation();
2843
4364
  this.clear();
2844
4365
  }, this));
2845
- }
4366
+
4367
+ this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
4368
+ }
2846
4369
  },
2847
4370
 
2848
4371
  value2html: function(value, element) {
@@ -2865,7 +4388,7 @@ $(function(){
2865
4388
  },
2866
4389
 
2867
4390
  value2str: function(value) {
2868
- return $.datepicker.formatDate(this.options.datepicker.dateFormat, value);
4391
+ return $.datepicker.formatDate(this.options.format, value);
2869
4392
  },
2870
4393
 
2871
4394
  str2value: function(str) {
@@ -2876,13 +4399,13 @@ $(function(){
2876
4399
  //if string does not match format, UI datepicker throws exception
2877
4400
  var d;
2878
4401
  try {
2879
- d = $.datepicker.parseDate(this.options.datepicker.dateFormat, str);
4402
+ d = $.datepicker.parseDate(this.options.format, str);
2880
4403
  } catch(e) {}
2881
4404
 
2882
4405
  return d;
2883
4406
  },
2884
4407
 
2885
- value2submit: function(value) {
4408
+ value2submit: function(value) {
2886
4409
  return this.value2str(value);
2887
4410
  },
2888
4411
 
@@ -2917,12 +4440,12 @@ $(function(){
2917
4440
  @property tpl
2918
4441
  @default <div></div>
2919
4442
  **/
2920
- tpl:'<div></div>',
4443
+ tpl:'<div class="editable-date"></div>',
2921
4444
  /**
2922
4445
  @property inputclass
2923
- @default 'editable-date'
4446
+ @default null
2924
4447
  **/
2925
- inputclass: 'editable-date',
4448
+ inputclass: null,
2926
4449
  /**
2927
4450
  Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
2928
4451
  Full list of tokens: http://docs.jquery.com/UI/Datepicker/formatDate
@@ -2957,7 +4480,8 @@ $(function(){
2957
4480
  datepicker: {
2958
4481
  firstDay: 0,
2959
4482
  changeYear: true,
2960
- changeMonth: true
4483
+ changeMonth: true,
4484
+ showOtherMonths: true
2961
4485
  },
2962
4486
  /**
2963
4487
  Text shown as clear date button.
@@ -2971,6 +4495,84 @@ $(function(){
2971
4495
  });
2972
4496
 
2973
4497
  $.fn.editabletypes.dateui = DateUI;
2974
- $.fn.editabletypes.date = DateUI;
2975
4498
 
2976
4499
  }(window.jQuery));
4500
+
4501
+ /**
4502
+ jQuery UI datefield input - modification for inline mode.
4503
+ Shows normal <input type="text"> and binds popup datepicker.
4504
+ Automatically shown in inline mode.
4505
+
4506
+ @class dateuifield
4507
+ @extends dateui
4508
+
4509
+ @since 1.4.0
4510
+ **/
4511
+ (function ($) {
4512
+ "use strict";
4513
+
4514
+ var DateUIField = function (options) {
4515
+ this.init('dateuifield', options, DateUIField.defaults);
4516
+ this.initPicker(options, DateUIField.defaults);
4517
+ };
4518
+
4519
+ $.fn.editableutils.inherit(DateUIField, $.fn.editabletypes.dateui);
4520
+
4521
+ $.extend(DateUIField.prototype, {
4522
+ render: function () {
4523
+ // this.$input = this.$tpl.find('input');
4524
+ this.$input.datepicker(this.options.datepicker);
4525
+ $.fn.editabletypes.text.prototype.renderClear.call(this);
4526
+ },
4527
+
4528
+ value2input: function(value) {
4529
+ this.$input.val($.datepicker.formatDate(this.options.viewformat, value));
4530
+ },
4531
+
4532
+ input2value: function() {
4533
+ return this.html2value(this.$input.val());
4534
+ },
4535
+
4536
+ activate: function() {
4537
+ $.fn.editabletypes.text.prototype.activate.call(this);
4538
+ },
4539
+
4540
+ toggleClear: function() {
4541
+ $.fn.editabletypes.text.prototype.toggleClear.call(this);
4542
+ },
4543
+
4544
+ autosubmit: function() {
4545
+ //reset autosubmit to empty
4546
+ }
4547
+ });
4548
+
4549
+ DateUIField.defaults = $.extend({}, $.fn.editabletypes.dateui.defaults, {
4550
+ /**
4551
+ @property tpl
4552
+ @default <input type="text">
4553
+ **/
4554
+ tpl: '<input type="text"/>',
4555
+ /**
4556
+ @property inputclass
4557
+ @default null
4558
+ **/
4559
+ inputclass: null,
4560
+
4561
+ /* datepicker config */
4562
+ datepicker: {
4563
+ showOn: "button",
4564
+ buttonImage: "http://jqueryui.com/resources/demos/datepicker/images/calendar.gif",
4565
+ buttonImageOnly: true,
4566
+ firstDay: 0,
4567
+ changeYear: true,
4568
+ changeMonth: true,
4569
+ showOtherMonths: true
4570
+ },
4571
+
4572
+ /* disable clear link */
4573
+ clear: false
4574
+ });
4575
+
4576
+ $.fn.editabletypes.dateuifield = DateUIField;
4577
+
4578
+ }(window.jQuery));