bootstrap-x-editable-rails 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -15,4 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
- .DS_Store
18
+ .DS_Store
19
+ *.sublime-workspace
@@ -0,0 +1,13 @@
1
+ {
2
+ "folders":
3
+ [
4
+ {
5
+ "path": "."
6
+ }
7
+ ],
8
+ "settings":
9
+ {
10
+ "tab_size": 2,
11
+ "translate_tabs_to_spaces": true
12
+ }
13
+ }
@@ -1,5 +1,5 @@
1
1
  module BootstrapXEditableRails
2
2
  module Rails
3
- VERSION = "1.3.0"
3
+ VERSION = "1.4.0"
4
4
  end
5
5
  end
@@ -1,7 +1,7 @@
1
- /*! X-editable - v1.3.0
1
+ /*! X-editable - v1.4.0
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.
@@ -20,24 +20,16 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
20
20
  if(!this.options.scope) {
21
21
  this.options.scope = this;
22
22
  }
23
- this.initInput();
23
+ //nothing shown after init
24
24
  };
25
25
 
26
26
  EditableForm.prototype = {
27
27
  constructor: EditableForm,
28
28
  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
-
29
+ //take input from options (as it is created in editable-element)
30
+ this.input = this.options.input;
31
+
32
+ //set initial value
41
33
  this.value = this.input.str2value(this.options.value);
42
34
  },
43
35
  initTemplate: function() {
@@ -52,47 +44,49 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
52
44
  @method render
53
45
  **/
54
46
  render: function() {
47
+ //init loader
55
48
  this.$loading = $($.fn.editableform.loading);
56
49
  this.$div.empty().append(this.$loading);
57
- this.showLoading();
58
50
 
59
51
  //init form template and buttons
60
- this.initTemplate();
52
+ this.initTemplate();
61
53
  if(this.options.showbuttons) {
62
54
  this.initButtons();
63
55
  } else {
64
56
  this.$form.find('.editable-buttons').remove();
65
57
  }
66
58
 
59
+ //show loading state
60
+ this.showLoading();
61
+
67
62
  /**
68
63
  Fired when rendering starts
69
64
  @event rendering
70
65
  @param {Object} event event object
71
66
  **/
72
67
  this.$div.triggerHandler('rendering');
68
+
69
+ //init input
70
+ this.initInput();
71
+
72
+ //append input to form
73
+ this.input.prerender();
74
+ this.$form.find('div.editable-input').append(this.input.$tpl);
73
75
 
76
+ //append form to container
77
+ this.$div.append(this.$form);
78
+
74
79
  //render input
75
80
  $.when(this.input.render())
76
81
  .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
82
+ //setup input to submit automatically when no buttons shown
81
83
  if(!this.options.showbuttons) {
82
84
  this.input.autosubmit();
83
85
  }
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
86
 
93
87
  //attach 'cancel' handler
94
88
  this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
95
-
89
+
96
90
  if(this.input.error) {
97
91
  this.error(this.input.error);
98
92
  this.$form.find('.editable-submit').attr('disabled', true);
@@ -116,6 +110,11 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
116
110
  this.$div.triggerHandler('rendered');
117
111
 
118
112
  this.showForm();
113
+
114
+ //call postrender method to perform actions required visibility of form
115
+ if(this.input.postrender) {
116
+ this.input.postrender();
117
+ }
119
118
  }, this));
120
119
  },
121
120
  cancel: function() {
@@ -127,11 +126,17 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
127
126
  this.$div.triggerHandler('cancel');
128
127
  },
129
128
  showLoading: function() {
130
- var w;
129
+ var w, h;
131
130
  if(this.$form) {
132
- //set loading size equal to form
133
- this.$loading.width(this.$form.outerWidth());
134
- this.$loading.height(this.$form.outerHeight());
131
+ //set loading size equal to form
132
+ w = this.$form.outerWidth();
133
+ h = this.$form.outerHeight();
134
+ if(w) {
135
+ this.$loading.width(w);
136
+ }
137
+ if(h) {
138
+ this.$loading.height(h);
139
+ }
135
140
  this.$form.hide();
136
141
  } else {
137
142
  //stretch loading to fill container width
@@ -159,14 +164,23 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
159
164
 
160
165
  error: function(msg) {
161
166
  var $group = this.$form.find('.control-group'),
162
- $block = this.$form.find('.editable-error-block');
167
+ $block = this.$form.find('.editable-error-block'),
168
+ lines;
163
169
 
164
170
  if(msg === false) {
165
171
  $group.removeClass($.fn.editableform.errorGroupClass);
166
172
  $block.removeClass($.fn.editableform.errorBlockClass).empty().hide();
167
173
  } else {
174
+ //convert newline to <br> for more pretty error display
175
+ if(msg) {
176
+ lines = msg.split("\n");
177
+ for (var i = 0; i < lines.length; i++) {
178
+ lines[i] = $('<div>').text(lines[i]).html();
179
+ }
180
+ msg = lines.join('<br>');
181
+ }
168
182
  $group.addClass($.fn.editableform.errorGroupClass);
169
- $block.addClass($.fn.editableform.errorBlockClass).text(msg).show();
183
+ $block.addClass($.fn.editableform.errorBlockClass).html(msg).show();
170
184
  }
171
185
  },
172
186
 
@@ -299,10 +313,15 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
299
313
  },
300
314
 
301
315
  option: function(key, value) {
302
- this.options[key] = value;
316
+ if(key in this.options) {
317
+ this.options[key] = value;
318
+ }
319
+
303
320
  if(key === 'value') {
304
321
  this.setValue(value);
305
322
  }
323
+
324
+ //do not pass option to input as it is passed in editable-element
306
325
  },
307
326
 
308
327
  setValue: function(value, convertStr) {
@@ -311,6 +330,11 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
311
330
  } else {
312
331
  this.value = value;
313
332
  }
333
+
334
+ //if form is visible, update input
335
+ if(this.$form && this.$form.is(':visible')) {
336
+ this.input.value2input(this.value);
337
+ }
314
338
  }
315
339
  };
316
340
 
@@ -372,7 +396,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
372
396
  url: function(params) {
373
397
  if(params.value === 'abc') {
374
398
  var d = new $.Deferred;
375
- return d.reject('field cannot be "abc"'); //returning error via deferred object
399
+ return d.reject('error message'); //returning error via deferred object
376
400
  } else {
377
401
  someModel.set(params.name, params.value); //save data in some js model
378
402
  }
@@ -463,21 +487,21 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
463
487
  /**
464
488
  Additional options for ajax request.
465
489
  List of values: http://api.jquery.com/jQuery.ajax
466
-
490
+
467
491
  @property ajaxOptions
468
492
  @type object
469
493
  @default null
470
494
  @since 1.1.1
495
+ @example
496
+ ajaxOptions: {
497
+ type: 'put',
498
+ dataType: 'json'
499
+ }
471
500
  **/
472
501
  ajaxOptions: null,
473
502
  /**
474
503
  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
- }
504
+ Form without buttons is auto-submitted.
481
505
 
482
506
  @property showbuttons
483
507
  @type boolean
@@ -621,19 +645,22 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
621
645
  return newObj;
622
646
  },
623
647
 
624
- /**
625
- * exclude complex objects from $.data() before pass to config
648
+ /*
649
+ exclude complex objects from $.data() before pass to config
626
650
  */
627
651
  getConfigData: function($element) {
628
652
  var data = {};
629
653
  $.each($element.data(), function(k, v) {
630
- if(typeof v !== 'object' || (v && typeof v === 'object' && v.constructor === Object)) {
654
+ if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
631
655
  data[k] = v;
632
656
  }
633
657
  });
634
658
  return data;
635
659
  },
636
660
 
661
+ /*
662
+ returns keys of object
663
+ */
637
664
  objectKeys: function(o) {
638
665
  if (Object.keys) {
639
666
  return Object.keys(o);
@@ -657,9 +684,82 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
657
684
  **/
658
685
  escape: function(str) {
659
686
  return $('<div>').text(str).html();
660
- }
687
+ },
688
+
689
+ /*
690
+ returns array items from sourceData having value property equal or inArray of 'value'
691
+ */
692
+ itemsByValue: function(value, sourceData) {
693
+ if(!sourceData || value === null) {
694
+ return [];
695
+ }
696
+
697
+ //convert to array
698
+ if(!$.isArray(value)) {
699
+ value = [value];
700
+ }
701
+
702
+ /*jslint eqeq: true*/
703
+ var result = $.grep(sourceData, function(o){
704
+ return $.grep(value, function(v){ return v == o.value; }).length;
705
+ });
706
+ /*jslint eqeq: false*/
707
+
708
+ return result;
709
+ },
710
+
711
+ /*
712
+ Returns input by options: type, mode.
713
+ */
714
+ createInput: function(options) {
715
+ var TypeConstructor, typeOptions, input,
716
+ type = options.type;
717
+
718
+ //`date` is some kind of virtual type that is transformed to one of exact types
719
+ //depending on mode and core lib
720
+ if(type === 'date') {
721
+ //inline
722
+ if(options.mode === 'inline') {
723
+ if($.fn.editabletypes.datefield) {
724
+ type = 'datefield';
725
+ } else if($.fn.editabletypes.dateuifield) {
726
+ type = 'dateuifield';
727
+ }
728
+ //popup
729
+ } else {
730
+ if($.fn.editabletypes.date) {
731
+ type = 'date';
732
+ } else if($.fn.editabletypes.dateui) {
733
+ type = 'dateui';
734
+ }
735
+ }
736
+
737
+ //if type still `date` and not exist in types, replace with `combodate` that is base input
738
+ if(type === 'date' && !$.fn.editabletypes.date) {
739
+ type = 'combodate';
740
+ }
741
+ }
742
+
743
+ //change wysihtml5 to textarea for jquery UI and plain versions
744
+ if(type === 'wysihtml5' && !$.fn.editabletypes[type]) {
745
+ type = 'textarea';
746
+ }
747
+
748
+ //create input of specified type. Input will be used for converting value, not in form
749
+ if(typeof $.fn.editabletypes[type] === 'function') {
750
+ TypeConstructor = $.fn.editabletypes[type];
751
+ typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults));
752
+ input = new TypeConstructor(typeOptions);
753
+ return input;
754
+ } else {
755
+ $.error('Unknown type: '+ type);
756
+ return false;
757
+ }
758
+ }
759
+
661
760
  };
662
761
  }(window.jQuery));
762
+
663
763
  /**
664
764
  Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
665
765
  This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
@@ -671,12 +771,16 @@ Applied as jQuery method.
671
771
  **/
