bootstrap-editable-rails 0.0.4 → 0.0.5

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/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