bootstrap-editable-rails 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -89,6 +89,23 @@ def update
89
89
  end
90
90
  ```
91
91
 
92
+ #### Known issue
93
+
94
+ The scaffold above will not work with jQuery 1.9.0 (included in jquery-rails 2.2.0) because of jQuery's bug.
95
+
96
+ https://github.com/jquery/jquery/pull/1142
97
+
98
+ To avoid it, you can do one of the following (and so on).
99
+
100
+ 1. Respond with some value, not empty
101
+
102
+ `format.json { render json: @post } # 200 OK`
103
+
104
+ 2. Not use jQuery 1.9.0
105
+
106
+ `gem 'jquery-rails', '2.1.4'` in Gemfile
107
+
108
+
92
109
 
93
110
  ## Contributing
94
111
 
@@ -17,5 +17,5 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
19
 
20
- gem.add_dependency "railties", "~> 3.1"
20
+ gem.add_dependency "railties", ">= 3.1"
21
21
  end
@@ -1,7 +1,7 @@
1
1
  module Bootstrap
2
2
  module Editable
3
3
  module Rails
4
- VERSION = "0.0.4"
4
+ VERSION = "0.0.5"
5
5
  end
6
6
  end
7
7
  end
@@ -1,4 +1,4 @@
1
- /*! X-editable - v1.4.1
1
+ /*! X-editable - v1.4.3
2
2
  * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
3
3
  * http://github.com/vitalets/x-editable
4
4
  * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
@@ -494,7 +494,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
494
494
  **/
495
495
  success: null,
496
496
  /**
497
- Additional options for ajax request.
497
+ Additional options for submit ajax request.
498
498
  List of values: http://api.jquery.com/jQuery.ajax
499
499
 
500
500
  @property ajaxOptions
@@ -711,7 +711,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
711
711
 
712
712
  $.each(sourceData, function(i, o) {
713
713
  if(o.children) {
714
- result = result.concat(that.itemsByValue(value, o.children));
714
+ result = result.concat(that.itemsByValue(value, o.children, valueProp));
715
715
  } else {
716
716
  /*jslint eqeq: true*/
717
717
  if(isValArray) {
@@ -831,7 +831,8 @@ Applied as jQuery method.
831
831
  }
832
832
  });
833
833
 