672
772
  (function ($) {
673
773
 
674
- var EditableContainer = function (element, options) {
774
+ var Popup = function (element, options) {
675
775
  this.init(element, options);
676
776
  };
777
+
778
+ var Inline = function (element, options) {
779
+ this.init(element, options);
780
+ };
677
781
 
678
782
  //methods
679
- EditableContainer.prototype = {
783
+ Popup.prototype = {
680
784
  containerName: null, //tbd in child class
681
785
  innerCss: null, //tbd in child class
682
786
  init: function(element, options) {
@@ -684,6 +788,10 @@ Applied as jQuery method.
684
788
  //todo: what is in priority: data or js?
685
789
  this.options = $.extend({}, $.fn.editableContainer.defaults, $.fn.editableutils.getConfigData(this.$element), options);
686
790
  this.splitOptions();
791
+
792
+ //set scope of form callbacks to element
793
+ this.formOptions.scope = this.$element[0];
794
+
687
795
  this.initContainer();
688
796
 
689
797
  //bind 'destroyed' listener to destroy container when element is removed from dom
@@ -691,7 +799,7 @@ Applied as jQuery method.
691
799
  this.destroy();
692
800
  }, this));
693
801
 
694
- //attach document handlers (once)
802
+ //attach document handler to close containers on click / escape
695
803
  if(!$(document).data('editable-handlers-attached')) {
696
804
  //close all on escape
697
805
  $(document).on('keyup.editable', function (e) {
@@ -703,15 +811,22 @@ Applied as jQuery method.
703
811
 
704
812
  //close containers when click outside
705
813
  $(document).on('click.editable', function(e) {
706
- var $target = $(e.target);
814
+ var $target = $(e.target), i,
815
+ exclude_classes = ['.editable-container',
816
+ '.ui-datepicker-header',
817
+ '.modal-backdrop',
818
+ '.bootstrap-wysihtml5-insert-image-modal',
819
+ '.bootstrap-wysihtml5-insert-link-modal'];
707
820
 
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);
821
+ //if click inside one of exclude classes --> no nothing
822
+ for(i=0; i<exclude_classes.length; i++) {
823
+ if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) {
824
+ return;
825
+ }
714
826
  }
827
+
828
+ //close all open containers (except one - target)
829
+ Popup.prototype.closeOthers(e.target);
715
830
  });
716
831
 
717
832
  $(document).data('editable-handlers-attached', true);
@@ -723,6 +838,7 @@ Applied as jQuery method.
723
838
  this.containerOptions = {};
724
839
  this.formOptions = {};
725
840
  var cDef = $.fn[this.containerName].defaults;
841
+ //keys defined in container defaults go to container, others go to form
726
842
  for(var k in this.options) {
727
843
  if(k in cDef) {
728
844
  this.containerOptions[k] = this.options[k];
@@ -732,18 +848,34 @@ Applied as jQuery method.
732
848
  }
733
849
  },
734
850
 
851
+ /*
852
+ Returns jquery object of container
853
+ @method tip()
854
+ */
855
+ tip: function() {
856
+ return this.container() ? this.container().$tip : null;
857
+ },
858
+
859
+ /* returns container object */
860
+ container: function() {
861
+ return this.$element.data(this.containerName);
862
+ },
863
+
864
+ call: function() {
865
+ this.$element[this.containerName].apply(this.$element, arguments);
866
+ },
867
+
735
868
  initContainer: function(){
736
869
  this.call(this.containerOptions);
737
870
  },
738
871
 
739
- initForm: function() {
740
- this.formOptions.scope = this.$element[0]; //set scope of form callbacks to element
741
- this.$form = $('<div>')
872
+ renderForm: function() {
873
+ this.$form
742
874
  .editableform(this.formOptions)
743
875
  .on({
744
- save: $.proxy(this.save, this),
745
- cancel: $.proxy(function(){ this.hide('cancel'); }, this),
746
- nochange: $.proxy(function(){ this.hide('nochange'); }, this),
876
+ save: $.proxy(this.save, this), //click on submit button (value changed)
877
+ nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)
878
+ cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button
747
879
  show: $.proxy(this.setPosition, this), //re-position container every time form is shown (occurs each time after loading state)
748
880
  rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
749
881
  rendered: $.proxy(function(){
@@ -760,31 +892,16 @@ Applied as jQuery method.
760
892
  **/
761
893
  this.$element.triggerHandler('shown');
762
894
  }, this)
763
- });
764
- return this.$form;
895
+ })
896
+ .editableform('render');
765
897
  },
766
898
 
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
899
  /**
784
900
  Shows container with form
785
901
  @method show()
786
902
  @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
787
- **/
903
+ **/
904
+ /* Note: poshytip owerwrites this method totally! */
788
905
  show: function (closeAll) {
789
906
  this.$element.addClass('editable-open');
790
907
  if(closeAll !== false) {
@@ -792,16 +909,37 @@ Applied as jQuery method.
792
909
  this.closeOthers(this.$element[0]);
793
910
  }
794
911
 
912
+ //show container itself
795
913
  this.innerShow();
796
- },
797
-
798
- /* internal show method. To be overwritten in child classes */
799
- innerShow: function () {
800
- this.call('show');
801
914
  this.tip().addClass('editable-container');
802
- this.initForm();
803
- this.tip().find(this.innerCss).empty().append(this.$form);
804
- this.$form.editableform('render');
915
+
916
+ /*
917
+ Currently, form is re-rendered on every show.
918
+ The main reason is that we dont know, what container will do with content when closed:
919
+ remove(), detach() or just hide().
920
+
921
+ Detaching form itself before hide and re-insert before show is good solution,
922
+ but visually it looks ugly, as container changes size before hide.
923
+ */
924
+
925
+ //if form already exist - delete previous data
926
+ if(this.$form) {
927
+ //todo: destroy prev data!
928
+ //this.$form.destroy();
929
+ }
930
+
931
+ this.$form = $('<div>');
932
+
933
+ //insert form into container body
934
+ if(this.tip().is(this.innerCss)) {
935
+ //for inline container
936
+ this.tip().append(this.$form);
937
+ } else {
938
+ this.tip().find(this.innerCss).append(this.$form);
939
+ }
940
+
941
+ //render form
942
+ this.renderForm();
805
943
  },
806
944
 
807
945
  /**
@@ -813,8 +951,10 @@ Applied as jQuery method.
813
951
  if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
814
952
  return;
815
953
  }
954
+
816
955
  this.$element.removeClass('editable-open');
817
956
  this.innerHide();
957
+
818
958
  /**
819
959
  Fired when container was hidden. It occurs on both save or cancel.
820
960
 
@@ -832,9 +972,14 @@ Applied as jQuery method.
832
972
  this.$element.triggerHandler('hidden', reason);
833
973
  },
834
974
 
975
+ /* internal show method. To be overwritten in child classes */
976
+ innerShow: function () {
977
+
978
+ },
979
+
835
980
  /* internal hide method. To be overwritten in child classes */
836
981
  innerHide: function () {
837
- this.call('hide');
982
+
838
983
  },
839
984
 
840
985
  /**
@@ -843,7 +988,7 @@ Applied as jQuery method.
843
988
  @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
844
989
  **/
845
990
  toggle: function(closeAll) {
846
- if(this.tip && this.tip().is(':visible')) {
991
+ if(this.container() && this.tip() && this.tip().is(':visible')) {
847
992
  this.hide();
848
993
  } else {
849
994
  this.show(closeAll);
@@ -911,9 +1056,17 @@ Applied as jQuery method.
911
1056
  @method destroy()
912
1057
  **/
913
1058
  destroy: function() {
914
- this.call('destroy');
1059
+ this.hide();
1060
+ this.innerDestroy();
1061
+ this.$element.off('destroyed');
1062
+ this.$element.removeData('editableContainer');
915
1063
  },
916
1064
 
1065
+ /* to be overwritten in child classes */
1066
+ innerDestroy: function() {
1067
+
1068
+ },
1069
+
917
1070
  /*
918
1071
  Closes other containers except one related to passed element.
919
1072
  Other containers can be cancelled or submitted (depends on onblur option)
@@ -972,11 +1125,12 @@ Applied as jQuery method.
972
1125
  return this.each(function () {
973
1126
  var $this = $(this),
974
1127
  dataKey = 'editableContainer',
975
- data = $this.data(dataKey),
976
- options = typeof option === 'object' && option;
1128
+ data = $this.data(dataKey),
1129
+ options = typeof option === 'object' && option,
1130
+ Constructor = (options.mode === 'inline') ? Inline : Popup;
977
1131
 
978
1132
  if (!data) {
979
- $this.data(dataKey, (data = new EditableContainer(this, options)));
1133
+ $this.data(dataKey, (data = new Constructor(this, options)));
980
1134
  }
981
1135
 
982
1136
  if (typeof option === 'string') { //call method
@@ -985,8 +1139,9 @@ Applied as jQuery method.
985
1139
  });
986
1140
  };
987
1141
 
988
- //store constructor
989
- $.fn.editableContainer.Constructor = EditableContainer;
1142
+ //store constructors
1143
+ $.fn.editableContainer.Popup = Popup;
1144
+ $.fn.editableContainer.Inline = Inline;
990
1145
 
991
1146
  //defaults
992
1147
  $.fn.editableContainer.defaults = {
@@ -1025,7 +1180,25 @@ Applied as jQuery method.
1025
1180
  @default 'cancel'
1026
1181
  @since 1.1.1
1027
1182
  **/
1028
- onblur: 'cancel'
1183
+ onblur: 'cancel',
1184
+
1185
+ /**
1186
+ Animation speed (inline mode)
1187
+ @property anim
1188
+ @type string
1189
+ @default 'fast'
1190
+ **/
1191
+ anim: 'fast',
1192
+
1193
+ /**
1194
+ Mode of editable, can be `popup` or `inline`
1195
+
1196
+ @property mode
1197
+ @type string
1198
+ @default 'popup'
1199
+ @since 1.4.0
1200
+ **/
1201
+ mode: 'popup'
1029
1202
  };
1030
1203
 
1031
1204
  /*
@@ -1042,6 +1215,56 @@ Applied as jQuery method.
1042
1215
 
1043
1216
  }(window.jQuery));
1044
1217
 
1218
+ /**
1219
+ * Editable Inline
1220
+ * ---------------------
1221
+ */
1222
+ (function ($) {
1223
+
1224
+ //copy prototype from EditableContainer
1225
+ //extend methods
1226
+ $.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, {
1227
+ containerName: 'editableform',
1228
+ innerCss: '.editable-inline',
1229
+
1230
+ initContainer: function(){
1231
+ //container is <span> element
1232
+ this.$tip = $('<span></span>').addClass('editable-inline');
1233
+
1234
+ //convert anim to miliseconds (int)
1235
+ if(!this.options.anim) {
1236
+ this.options.anim = 0;
1237
+ }
1238
+ },
1239
+
1240
+ splitOptions: function() {
1241
+ //all options are passed to form
1242
+ this.containerOptions = {};
1243
+ this.formOptions = this.options;
1244
+ },
1245
+
1246
+ tip: function() {
1247
+ return this.$tip;
1248
+ },
1249
+
1250
+ innerShow: function () {
1251
+ this.$element.hide();
1252
+ this.tip().insertAfter(this.$element).show();
1253
+ },
1254
+
1255
+ innerHide: function () {
1256
+ this.$tip.hide(this.options.anim, $.proxy(function() {
1257
+ this.$element.show();
1258
+ this.tip().empty().remove();
1259
+ }, this));
1260
+ },
1261
+
1262
+ innerDestroy: function() {
1263
+ this.tip().remove();
1264
+ }
1265
+ });
1266
+
1267
+ }(window.jQuery));
1045
1268
  /**
1046
1269
  Makes editable any HTML element on the page. Applied as jQuery method.
1047
1270
 
@@ -1059,27 +1282,15 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1059
1282
  Editable.prototype = {
1060
1283
  constructor: Editable,
1061
1284
  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
-
1285
+ var isValueByText = false,
1286
+ doAutotext, finalize;
1287
+
1073
1288
  //name
1074
1289
  this.options.name = this.options.name || this.$element.attr('id');
1075
1290
 
1076
1291
  //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);
1292
+ this.input = $.fn.editableutils.createInput(this.options);
1293
+ if(!this.input) {
1083
1294
  return;
1084
1295
  }
1085
1296
 
@@ -1140,8 +1351,12 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1140
1351
 
1141
1352
  @event init
1142
1353
  @param {Object} event event object
1143
- @param {Object} editable editable instance
1354
+ @param {Object} editable editable instance (as here it cannot accessed via data('editable'))
1144
1355
  @since 1.2.0
1356
+ @example
1357
+ $('#username').on('init', function(e, editable) {
1358
+ alert('initialized ' + editable.options.name);
1359
+ });
1145
1360
  **/
1146
1361
  this.$element.triggerHandler('init', this);
1147
1362
  }, this));
@@ -1152,18 +1367,20 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1152
1367
  Can call custom display method from options.
1153
1368
  Can return deferred object.
1154
1369
  @method render()
1370
+ @param {mixed} response server response (if exist) to pass into display function
1155
1371
  */