834
- //close containers when click outside
834
+ //close containers when click outside
835
+ //(mousedown could be better than click, it closes everything also on drag drop)
835
836
  $(document).on('click.editable', function(e) {
836
837
  var $target = $(e.target), i,
837
838
  exclude_classes = ['.editable-container',
@@ -880,7 +881,7 @@ Applied as jQuery method.
880
881
 
881
882
  /* returns container object */
882
883
  container: function() {
883
- return this.$element.data(this.containerName);
884
+ return this.$element.data(this.containerDataName || this.containerName);
884
885
  },
885
886
 
886
887
  call: function() {
@@ -909,8 +910,8 @@ Applied as jQuery method.
909
910
  @param {Object} event event object
910
911
  @example
911
912
  $('#username').on('shown', function() {
912
- var $tip = $(this).data('editableContainer').tip();
913
- $tip.find('input').val('overwriting value of input..');
913
+ var editable = $(this).data('editable');
914
+ editable.input.$input.val('overwriting value of input..');
914
915
  });
915
916
  **/
916
917
  this.$element.triggerHandler('shown');
@@ -1211,9 +1212,9 @@ Applied as jQuery method.
1211
1212
  Animation speed (inline mode)
1212
1213
  @property anim
1213
1214
  @type string
1214
- @default 'fast'
1215
+ @default false
1215
1216
  **/
1216
- anim: 'fast',
1217
+ anim: false,
1217
1218
 
1218
1219
  /**
1219
1220
  Mode of editable, can be `popup` or `inline`
@@ -1320,7 +1321,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1320
1321
  //name
1321
1322
  this.options.name = this.options.name || this.$element.attr('id');
1322
1323
 
1323
- //create input of specified type. Input will be used for converting value, not in form
1324
+ //create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select)
1325
+ //also we set scope option to have access to element inside input specific callbacks (e. g. source as function)
1326
+ this.options.scope = this.$element[0];
1324
1327
  this.input = $.fn.editableutils.createInput(this.options);
1325
1328
  if(!this.input) {
1326
1329
  return;
@@ -1371,9 +1374,19 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1371
1374
  }
1372
1375
 
1373
1376
  //check conditions for autotext:
1374
- //if value was generated by text or value is empty, no sense to run autotext
1375
- doAutotext = !isValueByText && this.value !== null && this.value !== undefined;
1376
- doAutotext &= (this.options.autotext === 'always') || (this.options.autotext === 'auto' && !this.$element.text().length);
1377
+ switch(this.options.autotext) {
1378
+ case 'always':
1379
+ doAutotext = true;
1380
+ break;
1381
+ case 'auto':
1382
+ //if element text is empty and value is defined and value not generated by text --> run autotext
1383
+ doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText;
1384
+ break;
1385
+ default:
1386
+ doAutotext = false;
1387
+ }
1388
+
1389
+ //depending on autotext run render() or just finilize init
1377
1390
  $.when(doAutotext ? this.render() : true).then($.proxy(function() {
1378
1391
  if(this.options.disabled) {
1379
1392
  this.disable();
@@ -1409,6 +1422,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1409
1422
  this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){
1410
1423
  var $target = $(e.target);
1411
1424
  if(!$target.data('editable')) {
1425
+ //if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user)
1426
+ //see https://github.com/vitalets/x-editable/issues/137
1427
+ if($target.hasClass(this.options.emptyclass)) {
1428
+ $target.empty();
1429
+ }
1412
1430
  $target.editable(this.options).trigger(e);
1413
1431
  }
1414
1432
  }, this));
@@ -1530,7 +1548,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1530
1548
  if(this.options.display === false) {
1531
1549
  return;
1532
1550
  }
1533
-
1551
+
1534
1552
  this.isEmpty = isEmpty !== undefined ? isEmpty : $.trim(this.$element.text()) === '';
1535
1553
 
1536
1554
  //emptytext shown only for enabled
@@ -1751,11 +1769,14 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1751
1769
  return result;
1752
1770
 
1753
1771
  /**
1754
- Returns current values of editable elements. If value is <code>null</code> or <code>undefined</code> it will not be returned
1772
+ Returns current values of editable elements.
1773
+ Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.
1774
+ If value of some editable is `null` or `undefined` it is excluded from result object.
1775
+
1755
1776
  @method getValue()
1756
1777
  @returns {Object} object of element names and values
1757
1778
  @example
1758
- $('#username, #fullname').editable('validate');
1779
+ $('#username, #fullname').editable('getValue');
1759
1780
  // possible result:
1760
1781
  {
1761
1782
  username: "superuser",
@@ -1892,8 +1913,20 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1892
1913
  **/
1893
1914
  autotext: 'auto',
1894
1915
  /**
1895
- Initial value of input. If not set, taken from element's text.
1896
-
1916
+ Initial value of input. If not set, taken from element's text.
1917
+ Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option).
1918
+ For example, to display currency sign:
1919
+ @example
1920
+ <a id="price" data-type="text" data-value="100"></a>
1921
+ <script>
1922
+ $('#price').editable({
1923
+ ...
1924
+ display: function(value) {
1925
+ $(this).text(value + '$');
1926
+ }
1927
+ })
1928
+ </script>
1929
+
1897
1930
  @property value
1898
1931
  @type mixed
1899
1932
  @default element's text
@@ -1904,12 +1937,12 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1904
1937
  If `null`, default input's display used.
1905
1938
  If `false`, no displaying methods will be called, element's text will never change.
1906
1939
  Runs under element's scope.
1907
- _Parameters:_
1940
+ _**Parameters:**_
1908
1941
 
1909
1942
  * `value` current value to be displayed
1910
1943
  * `response` server response (if display called after ajax submit), since 1.4.0
1911
1944
 
1912
- For **inputs with source** (select, checklist) parameters are different:
1945
+ For _inputs with source_ (select, checklist) parameters are different:
1913
1946
 
1914
1947
  * `value` current value to be displayed
1915
1948
  * `sourceData` array of items for current input (e.g. dropdown items)
@@ -1956,10 +1989,12 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1956
1989
  **/
1957
1990
  unsavedclass: 'editable-unsaved',
1958
1991
  /**
1959
- If a css selector is provided, editable will be delegated to the specified targets.
1992
+ If selector is provided, editable will be delegated to the specified targets.
1960
1993
  Usefull for dynamically generated DOM elements.
1961
- **Please note**, that delegated targets can't use `emptytext` and `autotext` options,
1962
- as they are initialized after first click.
1994
+ **Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options,
1995
+ as they actually become editable only after first click.
1996
+ You should manually set class `editable-click` to these elements.
1997
+ Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element:
1963
1998
 
1964
1999
  @property selector
1965
2000
  @type string
@@ -1967,8 +2002,10 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1967
2002
  @default null
1968
2003
  @example
1969
2004
  <div id="user">
1970
- <a href="#" data-name="username" data-type="text" title="Username">awesome</a>
1971
- <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" title="Group">Operator</a>
2005
+ <!-- empty -->
2006
+ <a href="#" data-name="username" data-type="text" class="editable-click editable-empty" data-value="" title="Username">Empty</a>
2007
+ <!-- non-empty -->
2008
+ <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" class="editable-click" title="Group">Operator</a>
1972
2009
  </div>
1973
2010
 
1974
2011
  <script>
@@ -2145,7 +2182,7 @@ To create your own input you can inherit from this class.
2145
2182
  },
2146
2183
 
2147
2184
  setAttr: function(attr) {
2148
- if (this.options[attr]) {
2185
+ if (this.options[attr] !== undefined && this.options[attr] !== null) {
2149
2186
  this.$input.attr(attr, this.options[attr]);
2150
2187
  }
2151
2188
  },
@@ -2172,7 +2209,10 @@ To create your own input you can inherit from this class.
2172
2209
  @type string
2173
2210
  @default input-medium
2174
2211
  **/
2175
- inputclass: 'input-medium'
2212
+ inputclass: 'input-medium',
2213
+ //scope for external methods (e.g. source defined as function)
2214
+ //for internal use only
2215
+ scope: null
2176
2216
  };
2177
2217
 
2178
2218
  $.extend($.fn.editabletypes, {abstractinput: AbstractInput});
@@ -2251,12 +2291,19 @@ List - abstract class for inputs that have source option loaded from js array or
2251
2291
  error.call(this);
2252
2292
  return;
2253
2293
  }
2294
+
2295
+ var source = this.options.source;
2296
+
2297
+ //run source if it function
2298
+ if ($.isFunction(source)) {
2299
+ source = source.call(this.options.scope);
2300
+ }
2254
2301
 
2255
2302
  //loading from url
2256
- if (typeof this.options.source === 'string') {
2303
+ if (typeof source === 'string') {
2257
2304
  //try to get from cache
2258
2305
  if(this.options.sourceCache) {
2259
- var cacheID = this.options.source,
2306
+ var cacheID = source,
2260
2307
  cache;
2261
2308
 
2262
2309
  if (!$(document).data(cacheID)) {
@@ -2289,7 +2336,7 @@ List - abstract class for inputs that have source option loaded from js array or
2289
2336
 
2290
2337
  //loading sourceData from server
2291
2338
  $.ajax({
2292
- url: this.options.source,
2339
+ url: source,
2293
2340
  type: 'get',
2294
2341
  cache: false,
2295
2342
  dataType: 'json',
@@ -2324,12 +2371,8 @@ List - abstract class for inputs that have source option loaded from js array or
2324
2371
  }
2325
2372
  }, this)
2326
2373
  });
2327
- } else { //options as json/array/function
2328
- if ($.isFunction(this.options.source)) {
2329
- this.sourceData = this.makeArray(this.options.source());
2330
- } else {
2331
- this.sourceData = this.makeArray(this.options.source);
2332
- }
2374
+ } else { //options as json/array
2375
+ this.sourceData = this.makeArray(source);
2333
2376
 
2334
2377
  if($.isArray(this.sourceData)) {
2335
2378
  this.doPrepend();
@@ -2346,16 +2389,20 @@ List - abstract class for inputs that have source option loaded from js array or
2346
2389
  }
2347
2390
 
2348
2391
  if(!$.isArray(this.prependData)) {
2392
+ //run prepend if it is function (once)
2393
+ if ($.isFunction(this.options.prepend)) {
2394
+ this.options.prepend = this.options.prepend.call(this.options.scope);
2395
+ }
2396
+
2349
2397
  //try parse json in single quotes
2350
2398
  this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
2399
+
2400
+ //convert prepend from string to object
2351
2401
  if (typeof this.options.prepend === 'string') {
2352
2402
  this.options.prepend = {'': this.options.prepend};
2353
- }
2354
- if (typeof this.options.prepend === 'function') {
2355
- this.prependData = this.makeArray(this.options.prepend());
2356
- } else {
2357
- this.prependData = this.makeArray(this.options.prepend);
2358
2403
  }
2404
+
2405
+ this.prependData = this.makeArray(this.options.prepend);
2359
2406
  }
2360
2407
 
2361
2408
  if($.isArray(this.prependData) && $.isArray(this.sourceData)) {
@@ -2536,8 +2583,20 @@ $(function(){
2536
2583
  if (this.options.clear) {
2537
2584
  this.$clear = $('<span class="editable-clear-x"></span>');
2538
2585
  this.$input.after(this.$clear)
2539
- .css('padding-right', 20)
2540
- .keyup($.proxy(this.toggleClear, this))
2586
+ .css('padding-right', 24)
2587
+ .keyup($.proxy(function(e) {
2588
+ //arrows, enter, tab, etc
2589
+ if(~$.inArray(e.keyCode, [40,38,9,13,27])) {
2590
+ return;
2591
+ }
2592
+
2593
+ clearTimeout(this.t);
2594
+ var that = this;
2595
+ this.t = setTimeout(function() {
2596
+ that.toggleClear(e);
2597
+ }, 100);
2598
+
2599
+ }, this))
2541
2600
  .parent().css('position', 'relative');
2542
2601
 
2543
2602
  this.$clear.click($.proxy(this.clear, this));
@@ -2555,19 +2614,24 @@ $(function(){
2555
2614
  delta = 3;
2556
2615
  }
2557
2616
 
2558
- this.$clear.css({top: delta, right: delta});
2617
+ this.$clear.css({bottom: delta, right: delta});
2559
2618
  }
2560
2619
  },
2561
2620
 
2562
2621
  //show / hide clear button
2563
- toggleClear: function() {
2622
+ toggleClear: function(e) {
2564
2623
  if(!this.$clear) {
2565
2624
  return;
2566
2625
  }
2567
2626
 
2568
- if(this.$input.val().length) {
2627
+ var len = this.$input.val().length,
2628
+ visible = this.$clear.is(':visible');
2629
+
2630
+ if(len && !visible) {
2569
2631
  this.$clear.show();
2570
- } else {
2632
+ }
2633
+
2634
+ if(!len && visible) {
2571
2635
  this.$clear.hide();
2572
2636
  }
2573
2637
  },
@@ -2733,7 +2797,6 @@ $(function(){
2733
2797
  {value: 2, text: 'Blocked'},
2734
2798
  {value: 3, text: 'Deleted'}
2735
2799
  ]
2736
- }
2737
2800
  });
2738
2801
  });
2739
2802
  </script>
@@ -2803,7 +2866,8 @@ $(function(){
2803
2866
 
2804
2867
  $.fn.editabletypes.select = Select;
2805
2868
 
2806
- }(window.jQuery));
2869
+ }(window.jQuery));
2870
+
2807
2871
  /**
2808
2872
  List of checkboxes.
2809
2873
  Internally value stored as javascript array of values.
@@ -2822,7 +2886,6 @@ $(function(){
2822
2886
  {value: 2, text: 'option2'},
2823
2887
  {value: 3, text: 'option3'}
2824
2888
  ]
2825
- }
2826
2889
  });
2827
2890
  });
2828
2891
  </script>
@@ -3132,6 +3195,9 @@ You should manually include select2 distributive:
3132
3195
  <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
3133
3196
  <script src="select2/select2.js"></script>
3134
3197
 
3198
+ **Note:** currently `ajax` source for select2 is not supported, as it's not possible to load it in closed select2 state.
3199
+ The solution is to load source manually and assign statically.
3200
+
3135
3201
  @class select2
3136
3202
  @extends abstractinput
3137
3203
  @since 1.4.1
@@ -3161,14 +3227,14 @@ $(function(){
3161
3227
  options.select2 = options.select2 || {};
3162
3228
 
3163
3229
  var that = this,
3164
- mixin = {
3230
+ mixin = { //mixin to select2 options
3165
3231
  placeholder: options.placeholder
3166
3232
  };
3167
3233
 
3168
3234
  //detect whether it is multi-valued
3169
3235
  this.isMultiple = options.select2.tags || options.select2.multiple;
3170
3236
 
3171
- //if not `tags` mode, we need define init set data from source
3237
+ //if not `tags` mode, we need define initSelection to set data from source
3172
3238
  if(!options.select2.tags) {
3173
3239
  if(options.source) {
3174
3240
  mixin.data = options.source;
@@ -3176,6 +3242,23 @@ $(function(){
3176
3242
 
3177
3243
  //this function can be defaulted in seletc2. See https://github.com/ivaynberg/select2/issues/710
3178
3244
  mixin.initSelection = function (element, callback) {
3245
+ //temp: try update results
3246
+ /*
3247
+ if(options.select2 && options.select2.ajax) {
3248
+ console.log('attached');
3249
+ var original = $(element).data('select2').postprocessResults;
3250
+ console.log(original);
3251
+ $(element).data('select2').postprocessResults = function(data, initial) {
3252
+ console.log('postprocess');
3253
+ // this.element.triggerHandler('loaded', [data]);
3254
+ original.apply(this, arguments);
3255
+ }
3256
+
3257
+ // $(element).on('loaded', function(){console.log('loaded');});
3258
+ $(element).data('select2').updateResults(true);
3259
+ }
3260
+ */
3261
+
3179
3262
  var val = that.str2value(element.val()),
3180
3263
  data = $.fn.editableutils.itemsByValue(val, mixin.data, 'id');
3181
3264
 
@@ -3200,18 +3283,30 @@ $(function(){
3200
3283
  //apply select2
3201
3284
  this.$input.select2(this.options.select2);
3202
3285
 
3286
+ //when data is loaded via ajax, we need to know when it's done
3287
+ if('ajax' in this.options.select2) {
3288
+ /*
3289
+ console.log('attached');
3290
+ var original = this.$input.data('select2').postprocessResults;
3291
+ this.$input.data('select2').postprocessResults = function(data, initial) {
3292
+ this.element.triggerHandler('loaded', [data]);
3293
+ original.apply(this, arguments);
3294
+ }
3295
+ */
3296
+ }
3297
+
3298
+
3203
3299
  //trigger resize of editableform to re-position container in multi-valued mode
3204
3300
  if(this.isMultiple) {
3205
3301
  this.$input.on('change', function() {
3206
3302
  $(this).closest('form').parent().triggerHandler('resize');
3207
3303
  });
3208
- }
3209
-
3210
- },
3304
+ }
3305
+ },
3211
3306
 
3212
3307
  value2html: function(value, element) {
3213
3308
  var text = '', data;
3214
- if(this.$input) { //when submitting form
3309
+ if(this.$input) { //called when submitting form and select2 already exists
3215
3310
  data = this.$input.select2('data');
3216
3311
  } else { //on init (autotext)
3217
3312
  //here select2 instance not created yet and data may be even not loaded.
@@ -3220,6 +3315,8 @@ $(function(){
3220
3315
  data = value;
3221
3316
  } else if(this.options.select2.data) {
3222
3317
  data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id');
3318
+ } else {
3319
+ //if('ajax' in this.options.select2) {
3223
3320
  }
3224
3321
  }
3225
3322
 
@@ -3243,7 +3340,7 @@ $(function(){
3243
3340
  },
3244
3341
 
3245
3342
  value2input: function(value) {
3246
- this.$input.val(value).trigger('change');
3343
+ this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
3247
3344
  },
3248
3345
 
3249
3346
  input2value: function() {
@@ -3268,7 +3365,15 @@ $(function(){
3268
3365
  }
3269
3366
 
3270
3367
  return val;
3271
- }
3368
+ },
3369
+
3370
+ autosubmit: function() {
3371
+ this.$input.on('change', function(e, isInitial){
3372
+ if(!isInitial) {
3373
+ $(this).closest('form').submit();
3374
+ }
3375
+ });
3376
+ }
3272
3377
 
3273
3378
  });
3274
3379
 
@@ -3318,7 +3423,7 @@ $(function(){
3318
3423
 
3319
3424
  }(window.jQuery));
3320
3425
  /**
3321
- * Combodate - 1.0.1
3426
+ * Combodate - 1.0.2
3322
3427
  * Dropdown date and time picker.
3323
3428
  * Converts text input into dropdowns to pick day, month, year, hour, minute and second.
3324
3429
  * Uses momentjs as datetime library http://momentjs.com.
@@ -3421,9 +3526,13 @@ $(function(){
3421
3526
  Initialize items of combos. Handles `firstItem` option
3422
3527
  */
3423
3528
  initItems: function(key) {
3424
- var values = [];
3529
+ var values = [],
3530
+ relTime;
3531
+
3425
3532
  if(this.options.firstItem === 'name') {
3426
- var header = typeof moment.relativeTime[key] === 'function' ? moment.relativeTime[key](1, true, key, false) : moment.relativeTime[key];
3533
+ //need both to suuport moment ver < 2 and >= 2
3534
+ relTime = moment.relativeTime || moment.langData()._relativeTime;
3535
+ var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];
3427
3536
  //take last entry (see momentjs lang files structure)
3428
3537
  header = header.split(' ').reverse()[0];
3429
3538
  values.push(['', header]);
@@ -3469,9 +3578,9 @@ $(function(){
3469
3578
 
3470
3579
  for(i=0; i<=11; i++) {
3471
3580
  if(longNames) {
3472
- name = moment.months[i];
3581
+ name = moment().month(i).format('MMMM');
3473
3582
  } else if(shortNames) {
3474
- name = moment.monthsShort[i];
3583
+ name = moment().month(i).format('MMM');
3475
3584
  } else if(twoDigit) {
3476
3585
  name = this.leadZero(i+1);
3477
3586
  } else {
@@ -3717,7 +3826,7 @@ $(function(){
3717
3826
  }(window.jQuery));
3718
3827
  /**
3719
3828
  Combodate input - dropdown date and time picker.
3720
- Based on [combodate](http://vitalets.github.com/combodate) plugin. To use it you should manually include [momentjs](http://momentjs.com).
3829
+ Based on [combodate](http://vitalets.github.com/combodate) plugin (included). To use it you should manually include [momentjs](http://momentjs.com).
3721
3830
 
3722
3831
  <script src="js/moment.min.js"></script>
3723
3832
 
@@ -3765,6 +3874,9 @@ $(function(){
3765
3874
  this.options.viewformat = this.options.format;
3766
3875
  }
3767
3876
 
3877
+ //try parse combodate config defined as json string in data-combodate
3878
+ options.combodate = $.fn.editableutils.tryParseJson(options.combodate, true);
3879
+
3768
3880
  //overriding combodate config (as by default jQuery extend() is not recursive)
3769
3881
  this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, {
3770
3882
  format: this.options.format,
@@ -4149,12 +4261,24 @@ $(function(){
4149
4261
  },
4150
4262
 
4151
4263
  autosubmit: function() {
4264
+ this.$input.on('mouseup', '.day', function(e){
4265
+ if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) {
4266
+ return;
4267
+ }
4268
+ var $form = $(this).closest('form');
4269
+ setTimeout(function() {
4270
+ $form.submit();
4271
+ }, 200);
4272
+ });
4273
+ //changedate is not suitable as it triggered when showing datepicker. see #149
4274
+ /*
4152
4275
  this.$input.on('changeDate', function(e){
4153
4276
  var $form = $(this).closest('form');
4154
4277
  setTimeout(function() {
4155
4278
  $form.submit();
4156
4279
  }, 200);
4157
4280
  });
4281
+ */
4158
4282
  }
4159
4283
 
4160
4284
  });
@@ -4197,12 +4321,14 @@ $(function(){
4197
4321
  @default {
4198
4322
  weekStart: 0,
4199
4323
  startView: 0,
4324
+ minViewMode: 0,
4200
4325
  autoclose: false
4201
4326
  }
4202
4327
  **/
4203
4328
  datepicker:{
4204
4329
  weekStart: 0,
4205
4330
  startView: 0,
4331
+ minViewMode: 0,
4206
4332
  autoclose: false
4207
4333
  },
4208
4334
  /**
@@ -4291,6 +4417,7 @@ Automatically shown in inline mode.
4291
4417
  datepicker: {
4292
4418
  weekStart: 0,
4293
4419
  startView: 0,
4420
+ minViewMode: 0,
4294
4421
  autoclose: true
4295
4422
  }
4296
4423
  });
@@ -4335,9 +4462,10 @@ Automatically shown in inline mode.
4335
4462
 
4336
4463
  this.element = $(element);
4337
4464
  this.language = options.language||this.element.data('date-language')||"en";
4465
+ this.language = this.language in dates ? this.language : this.language.split('-')[0]; //Check if "de-DE" style date is available, if not language should fallback to 2 letter code eg "de"
4338
4466
  this.language = this.language in dates ? this.language : "en";
4339
4467
  this.isRTL = dates[this.language].rtl||false;
4340
- this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
4468
+ this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||dates[this.language].format||'mm/dd/yyyy');
4341
4469
  this.isInline = false;
4342
4470
  this.isInput = this.element.is('input');
4343
4471
  this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
@@ -4353,7 +4481,7 @@ Automatically shown in inline mode.
4353
4481
  } else if ('dateForceParse' in this.element.data()) {
4354
4482
  this.forceParse = this.element.data('date-force-parse');
4355
4483
  }
4356
-
4484
+
4357
4485
 
4358
4486
  this.picker = $(DPGlobal.template)
4359
4487
  .appendTo(this.isInline ? this.element : 'body')
@@ -4374,7 +4502,7 @@ Automatically shown in inline mode.
4374
4502
  }
4375
4503
  $(document).on('mousedown', function (e) {
4376
4504
  // Clicked outside the datepicker, hide it
4377
- if ($(e.target).closest('.datepicker').length === 0) {
4505
+ if ($(e.target).closest('.datepicker.datepicker-inline, .datepicker.datepicker-dropdown').length === 0) {
4378
4506
  that.hide();
4379
4507
  }
4380
4508
  });
@@ -4405,9 +4533,38 @@ Automatically shown in inline mode.
4405
4533
  break;
4406
4534
  }
4407
4535
 
4536
+ this.minViewMode = options.minViewMode||this.element.data('date-min-view-mode')||0;
4537
+ if (typeof this.minViewMode === 'string') {
4538
+ switch (this.minViewMode) {
4539
+ case 'months':
4540
+ this.minViewMode = 1;
4541
+ break;
4542
+ case 'years':
4543
+ this.minViewMode = 2;
4544
+ break;
4545
+ default:
4546
+ this.minViewMode = 0;
4547
+ break;
4548
+ }
4549
+ }
4550
+
4551
+ this.viewMode = this.startViewMode = Math.max(this.startViewMode, this.minViewMode);
4552
+
4408
4553
  this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false);
4409
4554
  this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false);
4410
4555
 
4556
+ this.calendarWeeks = false;
4557
+ if ('calendarWeeks' in options) {
4558
+ this.calendarWeeks = options.calendarWeeks;
4559
+ } else if ('dateCalendarWeeks' in this.element.data()) {
4560
+ this.calendarWeeks = this.element.data('date-calendar-weeks');
4561
+ }
4562
+ if (this.calendarWeeks)
4563
+ this.picker.find('tfoot th.today')
4564
+ .attr('colspan', function(i, val){
4565
+ return parseInt(val) + 1;
4566
+ });
4567
+
4411
4568
  this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7);
4412
4569
  this.weekEnd = ((this.weekStart + 6) % 7);
4413
4570
  this.startDate = -Infinity;
@@ -4497,6 +4654,7 @@ Automatically shown in inline mode.
4497
4654
 
4498
4655
  hide: function(e){
4499
4656
  if(this.isInline) return;
4657
+ if (!this.picker.is(':visible')) return;
4500
4658
  this.picker.hide();
4501
4659
  $(window).off('resize', this.place);
4502
4660
  this.viewMode = this.startViewMode;
@@ -4618,7 +4776,6 @@ Automatically shown in inline mode.
4618
4776
 
4619
4777
  if(fromArgs) this.setValue();
4620
4778
 
4621
- var oldViewDate = this.viewDate;
4622
4779
  if (this.date < this.startDate) {
4623
4780
  this.viewDate = new Date(this.startDate);
4624
4781
  } else if (this.date > this.endDate) {
@@ -4626,19 +4783,17 @@ Automatically shown in inline mode.
4626
4783
  } else {
4627
4784
  this.viewDate = new Date(this.date);
4628
4785
  }
4629
-
4630
- if (oldViewDate && oldViewDate.getTime() != this.viewDate.getTime()){
4631
- this.element.trigger({
4632
- type: 'changeDate',
4633
- date: this.viewDate
4634
- });
4635
- }
4636
4786
  this.fill();
4637
4787
  },
4638
4788
 
4639
4789
  fillDow: function(){
4640
4790
  var dowCnt = this.weekStart,
4641
4791
  html = '<tr>';
4792
+ if(this.calendarWeeks){
4793
+ var cell = '<th class="cw">&nbsp;</th>';
4794
+ html += cell;
4795
+ this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
4796
+ }
4642
4797
  while (dowCnt < this.weekStart + 7) {
4643
4798
  html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>';
4644
4799
  }
@@ -4665,7 +4820,7 @@ Automatically shown in inline mode.
4665
4820
  endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
4666
4821
  currentDate = this.date && this.date.valueOf(),
4667
4822
  today = new Date();
4668
- this.picker.find('.datepicker-days thead th:eq(1)')
4823
+ this.picker.find('.datepicker-days thead th.switch')
4669
4824
  .text(dates[this.language].months[month]+' '+year);
4670
4825
  this.picker.find('tfoot th.today')
4671
4826
  .text(dates[this.language].today)
@@ -4684,6 +4839,21 @@ Automatically shown in inline mode.
4684
4839
  while(prevMonth.valueOf() < nextMonth) {
4685
4840
  if (prevMonth.getUTCDay() == this.weekStart) {
4686
4841
  html.push('<tr>');
4842
+ if(this.calendarWeeks){
4843
+ // ISO 8601: First week contains first thursday.
4844
+ // ISO also states week starts on Monday, but we can be more abstract here.
4845
+ var
4846
+ // Start of current week: based on weekstart/current date
4847
+ ws = new Date(+prevMonth + (this.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
4848
+ // Thursday of this week
4849
+ th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
4850
+ // First Thursday of year, year from thursday
4851
+ yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),
4852
+ // Calendar week: ms between thursdays, div ms per day, div 7 days
4853
+ calWeek = (th - yth) / 864e5 / 7 + 1;
4854
+ html.push('<td class="cw">'+ calWeek +'</td>');
4855
+
4856
+ }
4687
4857
  }
4688
4858
  clsName = '';
4689
4859
  if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
@@ -4819,19 +4989,29 @@ Automatically shown in inline mode.
4819
4989
  if (!target.is('.disabled')) {
4820
4990
  this.viewDate.setUTCDate(1);
4821
4991
  if (target.is('.month')) {
4992
+ var day = 1;
4822
4993
  var month = target.parent().find('span').index(target);
4994
+ var year = this.viewDate.getUTCFullYear();
4823
4995
  this.viewDate.setUTCMonth(month);
4824
4996
  this.element.trigger({
4825
4997
  type: 'changeMonth',
4826
4998
  date: this.viewDate
4827
4999
  });
5000
+ if ( this.minViewMode == 1 ) {
5001
+ this._setDate(UTCDate(year, month, day,0,0,0,0));
5002
+ }
4828
5003
  } else {
4829
5004
  var year = parseInt(target.text(), 10)||0;
5005
+ var day = 1;
5006
+ var month = 0;
4830
5007
  this.viewDate.setUTCFullYear(year);
4831
5008
  this.element.trigger({
4832
5009
  type: 'changeYear',
4833
5010
  date: this.viewDate
4834
5011
  });
5012
+ if ( this.minViewMode == 2 ) {
5013
+ this._setDate(UTCDate(year, month, day,0,0,0,0));
5014
+ }
4835
5015
  }
4836
5016
  this.showMode(-1);
4837
5017
  this.fill();
@@ -5028,7 +5208,7 @@ Automatically shown in inline mode.
5028
5208
 
5029
5209
  showMode: function(dir) {
5030
5210
  if (dir) {
5031
- this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir));
5211
+ this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
5032
5212
  }
5033
5213
  /*
5034
5214
  vitalets: fixing bug of very special conditions:
@@ -5288,7 +5468,6 @@ $(function(){
5288
5468
  {value: 'us', text: 'United States'},
5289
5469
  {value: 'ru', text: 'Russia'}
5290
5470
  ]
5291
- }
5292
5471
  });
5293
5472
  });
5294
5473
  </script>
@@ -5320,8 +5499,11 @@ $(function(){
5320
5499
  //apply typeahead
5321
5500
  this.$input.typeahead(this.options.typeahead);
5322
5501
 
5323
- //attach own render method
5324
- this.$input.data('typeahead').render = $.proxy(this.typeaheadRender, this.$input.data('typeahead'));
5502
+ //patch some methods in typeahead
5503
+ var ta = this.$input.data('typeahead');
5504
+ ta.render = $.proxy(this.typeaheadRender, ta);
5505
+ ta.select = $.proxy(this.typeaheadSelect, ta);
5506
+ ta.move = $.proxy(this.typeaheadMove, ta);
5325
5507
 
5326
5508
  this.renderClear();
5327
5509
  this.setClass();
@@ -5400,7 +5582,7 @@ $(function(){
5400
5582
  /*
5401
5583
  Typeahead option methods used as defaults
5402
5584
  */
5403
- /*jshint eqeqeq:false, curly: false, laxcomma: true*/
5585
+ /*jshint eqeqeq:false, curly: false, laxcomma: true, asi: true*/
5404
5586
  matcher: function (item) {
5405
5587
  return $.fn.typeahead.Constructor.prototype.matcher.call(this, item.text);
5406
5588
  },
@@ -5424,7 +5606,6 @@ $(function(){
5424
5606
  return $.fn.typeahead.Constructor.prototype.highlighter.call(this, item.text);
5425
5607
  },
5426
5608
  updater: function (item) {
5427
- item = this.$menu.find('.active').data('item');
5428
5609
  this.$element.data('value', item.value);
5429
5610
  return item.text;
5430
5611
  },
@@ -5435,7 +5616,7 @@ $(function(){
5435
5616
  There are a lot of disscussion in bootstrap repo on this point and still no result.
5436
5617
  See https://github.com/twitter/bootstrap/issues/5967
5437
5618
 
5438
- This function just store item in via jQuery data() method instead of attr('data-value')
5619
+ This function just store item via jQuery data() method instead of attr('data-value')
5439
5620
  */
5440
5621
  typeaheadRender: function (items) {
5441
5622
  var that = this;
@@ -5447,11 +5628,57 @@ $(function(){
5447
5628
  return i[0];
5448
5629
  });
5449
5630
 
5450
- items.first().addClass('active');
5631
+ //add option to disable autoselect of first line
5632
+ //see https://github.com/twitter/bootstrap/pull/4164
5633
+ if (this.options.autoSelect) {
5634
+ items.first().addClass('active');
5635
+ }
5451
5636
  this.$menu.html(items);
5452
5637
  return this;
5638
+ },
5639
+
5640
+ //add option to disable autoselect of first line
5641
+ //see https://github.com/twitter/bootstrap/pull/4164
5642
+ typeaheadSelect: function () {
5643
+ var val = this.$menu.find('.active').data('item')
5644
+ if(this.options.autoSelect || val){
5645
+ this.$element
5646
+ .val(this.updater(val))
5647
+ .change()
5648
+ }
5649
+ return this.hide()
5650
+ },
5651
+
5652
+ /*
5653
+ if autoSelect = false and nothing matched we need extra press onEnter that is not convinient.
5654
+ This patch fixes it.
5655
+ */
5656
+ typeaheadMove: function (e) {
5657
+ if (!this.shown) return
5658
+
5659
+ switch(e.keyCode) {
5660
+ case 9: // tab
5661
+ case 13: // enter
5662
+ case 27: // escape
5663
+ if (!this.$menu.find('.active').length) return
5664
+ e.preventDefault()
5665
+ break
5666
+
5667
+ case 38: // up arrow
5668
+ e.preventDefault()
5669
+ this.prev()
5670
+ break
5671
+
5672
+ case 40: // down arrow
5673
+ e.preventDefault()
5674
+ this.next()
5675
+ break
5676
+ }
5677
+
5678
+ e.stopPropagation()
5453
5679
  }
5454
- /*jshint eqeqeq: true, curly: true, laxcomma: false*/
5680
+
5681
+ /*jshint eqeqeq: true, curly: true, laxcomma: false, asi: false*/
5455
5682
 
5456
5683
  });
5457
5684