1156
- render: function() {
1372
+ render: function(response) {
1157
1373
  //do not display anything
1158
1374
  if(this.options.display === false) {
1159
1375
  return;
1160
1376
  }
1377
+
1161
1378
  //if it is input with source, we pass callback in third param to be called when source is loaded
1162
1379
  if(this.input.options.hasOwnProperty('source')) {
1163
- return this.input.value2html(this.value, this.$element[0], this.options.display);
1380
+ return this.input.value2html(this.value, this.$element[0], this.options.display, response);
1164
1381
  //if display method defined --> use it
1165
1382
  } else if(typeof this.options.display === 'function') {
1166
- return this.options.display.call(this.$element[0], this.value);
1383
+ return this.options.display.call(this.$element[0], this.value, response);
1167
1384
  //else use input's original value2html() method
1168
1385
  } else {
1169
1386
  return this.input.value2html(this.value, this.$element[0]);
@@ -1233,12 +1450,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1233
1450
 
1234
1451
  //disabled
1235
1452
  if(key === 'disabled') {
1236
- if(value) {
1237
- this.disable();
1238
- } else {
1239
- this.enable();
1240
- }
1241
- return;
1453
+ return value ? this.disable() : this.enable();
1242
1454
  }
1243
1455
 
1244
1456
  //value
@@ -1250,6 +1462,12 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1250
1462
  if(this.container) {
1251
1463
  this.container.option(key, value);
1252
1464
  }
1465
+
1466
+ //pass option to input directly (as it points to the same in form)
1467
+ if(this.input.option) {
1468
+ this.input.option(key, value);
1469
+ }
1470
+
1253
1471
  },
1254
1472
 
1255
1473
  /*
@@ -1291,7 +1509,8 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1291
1509
  //init editableContainer: popover, tooltip, inline, etc..
1292
1510
  if(!this.container) {
1293
1511
  var containerOptions = $.extend({}, this.options, {
1294
- value: this.value
1512
+ value: this.value,
1513
+ input: this.input //pass input to form (as it is already created)
1295
1514
  });
1296
1515
  this.$element.editableContainer(containerOptions);
1297
1516
  this.$element.on("save.internal", $.proxy(this.save, this));
@@ -1338,8 +1557,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1338
1557
  this.$element.removeClass('editable-unsaved');
1339
1558
  }
1340
1559
 
1341
- // this.hide();
1342
- this.setValue(params.newValue);
1560
+ this.setValue(params.newValue, false, params.response);
1343
1561
 
1344
1562
  /**
1345
1563
  Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
@@ -1351,13 +1569,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1351
1569
  @param {Object} params.response ajax response
1352
1570
  @example
1353
1571
  $('#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
- }
1572
+ alert('Saved value: ' + params.newValue);
1361
1573
  });
1362
1574
  **/
1363
1575
  //event itself is triggered by editableContainer. Description here is only for documentation
@@ -1375,7 +1587,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1375
1587
  @param {mixed} value new value
1376
1588
  @param {boolean} convertStr whether to convert value from string to internal format
1377
1589
  **/
1378
- setValue: function(value, convertStr) {
1590
+ setValue: function(value, convertStr, response) {
1379
1591
  if(convertStr) {
1380
1592
  this.value = this.input.str2value(value);
1381
1593
  } else {
@@ -1384,7 +1596,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1384
1596
  if(this.container) {
1385
1597
  this.container.option('value', this.value);
1386
1598
  }
1387
- $.when(this.render())
1599
+ $.when(this.render(response))
1388
1600
  .then($.proxy(function() {
1389
1601
  this.handleEmpty();
1390
1602
  }, this));
@@ -1398,7 +1610,28 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1398
1610
  if(this.container) {
1399
1611
  this.container.activate();
1400
1612
  }
1401
- }
1613
+ },
1614
+
1615
+ /**
1616
+ Removes editable feature from element
1617
+ @method destroy()
1618
+ **/
1619
+ destroy: function() {
1620
+ if(this.container) {
1621
+ this.container.destroy();
1622
+ }
1623
+
1624
+ if(this.options.toggle !== 'manual') {
1625
+ this.$element.removeClass('editable-click');
1626
+ this.$element.off(this.options.toggle + '.editable');
1627
+ }
1628
+
1629
+ this.$element.off("save.internal");
1630
+
1631
+ this.$element.removeClass('editable');
1632
+ this.$element.removeClass('editable-open');
1633
+ this.$element.removeData('editable');
1634
+ }
1402
1635
  };
1403
1636
 
1404
1637
  /* EDITABLE PLUGIN DEFINITION
@@ -1584,7 +1817,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1584
1817
  **/
1585
1818
  autotext: 'auto',
1586
1819
  /**
1587
- Initial value of input. Taken from <code>data-value</code> or element's text.
1820
+ Initial value of input. If not set, taken from element's text.
1588
1821
 
1589
1822
  @property value
1590
1823
  @type mixed
@@ -1593,10 +1826,21 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1593
1826
  value: null,
1594
1827
  /**
1595
1828
  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.
1829
+ If `null`, default input's display used.
1830
+ If `false`, no displaying methods will be called, element's text will never change.
1598
1831
  Runs under element's scope.
1599
- Second parameter __sourceData__ is passed for inputs with source (select, checklist).
1832
+ _Parameters:_
1833
+
1834
+ * `value` current value to be displayed
1835
+ * `response` server response (if display called after ajax submit), since 1.4.0
1836
+
1837
+ For **inputs with source** (select, checklist) parameters are different:
1838
+
1839
+ * `value` current value to be displayed
1840
+ * `sourceData` array of items for current input (e.g. dropdown items)
1841
+ * `response` server response (if display called after ajax submit), since 1.4.0
1842
+
1843
+ To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`.
1600
1844
 
1601
1845
  @property display
1602
1846
  @type function|boolean
@@ -1604,8 +1848,16 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1604
1848
  @since 1.2.0
1605
1849
  @example
1606
1850
  display: function(value, sourceData) {
1607
- var escapedValue = $('<div>').text(value).html();
1608
- $(this).html('<b>'+escapedValue+'</b>');
1851
+ //display checklist as comma-separated values
1852
+ var html = [],
1853
+ checked = $.fn.editableutils.itemsByValue(value, sourceData);
1854
+
1855
+ if(checked.length) {
1856
+ $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
1857
+ $(this).html(html.join(', '));
1858
+ } else {
1859
+ $(this).empty();
1860
+ }
1609
1861
  }
1610
1862
  **/
1611
1863
  display: null
@@ -1635,26 +1887,27 @@ To create your own input you can inherit from this class.
1635
1887
  **/
1636
1888
  init: function(type, options, defaults) {
1637
1889
  this.type = type;
1638
- this.options = $.extend({}, defaults, options);
1639
- this.$input = null;
1640
- this.$clear = null;
1641
- this.error = null;
1890
+ this.options = $.extend({}, defaults, options);
1891
+ },
1892
+
1893
+ /*
1894
+ this method called before render to init $tpl that is inserted in DOM
1895
+ */
1896
+ prerender: function() {
1897
+ this.$tpl = $(this.options.tpl); //whole tpl as jquery object
1898
+ this.$input = this.$tpl; //control itself, can be changed in render method
1899
+ this.$clear = null; //clear button
1900
+ this.error = null; //error message, if input cannot be rendered
1642
1901
  },
1643
1902
 
1644
1903
  /**
1645
1904
  Renders input from tpl. Can return jQuery deferred object.
1905
+ Can be overwritten in child objects
1646
1906
 
1647
1907
  @method render()
1648
1908
  **/
1649
1909
  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
- }
1910
+
1658
1911
  },
1659
1912
 
1660
1913
  /**
@@ -1691,7 +1944,7 @@ To create your own input you can inherit from this class.
1691
1944
  },
1692
1945
 
1693
1946
  /**
1694
- Converts string received from server into value.
1947
+ Converts string received from server into value. Usually from `data-value` attribute.
1695
1948
 
1696
1949
  @method str2value(str)
1697
1950
  @param {string} str
@@ -1702,7 +1955,7 @@ To create your own input you can inherit from this class.
1702
1955
  },
1703
1956
 
1704
1957
  /**
1705
- Converts value for submitting to server
1958
+ Converts value for submitting to server. Result can be string or object.
1706
1959
 
1707
1960
  @method value2submit(value)
1708
1961
  @param {mixed} value
@@ -1763,7 +2016,25 @@ To create your own input you can inherit from this class.
1763
2016
  **/
1764
2017
  autosubmit: function() {
1765
2018
 
2019
+ },
2020
+
2021
+ // -------- helper functions --------
2022
+ setClass: function() {
2023
+ if(this.options.inputclass) {
2024
+ this.$input.addClass(this.options.inputclass);
2025
+ }
2026
+ },
2027
+
2028
+ setAttr: function(attr) {
2029
+ if (this.options[attr]) {
2030
+ this.$input.attr(attr, this.options[attr]);
2031
+ }
2032
+ },
2033
+
2034
+ option: function(key, value) {
2035
+ this.options[key] = value;
1766
2036
  }
2037
+
1767
2038
  };
1768
2039
 
1769
2040
  AbstractInput.defaults = {
@@ -1813,11 +2084,9 @@ List - abstract class for inputs that have source option loaded from js array or
1813
2084
 
1814
2085
  $.extend(List.prototype, {
1815
2086
  render: function () {
1816
- List.superclass.render.call(this);
1817
2087
  var deferred = $.Deferred();
2088
+
1818
2089
  this.error = null;
1819
- this.sourceData = null;
1820
- this.prependData = null;
1821
2090
  this.onSourceReady(function () {
1822
2091
  this.renderList();
1823
2092
  deferred.resolve();
@@ -1833,20 +2102,24 @@ List - abstract class for inputs that have source option loaded from js array or
1833
2102
  return null; //can't set value by text
1834
2103
  },
1835
2104
 
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
- });
2105
+ value2html: function (value, element, display, response) {
2106
+ var deferred = $.Deferred(),
2107
+ success = function () {
2108
+ if(typeof display === 'function') {
2109
+ //custom display method
2110
+ display.call(element, value, this.sourceData, response);
2111
+ } else {
2112
+ this.value2htmlFinal(value, element);
2113
+ }
2114
+ deferred.resolve();
2115
+ };
2116
+
2117
+ //for null value just call success without loading source
2118
+ if(value === null) {
2119
+ success.call(this);
2120
+ } else {
2121
+ this.onSourceReady(success, function () { deferred.resolve(); });
2122
+ }
1850
2123
 
1851
2124
  return deferred.promise();
1852
2125
  },
@@ -1872,7 +2145,7 @@ List - abstract class for inputs that have source option loaded from js array or
1872
2145
  if (typeof this.options.source === 'string') {
1873
2146
  //try to get from cache
1874
2147
  if(this.options.sourceCache) {
1875
- var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''),
2148
+ var cacheID = this.options.source,
1876
2149
  cache;
1877
2150
 
1878
2151
  if (!$(document).data(cacheID)) {
@@ -1883,11 +2156,13 @@ List - abstract class for inputs that have source option loaded from js array or
1883
2156
  //check for cached data
1884
2157
  if (cache.loading === false && cache.sourceData) { //take source from cache
1885
2158
  this.sourceData = cache.sourceData;
2159
+ this.doPrepend();
1886
2160
  success.call(this);
1887
2161
  return;
1888
2162
  } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
1889
2163
  cache.callbacks.push($.proxy(function () {
1890
2164
  this.sourceData = cache.sourceData;
2165
+ this.doPrepend();
1891
2166
  success.call(this);
1892
2167
  }, this));
1893
2168
 
@@ -1906,7 +2181,6 @@ List - abstract class for inputs that have source option loaded from js array or
1906
2181
  url: this.options.source,
1907
2182
  type: 'get',
1908
2183
  cache: false,
1909
- data: this.options.name ? {name: this.options.name} : {},
1910
2184
  dataType: 'json',
1911
2185
  success: $.proxy(function (data) {
1912
2186
  if(cache) {
@@ -1914,17 +2188,19 @@ List - abstract class for inputs that have source option loaded from js array or
1914
2188
  }
1915
2189
  this.sourceData = this.makeArray(data);
1916
2190
  if($.isArray(this.sourceData)) {
1917
- this.doPrepend();
1918
- success.call(this);
1919
2191
  if(cache) {
1920
2192
  //store result in cache
1921
2193
  cache.sourceData = this.sourceData;
1922
- $.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields
2194
+ //run success callbacks for other fields waiting for this source
2195
+ $.each(cache.callbacks, function () { this.call(); });
1923
2196
  }
2197
+ this.doPrepend();
2198
+ success.call(this);
1924
2199
  } else {
1925
2200
  error.call(this);
1926
2201
  if(cache) {
1927
- $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
2202
+ //run error callbacks for other fields waiting for this source
2203
+ $.each(cache.err_callbacks, function () { this.call(); });
1928
2204
  }
1929
2205
  }
1930
2206
  }, this),
@@ -1937,8 +2213,13 @@ List - abstract class for inputs that have source option loaded from js array or
1937
2213
  }
1938
2214
  }, this)
1939
2215
  });
1940
- } else { //options as json/array
1941
- this.sourceData = this.makeArray(this.options.source);
2216
+ } else { //options as json/array/function
2217
+ if (typeof this.options.source === 'function') {
2218
+ this.sourceData = this.makeArray(this.options.source());
2219
+ } else {
2220
+ this.sourceData = this.makeArray(this.options.source);
2221
+ }
2222
+
1942
2223
  if($.isArray(this.sourceData)) {
1943
2224
  this.doPrepend();
1944
2225
  success.call(this);
@@ -1959,7 +2240,11 @@ List - abstract class for inputs that have source option loaded from js array or
1959
2240
  if (typeof this.options.prepend === 'string') {
1960
2241
  this.options.prepend = {'': this.options.prepend};
1961
2242
  }
1962
- this.prependData = this.makeArray(this.options.prepend);
2243
+ if (typeof this.options.prepend === 'function') {
2244
+ this.prependData = this.makeArray(this.options.prepend());
2245
+ } else {
2246
+ this.prependData = this.makeArray(this.options.prepend);
2247
+ }
1963
2248
  }
1964
2249
 
1965
2250
  if($.isArray(this.prependData) && $.isArray(this.sourceData)) {
@@ -2021,41 +2306,41 @@ List - abstract class for inputs that have source option loaded from js array or
2021
2306
  return result;
2022
2307
  },
2023
2308
 
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
- }
2309
+ option: function(key, value) {
2310
+ this.options[key] = value;
2311
+ if(key === 'source') {
2312
+ this.sourceData = null;
2034
2313
  }
2314
+ if(key === 'prepend') {
2315
+ this.prependData = null;
2316
+ }
2035
2317
  }
2036
2318
 
2037
2319
  });
2038
2320
 
2039
2321
  List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
2040
2322
  /**
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.
2323
+ Source data for list.
2324
+ If **array** - it should be in format: `[{value: 1, text: "text1"}, {...}]`
2325
+ For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order.
2045
2326
 
2327
+ 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.
2328
+
2329
+ If **function**, it should return data in format above (since 1.4.0).
2330
+
2046
2331
  @property source
2047
- @type string|array|object
2332
+ @type string | array | object | function
2048
2333
  @default null
2049
2334
  **/
2050
- source:null,
2335
+ source: null,
2051
2336
  /**
2052
2337
  Data automatically prepended to the beginning of dropdown list.
2053
2338
 
2054
2339
  @property prepend
2055
- @type string|array|object
2340
+ @type string | array | object | function
2056
2341
  @default false
2057
2342
  **/
2058
- prepend:false,
2343
+ prepend: false,
2059
2344
  /**
2060
2345
  Error message when list cannot be loaded (e.g. ajax error)
2061
2346
 
@@ -2079,6 +2364,7 @@ List - abstract class for inputs that have source option loaded from js array or
2079
2364
  $.fn.editabletypes.list = List;
2080
2365
 
2081
2366
  }(window.jQuery));
2367
+
2082
2368
  /**
2083
2369
  Text input
2084
2370
 
@@ -2104,11 +2390,64 @@ $(function(){
2104
2390
  $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
2105
2391
 
2106
2392
  $.extend(Text.prototype, {
2107
- activate: function() {
2108
- if(this.$input.is(':visible')) {
2393
+ render: function() {
2394
+ this.renderClear();
2395
+ this.setClass();
2396
+ this.setAttr('placeholder');
2397
+ },
2398
+
2399
+ activate: function() {
2400
+ if(this.$input.is(':visible')) {
2109
2401
  this.$input.focus();
2110
2402
  $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
2403
+ if(this.toggleClear) {
2404
+ this.toggleClear();
2405
+ }
2406
+ }
2407
+ },
2408
+
2409
+ //render clear button
2410
+ renderClear: function() {
2411
+ if (this.options.clear) {
2412
+ this.$clear = $('<span class="editable-clear-x"></span>');
2413
+ this.$input.after(this.$clear)
2414
+ .css('padding-right', 20)
2415
+ .keyup($.proxy(this.toggleClear, this))
2416
+ .parent().css('position', 'relative');
2417
+
2418
+ this.$clear.click($.proxy(function(){
2419
+ this.$clear.hide();
2420
+ this.$input.val('').focus();
2421
+ }, this));
2422
+ }
2423
+ },
2424
+
2425
+ postrender: function() {
2426
+ if(this.$clear) {
2427
+ //can position clear button only here, when form is shown and height can be calculated
2428
+ var h = this.$input.outerHeight() || 20,
2429
+ delta = (h - this.$clear.height()) / 2;
2430
+
2431
+ //workaround for plain-popup
2432
+ if(delta < 3) {
2433
+ delta = 3;
2434
+ }
2435
+
2436
+ this.$clear.css({top: delta, right: delta});
2437
+ }
2438
+ },
2439
+
2440
+ //show / hide clear button
2441
+ toggleClear: function() {
2442
+ if(!this.$clear) {
2443
+ return;
2111
2444
  }
2445
+
2446
+ if(this.$input.val()) {
2447
+ this.$clear.show();
2448
+ } else {
2449
+ this.$clear.hide();
2450
+ }
2112
2451
  }
2113
2452
  });
2114
2453
 
@@ -2125,7 +2464,16 @@ $(function(){
2125
2464
  @type string
2126
2465
  @default null
2127
2466
  **/
2128
- placeholder: null
2467
+ placeholder: null,
2468
+
2469
+ /**
2470
+ Whether to show `clear` button
2471
+
2472
+ @property clear
2473
+ @type boolean
2474
+ @default true
2475
+ **/
2476
+ clear: true
2129
2477
  });
2130
2478
 
2131
2479
  $.fn.editabletypes.text = Text;
@@ -2144,7 +2492,8 @@ Textarea input
2144
2492
  $(function(){
2145
2493
  $('#comments').editable({
2146
2494
  url: '/post',
2147
- title: 'Enter comments'
2495
+ title: 'Enter comments',
2496
+ rows: 10
2148
2497
  });
2149
2498
  });
2150
2499
  </script>
@@ -2159,8 +2508,10 @@ $(function(){
2159
2508
 
2160
2509
  $.extend(Textarea.prototype, {
2161
2510
  render: function () {
2162
- Textarea.superclass.render.call(this);
2163
-
2511
+ this.setClass();
2512
+ this.setAttr('placeholder');
2513
+ this.setAttr('rows');
2514
+
2164
2515
  //ctrl + enter
2165
2516
  this.$input.keydown(function (e) {
2166
2517
  if (e.ctrlKey && e.which === 13) {
@@ -2185,43 +2536,56 @@ $(function(){
2185
2536
  if(!html) {
2186
2537
  return '';
2187
2538
  }
2539
+
2540
+ var regex = new RegExp(String.fromCharCode(10), 'g');
2188
2541
  var lines = html.split(/<br\s*\/?>/i);
2189
2542
  for (var i = 0; i < lines.length; i++) {
2190
- lines[i] = $('<div>').html(lines[i]).text();
2543
+ var text = $('<div>').html(lines[i]).text();
2544
+
2545
+ // Remove newline characters (\n) to avoid them being converted by value2html() method
2546
+ // thus adding extra <br> tags
2547
+ text = text.replace(regex, '');
2548
+
2549
+ lines[i] = text;
2191
2550
  }
2192
- return lines.join("\n");
2193
- },
2551
+ return lines.join("\n");
2552
+ },
2194
2553
 
2195
2554
  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
- }
2555
+ $.fn.editabletypes.text.prototype.activate.call(this);
2556
+ }
2201
2557
  });
2202
2558
 
2203
2559
  Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
2204
2560
  /**
2205
- @property tpl
2561
+ @property tpl
2206
2562
  @default <textarea></textarea>
2207
- **/
2563
+ **/
2208
2564
  tpl:'<textarea></textarea>',
2209
2565
  /**
2210
- @property inputclass
2566
+ @property inputclass
2211
2567
  @default input-large
2212
- **/
2568
+ **/
2213
2569
  inputclass: 'input-large',
2214
2570
  /**
2215
2571
  Placeholder attribute of input. Shown when input is empty.
2216
2572
 
2217
- @property placeholder
2573
+ @property placeholder
2218
2574
  @type string
2219
2575
  @default null
2220
- **/
2221
- placeholder: null
2576
+ **/
2577
+ placeholder: null,
2578
+ /**
2579
+ Number of rows in textarea
2580
+
2581
+ @property rows
2582
+ @type integer
2583
+ @default 7
2584
+ **/
2585
+ rows: 7
2222
2586
  });
2223
2587
 
2224
- $.fn.editabletypes.textarea = Textarea;
2588
+ $.fn.editabletypes.textarea = Textarea;
2225
2589
 
2226
2590
  }(window.jQuery));
2227
2591
 
@@ -2257,6 +2621,8 @@ $(function(){
2257
2621
 
2258
2622
  $.extend(Select.prototype, {
2259
2623
  renderList: function() {
2624
+ this.$input.empty();
2625
+
2260
2626
  if(!$.isArray(this.sourceData)) {
2261
2627
  return;
2262
2628
  }
@@ -2265,6 +2631,8 @@ $(function(){
2265
2631
  this.$input.append($('<option>', {value: this.sourceData[i].value}).text(this.sourceData[i].text));
2266
2632
  }
2267
2633
 
2634
+ this.setClass();
2635
+
2268
2636
  //enter submit
2269
2637
  this.$input.on('keydown.editable', function (e) {
2270
2638
  if (e.which === 13) {
@@ -2274,11 +2642,14 @@ $(function(){
2274
2642
  },
2275
2643
 
2276
2644
  value2htmlFinal: function(value, element) {
2277
- var text = '', item = this.itemByVal(value);
2278
- if(item) {
2279
- text = item.text;
2645
+ var text = '',
2646
+ items = $.fn.editableutils.itemsByValue(value, this.sourceData);
2647
+
2648
+ if(items.length) {
2649
+ text = items[0].text;
2280
2650
  }
2281
- Select.superclass.constructor.superclass.value2html(text, element);
2651
+
2652
+ $(element).text(text);
2282
2653
  },
2283
2654
 
2284
2655
  autosubmit: function() {
@@ -2333,6 +2704,9 @@ $(function(){
2333
2704
  $.extend(Checklist.prototype, {
2334
2705
  renderList: function() {
2335
2706
  var $label, $div;
2707
+
2708
+ this.$tpl.empty();
2709
+
2336
2710
  if(!$.isArray(this.sourceData)) {
2337
2711
  return;
2338
2712
  }
@@ -2345,8 +2719,11 @@ $(function(){
2345
2719
  }))
2346
2720
  .append($('<span>').text(' '+this.sourceData[i].text));
2347
2721
 
2348
- $('<div>').append($label).appendTo(this.$input);
2722
+ $('<div>').append($label).appendTo(this.$tpl);
2349
2723
  }
2724
+
2725
+ this.$input = this.$tpl.find('input[type="checkbox"]');
2726
+ this.setClass();
2350
2727
  },
2351
2728
 
2352
2729
  value2str: function(value) {
@@ -2367,10 +2744,9 @@ $(function(){
2367
2744
 
2368
2745
  //set checked on required checkboxes
2369
2746
  value2input: function(value) {
2370
- var $checks = this.$input.find('input[type="checkbox"]');
2371
- $checks.removeAttr('checked');
2747
+ this.$input.removeAttr('checked');
2372
2748
  if($.isArray(value) && value.length) {
2373
- $checks.each(function(i, el) {
2749
+ this.$input.each(function(i, el) {
2374
2750
  var $el = $(el);
2375
2751
  // cannot use $.inArray as it performs strict comparison
2376
2752
  $.each(value, function(j, val){
@@ -2386,7 +2762,7 @@ $(function(){
2386
2762
 
2387
2763
  input2value: function() {
2388
2764
  var checked = [];
2389
- this.$input.find('input:checked').each(function(i, el) {
2765
+ this.$input.filter(':checked').each(function(i, el) {
2390
2766
  checked.push($(el).val());
2391
2767
  });
2392
2768
  return checked;
@@ -2395,11 +2771,8 @@ $(function(){
2395
2771
  //collect text of checked boxes
2396
2772
  value2htmlFinal: function(value, element) {
2397
2773
  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*/
2774
+ checked = $.fn.editableutils.itemsByValue(value, this.sourceData);
2775
+
2403
2776
  if(checked.length) {
2404
2777
  $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
2405
2778
  $(element).html(html.join('<br>'));
@@ -2409,11 +2782,11 @@ $(function(){
2409
2782
  },
2410
2783
 
2411
2784
  activate: function() {
2412
- this.$input.find('input[type="checkbox"]').first().focus();
2785
+ this.$input.first().focus();
2413
2786
  },
2414
2787
 
2415
2788
  autosubmit: function() {
2416
- this.$input.find('input[type="checkbox"]').on('keydown', function(e){
2789
+ this.$input.on('keydown', function(e){
2417
2790
  if (e.which === 13) {
2418
2791
  $(this).closest('form').submit();
2419
2792
  }
@@ -2426,21 +2799,21 @@ $(function(){
2426
2799
  @property tpl
2427
2800
  @default <div></div>
2428
2801
  **/
2429
- tpl:'<div></div>',
2802
+ tpl:'<div class="editable-checklist"></div>',
2430
2803
 
2431
2804
  /**
2432
2805
  @property inputclass
2433
2806
  @type string
2434
- @default editable-checklist
2807
+ @default null
2435
2808
  **/
2436
- inputclass: 'editable-checklist',
2809
+ inputclass: null,
2437
2810
 
2438
2811
  /**
2439
- Separator of values when reading from 'data-value' string
2812
+ Separator of values when reading from `data-value` attribute
2440
2813
 
2441
2814
  @property separator
2442
2815
  @type string
2443
- @default ', '
2816
+ @default ','
2444
2817
  **/
2445
2818
  separator: ','
2446
2819
  });
@@ -2571,18 +2944,9 @@ Number
2571
2944
  $.extend(NumberInput.prototype, {
2572
2945
  render: function () {
2573
2946
  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);
2581
- }
2582
-
2583
- if (this.options.step !== null) {
2584
- this.$input.attr('step', this.options.step);
2585
- }
2947
+ this.setAttr('min');
2948
+ this.setAttr('max');
2949
+ this.setAttr('step');
2586
2950
  }
2587
2951
  });
2588
2952
  NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
@@ -2606,29 +2970,19 @@ Range (inherit from number)
2606
2970
  $.fn.editableutils.inherit(Range, $.fn.editabletypes.number);
2607
2971
  $.extend(Range.prototype, {
2608
2972
  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
- }
2617
-
2618
- if (this.options.max !== null) {
2619
- $slider.attr('max', this.options.max);
2620
- }
2973
+ this.$input = this.$tpl.filter('input');
2621
2974
 
2622
- if (this.options.step !== null) {
2623
- $slider.attr('step', this.options.step);
2624
- }
2975
+ this.setClass();
2976
+ this.setAttr('min');
2977
+ this.setAttr('max');
2978
+ this.setAttr('step');
2625
2979
 
2626
- $slider.on('input', function(){
2980
+ this.$input.on('input', function(){
2627
2981
  $(this).siblings('output').text($(this).val());
2628
2982
  });
2629
2983
  },
2630
2984
  activate: function() {
2631
- this.$input.filter('input').focus();
2985
+ this.$input.focus();
2632
2986
  }
2633
2987
  });
2634
2988
  Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {
@@ -2637,6 +2991,590 @@ Range (inherit from number)
2637
2991
  });
2638
2992
  $.fn.editabletypes.range = Range;
2639
2993
  }(window.jQuery));
2994
+ /**
2995
+ * Combodate - 1.0.1
2996
+ * Dropdown date and time picker.
2997
+ * Converts text input into dropdowns to pick day, month, year, hour, minute and second.
2998
+ * Uses momentjs as datetime library http://momentjs.com.
2999
+ * For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang
3000
+ *
3001
+ * Author: Vitaliy Potapov
3002
+ * Project page: http://github.com/vitalets/combodate
3003
+ * Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
3004
+ **/
3005
+ (function ($) {
3006
+
3007
+ var Combodate = function (element, options) {
3008
+ this.$element = $(element);
3009
+ if(!this.$element.is('input')) {
3010
+ $.error('Combodate should be applied to INPUT element');
3011
+ return;
3012
+ }
3013
+ this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data());
3014
+ this.init();
3015
+ };
3016
+
3017
+ Combodate.prototype = {
3018
+ constructor: Combodate,
3019
+ init: function () {
3020
+ this.map = {
3021
+ //key regexp moment.method
3022
+ day: ['D', 'date'],
3023
+ month: ['M', 'month'],
3024
+ year: ['Y', 'year'],
3025
+ hour: ['[Hh]', 'hours'],
3026
+ minute: ['m', 'minutes'],
3027
+ second: ['s', 'seconds'],
3028
+ ampm: ['[Aa]', '']
3029
+ };
3030
+
3031
+ this.$widget = $('<span class="combodate"></span>').html(this.getTemplate());
3032
+
3033
+ this.initCombos();
3034
+
3035
+ //update original input on change
3036
+ this.$widget.on('change', 'select', $.proxy(function(){
3037
+ this.$element.val(this.getValue());
3038
+ }, this));
3039
+
3040
+ this.$widget.find('select').css('width', 'auto');
3041
+
3042
+ //hide original input and insert widget
3043
+ this.$element.hide().after(this.$widget);
3044
+
3045
+ //set initial value
3046
+ this.setValue(this.$element.val() || this.options.value);
3047
+ },
3048
+
3049
+ /*
3050
+ Replace tokens in template with <select> elements
3051
+ */
3052
+ getTemplate: function() {
3053
+ var tpl = this.options.template;
3054
+
3055
+ //first pass
3056
+ $.each(this.map, function(k, v) {
3057
+ v = v[0];
3058
+ var r = new RegExp(v+'+'),
3059
+ token = v.length > 1 ? v.substring(1, 2) : v;
3060
+
3061
+ tpl = tpl.replace(r, '{'+token+'}');
3062
+ });
3063
+
3064
+ //replace spaces with &nbsp;
3065
+ tpl = tpl.replace(/ /g, '&nbsp;');
3066
+
3067
+ //second pass
3068
+ $.each(this.map, function(k, v) {
3069
+ v = v[0];
3070
+ var token = v.length > 1 ? v.substring(1, 2) : v;
3071
+
3072
+ tpl = tpl.replace('{'+token+'}', '<select class="'+k+'"></select>');
3073
+ });
3074
+
3075
+ return tpl;
3076
+ },
3077
+
3078
+ /*
3079
+ Initialize combos that presents in template
3080
+ */
3081
+ initCombos: function() {
3082
+ var that = this;
3083
+ $.each(this.map, function(k, v) {
3084
+ var $c = that.$widget.find('.'+k), f, items;
3085
+ if($c.length) {
3086
+ that['$'+k] = $c; //set properties like this.$day, this.$month etc.
3087
+ f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1); //define method name to fill items, e.g `fillDays`
3088
+ items = that[f]();
3089
+ that['$'+k].html(that.renderItems(items));
3090
+ }
3091
+ });
3092
+ },
3093
+
3094
+ /*
3095
+ Initialize items of combos. Handles `firstItem` option
3096
+ */
3097
+ initItems: function(key) {
3098
+ var values = [];
3099
+ if(this.options.firstItem === 'name') {
3100
+ var header = typeof moment.relativeTime[key] === 'function' ? moment.relativeTime[key](1, true, key, false) : moment.relativeTime[key];
3101
+ //take last entry (see momentjs lang files structure)
3102
+ header = header.split(' ').reverse()[0];
3103
+ values.push(['', header]);
3104
+ } else if(this.options.firstItem === 'empty') {
3105
+ values.push(['', '']);
3106
+ }
3107
+ return values;
3108
+ },
3109
+
3110
+ /*
3111
+ render items to string of <option> tags
3112
+ */
3113
+ renderItems: function(items) {
3114
+ var str = [];
3115
+ for(var i=0; i<items.length; i++) {
3116
+ str.push('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');
3117
+ }
3118
+ return str.join("\n");
3119
+ },
3120
+
3121
+ /*
3122
+ fill day
3123
+ */
3124
+ fillDay: function() {
3125
+ var items = this.initItems('d'), name, i,
3126
+ twoDigit = this.options.template.indexOf('DD') !== -1;
3127
+
3128
+ for(i=1; i<=31; i++) {
3129
+ name = twoDigit ? this.leadZero(i) : i;
3130
+ items.push([i, name]);
3131
+ }
3132
+ return items;
3133
+ },
3134
+
3135
+ /*
3136
+ fill month
3137
+ */
3138
+ fillMonth: function() {
3139
+ var items = this.initItems('M'), name, i,
3140
+ longNames = this.options.template.indexOf('MMMM') !== -1,
3141
+ shortNames = this.options.template.indexOf('MMM') !== -1,
3142
+ twoDigit = this.options.template.indexOf('MM') !== -1;
3143
+
3144
+ for(i=0; i<=11; i++) {
3145
+ if(longNames) {
3146
+ name = moment.months[i];
3147
+ } else if(shortNames) {
3148
+ name = moment.monthsShort[i];
3149
+ } else if(twoDigit) {
3150
+ name = this.leadZero(i+1);
3151
+ } else {
3152
+ name = i+1;
3153
+ }
3154
+ items.push([i, name]);
3155
+ }
3156
+ return items;
3157
+ },
3158
+
3159
+ /*
3160
+ fill year
3161
+ */
3162
+ fillYear: function() {
3163
+ var items = this.initItems('y'), name, i,
3164
+ longNames = this.options.template.indexOf('YYYY') !== -1;
3165
+
3166
+ for(i=this.options.maxYear; i>=this.options.minYear; i--) {
3167
+ name = longNames ? i : (i+'').substring(2);
3168
+ items.push([i, name]);
3169
+ }
3170
+ return items;
3171
+ },
3172
+
3173
+ /*
3174
+ fill hour
3175
+ */
3176
+ fillHour: function() {
3177
+ var items = this.initItems('h'), name, i,
3178
+ h12 = this.options.template.indexOf('h') !== -1,
3179
+ h24 = this.options.template.indexOf('H') !== -1,
3180
+ twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
3181
+ max = h12 ? 12 : 23;
3182
+
3183
+ for(i=0; i<=max; i++) {
3184
+ name = twoDigit ? this.leadZero(i) : i;
3185
+ items.push([i, name]);
3186
+ }
3187
+ return items;
3188
+ },
3189
+
3190
+ /*
3191
+ fill minute
3192
+ */
3193
+ fillMinute: function() {
3194
+ var items = this.initItems('m'), name, i,
3195
+ twoDigit = this.options.template.indexOf('mm') !== -1;
3196
+
3197
+ for(i=0; i<=59; i+= this.options.minuteStep) {
3198
+ name = twoDigit ? this.leadZero(i) : i;
3199
+ items.push([i, name]);
3200
+ }
3201
+ return items;
3202
+ },
3203
+
3204
+ /*
3205
+ fill second
3206
+ */
3207
+ fillSecond: function() {
3208
+ var items = this.initItems('s'), name, i,
3209
+ twoDigit = this.options.template.indexOf('ss') !== -1;
3210
+
3211
+ for(i=0; i<=59; i+= this.options.secondStep) {
3212
+ name = twoDigit ? this.leadZero(i) : i;
3213
+ items.push([i, name]);
3214
+ }
3215
+ return items;
3216
+ },
3217
+
3218
+ /*
3219
+ fill ampm
3220
+ */
3221
+ fillAmpm: function() {
3222
+ var ampmL = this.options.template.indexOf('a') !== -1,
3223
+ ampmU = this.options.template.indexOf('A') !== -1,
3224
+ items = [
3225
+ ['am', ampmL ? 'am' : 'AM'],
3226
+ ['pm', ampmL ? 'pm' : 'PM']
3227
+ ];
3228
+ return items;
3229
+ },
3230
+
3231
+ /*
3232
+ Returns current date value.
3233
+ If format not specified - `options.format` used.
3234
+ If format = `null` - Moment object returned.
3235
+ */
3236
+ getValue: function(format) {
3237
+ var dt, values = {},
3238
+ that = this,
3239
+ notSelected = false;
3240
+
3241
+ //getting selected values
3242
+ $.each(this.map, function(k, v) {
3243
+ if(k === 'ampm') {
3244
+ return;
3245
+ }
3246
+ var def = k === 'day' ? 1 : 0;
3247
+
3248
+ values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def;
3249
+
3250
+ if(isNaN(values[k])) {
3251
+ notSelected = true;
3252
+ return false;
3253
+ }
3254
+ });
3255
+
3256
+ //if at least one visible combo not selected - return empty string
3257
+ if(notSelected) {
3258
+ return '';
3259
+ }
3260
+
3261
+ //convert hours if 12h format
3262
+ if(this.$ampm) {
3263
+ values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
3264
+ if(values.hour === 24) {
3265
+ values.hour = 0;
3266
+ }
3267
+ }
3268
+
3269
+ dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
3270
+
3271
+ //highlight invalid date
3272
+ this.highlight(dt);
3273
+
3274
+ format = format === undefined ? this.options.format : format;
3275
+ if(format === null) {
3276
+ return dt.isValid() ? dt : null;
3277
+ } else {
3278
+ return dt.isValid() ? dt.format(format) : '';
3279
+ }
3280
+ },
3281
+
3282
+ setValue: function(value) {
3283
+ if(!value) {
3284
+ return;
3285
+ }
3286
+
3287
+ var dt = typeof value === 'string' ? moment(value, this.options.format) : moment(value),
3288
+ that = this,
3289
+ values = {};
3290
+
3291
+ if(dt.isValid()) {
3292
+ //read values from date object
3293
+ $.each(this.map, function(k, v) {
3294
+ if(k === 'ampm') {
3295
+ return;
3296
+ }
3297
+ values[k] = dt[v[1]]();
3298
+ });
3299
+
3300
+ if(this.$ampm) {
3301
+ if(values.hour > 12) {
3302
+ values.hour -= 12;
3303
+ values.ampm = 'pm';
3304
+ } else {
3305
+ values.ampm = 'am';
3306
+ }
3307
+ }
3308
+
3309
+ $.each(values, function(k, v) {
3310
+ if(that['$'+k]) {
3311
+ that['$'+k].val(v);
3312
+ }
3313
+ });
3314
+
3315
+ this.$element.val(dt.format(this.options.format));
3316
+ }
3317
+ },
3318
+
3319
+ /*
3320
+ highlight combos if date is invalid
3321
+ */
3322
+ highlight: function(dt) {
3323
+ if(!dt.isValid()) {
3324
+ if(this.options.errorClass) {
3325
+ this.$widget.addClass(this.options.errorClass);
3326
+ } else {
3327
+ //store original border color
3328
+ if(!this.borderColor) {
3329
+ this.borderColor = this.$widget.find('select').css('border-color');
3330
+ }
3331
+ this.$widget.find('select').css('border-color', 'red');
3332
+ }
3333
+ } else {
3334
+ if(this.options.errorClass) {
3335
+ this.$widget.removeClass(this.options.errorClass);
3336
+ } else {
3337
+ this.$widget.find('select').css('border-color', this.borderColor);
3338
+ }
3339
+ }
3340
+ },
3341
+
3342
+ leadZero: function(v) {
3343
+ return v <= 9 ? '0' + v : v;
3344
+ },
3345
+
3346
+ destroy: function() {
3347
+ this.$widget.remove();
3348
+ this.$element.removeData('combodate').show();
3349
+ }
3350
+
3351
+ //todo: clear method
3352
+ };
3353
+
3354
+ $.fn.combodate = function ( option ) {
3355
+ var d, args = Array.apply(null, arguments);
3356
+ args.shift();
3357
+
3358
+ //getValue returns date as string / object (not jQuery object)
3359
+ if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) {
3360
+ return d.getValue.apply(d, args);
3361
+ }
3362
+
3363
+ return this.each(function () {
3364
+ var $this = $(this),
3365
+ data = $this.data('combodate'),
3366
+ options = typeof option == 'object' && option;
3367
+ if (!data) {
3368
+ $this.data('combodate', (data = new Combodate(this, options)));
3369
+ }
3370
+ if (typeof option == 'string' && typeof data[option] == 'function') {
3371
+ data[option].apply(data, args);
3372
+ }
3373
+ });
3374
+ };
3375
+
3376
+ $.fn.combodate.defaults = {
3377
+ //in this format value stored in original input
3378
+ format: 'DD-MM-YYYY HH:mm',
3379
+ //in this format items in dropdowns are displayed
3380
+ template: 'D / MMM / YYYY H : mm',
3381
+ //initial value, can be `new Date()`
3382
+ value: null,
3383
+ minYear: 1970,
3384
+ maxYear: 2015,
3385
+ minuteStep: 5,
3386
+ secondStep: 1,
3387
+ firstItem: 'empty', //'name', 'empty', 'none'
3388
+ errorClass: null
3389
+ };
3390
+
3391
+ }(window.jQuery));
3392
+ /**
3393
+ Combodate input - dropdown date and time picker.
3394
+ Based on [combodate](http://vitalets.github.com/combodate) plugin. To use it you should manually include [momentjs](http://momentjs.com).
3395
+
3396
+ <script src="js/moment.min.js"></script>
3397
+
3398
+ Allows to input:
3399
+
3400
+ * only date
3401
+ * only time
3402
+ * both date and time
3403
+
3404
+ Please note, that format is taken from momentjs and **not compatible** with bootstrap-datepicker / jquery UI datepicker.
3405
+ Internally value stored as `momentjs` object.
3406
+
3407
+ @class combodate
3408
+ @extends abstractinput
3409
+ @final
3410
+ @since 1.4.0
3411
+ @example
3412
+ <a href="#" id="dob" data-type="combodate" data-pk="1" data-url="/post" data-value="1984-05-15" data-original-title="Select date"></a>
3413
+ <script>
3414
+ $(function(){
3415
+ $('#dob').editable({
3416
+ format: 'YYYY-MM-DD',
3417
+ viewformat: 'DD.MM.YYYY',
3418
+ template: 'D / MMMM / YYYY',
3419
+ combodate: {
3420
+ minYear: 2000,
3421
+ maxYear: 2015,
3422
+ minuteStep: 1
3423
+ }
3424
+ }
3425
+ });
3426
+ });
3427
+ </script>
3428
+ **/
3429
+
3430
+ /*global moment*/
3431
+
3432
+ (function ($) {
3433
+
3434
+ var Constructor = function (options) {
3435
+ this.init('combodate', options, Constructor.defaults);
3436
+
3437
+ //by default viewformat equals to format
3438
+ if(!this.options.viewformat) {
3439
+ this.options.viewformat = this.options.format;
3440
+ }
3441
+
3442
+ //overriding combodate config (as by default jQuery extend() is not recursive)
3443
+ this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, {
3444
+ format: this.options.format,
3445
+ template: this.options.template
3446
+ });
3447
+ };
3448
+
3449
+ $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
3450
+
3451
+ $.extend(Constructor.prototype, {
3452
+ render: function () {
3453
+ this.$input.combodate(this.options.combodate);
3454
+
3455
+ //"clear" link
3456
+ /*
3457
+ if(this.options.clear) {
3458
+ this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
3459
+ e.preventDefault();
3460
+ e.stopPropagation();
3461
+ this.clear();
3462
+ }, this));
3463
+
3464
+ this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
3465
+ }
3466
+ */
3467
+ },
3468
+
3469
+ value2html: function(value, element) {
3470
+ var text = value ? value.format(this.options.viewformat) : '';
3471
+ $(element).text(text);
3472
+ },
3473
+
3474
+ html2value: function(html) {
3475
+ return html ? moment(html, this.options.viewformat) : null;
3476
+ },
3477
+
3478
+ value2str: function(value) {
3479
+ return value ? value.format(this.options.format) : '';
3480
+ },
3481
+
3482
+ str2value: function(str) {
3483
+ return str ? moment(str, this.options.format) : null;
3484
+ },
3485
+
3486
+ value2submit: function(value) {
3487
+ return this.value2str(value);
3488
+ },
3489
+
3490
+ value2input: function(value) {
3491
+ this.$input.combodate('setValue', value);
3492
+ },
3493
+
3494
+ input2value: function() {
3495
+ return this.$input.combodate('getValue', null);
3496
+ },
3497
+
3498
+ activate: function() {
3499
+ this.$input.siblings('.combodate').find('select').eq(0).focus();
3500
+ },
3501
+
3502
+ /*
3503
+ clear: function() {
3504
+ this.$input.data('datepicker').date = null;
3505
+ this.$input.find('.active').removeClass('active');
3506
+ },
3507
+ */
3508
+
3509
+ autosubmit: function() {
3510
+
3511
+ }
3512
+
3513
+ });
3514
+
3515
+ Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
3516
+ /**
3517
+ @property tpl
3518
+ @default <input type="text">
3519
+ **/
3520
+ tpl:'<input type="text">',
3521
+ /**
3522
+ @property inputclass
3523
+ @default null
3524
+ **/
3525
+ inputclass: null,
3526
+ /**
3527
+ Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
3528
+ See list of tokens in [momentjs docs](http://momentjs.com/docs/#/parsing/string-format)
3529
+
3530
+ @property format
3531
+ @type string
3532
+ @default YYYY-MM-DD
3533
+ **/
3534
+ format:'YYYY-MM-DD',
3535
+ /**
3536
+ Format used for displaying date. Also applied when converting date from element's text on init.
3537
+ If not specified equals to `format`.
3538
+
3539
+ @property viewformat
3540
+ @type string
3541
+ @default null
3542
+ **/
3543
+ viewformat: null,
3544
+ /**
3545
+ Template used for displaying dropdowns.
3546
+
3547
+ @property template
3548
+ @type string
3549
+ @default D / MMM / YYYY
3550
+ **/
3551
+ template: 'D / MMM / YYYY',
3552
+ /**
3553
+ Configuration of combodate.
3554
+ Full list of options: http://vitalets.github.com/combodate/#docs
3555
+
3556
+ @property combodate
3557
+ @type object
3558
+ @default null
3559
+ **/
3560
+ combodate: null
3561
+
3562
+ /*
3563
+ (not implemented yet)
3564
+ Text shown as clear date button.
3565
+ If <code>false</code> clear button will not be rendered.
3566
+
3567
+ @property clear
3568
+ @type boolean|string
3569
+ @default 'x clear'
3570
+ */
3571
+ //clear: '&times; clear'
3572
+ });
3573
+
3574
+ $.fn.editabletypes.combodate = Constructor;
3575
+
3576
+ }(window.jQuery));
3577
+
2640
3578
  /*
2641
3579
  Editableform based on Twitter Bootstrap
2642
3580
  */
@@ -2666,7 +3604,7 @@ Editableform based on Twitter Bootstrap
2666
3604
  (function ($) {
2667
3605
 
2668
3606
  //extend methods
2669
- $.extend($.fn.editableContainer.Constructor.prototype, {
3607
+ $.extend($.fn.editableContainer.Popup.prototype, {
2670
3608
  containerName: 'popover',
2671
3609
  //for compatibility with bootstrap <= 2.2.1 (content inserted into <p> instead of directly .popover-content)
2672
3610
  innerCss: $($.fn.popover.defaults.template).find('p').length ? '.popover-content p' : '.popover-content',
@@ -2675,10 +3613,39 @@ Editableform based on Twitter Bootstrap
2675
3613
  $.extend(this.containerOptions, {
2676
3614
  trigger: 'manual',
2677
3615
  selector: false,
2678
- content: ' '
3616
+ content: ' ',
3617
+ template: $.fn.popover.defaults.template
2679
3618
  });
3619
+
3620
+ //as template property is used in inputs, hide it from popover
3621
+ var t;
3622
+ if(this.$element.data('template')) {
3623
+ t = this.$element.data('template');
3624
+ this.$element.removeData('template');
3625
+ }
3626
+
2680
3627
  this.call(this.containerOptions);
2681
- },
3628
+
3629
+ if(t) {
3630
+ //restore data('template')
3631
+ this.$element.data('template', t);
3632
+ }
3633
+ },
3634
+
3635
+ /* show */
3636
+ innerShow: function () {
3637
+ this.call('show');
3638
+ },
3639
+
3640
+ /* hide */
3641
+ innerHide: function () {
3642
+ this.call('hide');
3643
+ },
3644
+
3645
+ /* destroy */
3646
+ innerDestroy: function() {
3647
+ this.call('destroy');
3648
+ },
2682
3649
 
2683
3650
  setContainerOption: function(key, value) {
2684
3651
  this.container().options[key] = value;
@@ -2742,18 +3709,13 @@ Editableform based on Twitter Bootstrap
2742
3709
  }
2743
3710
  });
2744
3711
 
2745
- //defaults
2746
- /*
2747
- $.fn.editableContainer.defaults = $.extend({}, $.fn.popover.defaults, $.fn.editableContainer.defaults, {
2748
-
2749
- });
2750
- */
2751
-
2752
3712
  }(window.jQuery));
2753
3713
  /**
2754
3714
  Bootstrap-datepicker.
2755
- Description and examples: http://vitalets.github.com/bootstrap-datepicker.
2756
- For localization you can include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
3715
+ Description and examples: https://github.com/eternicode/bootstrap-datepicker.
3716
+ For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
3717
+ and set `language` option.
3718
+ Since 1.4.0 date has different appearance in **popup** and **inline** modes.
2757
3719
 
2758
3720
  @class date
2759
3721
  @extends abstractinput
@@ -2777,45 +3739,52 @@ $(function(){
2777
3739
 
2778
3740
  var Date = function (options) {
2779
3741
  this.init('date', options, Date.defaults);
2780
-
2781
- //set popular options directly from settings or data-* attributes
2782
- var directOptions = $.fn.editableutils.sliceObj(this.options, ['format']);
2783
-
2784
- //overriding datepicker config (as by default jQuery extend() is not recursive)
2785
- this.options.datepicker = $.extend({}, Date.defaults.datepicker, directOptions, options.datepicker);
2786
-
2787
- //by default viewformat equals to format
2788
- if(!this.options.viewformat) {
2789
- this.options.viewformat = this.options.datepicker.format;
2790
- }
2791
-
2792
- //language
2793
- this.options.datepicker.language = this.options.datepicker.language || 'en';
2794
-
2795
- //store DPglobal
2796
- this.dpg = $.fn.datepicker.DPGlobal;
2797
-
2798
- //store parsed formats
2799
- this.parsedFormat = this.dpg.parseFormat(this.options.datepicker.format);
2800
- this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
3742
+ this.initPicker(options, Date.defaults);
2801
3743
  };
2802
3744
 
2803
3745
  $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
2804
3746
 
2805
3747
  $.extend(Date.prototype, {
3748
+ initPicker: function(options, defaults) {
3749
+ //'format' is set directly from settings or data-* attributes
3750
+
3751
+ //by default viewformat equals to format
3752
+ if(!this.options.viewformat) {
3753
+ this.options.viewformat = this.options.format;
3754
+ }
3755
+
3756
+ //overriding datepicker config (as by default jQuery extend() is not recursive)
3757
+ //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
3758
+ this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
3759
+ format: this.options.viewformat
3760
+ });
3761
+
3762
+ //language
3763
+ this.options.datepicker.language = this.options.datepicker.language || 'en';
3764
+
3765
+ //store DPglobal
3766
+ this.dpg = $.fn.datepicker.DPGlobal;
3767
+
3768
+ //store parsed formats
3769
+ this.parsedFormat = this.dpg.parseFormat(this.options.format);
3770
+ this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
3771
+ },
3772
+
2806
3773
  render: function () {
2807
- Date.superclass.render.call(this);
2808
3774
  this.$input.datepicker(this.options.datepicker);
2809
-
3775
+
3776
+ //"clear" link
2810
3777
  if(this.options.clear) {
2811
3778
  this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
2812
3779
  e.preventDefault();
2813
3780
  e.stopPropagation();
2814
3781
  this.clear();
2815
3782
  }, this));
2816
- }
3783
+
3784
+ this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
3785
+ }
2817
3786
  },
2818
-
3787
+
2819
3788
  value2html: function(value, element) {
2820
3789
  var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
2821
3790
  Date.superclass.value2html(text, element);
@@ -2869,12 +3838,12 @@ $(function(){
2869
3838
  @property tpl
2870
3839
  @default <div></div>
2871
3840
  **/
2872
- tpl:'<div></div>',
3841
+ tpl:'<div class="editable-date well"></div>',
2873
3842
  /**
2874
3843
  @property inputclass
2875
- @default editable-date well
3844
+ @default null
2876
3845
  **/
2877
- inputclass: 'editable-date well',
3846
+ inputclass: null,
2878
3847
  /**
2879
3848
  Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
2880
3849
  Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>
@@ -2925,6 +3894,84 @@ $(function(){
2925
3894
 
2926
3895
  }(window.jQuery));
2927
3896
 
3897
+ /**
3898
+ Bootstrap datefield input - modification for inline mode.
3899
+ Shows normal <input type="text"> and binds popup datepicker.
3900
+ Automatically shown in inline mode.
3901
+
3902
+ @class datefield
3903
+ @extends date
3904
+
3905
+ @since 1.4.0
3906
+ **/
3907
+ (function ($) {
3908
+
3909
+ var DateField = function (options) {
3910
+ this.init('datefield', options, DateField.defaults);
3911
+ this.initPicker(options, DateField.defaults);
3912
+ };
3913
+
3914
+ $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
3915
+
3916
+ $.extend(DateField.prototype, {
3917
+ render: function () {
3918
+ this.$input = this.$tpl.find('input');
3919
+ this.setClass();
3920
+ this.setAttr('placeholder');
3921
+
3922
+ this.$tpl.datepicker(this.options.datepicker);
3923
+
3924
+ //need to disable original event handlers
3925
+ this.$input.off('focus keydown');
3926
+
3927
+ //update value of datepicker
3928
+ this.$input.keyup($.proxy(function(){
3929
+ this.$tpl.removeData('date');
3930
+ this.$tpl.datepicker('update');
3931
+ }, this));
3932
+
3933
+ },
3934
+
3935
+ value2input: function(value) {
3936
+ this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');
3937
+ this.$tpl.datepicker('update');
3938
+ },
3939
+
3940
+ input2value: function() {
3941
+ return this.html2value(this.$input.val());
3942
+ },
3943
+
3944
+ activate: function() {
3945
+ $.fn.editabletypes.text.prototype.activate.call(this);
3946
+ },
3947
+
3948
+ autosubmit: function() {
3949
+ //reset autosubmit to empty
3950
+ }
3951
+ });
3952
+
3953
+ DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {
3954
+ /**
3955
+ @property tpl
3956
+ **/
3957
+ tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
3958
+ /**
3959
+ @property inputclass
3960
+ @default 'input-small'
3961
+ **/
3962
+ inputclass: 'input-small',
3963
+
3964
+ /* datepicker config */
3965
+ datepicker: {
3966
+ weekStart: 0,
3967
+ startView: 0,
3968
+ autoclose: true
3969
+ }
3970
+ });
3971
+
3972
+ $.fn.editabletypes.datefield = DateField;
3973
+
3974
+ }(window.jQuery));
2928
3975
  /* =========================================================
2929
3976
  * bootstrap-datepicker.js
2930
3977
  * http://www.eyecon.ro/bootstrap-datepicker
@@ -2963,51 +4010,45 @@ $(function(){
2963
4010
  this.element = $(element);
2964
4011
  this.language = options.language||this.element.data('date-language')||"en";
2965
4012
  this.language = this.language in dates ? this.language : "en";
4013
+ this.isRTL = dates[this.language].rtl||false;
2966
4014
  this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
2967
- this.isInline = false;
4015
+ this.isInline = false;
2968
4016
  this.isInput = this.element.is('input');
2969
4017
  this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
2970
4018
  this.hasInput = this.component && this.element.find('input').length;
2971
4019
  if(this.component && this.component.length === 0)
2972
4020
  this.component = false;
2973
4021
 
2974
- if (this.isInput) { //single input
2975
- this.element.on({
2976
- focus: $.proxy(this.show, this),
2977
- keyup: $.proxy(this.update, this),
2978
- keydown: $.proxy(this.keydown, this)
2979
- });
2980
- } else if(this.component && this.hasInput) { //component: input + button
2981
- // For components that are not readonly, allow keyboard nav
2982
- this.element.find('input').on({
2983
- focus: $.proxy(this.show, this),
2984
- keyup: $.proxy(this.update, this),
2985
- keydown: $.proxy(this.keydown, this)
2986
- });
2987
-
2988
- this.component.on('click', $.proxy(this.show, this));
2989
- } else if(this.element.is('div')) { //inline datepicker
2990
- this.isInline = true;
2991
- } else {
2992
- this.element.on('click', $.proxy(this.show, this));
2993
- }
2994
-
2995
- this.picker = $(DPGlobal.template)
2996
- .appendTo(this.isInline ? this.element : 'body')
2997
- .on({
2998
- click: $.proxy(this.click, this),
2999
- mousedown: $.proxy(this.mousedown, this)
3000
- });
3001
-
3002
- if(this.isInline) {
3003
- this.picker.addClass('datepicker-inline');
3004
- } else {
3005
- this.picker.addClass('dropdown-menu');
3006
- }
4022
+ this._attachEvents();
3007
4023
 
4024
+ this.forceParse = true;
4025
+ if ('forceParse' in options) {
4026
+ this.forceParse = options.forceParse;
4027
+ } else if ('dateForceParse' in this.element.data()) {
4028
+ this.forceParse = this.element.data('date-force-parse');
4029
+ }
4030
+
4031
+
4032
+ this.picker = $(DPGlobal.template)
4033
+ .appendTo(this.isInline ? this.element : 'body')
4034
+ .on({
4035
+ click: $.proxy(this.click, this),
4036
+ mousedown: $.proxy(this.mousedown, this)
4037
+ });
4038
+
4039
+ if(this.isInline) {
4040
+ this.picker.addClass('datepicker-inline');
4041
+ } else {
4042
+ this.picker.addClass('datepicker-dropdown dropdown-menu');
4043
+ }
4044
+ if (this.isRTL){
4045
+ this.picker.addClass('datepicker-rtl');
4046
+ this.picker.find('.prev i, .next i')
4047
+ .toggleClass('icon-arrow-left icon-arrow-right');
4048
+ }
3008
4049
  $(document).on('mousedown', function (e) {
3009
4050
  // Clicked outside the datepicker, hide it
3010
- if ($(e.target).closest('.datepicker').length == 0) {
4051
+ if ($(e.target).closest('.datepicker').length === 0) {
3011
4052
  that.hide();
3012
4053
  }
3013
4054
  });
@@ -3026,6 +4067,7 @@ $(function(){
3026
4067
  this.keyboardNavigation = this.element.data('date-keyboard-navigation');
3027
4068
  }
3028
4069
 
4070
+ this.viewMode = this.startViewMode = 0;
3029
4071
  switch(options.startView || this.element.data('date-start-view')){
3030
4072
  case 2:
3031
4073
  case 'decade':
@@ -3035,11 +4077,6 @@ $(function(){
3035
4077
  case 'year':
3036
4078
  this.viewMode = this.startViewMode = 1;
3037
4079
  break;
3038
- case 0:
3039
- case 'month':
3040
- default:
3041
- this.viewMode = this.startViewMode = 0;
3042
- break;
3043
4080
  }
3044
4081
 
3045
4082
  this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false);
@@ -3049,21 +4086,73 @@ $(function(){
3049
4086
  this.weekEnd = ((this.weekStart + 6) % 7);
3050
4087
  this.startDate = -Infinity;
3051
4088
  this.endDate = Infinity;
4089
+ this.daysOfWeekDisabled = [];
3052
4090
  this.setStartDate(options.startDate||this.element.data('date-startdate'));
3053
4091
  this.setEndDate(options.endDate||this.element.data('date-enddate'));
4092
+ this.setDaysOfWeekDisabled(options.daysOfWeekDisabled||this.element.data('date-days-of-week-disabled'));
3054
4093
  this.fillDow();
3055
4094
  this.fillMonths();
3056
4095
  this.update();
3057
4096
  this.showMode();
3058
4097
 
3059
- if(this.isInline) {
3060
- this.show();
3061
- }
4098
+ if(this.isInline) {
4099
+ this.show();
4100
+ }
3062
4101
  };
3063
4102
 
3064
4103
  Datepicker.prototype = {
3065
4104
  constructor: Datepicker,
3066
4105
 
4106
+ _events: [],
4107
+ _attachEvents: function(){
4108
+ this._detachEvents();
4109
+ if (this.isInput) { // single input
4110
+ this._events = [
4111
+ [this.element, {
4112
+ focus: $.proxy(this.show, this),
4113
+ keyup: $.proxy(this.update, this),
4114
+ keydown: $.proxy(this.keydown, this)
4115
+ }]
4116
+ ];
4117
+ }
4118
+ else if (this.component && this.hasInput){ // component: input + button
4119
+ this._events = [
4120
+ // For components that are not readonly, allow keyboard nav
4121
+ [this.element.find('input'), {
4122
+ focus: $.proxy(this.show, this),
4123
+ keyup: $.proxy(this.update, this),
4124
+ keydown: $.proxy(this.keydown, this)
4125
+ }],
4126
+ [this.component, {
4127
+ click: $.proxy(this.show, this)
4128
+ }]
4129
+ ];
4130
+ }
4131
+ else if (this.element.is('div')) { // inline datepicker
4132
+ this.isInline = true;
4133
+ }
4134
+ else {
4135
+ this._events = [
4136
+ [this.element, {
4137
+ click: $.proxy(this.show, this)
4138
+ }]
4139
+ ];
4140
+ }
4141
+ for (var i=0, el, ev; i<this._events.length; i++){
4142
+ el = this._events[i][0];
4143
+ ev = this._events[i][1];
4144
+ el.on(ev);
4145
+ }
4146
+ },
4147
+ _detachEvents: function(){
4148
+ for (var i=0, el, ev; i<this._events.length; i++){
4149
+ el = this._events[i][0];
4150
+ ev = this._events[i][1];
4151
+ el.off(ev);
4152
+ }
4153
+ this._events = [];
4154
+ },
4155
+
3067
4156
  show: function(e) {
3068
4157
  this.picker.show();
3069
4158
  this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
@@ -3081,7 +4170,7 @@ $(function(){
3081
4170
  },
3082
4171
 
3083
4172
  hide: function(e){
3084
- if(this.isInline) return;
4173
+ if(this.isInline) return;
3085
4174
  this.picker.hide();
3086
4175
  $(window).off('resize', this.place);
3087
4176
  this.viewMode = this.startViewMode;
@@ -3089,7 +4178,14 @@ $(function(){
3089
4178
  if (!this.isInput) {
3090
4179
  $(document).off('mousedown', this.hide);
3091
4180
  }
3092
- if (e && e.currentTarget.value)
4181
+
4182
+ if (
4183
+ this.forceParse &&
4184
+ (
4185
+ this.isInput && this.element.val() ||
4186
+ this.hasInput && this.element.find('input').val()
4187
+ )
4188
+ )
3093
4189
  this.setValue();
3094
4190
  this.element.trigger({
3095
4191
  type: 'hide',
@@ -3097,9 +4193,15 @@ $(function(){
3097
4193
  });
3098
4194
  },
3099
4195
 
4196
+ remove: function() {
4197
+ this._detachEvents();
4198
+ this.picker.remove();
4199
+ delete this.element.data().datepicker;
4200
+ },
4201
+
3100
4202
  getDate: function() {
3101
4203
  var d = this.getUTCDate();
3102
- return new Date(d.getTime() + (d.getTimezoneOffset()*60000))
4204
+ return new Date(d.getTime() + (d.getTimezoneOffset()*60000));
3103
4205
  },
3104
4206
 
3105
4207
  getUTCDate: function() {
@@ -3119,18 +4221,19 @@ $(function(){
3119
4221
  var formatted = this.getFormattedDate();
3120
4222
  if (!this.isInput) {
3121
4223
  if (this.component){
3122
- this.element.find('input').prop('value', formatted);
4224
+ this.element.find('input').val(formatted);
3123
4225
  }
3124
4226
  this.element.data('date', formatted);
3125
4227
  } else {
3126
- this.element.prop('value', formatted);
4228
+ this.element.val(formatted);
3127
4229
  }
3128
4230
  },
3129
4231
 
3130
- getFormattedDate: function(format) {
3131
- if(format == undefined) format = this.format;
3132
- return DPGlobal.formatDate(this.date, format, this.language);
3133
- },
4232
+ getFormattedDate: function(format) {
4233
+ if (format === undefined)
4234
+ format = this.format;
4235
+ return DPGlobal.formatDate(this.date, format, this.language);
4236
+ },
3134
4237
 
3135
4238
  setStartDate: function(startDate){
3136
4239
  this.startDate = startDate||-Infinity;
@@ -3150,32 +4253,46 @@ $(function(){
3150
4253
  this.updateNavArrows();
3151
4254
  },
3152
4255
 
4256
+ setDaysOfWeekDisabled: function(daysOfWeekDisabled){
4257
+ this.daysOfWeekDisabled = daysOfWeekDisabled||[];
4258
+ if (!$.isArray(this.daysOfWeekDisabled)) {
4259
+ this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/);
4260
+ }
4261
+ this.daysOfWeekDisabled = $.map(this.daysOfWeekDisabled, function (d) {
4262
+ return parseInt(d, 10);
4263
+ });
4264
+ this.update();
4265
+ this.updateNavArrows();
4266
+ },
4267
+
3153
4268
  place: function(){
3154
- if(this.isInline) return;
4269
+ if(this.isInline) return;
3155
4270
  var zIndex = parseInt(this.element.parents().filter(function() {
3156
4271
  return $(this).css('z-index') != 'auto';
3157
4272
  }).first().css('z-index'))+10;
3158
4273
  var offset = this.component ? this.component.offset() : this.element.offset();
4274
+ var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(true);
3159
4275
  this.picker.css({
3160
- top: offset.top + this.height,
4276
+ top: offset.top + height,
3161
4277
  left: offset.left,
3162
4278
  zIndex: zIndex
3163
4279
  });
3164
4280
  },
3165
4281
 
3166
4282
  update: function(){
3167
- var date, fromArgs = false;
3168
- if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
3169
- date = arguments[0];
3170
- fromArgs = true;
3171
- } else {
3172
- date = this.isInput ? this.element.prop('value') : this.element.data('date') || this.element.find('input').prop('value');
3173
- }
4283
+ var date, fromArgs = false;
4284
+ if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
4285
+ date = arguments[0];
4286
+ fromArgs = true;
4287
+ } else {
4288
+ date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();
4289
+ }
3174
4290
 
3175
4291
  this.date = DPGlobal.parseDate(date, this.format, this.language);
3176
4292
 
3177
- if(fromArgs) this.setValue();
4293
+ if(fromArgs) this.setValue();
3178
4294
 
4295
+ var oldViewDate = this.viewDate;
3179
4296
  if (this.date < this.startDate) {
3180
4297
  this.viewDate = new Date(this.startDate);
3181
4298
  } else if (this.date > this.endDate) {
@@ -3183,12 +4300,19 @@ $(function(){
3183
4300
  } else {
3184
4301
  this.viewDate = new Date(this.date);
3185
4302
  }
4303
+
4304
+ if (oldViewDate && oldViewDate.getTime() != this.viewDate.getTime()){
4305
+ this.element.trigger({
4306
+ type: 'changeDate',
4307
+ date: this.viewDate
4308
+ });
4309
+ }
3186
4310
  this.fill();
3187
4311
  },
3188
4312
 
3189
4313
  fillDow: function(){
3190
- var dowCnt = this.weekStart;
3191
- var html = '<tr>';
4314
+ var dowCnt = this.weekStart,
4315
+ html = '<tr>';
3192
4316
  while (dowCnt < this.weekStart + 7) {
3193
4317
  html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>';
3194
4318
  }
@@ -3197,8 +4321,8 @@ $(function(){
3197
4321
  },
3198
4322
 
3199
4323
  fillMonths: function(){
3200
- var html = '';
3201
- var i = 0
4324
+ var html = '',
4325
+ i = 0;
3202
4326
  while (i < 12) {
3203
4327
  html += '<span class="month">'+dates[this.language].monthsShort[i++]+'</span>';
3204
4328
  }
@@ -3251,7 +4375,8 @@ $(function(){
3251
4375
  if (currentDate && prevMonth.valueOf() == currentDate) {
3252
4376
  clsName += ' active';
3253
4377
  }
3254
- if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate) {
4378
+ if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate ||
4379
+ $.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1) {
3255
4380
  clsName += ' disabled';
3256
4381
  }
3257
4382
  html.push('<td class="day'+clsName+'">'+prevMonth.getUTCDate() + '</td>');
@@ -3392,7 +4517,7 @@ $(function(){
3392
4517
  var year = this.viewDate.getUTCFullYear(),
3393
4518
  month = this.viewDate.getUTCMonth();
3394
4519
  if (target.is('.old')) {
3395
- if (month == 0) {
4520
+ if (month === 0) {
3396
4521
  month = 11;
3397
4522
  year -= 1;
3398
4523
  } else {
@@ -3432,8 +4557,8 @@ $(function(){
3432
4557
  }
3433
4558
  if (element) {
3434
4559
  element.change();
3435
- if (this.autoclose) {
3436
- this.hide();
4560
+ if (this.autoclose && (!which || which == 'date')) {
4561
+ this.hide();
3437
4562
  }
3438
4563
  }
3439
4564
  },
@@ -3579,16 +4704,16 @@ $(function(){
3579
4704
  if (dir) {
3580
4705
  this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir));
3581
4706
  }
3582
- /*
3583
- vitalets: fixing bug of very special conditions:
3584
- jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
3585
- Method show() does not set display css correctly and datepicker is not shown.
3586
- Changed to .css('display', 'block') solve the problem.
3587
- See https://github.com/vitalets/x-editable/issues/37
3588
-
3589
- In jquery 1.7.2+ everything works fine.
3590
- */
3591
- //this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
4707
+ /*
4708
+ vitalets: fixing bug of very special conditions:
4709
+ jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
4710
+ Method show() does not set display css correctly and datepicker is not shown.
4711
+ Changed to .css('display', 'block') solve the problem.
4712
+ See https://github.com/vitalets/x-editable/issues/37
4713
+
4714
+ In jquery 1.7.2+ everything works fine.
4715
+ */
4716
+ //this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
3592
4717
  this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
3593
4718
  this.updateNavArrows();
3594
4719
  }
@@ -3622,7 +4747,7 @@ $(function(){
3622
4747
  monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
3623
4748
  today: "Today"
3624
4749
  }
3625
- }
4750
+ };
3626
4751
 
3627
4752
  var DPGlobal = {
3628
4753
  modes: [
@@ -3642,28 +4767,28 @@ $(function(){
3642
4767
  navStep: 10
3643
4768
  }],
3644
4769
  isLeapYear: function (year) {
3645
- return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
4770
+ return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
3646
4771
  },
3647
4772
  getDaysInMonth: function (year, month) {
3648
- return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
4773
+ return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
3649
4774
  },
3650
- validParts: /dd?|mm?|MM?|yy(?:yy)?/g,
3651
- nonpunctuation: /[^ -\/:-@\[-`{-~\t\n\r]+/g,
4775
+ validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
4776
+ nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
3652
4777
  parseFormat: function(format){
3653
4778
  // IE treats \0 as a string end in inputs (truncating the value),
3654
4779
  // so it's a bad format delimiter, anyway
3655
4780
  var separators = format.replace(this.validParts, '\0').split('\0'),
3656
4781
  parts = format.match(this.validParts);
3657
- if (!separators || !separators.length || !parts || parts.length == 0){
4782
+ if (!separators || !separators.length || !parts || parts.length === 0){
3658
4783
  throw new Error("Invalid date format.");
3659
4784
  }
3660
4785
  return {separators: separators, parts: parts};
3661
4786
  },
3662
4787
  parseDate: function(date, format, language) {
3663
4788
  if (date instanceof Date) return date;
3664
- if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) {
3665
- var part_re = /([-+]\d+)([dmwy])/,
3666
- parts = date.match(/([-+]\d+)([dmwy])/g),
4789
+ if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {
4790
+ var part_re = /([\-+]\d+)([dmwy])/,
4791
+ parts = date.match(/([\-+]\d+)([dmwy])/g),
3667
4792
  part, dir;
3668
4793
  date = new Date();
3669
4794
  for (var i=0; i<parts.length; i++) {
@@ -3708,10 +4833,18 @@ $(function(){
3708
4833
  setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
3709
4834
  setters_map['dd'] = setters_map['d'];
3710
4835
  date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
3711
- if (parts.length == format.parts.length) {
3712
- for (var i=0, cnt = format.parts.length; i < cnt; i++) {
4836
+ var fparts = format.parts.slice();
4837
+ // Remove noop parts
4838
+ if (parts.length != fparts.length) {
4839
+ fparts = $(fparts).filter(function(i,p){
4840
+ return $.inArray(p, setters_order) !== -1;
4841
+ }).toArray();
4842
+ }
4843
+ // Process remainder
4844
+ if (parts.length == fparts.length) {
4845
+ for (var i=0, cnt = fparts.length; i < cnt; i++) {
3713
4846
  val = parseInt(parts[i], 10);
3714
- part = format.parts[i];
4847
+ part = fparts[i];
3715
4848
  if (isNaN(val)) {
3716
4849
  switch(part) {
3717
4850
  case 'MM':
@@ -3736,8 +4869,8 @@ $(function(){
3736
4869
  }
3737
4870
  for (var i=0, s; i<setters_order.length; i++){
3738
4871
  s = setters_order[i];
3739
- if (s in parsed)
3740
- setters_map[s](date, parsed[s])
4872
+ if (s in parsed && !isNaN(parsed[s]))
4873
+ setters_map[s](date, parsed[s]);
3741
4874
  }
3742
4875
  }
3743
4876
  return date;
@@ -3745,6 +4878,8 @@ $(function(){
3745
4878
  formatDate: function(date, format, language){
3746
4879
  var val = {
3747
4880
  d: date.getUTCDate(),
4881
+ D: dates[language].daysShort[date.getUTCDay()],
4882
+ DD: dates[language].days[date.getUTCDay()],
3748
4883
  m: date.getUTCMonth() + 1,
3749
4884
  M: dates[language].monthsShort[date.getUTCMonth()],
3750
4885
  MM: dates[language].months[date.getUTCMonth()],
@@ -3757,7 +4892,7 @@ $(function(){
3757
4892
  seps = $.extend([], format.separators);
3758
4893
  for (var i=0, cnt = format.parts.length; i < cnt; i++) {
3759
4894
  if (seps.length)
3760
- date.push(seps.shift())
4895
+ date.push(seps.shift());
3761
4896
  date.push(val[format.parts[i]]);
3762
4897
  }
3763
4898
  return date.join('');
@@ -3795,7 +4930,7 @@ $(function(){
3795
4930
  '</table>'+
3796
4931
  '</div>'+
3797
4932
  '</div>';
3798
-
3799
- $.fn.datepicker.DPGlobal = DPGlobal;
3800
-
4933
+
4934
+ $.fn.datepicker.DPGlobal = DPGlobal;
4935
+
3801
4936
  }( window.jQuery );