bootstrap-x-editable-rails 1.4.5 → 1.4.5.1

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.
@@ -1,4 +1,4 @@
1
- /*! X-editable - v1.4.4
1
+ /*! X-editable - v1.4.5
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 */
@@ -65,6 +65,10 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
65
65
  //show loading state
66
66
  this.showLoading();
67
67
 
68
+ //flag showing is form now saving value to server.
69
+ //It is needed to wait when closing form.
70
+ this.isSaving = false;
71
+
68
72
  /**
69
73
  Fired when rendering starts
70
74
  @event rendering
@@ -217,31 +221,38 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
217
221
  return;
218
222
  }
219
223
 
224
+ //convert value for submitting to server
225
+ var submitValue = this.input.value2submit(newValue);
226
+
227
+ this.isSaving = true;
228
+
220
229
  //sending data to server
221
- $.when(this.save(newValue))
230
+ $.when(this.save(submitValue))
222
231
  .done($.proxy(function(response) {
232
+ this.isSaving = false;
233
+
223
234
  //run success callback
224
235
  var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
225
-
236
+
226
237
  //if success callback returns false --> keep form open and do not activate input
227
238
  if(res === false) {
228
239
  this.error(false);
229
240
  this.showForm(false);
230
241
  return;
231
- }
232
-
242
+ }
243
+
233
244
  //if success callback returns string --> keep form open, show error and activate input
234
245
  if(typeof res === 'string') {
235
246
  this.error(res);
236
247
  this.showForm();
237
248
  return;
238
- }
239
-
249
+ }
250
+
240
251
  //if success callback returns object like {newValue: <something>} --> use that value instead of submitted
241
252
  //it is usefull if you want to chnage value in url-function
242
253
  if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
243
254
  newValue = res.newValue;
244
- }
255
+ }
245
256
 
246
257
  //clear error message
247
258
  this.error(false);
@@ -251,37 +262,42 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
251
262
  @event save
252
263
  @param {Object} event event object
253
264
  @param {Object} params additional params
254
- @param {mixed} params.newValue submitted value
265
+ @param {mixed} params.newValue raw new value
266
+ @param {mixed} params.submitValue submitted value as string
255
267
  @param {Object} params.response ajax response
256
268
 
257
269
  @example
258
270
  $('#form-div').on('save'), function(e, params){
259
271
  if(params.newValue === 'username') {...}
260
- });
261
- **/
262
- this.$div.triggerHandler('save', {newValue: newValue, response: response});
272
+ });
273
+ **/
274
+ this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response});
263
275
  }, this))
264
276
  .fail($.proxy(function(xhr) {
277
+ this.isSaving = false;
278
+
265
279
  var msg;
266
280
  if(typeof this.options.error === 'function') {
267
281
  msg = this.options.error.call(this.options.scope, xhr, newValue);
268
282
  } else {
269
283
  msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';
270
284
  }
271
-
285
+
272
286
  this.error(msg);
273
287
  this.showForm();
274
288
  }, this));
275
289
  },
276
290
 
277
- save: function(newValue) {
278
- //convert value for submitting to server
279
- var submitValue = this.input.value2submit(newValue);
280
-
291
+ save: function(submitValue) {
281
292
  //try parse composite pk defined as json string in data-pk
282
293
  this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true);
283
294
 
284
295
  var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
296
+ /*
297
+ send on server in following cases:
298
+ 1. url is function
299
+ 2. url is string AND (pk defined OR send option = always)
300
+ */
285
301
  send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),
286
302
  params;
287
303
 
@@ -816,6 +832,27 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
816
832
  $.error('Unknown type: '+ type);
817
833
  return false;
818
834
  }
835
+ },
836
+
837
+ //see http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr
838
+ supportsTransitions: function () {
839
+ var b = document.body || document.documentElement,
840
+ s = b.style,
841
+ p = 'transition',
842
+ v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
843
+
844
+ if(typeof s[p] === 'string') {
845
+ return true;
846
+ }
847
+
848
+ // Tests for vendor specific prop
849
+ p = p.charAt(0).toUpperCase() + p.substr(1);
850
+ for(var i=0; i<v.length; i++) {
851
+ if(typeof s[v[i] + p] === 'string') {
852
+ return true;
853
+ }
854
+ }
855
+ return false;
819
856
  }
820
857
 
821
858
  };
@@ -856,6 +893,9 @@ Applied as jQuery method.
856
893
  this.formOptions.scope = this.$element[0];
857
894
 
858
895
  this.initContainer();
896
+
897
+ //flag to hide container, when saving value will finish
898
+ this.delayedHide = false;
859
899
 
860
900
  //bind 'destroyed' listener to destroy container when element is removed from dom
861
901
  this.$element.on('destroyed', $.proxy(function(){
@@ -960,7 +1000,14 @@ Applied as jQuery method.
960
1000
  save: $.proxy(this.save, this), //click on submit button (value changed)
961
1001
  nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)
962
1002
  cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button
963
- show: $.proxy(this.setPosition, this), //re-position container every time form is shown (occurs each time after loading state)
1003
+ show: $.proxy(function() {
1004
+ if(this.delayedHide) {
1005
+ this.hide(this.delayedHide.reason);
1006
+ this.delayedHide = false;
1007
+ } else {
1008
+ this.setPosition();
1009
+ }
1010
+ }, this), //re-position container every time form is shown (occurs each time after loading state)
964
1011
  rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
965
1012
  resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
966
1013
  rendered: $.proxy(function(){
@@ -1004,11 +1051,11 @@ Applied as jQuery method.
1004
1051
 
1005
1052
  /*
1006
1053
  Currently, form is re-rendered on every show.
1007
- The main reason is that we dont know, what container will do with content when closed:
1008
- remove(), detach() or just hide().
1054
+ The main reason is that we dont know, what will container do with content when closed:
1055
+ remove(), detach() or just hide() - it depends on container.
1009
1056
 
1010
1057
  Detaching form itself before hide and re-insert before show is good solution,
1011
- but visually it looks ugly, as container changes size before hide.
1058
+ but visually it looks ugly --> container changes size before hide.
1012
1059
  */
1013
1060
 
1014
1061
  //if form already exist - delete previous data
@@ -1041,10 +1088,18 @@ Applied as jQuery method.
1041
1088
  return;
1042
1089
  }
1043
1090
 
1091
+ //if form is saving value, schedule hide
1092
+ if(this.$form.data('editableform').isSaving) {
1093
+ this.delayedHide = {reason: reason};
1094
+ return;
1095
+ } else {
1096
+ this.delayedHide = false;
1097
+ }
1098
+
1044
1099
  this.$element.removeClass('editable-open');
1045
1100
  this.innerHide();
1046
-
1047
- /**
1101
+
1102
+ /**
1048
1103
  Fired when container was hidden. It occurs on both save or cancel.
1049
1104
  **Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.
1050
1105
  The workaround is to check `arguments.length` that is always `2` for x-editable.
@@ -1058,20 +1113,20 @@ Applied as jQuery method.
1058
1113
  //auto-open next editable
1059
1114
  $(this).closest('tr').next().find('.editable').editable('show');
1060
1115
  }
1061
- });
1062
- **/
1116
+ });
1117
+ **/
1063
1118
  this.$element.triggerHandler('hidden', reason || 'manual');
1064
1119
  },
1065
-
1120
+
1066
1121
  /* internal show method. To be overwritten in child classes */
1067
1122
  innerShow: function () {
1068
1123
 
1069
1124
  },
1070
-
1125
+
1071
1126
  /* internal hide method. To be overwritten in child classes */
1072
1127
  innerHide: function () {
1073
-
1074
- },
1128
+
1129
+ },
1075
1130
 
1076
1131
  /**
1077
1132
  Toggles container visibility (show / hide)
@@ -1116,7 +1171,7 @@ Applied as jQuery method.
1116
1171
  **/
1117
1172
  this.$element.triggerHandler('save', params);
1118
1173
 
1119
- //hide must be after trigger, as saving value may require methods od plugin, applied to input
1174
+ //hide must be after trigger, as saving value may require methods of plugin, applied to input
1120
1175
  this.hide('save');
1121
1176
  },
1122
1177
 
@@ -1276,7 +1331,7 @@ Applied as jQuery method.
1276
1331
  onblur: 'cancel',
1277
1332
 
1278
1333
  /**
1279
- Animation speed (inline mode)
1334
+ Animation speed (inline mode only)
1280
1335
  @property anim
1281
1336
  @type string
1282
1337
  @default false
@@ -1380,6 +1435,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1380
1435
  } else {
1381
1436
  this.init();
1382
1437
  }
1438
+
1439
+ //check for transition support
1440
+ if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
1441
+ this.options.highlight = false;
1442
+ }
1383
1443
  };
1384
1444
 
1385
1445
  Editable.prototype = {
@@ -1424,25 +1484,33 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1424
1484
  if(this.options.toggle !== 'manual') {
1425
1485
  this.$element.addClass('editable-click');
1426
1486
  this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
1427
- //prevent following link
1428
- e.preventDefault();
1487
+ //prevent following link if editable enabled
1488
+ if(!this.options.disabled) {
1489
+ e.preventDefault();
1490
+ }
1429
1491
 
1430
1492
  //stop propagation not required because in document click handler it checks event target
1431
1493
  //e.stopPropagation();
1432
1494
 
1433
1495
  if(this.options.toggle === 'mouseenter') {
1434
1496
  //for hover only show container
1435
- this.show();
1497
+ this.show();
1436
1498
  } else {
1437
1499
  //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
1438
1500
  var closeAll = (this.options.toggle !== 'click');
1439
1501
  this.toggle(closeAll);
1440
- }
1502
+ }
1441
1503
  }, this));
1442
1504
  } else {
1443
1505
  this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
1444
1506
  }
1445
1507
 
1508
+ //if display is function it's far more convinient to have autotext = always to render correctly on init
1509
+ //see https://github.com/vitalets/x-editable-yii/issues/34
1510
+ if(typeof this.options.display === 'function') {
1511
+ this.options.autotext = 'always';
1512
+ }
1513
+
1446
1514
  //check conditions for autotext:
1447
1515
  switch(this.options.autotext) {
1448
1516
  case 'always':
@@ -1621,12 +1689,29 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1621
1689
  return;
1622
1690
  }
1623
1691
 
1624
- this.isEmpty = isEmpty !== undefined ? isEmpty : $.trim(this.$element.text()) === '';
1692
+ /*
1693
+ isEmpty may be set directly as param of method.
1694
+ It is required when we enable/disable field and can't rely on content
1695
+ as node content is text: "Empty" that is not empty %)
1696
+ */
1697
+ if(isEmpty !== undefined) {
1698
+ this.isEmpty = isEmpty;
1699
+ } else {
1700
+ //detect empty
1701
+ if($.trim(this.$element.html()) === '') {
1702
+ this.isEmpty = true;
1703
+ } else if($.trim(this.$element.text()) !== '') {
1704
+ this.isEmpty = false;
1705
+ } else {
1706
+ //e.g. '<img>'
1707
+ this.isEmpty = !this.$element.height() || !this.$element.width();
1708
+ }
1709
+ }
1625
1710
 
1626
1711
  //emptytext shown only for enabled
1627
1712
  if(!this.options.disabled) {
1628
1713
  if (this.isEmpty) {
1629
- this.$element.text(this.options.emptytext);
1714
+ this.$element.html(this.options.emptytext);
1630
1715
  if(this.options.emptyclass) {
1631
1716
  this.$element.addClass(this.options.emptyclass);
1632
1717
  }
@@ -1721,6 +1806,21 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1721
1806
  }
1722
1807
  }
1723
1808
 
1809
+ //highlight when saving
1810
+ if(this.options.highlight) {
1811
+ var $e = this.$element,
1812
+ $bgColor = $e.css('background-color');
1813
+
1814
+ $e.css('background-color', this.options.highlight);
1815
+ setTimeout(function(){
1816
+ $e.css('background-color', $bgColor);
1817
+ $e.addClass('editable-bg-transition');
1818
+ setTimeout(function(){
1819
+ $e.removeClass('editable-bg-transition');
1820
+ }, 1700);
1821
+ }, 0);
1822
+ }
1823
+
1724
1824
  //set new value
1725
1825
  this.setValue(params.newValue, false, params.response);
1726
1826
 
@@ -1787,6 +1887,8 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1787
1887
  if(this.container) {
1788
1888
  this.container.destroy();
1789
1889
  }
1890
+
1891
+ this.input.destroy();
1790
1892
 
1791
1893
  if(this.options.toggle !== 'manual') {
1792
1894
  this.$element.removeClass('editable-click');
@@ -1844,28 +1946,37 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1844
1946
  /**
1845
1947
  Returns current values of editable elements.
1846
1948
  Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.
1847
- If value of some editable is `null` or `undefined` it is excluded from result object.
1949
+ If value of some editable is `null` or `undefined` it is excluded from result object.
1950
+ When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object.
1848
1951
 
1849
1952
  @method getValue()
1953
+ @param {bool} isSingle whether to return just value of single element
1850
1954
  @returns {Object} object of element names and values
1851
1955
  @example
1852
1956
  $('#username, #fullname').editable('getValue');
1853
- // possible result:
1957
+ //result:
1854
1958
  {
1855
1959
  username: "superuser",
1856
1960
  fullname: "John"
1857
1961
  }
1962
+ //isSingle = true
1963
+ $('#username').editable('getValue', true);
1964
+ //result "superuser"
1858
1965
  **/
1859
1966
  case 'getValue':
1860
- this.each(function () {
1861
- var $this = $(this), data = $this.data(datakey);
1862
- if (data && data.value !== undefined && data.value !== null) {
1863
- result[data.options.name] = data.input.value2submit(data.value);
1864
- }
1865
- });
1967
+ if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
1968
+ result = this.eq(0).data(datakey).value;
1969
+ } else {
1970
+ this.each(function () {
1971
+ var $this = $(this), data = $this.data(datakey);
1972
+ if (data && data.value !== undefined && data.value !== null) {
1973
+ result[data.options.name] = data.input.value2submit(data.value);
1974
+ }
1975
+ });
1976
+ }
1866
1977
  return result;
1867
1978
 
1868
- /**
1979
+ /**
1869
1980
  This method collects values from several editable elements and submit them all to server.
1870
1981
  Internally it runs client-side validation for all fields and submits only in case of success.
1871
1982
  See <a href="#newrecord">creating new records</a> for details.
@@ -2089,7 +2200,16 @@ Makes editable any HTML element on the page. Applied as jQuery method.
2089
2200
  });
2090
2201
  </script>
2091
2202
  **/
2092
- selector: null
2203
+ selector: null,
2204
+ /**
2205
+ Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers.
2206
+
2207
+ @property highlight
2208
+ @type string|boolean
2209
+ @since 1.4.5
2210
+ @default #FFFF80
2211
+ **/
2212
+ highlight: '#FFFF80'
2093
2213
  };
2094
2214
 
2095
2215
  }(window.jQuery));
@@ -2103,23 +2223,23 @@ To create your own input you can inherit from this class.
2103
2223
  **/
2104
2224
  (function ($) {
2105
2225
  "use strict";
2106
-
2226
+
2107
2227
  //types
2108
2228
  $.fn.editabletypes = {};
2109
-
2229
+
2110
2230
  var AbstractInput = function () { };
2111
2231
 
2112
2232
  AbstractInput.prototype = {
2113
2233
  /**
2114
2234
  Initializes input
2115
-
2235
+
2116
2236
  @method init()
2117
2237
  **/
2118
2238
  init: function(type, options, defaults) {
2119
2239
  this.type = type;
2120
2240
  this.options = $.extend({}, defaults, options);
2121
2241
  },
2122
-
2242
+
2123
2243
  /*
2124
2244
  this method called before render to init $tpl that is inserted in DOM
2125
2245
  */
@@ -2133,107 +2253,107 @@ To create your own input you can inherit from this class.
2133
2253
  /**
2134
2254
  Renders input from tpl. Can return jQuery deferred object.
2135
2255
  Can be overwritten in child objects
2136
-
2137
- @method render()
2138
- **/
2256
+
2257
+ @method render()
2258
+ **/
2139
2259
  render: function() {
2140
2260
 
2141
2261
  },
2142
2262
 
2143
2263
  /**
2144
2264
  Sets element's html by value.
2145
-
2146
- @method value2html(value, element)
2265
+
2266
+ @method value2html(value, element)
2147
2267
  @param {mixed} value
2148
2268
  @param {DOMElement} element
2149
- **/
2269
+ **/
2150
2270
  value2html: function(value, element) {
2151
- $(element).text(value);
2271
+ $(element).text($.trim(value));
2152
2272
  },
2153
-
2273
+
2154
2274
  /**
2155
2275
  Converts element's html to value
2156
-
2157
- @method html2value(html)
2276
+
2277
+ @method html2value(html)
2158
2278
  @param {string} html
2159
2279
  @returns {mixed}
2160
- **/
2280
+ **/
2161
2281
  html2value: function(html) {
2162
2282
  return $('<div>').html(html).text();
2163
2283
  },
2164
-
2284
+
2165
2285
  /**
2166
2286
  Converts value to string (for internal compare). For submitting to server used value2submit().
2167
-
2287
+
2168
2288
  @method value2str(value)
2169
2289
  @param {mixed} value
2170
2290
  @returns {string}
2171
- **/
2291
+ **/
2172
2292
  value2str: function(value) {
2173
2293
  return value;
2174
2294
  },
2175
-
2295
+
2176
2296
  /**
2177
2297
  Converts string received from server into value. Usually from `data-value` attribute.
2178
-
2179
- @method str2value(str)
2298
+
2299
+ @method str2value(str)
2180
2300
  @param {string} str
2181
2301
  @returns {mixed}
2182
- **/
2302
+ **/
2183
2303
  str2value: function(str) {
2184
2304
  return str;
2185
2305
  },
2186
2306
 
2187
2307
  /**
2188
2308
  Converts value for submitting to server. Result can be string or object.
2189
-
2309
+
2190
2310
  @method value2submit(value)
2191
2311
  @param {mixed} value
2192
2312
  @returns {mixed}
2193
- **/
2313
+ **/
2194
2314
  value2submit: function(value) {
2195
2315
  return value;
2196
- },
2197
-
2316
+ },
2317
+
2198
2318
  /**
2199
2319
  Sets value of input.
2200
-
2320
+
2201
2321
  @method value2input(value)
2202
2322
  @param {mixed} value
2203
- **/
2323
+ **/
2204
2324
  value2input: function(value) {
2205
2325
  this.$input.val(value);
2206
2326
  },
2207
-
2327
+
2208
2328
  /**
2209
2329
  Returns value of input. Value can be object (e.g. datepicker)
2210
-
2330
+
2211
2331
  @method input2value()
2212
- **/
2332
+ **/
2213
2333
  input2value: function() {
2214
2334
  return this.$input.val();
2215
2335
  },
2216
2336
 
2217
2337
  /**
2218
2338
  Activates input. For text it sets focus.
2219
-
2339
+
2220
2340
  @method activate()
2221
- **/
2341
+ **/
2222
2342
  activate: function() {
2223
2343
  if(this.$input.is(':visible')) {
2224
2344
  this.$input.focus();
2225
2345
  }
2226
2346
  },
2227
-
2347
+
2228
2348
  /**
2229
2349
  Creates input.
2230
-
2350
+
2231
2351
  @method clear()
2232
2352
  **/
2233
2353
  clear: function() {
2234
2354
  this.$input.val(null);
2235
2355
  },
2236
-
2356
+
2237
2357
  /**
2238
2358
  method to escape html.
2239
2359
  **/
@@ -2243,18 +2363,24 @@ To create your own input you can inherit from this class.
2243
2363
 
2244
2364
  /**
2245
2365
  attach handler to automatically submit form when value changed (useful when buttons not shown)
2246
- **/
2366
+ **/
2247
2367
  autosubmit: function() {
2248
2368
 
2249
2369
  },
2250
2370
 
2371
+ /**
2372
+ Additional actions when destroying element
2373
+ **/
2374
+ destroy: function() {
2375
+ },
2376
+
2251
2377
  // -------- helper functions --------
2252
2378
  setClass: function() {
2253
2379
  if(this.options.inputclass) {
2254
2380
  this.$input.addClass(this.options.inputclass);
2255
2381
  }
2256
2382
  },
2257
-
2383
+
2258
2384
  setAttr: function(attr) {
2259
2385
  if (this.options[attr] !== undefined && this.options[attr] !== null) {
2260
2386
  this.$input.attr(attr, this.options[attr]);
@@ -2356,30 +2482,33 @@ List - abstract class for inputs that have source option loaded from js array or
2356
2482
  // ------------- additional functions ------------
2357
2483
 
2358
2484
  onSourceReady: function (success, error) {
2485
+ //run source if it function
2486
+ var source;
2487
+ if ($.isFunction(this.options.source)) {
2488
+ source = this.options.source.call(this.options.scope);
2489
+ this.sourceData = null;
2490
+ //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed
2491
+ } else {
2492
+ source = this.options.source;
2493
+ }
2494
+
2359
2495
  //if allready loaded just call success
2360
- if($.isArray(this.sourceData)) {
2496
+ if(this.options.sourceCache && $.isArray(this.sourceData)) {
2361
2497
  success.call(this);
2362
2498
  return;
2363
2499
  }
2364
2500
 
2365
- // try parse json in single quotes (for double quotes jquery does automatically)
2501
+ //try parse json in single quotes (for double quotes jquery does automatically)
2366
2502
  try {
2367
- this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false);
2503
+ source = $.fn.editableutils.tryParseJson(source, false);
2368
2504
  } catch (e) {
2369
2505
  error.call(this);
2370
2506
  return;
2371
2507
  }
2372
-
2373
- var source = this.options.source;
2374
-
2375
- //run source if it function
2376
- if ($.isFunction(source)) {
2377
- source = source.call(this.options.scope);
2378
- }
2379
2508
 
2380
2509
  //loading from url
2381
2510
  if (typeof source === 'string') {
2382
- //try to get from cache
2511
+ //try to get sourceData from cache
2383
2512
  if(this.options.sourceCache) {
2384
2513
  var cacheID = source,
2385
2514
  cache;
@@ -3299,25 +3428,29 @@ Range (inherit from number)
3299
3428
  }(window.jQuery));
3300
3429
  /**
3301
3430
  Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
3302
- Please see [original docs](http://ivaynberg.github.com/select2) for detailed description and options.
3303
- You should manually include select2 distributive:
3431
+ Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
3432
+ Compatible **select2 version is 3.4.1**!
3433
+ You should manually download and include select2 distributive:
3304
3434
 
3305
3435
  <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
3306
3436
  <script src="select2/select2.js"></script>
3307
3437
 
3308
- For make it **Bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
3438
+ To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
3309
3439
 
3310
3440
  <link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>
3311
3441
 
3312
- **Note:** currently `ajax` source for select2 is not supported, as it's not possible to load it in closed select2 state.
3313
- The solution is to load source manually and assign statically.
3442
+ **Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.
3443
+ You need initially put both `data-value` and element's text youself:
3444
+
3445
+ <a href="#" data-type="select2" data-value="1">Text1</a>
3446
+
3314
3447
 
3315
3448
  @class select2
3316
3449
  @extends abstractinput
3317
3450
  @since 1.4.1
3318
3451
  @final
3319
3452
  @example
3320
- <a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-original-title="Select country"></a>
3453
+ <a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>
3321
3454
  <script>
3322
3455
  $(function(){
3323
3456
  $('#country').editable({
@@ -3338,56 +3471,47 @@ $(function(){
3338
3471
 
3339
3472
  var Constructor = function (options) {
3340
3473
  this.init('select2', options, Constructor.defaults);
3341
-
3474
+
3342
3475
  options.select2 = options.select2 || {};
3476
+
3477
+ this.sourceData = null;
3343
3478
 
3344
- var that = this,
3345
- mixin = { //mixin to select2 options
3346
- placeholder: options.placeholder
3347
- };
3348
-
3349
- //detect whether it is multi-valued
3350
- this.isMultiple = options.select2.tags || options.select2.multiple;
3479
+ //placeholder
3480
+ if(options.placeholder) {
3481
+ options.select2.placeholder = options.placeholder;
3482
+ }
3351
3483
 
3352
- //if not `tags` mode, we need define initSelection to set data from source
3353
- if(!options.select2.tags) {
3354
- if(options.source) {
3355
- mixin.data = options.source;
3356
- }
3484
+ //if not `tags` mode, use source
3485
+ if(!options.select2.tags && options.source) {
3486
+ var source = options.source;
3487
+ //if source is function, call it (once!)
3488
+ if ($.isFunction(options.source)) {
3489
+ source = options.source.call(options.scope);
3490
+ }
3357
3491
 
3358
- //this function can be defaulted in seletc2. See https://github.com/ivaynberg/select2/issues/710
3359
- mixin.initSelection = function (element, callback) {
3360
- //temp: try update results
3361
- /*
3362
- if(options.select2 && options.select2.ajax) {
3363
- console.log('attached');
3364
- var original = $(element).data('select2').postprocessResults;
3365
- console.log(original);
3366
- $(element).data('select2').postprocessResults = function(data, initial) {
3367
- console.log('postprocess');
3368
- // this.element.triggerHandler('loaded', [data]);
3369
- original.apply(this, arguments);
3370
- }
3371
-
3372
- // $(element).on('loaded', function(){console.log('loaded');});
3373
- $(element).data('select2').updateResults(true);
3492
+ if (typeof source === 'string') {
3493
+ options.select2.ajax = options.select2.ajax || {};
3494
+ //some default ajax params
3495
+ if(!options.select2.ajax.data) {
3496
+ options.select2.ajax.data = function(term) {return { query:term };};
3374
3497
  }
3375
- */
3376
-
3377
- var val = that.str2value(element.val()),
3378
- data = $.fn.editableutils.itemsByValue(val, mixin.data, 'id');
3379
-
3380
- //for single-valued mode should not use array. Take first element instead.
3381
- if($.isArray(data) && data.length && !that.isMultiple) {
3382
- data = data[0];
3498
+ if(!options.select2.ajax.results) {
3499
+ options.select2.ajax.results = function(data) { return {results:data };};
3383
3500
  }
3384
-
3385
- callback(data);
3386
- };
3387
- }
3501
+ options.select2.ajax.url = source;
3502
+ } else {
3503
+ //check format and convert x-editable format to select2 format (if needed)
3504
+ this.sourceData = this.convertSource(source);
3505
+ options.select2.data = this.sourceData;
3506
+ }
3507
+ }
3388
3508
 
3389
3509
  //overriding objects in config (as by default jQuery extend() is not recursive)
3390
- this.options.select2 = $.extend({}, Constructor.defaults.select2, mixin, options.select2);
3510
+ this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
3511
+
3512
+ //detect whether it is multi-valued
3513
+ this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
3514
+ this.isRemote = ('ajax' in this.options.select2);
3391
3515
  };
3392
3516
 
3393
3517
  $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
@@ -3395,21 +3519,17 @@ $(function(){
3395
3519
  $.extend(Constructor.prototype, {
3396
3520
  render: function() {
3397
3521
  this.setClass();
3522
+
3398
3523
  //apply select2
3399
3524
  this.$input.select2(this.options.select2);
3400
3525
 
3401
- //when data is loaded via ajax, we need to know when it's done
3402
- if('ajax' in this.options.select2) {
3403
- /*
3404
- console.log('attached');
3405
- var original = this.$input.data('select2').postprocessResults;
3406
- this.$input.data('select2').postprocessResults = function(data, initial) {
3407
- this.element.triggerHandler('loaded', [data]);
3408
- original.apply(this, arguments);
3409
- }
3410
- */
3526
+ //when data is loaded via ajax, we need to know when it's done to populate listData
3527
+ if(this.isRemote) {
3528
+ //listen to loaded event to populate data
3529
+ this.$input.on('select2-loaded', $.proxy(function(e) {
3530
+ this.sourceData = e.items.results;
3531
+ }, this));
3411
3532
  }
3412
-
3413
3533
 
3414
3534
  //trigger resize of editableform to re-position container in multi-valued mode
3415
3535
  if(this.isMultiple) {
@@ -3421,20 +3541,16 @@ $(function(){
3421
3541
 
3422
3542
  value2html: function(value, element) {
3423
3543
  var text = '', data;
3424
- if(this.$input) { //called when submitting form and select2 already exists
3425
- data = this.$input.select2('data');
3426
- } else { //on init (autotext)
3427
- //here select2 instance not created yet and data may be even not loaded.
3428
- //we can check data/tags property of select config and if exist lookup text
3429
- if(this.options.select2.tags) {
3430
- data = value;
3431
- } else if(this.options.select2.data) {
3432
- data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id');
3433
- } else {
3434
- //if('ajax' in this.options.select2) {
3435
- }
3544
+
3545
+ if(this.options.select2.tags) { //in tags mode just assign value
3546
+ data = value;
3547
+ } else if(this.sourceData) {
3548
+ data = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
3549
+ } else {
3550
+ //can not get list of possible values (e.g. autotext for select2 with ajax source)
3436
3551
  }
3437
3552
 
3553
+ //data may be array (when multiple values allowed)
3438
3554
  if($.isArray(data)) {
3439
3555
  //collect selected data and show with separator
3440
3556
  text = [];
@@ -3455,7 +3571,26 @@ $(function(){
3455
3571
  },
3456
3572
 
3457
3573
  value2input: function(value) {
3458
- this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
3574
+ //for remote source .val() is not working, need to look in sourceData
3575
+ if(this.isRemote) {
3576
+ //todo: check value for array
3577
+ var item, items;
3578
+ //if sourceData loaded, use it to get text for display
3579
+ if(this.sourceData) {
3580
+ items = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
3581
+ if(items.length) {
3582
+ item = items[0];
3583
+ }
3584
+ }
3585
+ //if item not found by sourceData, use element text (e.g. for the first show)
3586
+ if(!item) {
3587
+ item = {id: value, text: $(this.options.scope).text()};
3588
+ }
3589
+ //select2('data', ...) allows to set both id and text --> usefull for initial show when items are not loaded
3590
+ this.$input.select2('data', item).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
3591
+ } else {
3592
+ this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
3593
+ }
3459
3594
  },
3460
3595
 
3461
3596
  input2value: function() {
@@ -3488,6 +3623,22 @@ $(function(){
3488
3623
  $(this).closest('form').submit();
3489
3624
  }
3490
3625
  });
3626
+ },
3627
+
3628
+ /*
3629
+ Converts source from x-editable format: {value: 1, text: "1"} to
3630
+ select2 format: {id: 1, text: "1"}
3631
+ */
3632
+ convertSource: function(source) {
3633
+ if($.isArray(source) && source.length && source[0].value !== undefined) {
3634
+ for(var i = 0; i<source.length; i++) {
3635
+ if(source[i].value !== undefined) {
3636
+ source[i].id = source[i].value;
3637
+ delete source[i].value;
3638
+ }
3639
+ }
3640
+ }
3641
+ return source;
3491
3642
  }
3492
3643
 
3493
3644
  });
@@ -3539,12 +3690,22 @@ $(function(){
3539
3690
  }(window.jQuery));
3540
3691
 
3541
3692
  /**
3542
- * Combodate - 1.0.3
3693
+ * Combodate - 1.0.4
3543
3694
  * Dropdown date and time picker.
3544
3695
  * Converts text input into dropdowns to pick day, month, year, hour, minute and second.
3545
3696
  * Uses momentjs as datetime library http://momentjs.com.
3546
3697
  * For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang
3547
3698
  *
3699
+ * Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight
3700
+ * In combodate:
3701
+ * 12:00 pm --> 12:00 (24-h format, midday)
3702
+ * 12:00 am --> 00:00 (24-h format, midnight, start of day)
3703
+ *
3704
+ * Differs from momentjs parse rules:
3705
+ * 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change)
3706
+ * 00:00 am, 12:00 am --> 00:00 (24-h format, day not change)
3707
+ *
3708
+ *
3548
3709
  * Author: Vitaliy Potapov
3549
3710
  * Project page: http://github.com/vitalets/combodate
3550
3711
  * Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
@@ -3694,9 +3855,10 @@ $(function(){
3694
3855
 
3695
3856
  for(i=0; i<=11; i++) {
3696
3857
  if(longNames) {
3697
- name = moment().month(i).format('MMMM');
3858
+ //see https://github.com/timrwood/momentjs.com/pull/36
3859
+ name = moment().date(1).month(i).format('MMMM');
3698
3860
  } else if(shortNames) {
3699
- name = moment().month(i).format('MMM');
3861
+ name = moment().date(1).month(i).format('MMM');
3700
3862
  } else if(twoDigit) {
3701
3863
  name = this.leadZero(i+1);
3702
3864
  } else {
@@ -3732,9 +3894,10 @@ $(function(){
3732
3894
  h12 = this.options.template.indexOf('h') !== -1,
3733
3895
  h24 = this.options.template.indexOf('H') !== -1,
3734
3896
  twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
3897
+ min = h12 ? 1 : 0,
3735
3898
  max = h12 ? 12 : 23;
3736
3899
 
3737
- for(i=0; i<=max; i++) {
3900
+ for(i=min; i<=max; i++) {
3738
3901
  name = twoDigit ? this.leadZero(i) : i;
3739
3902
  items.push([i, name]);
3740
3903
  }
@@ -3783,7 +3946,7 @@ $(function(){
3783
3946
  },
3784
3947
 
3785
3948
  /*
3786
- Returns current date value.
3949
+ Returns current date value from combos.
3787
3950
  If format not specified - `options.format` used.
3788
3951
  If format = `null` - Moment object returned.
3789
3952
  */
@@ -3812,12 +3975,14 @@ $(function(){
3812
3975
  return '';
3813
3976
  }
3814
3977
 
3815
- //convert hours if 12h format
3978
+ //convert hours 12h --> 24h
3816
3979
  if(this.$ampm) {
3817
- values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
3818
- if(values.hour === 24) {
3819
- values.hour = 0;
3820
- }
3980
+ //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
3981
+ if(values.hour === 12) {
3982
+ values.hour = this.$ampm.val() === 'am' ? 0 : 12;
3983
+ } else {
3984
+ values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
3985
+ }
3821
3986
  }
3822
3987
 
3823
3988
  dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
@@ -3868,11 +4033,17 @@ $(function(){
3868
4033
  });
3869
4034
 
3870
4035
  if(this.$ampm) {
3871
- if(values.hour > 12) {
3872
- values.hour -= 12;
4036
+ //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
4037
+ if(values.hour >= 12) {
3873
4038
  values.ampm = 'pm';
4039
+ if(values.hour > 12) {
4040
+ values.hour -= 12;
4041
+ }
3874
4042
  } else {
3875
- values.ampm = 'am';
4043
+ values.ampm = 'am';
4044
+ if(values.hour === 0) {
4045
+ values.hour = 12;
4046
+ }
3876
4047
  }
3877
4048
  }
3878
4049
 
@@ -4244,7 +4415,7 @@ Editableform based on Twitter Bootstrap
4244
4415
  */
4245
4416
  /*jshint laxcomma: true*/
4246
4417
  setPosition: function () {
4247
-
4418
+
4248
4419
  (function() {
4249
4420
  var $tip = this.tip()
4250
4421
  , inside
@@ -4298,309 +4469,27 @@ Editableform based on Twitter Bootstrap
4298
4469
  });
4299
4470
 
4300
4471
  }(window.jQuery));
4301
- /**
4302
- Bootstrap-datepicker.
4303
- Description and examples: https://github.com/eternicode/bootstrap-datepicker.
4304
- For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
4305
- and set `language` option.
4306
- Since 1.4.0 date has different appearance in **popup** and **inline** modes.
4307
-
4308
- @class date
4309
- @extends abstractinput
4310
- @final
4311
- @example
4312
- <a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a>
4313
- <script>
4314
- $(function(){
4315
- $('#dob').editable({
4316
- format: 'yyyy-mm-dd',
4317
- viewformat: 'dd/mm/yyyy',
4318
- datepicker: {
4319
- weekStart: 1
4320
- }
4321
- }
4322
- });
4323
- });
4324
- </script>
4325
- **/
4326
- (function ($) {
4327
- "use strict";
4328
-
4329
- var Date = function (options) {
4330
- this.init('date', options, Date.defaults);
4331
- this.initPicker(options, Date.defaults);
4332
- };
4472
+ /* =========================================================
4473
+ * bootstrap-datepicker.js
4474
+ * http://www.eyecon.ro/bootstrap-datepicker
4475
+ * =========================================================
4476
+ * Copyright 2012 Stefan Petre
4477
+ * Improvements by Andrew Rowls
4478
+ *
4479
+ * Licensed under the Apache License, Version 2.0 (the "License");
4480
+ * you may not use this file except in compliance with the License.
4481
+ * You may obtain a copy of the License at
4482
+ *
4483
+ * http://www.apache.org/licenses/LICENSE-2.0
4484
+ *
4485
+ * Unless required by applicable law or agreed to in writing, software
4486
+ * distributed under the License is distributed on an "AS IS" BASIS,
4487
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4488
+ * See the License for the specific language governing permissions and
4489
+ * limitations under the License.
4490
+ * ========================================================= */
4333
4491
 
4334
- $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
4335
-
4336
- $.extend(Date.prototype, {
4337
- initPicker: function(options, defaults) {
4338
- //'format' is set directly from settings or data-* attributes
4339
-
4340
- //by default viewformat equals to format
4341
- if(!this.options.viewformat) {
4342
- this.options.viewformat = this.options.format;
4343
- }
4344
-
4345
- //overriding datepicker config (as by default jQuery extend() is not recursive)
4346
- //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
4347
- this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
4348
- format: this.options.viewformat
4349
- });
4350
-
4351
- //language
4352
- this.options.datepicker.language = this.options.datepicker.language || 'en';
4353
-
4354
- //store DPglobal
4355
- this.dpg = $.fn.datepicker.DPGlobal;
4356
-
4357
- //store parsed formats
4358
- this.parsedFormat = this.dpg.parseFormat(this.options.format);
4359
- this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
4360
- },
4361
-
4362
- render: function () {
4363
- this.$input.datepicker(this.options.datepicker);
4364
-
4365
- //"clear" link
4366
- if(this.options.clear) {
4367
- this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
4368
- e.preventDefault();
4369
- e.stopPropagation();
4370
- this.clear();
4371
- }, this));
4372
-
4373
- this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
4374
- }
4375
- },
4376
-
4377
- value2html: function(value, element) {
4378
- var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
4379
- Date.superclass.value2html(text, element);
4380
- },
4381
-
4382
- html2value: function(html) {
4383
- return html ? this.dpg.parseDate(html, this.parsedViewFormat, this.options.datepicker.language) : null;
4384
- },
4385
-
4386
- value2str: function(value) {
4387
- return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
4388
- },
4389
-
4390
- str2value: function(str) {
4391
- return str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datepicker.language) : null;
4392
- },
4393
-
4394
- value2submit: function(value) {
4395
- return this.value2str(value);
4396
- },
4397
-
4398
- value2input: function(value) {
4399
- this.$input.datepicker('update', value);
4400
- },
4401
-
4402
- input2value: function() {
4403
- return this.$input.data('datepicker').date;
4404
- },
4405
-
4406
- activate: function() {
4407
- },
4408
-
4409
- clear: function() {
4410
- this.$input.data('datepicker').date = null;
4411
- this.$input.find('.active').removeClass('active');
4412
- if(!this.options.showbuttons) {
4413
- this.$input.closest('form').submit();
4414
- }
4415
- },
4416
-
4417
- autosubmit: function() {
4418
- this.$input.on('mouseup', '.day', function(e){
4419
- if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) {
4420
- return;
4421
- }
4422
- var $form = $(this).closest('form');
4423
- setTimeout(function() {
4424
- $form.submit();
4425
- }, 200);
4426
- });
4427
- //changedate is not suitable as it triggered when showing datepicker. see #149
4428
- /*
4429
- this.$input.on('changeDate', function(e){
4430
- var $form = $(this).closest('form');
4431
- setTimeout(function() {
4432
- $form.submit();
4433
- }, 200);
4434
- });
4435
- */
4436
- }
4437
-
4438
- });
4439
-
4440
- Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
4441
- /**
4442
- @property tpl
4443
- @default <div></div>
4444
- **/
4445
- tpl:'<div class="editable-date well"></div>',
4446
- /**
4447
- @property inputclass
4448
- @default null
4449
- **/
4450
- inputclass: null,
4451
- /**
4452
- Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
4453
- Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>
4454
-
4455
- @property format
4456
- @type string
4457
- @default yyyy-mm-dd
4458
- **/
4459
- format:'yyyy-mm-dd',
4460
- /**
4461
- Format used for displaying date. Also applied when converting date from element's text on init.
4462
- If not specified equals to <code>format</code>
4463
-
4464
- @property viewformat
4465
- @type string
4466
- @default null
4467
- **/
4468
- viewformat: null,
4469
- /**
4470
- Configuration of datepicker.
4471
- Full list of options: http://vitalets.github.com/bootstrap-datepicker
4472
-
4473
- @property datepicker
4474
- @type object
4475
- @default {
4476
- weekStart: 0,
4477
- startView: 0,
4478
- minViewMode: 0,
4479
- autoclose: false
4480
- }
4481
- **/
4482
- datepicker:{
4483
- weekStart: 0,
4484
- startView: 0,
4485
- minViewMode: 0,
4486
- autoclose: false
4487
- },
4488
- /**
4489
- Text shown as clear date button.
4490
- If <code>false</code> clear button will not be rendered.
4491
-
4492
- @property clear
4493
- @type boolean|string
4494
- @default 'x clear'
4495
- **/
4496
- clear: '&times; clear'
4497
- });
4498
-
4499
- $.fn.editabletypes.date = Date;
4500
-
4501
- }(window.jQuery));
4502
-
4503
- /**
4504
- Bootstrap datefield input - modification for inline mode.
4505
- Shows normal <input type="text"> and binds popup datepicker.
4506
- Automatically shown in inline mode.
4507
-
4508
- @class datefield
4509
- @extends date
4510
-
4511
- @since 1.4.0
4512
- **/
4513
- (function ($) {
4514
- "use strict";
4515
-
4516
- var DateField = function (options) {
4517
- this.init('datefield', options, DateField.defaults);
4518
- this.initPicker(options, DateField.defaults);
4519
- };
4520
-
4521
- $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
4522
-
4523
- $.extend(DateField.prototype, {
4524
- render: function () {
4525
- this.$input = this.$tpl.find('input');
4526
- this.setClass();
4527
- this.setAttr('placeholder');
4528
-
4529
- this.$tpl.datepicker(this.options.datepicker);
4530
-
4531
- //need to disable original event handlers
4532
- this.$input.off('focus keydown');
4533
-
4534
- //update value of datepicker
4535
- this.$input.keyup($.proxy(function(){
4536
- this.$tpl.removeData('date');
4537
- this.$tpl.datepicker('update');
4538
- }, this));
4539
-
4540
- },
4541
-
4542
- value2input: function(value) {
4543
- this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');
4544
- this.$tpl.datepicker('update');
4545
- },
4546
-
4547
- input2value: function() {
4548
- return this.html2value(this.$input.val());
4549
- },
4550
-
4551
- activate: function() {
4552
- $.fn.editabletypes.text.prototype.activate.call(this);
4553
- },
4554
-
4555
- autosubmit: function() {
4556
- //reset autosubmit to empty
4557
- }
4558
- });
4559
-
4560
- DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {
4561
- /**
4562
- @property tpl
4563
- **/
4564
- tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
4565
- /**
4566
- @property inputclass
4567
- @default 'input-small'
4568
- **/
4569
- inputclass: 'input-small',
4570
-
4571
- /* datepicker config */
4572
- datepicker: {
4573
- weekStart: 0,
4574
- startView: 0,
4575
- minViewMode: 0,
4576
- autoclose: true
4577
- }
4578
- });
4579
-
4580
- $.fn.editabletypes.datefield = DateField;
4581
-
4582
- }(window.jQuery));
4583
- /* =========================================================
4584
- * bootstrap-datepicker.js
4585
- * http://www.eyecon.ro/bootstrap-datepicker
4586
- * =========================================================
4587
- * Copyright 2012 Stefan Petre
4588
- * Improvements by Andrew Rowls
4589
- *
4590
- * Licensed under the Apache License, Version 2.0 (the "License");
4591
- * you may not use this file except in compliance with the License.
4592
- * You may obtain a copy of the License at
4593
- *
4594
- * http://www.apache.org/licenses/LICENSE-2.0
4595
- *
4596
- * Unless required by applicable law or agreed to in writing, software
4597
- * distributed under the License is distributed on an "AS IS" BASIS,
4598
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4599
- * See the License for the specific language governing permissions and
4600
- * limitations under the License.
4601
- * ========================================================= */
4602
-
4603
- !function( $ ) {
4492
+ (function( $ ) {
4604
4493
 
4605
4494
  function UTCDate(){
4606
4495
  return new Date(Date.UTC.apply(Date, arguments));
@@ -4615,12 +4504,9 @@ Automatically shown in inline mode.
4615
4504
  var Datepicker = function(element, options) {
4616
4505
  var that = this;
4617
4506
 
4507
+ this._process_options(options);
4508
+
4618
4509
  this.element = $(element);
4619
- this.language = options.language||this.element.data('date-language')||"en";
4620
- 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"
4621
- this.language = this.language in dates ? this.language : "en";
4622
- this.isRTL = dates[this.language].rtl||false;
4623
- this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||dates[this.language].format||'mm/dd/yyyy');
4624
4510
  this.isInline = false;
4625
4511
  this.isInput = this.element.is('input');
4626
4512
  this.component = this.element.is('.date') ? this.element.find('.add-on, .btn') : false;
@@ -4628,13 +4514,6 @@ Automatically shown in inline mode.
4628
4514
  if(this.component && this.component.length === 0)
4629
4515
  this.component = false;
4630
4516
 
4631
- this.forceParse = true;
4632
- if ('forceParse' in options) {
4633
- this.forceParse = options.forceParse;
4634
- } else if ('dateForceParse' in this.element.data()) {
4635
- this.forceParse = this.element.data('date-force-parse');
4636
- }
4637
-
4638
4517
  this.picker = $(DPGlobal.template);
4639
4518
  this._buildEvents();
4640
4519
  this._attachEvents();
@@ -4644,65 +4523,17 @@ Automatically shown in inline mode.
4644
4523
  } else {
4645
4524
  this.picker.addClass('datepicker-dropdown dropdown-menu');
4646
4525
  }
4647
- if (this.isRTL){
4526
+
4527
+ if (this.o.rtl){
4648
4528
  this.picker.addClass('datepicker-rtl');
4649
4529
  this.picker.find('.prev i, .next i')
4650
4530
  .toggleClass('icon-arrow-left icon-arrow-right');
4651
4531
  }
4652
4532
 
4653
- this.autoclose = false;
4654
- if ('autoclose' in options) {
4655
- this.autoclose = options.autoclose;
4656
- } else if ('dateAutoclose' in this.element.data()) {
4657
- this.autoclose = this.element.data('date-autoclose');
4658
- }
4659
-
4660
- this.keyboardNavigation = true;
4661
- if ('keyboardNavigation' in options) {
4662
- this.keyboardNavigation = options.keyboardNavigation;
4663
- } else if ('dateKeyboardNavigation' in this.element.data()) {
4664
- this.keyboardNavigation = this.element.data('date-keyboard-navigation');
4665
- }
4666
-
4667
- this.viewMode = this.startViewMode = 0;
4668
- switch(options.startView || this.element.data('date-start-view')){
4669
- case 2:
4670
- case 'decade':
4671
- this.viewMode = this.startViewMode = 2;
4672
- break;
4673
- case 1:
4674
- case 'year':
4675
- this.viewMode = this.startViewMode = 1;
4676
- break;
4677
- }
4678
-
4679
- this.minViewMode = options.minViewMode||this.element.data('date-min-view-mode')||0;
4680
- if (typeof this.minViewMode === 'string') {
4681
- switch (this.minViewMode) {
4682
- case 'months':
4683
- this.minViewMode = 1;
4684
- break;
4685
- case 'years':
4686
- this.minViewMode = 2;
4687
- break;
4688
- default:
4689
- this.minViewMode = 0;
4690
- break;
4691
- }
4692
- }
4693
-
4694
- this.viewMode = this.startViewMode = Math.max(this.startViewMode, this.minViewMode);
4695
4533
 
4696
- this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false);
4697
- this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false);
4534
+ this.viewMode = this.o.startView;
4698
4535
 
4699
- this.calendarWeeks = false;
4700
- if ('calendarWeeks' in options) {
4701
- this.calendarWeeks = options.calendarWeeks;
4702
- } else if ('dateCalendarWeeks' in this.element.data()) {
4703
- this.calendarWeeks = this.element.data('date-calendar-weeks');
4704
- }
4705
- if (this.calendarWeeks)
4536
+ if (this.o.calendarWeeks)
4706
4537
  this.picker.find('tfoot th.today')
4707
4538
  .attr('colspan', function(i, val){
4708
4539
  return parseInt(val) + 1;
@@ -4710,14 +4541,10 @@ Automatically shown in inline mode.
4710
4541
 
4711
4542
  this._allow_update = false;
4712
4543
 
4713
- this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7);
4714
- this.weekEnd = ((this.weekStart + 6) % 7);
4715
- this.startDate = -Infinity;
4716
- this.endDate = Infinity;
4717
- this.daysOfWeekDisabled = [];
4718
- this.setStartDate(options.startDate||this.element.data('date-startdate'));
4719
- this.setEndDate(options.endDate||this.element.data('date-enddate'));
4720
- this.setDaysOfWeekDisabled(options.daysOfWeekDisabled||this.element.data('date-days-of-week-disabled'));
4544
+ this.setStartDate(this.o.startDate);
4545
+ this.setEndDate(this.o.endDate);
4546
+ this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled);
4547
+
4721
4548
  this.fillDow();
4722
4549
  this.fillMonths();
4723
4550
 
@@ -4734,6 +4561,68 @@ Automatically shown in inline mode.
4734
4561
  Datepicker.prototype = {
4735
4562
  constructor: Datepicker,
4736
4563
 
4564
+ _process_options: function(opts){
4565
+ // Store raw options for reference
4566
+ this._o = $.extend({}, this._o, opts);
4567
+ // Processed options
4568
+ var o = this.o = $.extend({}, this._o);
4569
+
4570
+ // Check if "de-DE" style date is available, if not language should
4571
+ // fallback to 2 letter code eg "de"
4572
+ var lang = o.language;
4573
+ if (!dates[lang]) {
4574
+ lang = lang.split('-')[0];
4575
+ if (!dates[lang])
4576
+ lang = defaults.language;
4577
+ }
4578
+ o.language = lang;
4579
+
4580
+ switch(o.startView){
4581
+ case 2:
4582
+ case 'decade':
4583
+ o.startView = 2;
4584
+ break;
4585
+ case 1:
4586
+ case 'year':
4587
+ o.startView = 1;
4588
+ break;
4589
+ default:
4590
+ o.startView = 0;
4591
+ }
4592
+
4593
+ switch (o.minViewMode) {
4594
+ case 1:
4595
+ case 'months':
4596
+ o.minViewMode = 1;
4597
+ break;
4598
+ case 2:
4599
+ case 'years':
4600
+ o.minViewMode = 2;
4601
+ break;
4602
+ default:
4603
+ o.minViewMode = 0;
4604
+ }
4605
+
4606
+ o.startView = Math.max(o.startView, o.minViewMode);
4607
+
4608
+ o.weekStart %= 7;
4609
+ o.weekEnd = ((o.weekStart + 6) % 7);
4610
+
4611
+ var format = DPGlobal.parseFormat(o.format)
4612
+ if (o.startDate !== -Infinity) {
4613
+ o.startDate = DPGlobal.parseDate(o.startDate, format, o.language);
4614
+ }
4615
+ if (o.endDate !== Infinity) {
4616
+ o.endDate = DPGlobal.parseDate(o.endDate, format, o.language);
4617
+ }
4618
+
4619
+ o.daysOfWeekDisabled = o.daysOfWeekDisabled||[];
4620
+ if (!$.isArray(o.daysOfWeekDisabled))
4621
+ o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/);
4622
+ o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function (d) {
4623
+ return parseInt(d, 10);
4624
+ });
4625
+ },
4737
4626
  _events: [],
4738
4627
  _secondaryEvents: [],
4739
4628
  _applyEvents: function(evs){
@@ -4794,7 +4683,12 @@ Automatically shown in inline mode.
4794
4683
  [$(document), {
4795
4684
  mousedown: $.proxy(function (e) {
4796
4685
  // Clicked outside the datepicker, hide it
4797
- if ($(e.target).closest('.datepicker.datepicker-inline, .datepicker.datepicker-dropdown').length === 0) {
4686
+ if (!(
4687
+ this.element.is(e.target) ||
4688
+ this.element.find(e.target).size() ||
4689
+ this.picker.is(e.target) ||
4690
+ this.picker.find(e.target).size()
4691
+ )) {
4798
4692
  this.hide();
4799
4693
  }
4800
4694
  }, this)
@@ -4815,6 +4709,19 @@ Automatically shown in inline mode.
4815
4709
  _detachSecondaryEvents: function(){
4816
4710
  this._unapplyEvents(this._secondaryEvents);
4817
4711
  },
4712
+ _trigger: function(event, altdate){
4713
+ var date = altdate || this.date,
4714
+ local_date = new Date(date.getTime() + (date.getTimezoneOffset()*60000));
4715
+
4716
+ this.element.trigger({
4717
+ type: event,
4718
+ date: local_date,
4719
+ format: $.proxy(function(altformat){
4720
+ var format = altformat || this.o.format;
4721
+ return DPGlobal.formatDate(date, format, this.o.language);
4722
+ }, this)
4723
+ });
4724
+ },
4818
4725
 
4819
4726
  show: function(e) {
4820
4727
  if (!this.isInline)
@@ -4826,10 +4733,7 @@ Automatically shown in inline mode.
4826
4733
  if (e) {
4827
4734
  e.preventDefault();
4828
4735
  }
4829
- this.element.trigger({
4830
- type: 'show',
4831
- date: this.date
4832
- });
4736
+ this._trigger('show');
4833
4737
  },
4834
4738
 
4835
4739
  hide: function(e){
@@ -4837,21 +4741,18 @@ Automatically shown in inline mode.
4837
4741
  if (!this.picker.is(':visible')) return;
4838
4742
  this.picker.hide().detach();
4839
4743
  this._detachSecondaryEvents();
4840
- this.viewMode = this.startViewMode;
4744
+ this.viewMode = this.o.startView;
4841
4745
  this.showMode();
4842
4746
 
4843
4747
  if (
4844
- this.forceParse &&
4748
+ this.o.forceParse &&
4845
4749
  (
4846
4750
  this.isInput && this.element.val() ||
4847
4751
  this.hasInput && this.element.find('input').val()
4848
4752
  )
4849
4753
  )
4850
4754
  this.setValue();
4851
- this.element.trigger({
4852
- type: 'hide',
4853
- date: this.date
4854
- });
4755
+ this._trigger('hide');
4855
4756
  },
4856
4757
 
4857
4758
  remove: function() {
@@ -4889,7 +4790,6 @@ Automatically shown in inline mode.
4889
4790
  if (this.component){
4890
4791
  this.element.find('input').val(formatted);
4891
4792
  }
4892
- this.element.data('date', formatted);
4893
4793
  } else {
4894
4794
  this.element.val(formatted);
4895
4795
  }
@@ -4897,36 +4797,24 @@ Automatically shown in inline mode.
4897
4797
 
4898
4798
  getFormattedDate: function(format) {
4899
4799
  if (format === undefined)
4900
- format = this.format;
4901
- return DPGlobal.formatDate(this.date, format, this.language);
4800
+ format = this.o.format;
4801
+ return DPGlobal.formatDate(this.date, format, this.o.language);
4902
4802
  },
4903
4803
 
4904
4804
  setStartDate: function(startDate){
4905
- this.startDate = startDate||-Infinity;
4906
- if (this.startDate !== -Infinity) {
4907
- this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language);
4908
- }
4805
+ this._process_options({startDate: startDate});
4909
4806
  this.update();
4910
4807
  this.updateNavArrows();
4911
4808
  },
4912
4809
 
4913
4810
  setEndDate: function(endDate){
4914
- this.endDate = endDate||Infinity;
4915
- if (this.endDate !== Infinity) {
4916
- this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language);
4917
- }
4811
+ this._process_options({endDate: endDate});
4918
4812
  this.update();
4919
4813
  this.updateNavArrows();
4920
4814
  },
4921
4815
 
4922
4816
  setDaysOfWeekDisabled: function(daysOfWeekDisabled){
4923
- this.daysOfWeekDisabled = daysOfWeekDisabled||[];
4924
- if (!$.isArray(this.daysOfWeekDisabled)) {
4925
- this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/);
4926
- }
4927
- this.daysOfWeekDisabled = $.map(this.daysOfWeekDisabled, function (d) {
4928
- return parseInt(d, 10);
4929
- });
4817
+ this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
4930
4818
  this.update();
4931
4819
  this.updateNavArrows();
4932
4820
  },
@@ -4955,16 +4843,17 @@ Automatically shown in inline mode.
4955
4843
  fromArgs = true;
4956
4844
  } else {
4957
4845
  date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();
4846
+ delete this.element.data().date;
4958
4847
  }
4959
4848
 
4960
- this.date = DPGlobal.parseDate(date, this.format, this.language);
4849
+ this.date = DPGlobal.parseDate(date, this.o.format, this.o.language);
4961
4850
 
4962
4851
  if(fromArgs) this.setValue();
4963
4852
 
4964
- if (this.date < this.startDate) {
4965
- this.viewDate = new Date(this.startDate);
4966
- } else if (this.date > this.endDate) {
4967
- this.viewDate = new Date(this.endDate);
4853
+ if (this.date < this.o.startDate) {
4854
+ this.viewDate = new Date(this.o.startDate);
4855
+ } else if (this.date > this.o.endDate) {
4856
+ this.viewDate = new Date(this.o.endDate);
4968
4857
  } else {
4969
4858
  this.viewDate = new Date(this.date);
4970
4859
  }
@@ -4972,15 +4861,15 @@ Automatically shown in inline mode.
4972
4861
  },
4973
4862
 
4974
4863
  fillDow: function(){
4975
- var dowCnt = this.weekStart,
4864
+ var dowCnt = this.o.weekStart,
4976
4865
  html = '<tr>';
4977
- if(this.calendarWeeks){
4866
+ if(this.o.calendarWeeks){
4978
4867
  var cell = '<th class="cw">&nbsp;</th>';
4979
4868
  html += cell;
4980
4869
  this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
4981
4870
  }
4982
- while (dowCnt < this.weekStart + 7) {
4983
- html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>';
4871
+ while (dowCnt < this.o.weekStart + 7) {
4872
+ html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';
4984
4873
  }
4985
4874
  html += '</tr>';
4986
4875
  this.picker.find('.datepicker-days thead').append(html);
@@ -4990,46 +4879,93 @@ Automatically shown in inline mode.
4990
4879
  var html = '',
4991
4880
  i = 0;
4992
4881
  while (i < 12) {
4993
- html += '<span class="month">'+dates[this.language].monthsShort[i++]+'</span>';
4882
+ html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>';
4994
4883
  }
4995
4884
  this.picker.find('.datepicker-months td').html(html);
4996
4885
  },
4997
4886
 
4887
+ setRange: function(range){
4888
+ if (!range || !range.length)
4889
+ delete this.range;
4890
+ else
4891
+ this.range = $.map(range, function(d){ return d.valueOf(); });
4892
+ this.fill();
4893
+ },
4894
+
4895
+ getClassNames: function(date){
4896
+ var cls = [],
4897
+ year = this.viewDate.getUTCFullYear(),
4898
+ month = this.viewDate.getUTCMonth(),
4899
+ currentDate = this.date.valueOf(),
4900
+ today = new Date();
4901
+ if (date.getUTCFullYear() < year || (date.getUTCFullYear() == year && date.getUTCMonth() < month)) {
4902
+ cls.push('old');
4903
+ } else if (date.getUTCFullYear() > year || (date.getUTCFullYear() == year && date.getUTCMonth() > month)) {
4904
+ cls.push('new');
4905
+ }
4906
+ // Compare internal UTC date with local today, not UTC today
4907
+ if (this.o.todayHighlight &&
4908
+ date.getUTCFullYear() == today.getFullYear() &&
4909
+ date.getUTCMonth() == today.getMonth() &&
4910
+ date.getUTCDate() == today.getDate()) {
4911
+ cls.push('today');
4912
+ }
4913
+ if (currentDate && date.valueOf() == currentDate) {
4914
+ cls.push('active');
4915
+ }
4916
+ if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate ||
4917
+ $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1) {
4918
+ cls.push('disabled');
4919
+ }
4920
+ if (this.range){
4921
+ if (date > this.range[0] && date < this.range[this.range.length-1]){
4922
+ cls.push('range');
4923
+ }
4924
+ if ($.inArray(date.valueOf(), this.range) != -1){
4925
+ cls.push('selected');
4926
+ }
4927
+ }
4928
+ return cls;
4929
+ },
4930
+
4998
4931
  fill: function() {
4999
4932
  var d = new Date(this.viewDate),
5000
4933
  year = d.getUTCFullYear(),
5001
4934
  month = d.getUTCMonth(),
5002
- startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
5003
- startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
5004
- endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
5005
- endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
4935
+ startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
4936
+ startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
4937
+ endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
4938
+ endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
5006
4939
  currentDate = this.date && this.date.valueOf(),
5007
- today = new Date();
5008
- this.picker.find('.datepicker-days thead th.switch')
5009
- .text(dates[this.language].months[month]+' '+year);
4940
+ tooltip;
4941
+ this.picker.find('.datepicker-days thead th.datepicker-switch')
4942
+ .text(dates[this.o.language].months[month]+' '+year);
5010
4943
  this.picker.find('tfoot th.today')
5011
- .text(dates[this.language].today)
5012
- .toggle(this.todayBtn !== false);
4944
+ .text(dates[this.o.language].today)
4945
+ .toggle(this.o.todayBtn !== false);
4946
+ this.picker.find('tfoot th.clear')
4947
+ .text(dates[this.o.language].clear)
4948
+ .toggle(this.o.clearBtn !== false);
5013
4949
  this.updateNavArrows();
5014
4950
  this.fillMonths();
5015
4951
  var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
5016
4952
  day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
5017
4953
  prevMonth.setUTCDate(day);
5018
- prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7);
4954
+ prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);
5019
4955
  var nextMonth = new Date(prevMonth);
5020
4956
  nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
5021
4957
  nextMonth = nextMonth.valueOf();
5022
4958
  var html = [];
5023
4959
  var clsName;
5024
4960
  while(prevMonth.valueOf() < nextMonth) {
5025
- if (prevMonth.getUTCDay() == this.weekStart) {
4961
+ if (prevMonth.getUTCDay() == this.o.weekStart) {
5026
4962
  html.push('<tr>');
5027
- if(this.calendarWeeks){
4963
+ if(this.o.calendarWeeks){
5028
4964
  // ISO 8601: First week contains first thursday.
5029
4965
  // ISO also states week starts on Monday, but we can be more abstract here.
5030
4966
  var
5031
4967
  // Start of current week: based on weekstart/current date
5032
- ws = new Date(+prevMonth + (this.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
4968
+ ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
5033
4969
  // Thursday of this week
5034
4970
  th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
5035
4971
  // First Thursday of year, year from thursday
@@ -5040,28 +4976,26 @@ Automatically shown in inline mode.
5040
4976
 
5041
4977
  }
5042
4978
  }
5043
- clsName = '';
5044
- if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
5045
- clsName += ' old';
5046
- } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
5047
- clsName += ' new';
5048
- }
5049
- // Compare internal UTC date with local today, not UTC today
5050
- if (this.todayHighlight &&
5051
- prevMonth.getUTCFullYear() == today.getFullYear() &&
5052
- prevMonth.getUTCMonth() == today.getMonth() &&
5053
- prevMonth.getUTCDate() == today.getDate()) {
5054
- clsName += ' today';
5055
- }
5056
- if (currentDate && prevMonth.valueOf() == currentDate) {
5057
- clsName += ' active';
5058
- }
5059
- if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate ||
5060
- $.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1) {
5061
- clsName += ' disabled';
5062
- }
5063
- html.push('<td class="day'+clsName+'">'+prevMonth.getUTCDate() + '</td>');
5064
- if (prevMonth.getUTCDay() == this.weekEnd) {
4979
+ clsName = this.getClassNames(prevMonth);
4980
+ clsName.push('day');
4981
+
4982
+ var before = this.o.beforeShowDay(prevMonth);
4983
+ if (before === undefined)
4984
+ before = {};
4985
+ else if (typeof(before) === 'boolean')
4986
+ before = {enabled: before};
4987
+ else if (typeof(before) === 'string')
4988
+ before = {classes: before};
4989
+ if (before.enabled === false)
4990
+ clsName.push('disabled');
4991
+ if (before.classes)
4992
+ clsName = clsName.concat(before.classes.split(/\s+/));
4993
+ if (before.tooltip)
4994
+ tooltip = before.tooltip;
4995
+
4996
+ clsName = $.unique(clsName);
4997
+ html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>');
4998
+ if (prevMonth.getUTCDay() == this.o.weekEnd) {
5065
4999
  html.push('</tr>');
5066
5000
  }
5067
5001
  prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
@@ -5096,7 +5030,7 @@ Automatically shown in inline mode.
5096
5030
  .find('td');
5097
5031
  year -= 1;
5098
5032
  for (var i = -1; i < 11; i++) {
5099
- html += '<span class="year'+(i == -1 || i == 10 ? ' old' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
5033
+ html += '<span class="year'+(i == -1 ? ' old' : i == 10 ? ' new' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
5100
5034
  year += 1;
5101
5035
  }
5102
5036
  yearCont.html(html);
@@ -5110,12 +5044,12 @@ Automatically shown in inline mode.
5110
5044
  month = d.getUTCMonth();
5111
5045
  switch (this.viewMode) {
5112
5046
  case 0:
5113
- if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) {
5047
+ if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()) {
5114
5048
  this.picker.find('.prev').css({visibility: 'hidden'});
5115
5049
  } else {
5116
5050
  this.picker.find('.prev').css({visibility: 'visible'});
5117
5051
  }
5118
- if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) {
5052
+ if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()) {
5119
5053
  this.picker.find('.next').css({visibility: 'hidden'});
5120
5054
  } else {
5121
5055
  this.picker.find('.next').css({visibility: 'visible'});
@@ -5123,12 +5057,12 @@ Automatically shown in inline mode.
5123
5057
  break;
5124
5058
  case 1:
5125
5059
  case 2:
5126
- if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
5060
+ if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()) {
5127
5061
  this.picker.find('.prev').css({visibility: 'hidden'});
5128
5062
  } else {
5129
5063
  this.picker.find('.prev').css({visibility: 'visible'});
5130
5064
  }
5131
- if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
5065
+ if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()) {
5132
5066
  this.picker.find('.next').css({visibility: 'hidden'});
5133
5067
  } else {
5134
5068
  this.picker.find('.next').css({visibility: 'visible'});
@@ -5144,7 +5078,7 @@ Automatically shown in inline mode.
5144
5078
  switch(target[0].nodeName.toLowerCase()) {
5145
5079
  case 'th':
5146
5080
  switch(target[0].className) {
5147
- case 'switch':
5081
+ case 'datepicker-switch':
5148
5082
  this.showMode(1);
5149
5083
  break;
5150
5084
  case 'prev':
@@ -5166,9 +5100,22 @@ Automatically shown in inline mode.
5166
5100
  date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
5167
5101
 
5168
5102
  this.showMode(-2);
5169
- var which = this.todayBtn == 'linked' ? null : 'view';
5103
+ var which = this.o.todayBtn == 'linked' ? null : 'view';
5170
5104
  this._setDate(date, which);
5171
5105
  break;
5106
+ case 'clear':
5107
+ var element;
5108
+ if (this.isInput)
5109
+ element = this.element;
5110
+ else if (this.component)
5111
+ element = this.element.find('input');
5112
+ if (element)
5113
+ element.val("").change();
5114
+ this._trigger('changeDate');
5115
+ this.update();
5116
+ if (this.o.autoclose)
5117
+ this.hide();
5118
+ break;
5172
5119
  }
5173
5120
  break;
5174
5121
  case 'span':
@@ -5179,11 +5126,8 @@ Automatically shown in inline mode.
5179
5126
  var month = target.parent().find('span').index(target);
5180
5127
  var year = this.viewDate.getUTCFullYear();
5181
5128
  this.viewDate.setUTCMonth(month);
5182
- this.element.trigger({
5183
- type: 'changeMonth',
5184
- date: this.viewDate
5185
- });
5186
- if ( this.minViewMode == 1 ) {
5129
+ this._trigger('changeMonth', this.viewDate);
5130
+ if (this.o.minViewMode === 1) {
5187
5131
  this._setDate(UTCDate(year, month, day,0,0,0,0));
5188
5132
  }
5189
5133
  } else {
@@ -5191,11 +5135,8 @@ Automatically shown in inline mode.
5191
5135
  var day = 1;
5192
5136
  var month = 0;
5193
5137
  this.viewDate.setUTCFullYear(year);
5194
- this.element.trigger({
5195
- type: 'changeYear',
5196
- date: this.viewDate
5197
- });
5198
- if ( this.minViewMode == 2 ) {
5138
+ this._trigger('changeYear', this.viewDate);
5139
+ if (this.o.minViewMode === 2) {
5199
5140
  this._setDate(UTCDate(year, month, day,0,0,0,0));
5200
5141
  }
5201
5142
  }
@@ -5232,15 +5173,12 @@ Automatically shown in inline mode.
5232
5173
 
5233
5174
  _setDate: function(date, which){
5234
5175
  if (!which || which == 'date')
5235
- this.date = date;
5176
+ this.date = new Date(date);
5236
5177
  if (!which || which == 'view')
5237
- this.viewDate = date;
5178
+ this.viewDate = new Date(date);
5238
5179
  this.fill();
5239
5180
  this.setValue();
5240
- this.element.trigger({
5241
- type: 'changeDate',
5242
- date: this.date
5243
- });
5181
+ this._trigger('changeDate');
5244
5182
  var element;
5245
5183
  if (this.isInput) {
5246
5184
  element = this.element;
@@ -5249,7 +5187,7 @@ Automatically shown in inline mode.
5249
5187
  }
5250
5188
  if (element) {
5251
5189
  element.change();
5252
- if (this.autoclose && (!which || which == 'date')) {
5190
+ if (this.o.autoclose && (!which || which == 'date')) {
5253
5191
  this.hide();
5254
5192
  }
5255
5193
  }
@@ -5300,7 +5238,7 @@ Automatically shown in inline mode.
5300
5238
  },
5301
5239
 
5302
5240
  dateWithinRange: function(date){
5303
- return date >= this.startDate && date <= this.endDate;
5241
+ return date >= this.o.startDate && date <= this.o.endDate;
5304
5242
  },
5305
5243
 
5306
5244
  keydown: function(e){
@@ -5319,7 +5257,7 @@ Automatically shown in inline mode.
5319
5257
  break;
5320
5258
  case 37: // left
5321
5259
  case 39: // right
5322
- if (!this.keyboardNavigation) break;
5260
+ if (!this.o.keyboardNavigation) break;
5323
5261
  dir = e.keyCode == 37 ? -1 : 1;
5324
5262
  if (e.ctrlKey){
5325
5263
  newDate = this.moveYear(this.date, dir);
@@ -5344,7 +5282,7 @@ Automatically shown in inline mode.
5344
5282
  break;
5345
5283
  case 38: // up
5346
5284
  case 40: // down
5347
- if (!this.keyboardNavigation) break;
5285
+ if (!this.o.keyboardNavigation) break;
5348
5286
  dir = e.keyCode == 38 ? -1 : 1;
5349
5287
  if (e.ctrlKey){
5350
5288
  newDate = this.moveYear(this.date, dir);
@@ -5376,10 +5314,7 @@ Automatically shown in inline mode.
5376
5314
  break;
5377
5315
  }
5378
5316
  if (dateChanged){
5379
- this.element.trigger({
5380
- type: 'changeDate',
5381
- date: this.date
5382
- });
5317
+ this._trigger('changeDate');
5383
5318
  var element;
5384
5319
  if (this.isInput) {
5385
5320
  element = this.element;
@@ -5394,7 +5329,7 @@ Automatically shown in inline mode.
5394
5329
 
5395
5330
  showMode: function(dir) {
5396
5331
  if (dir) {
5397
- this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
5332
+ this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir));
5398
5333
  }
5399
5334
  /*
5400
5335
  vitalets: fixing bug of very special conditions:
@@ -5411,24 +5346,151 @@ Automatically shown in inline mode.
5411
5346
  }
5412
5347
  };
5413
5348
 
5414
- $.fn.datepicker = function ( option ) {
5349
+ var DateRangePicker = function(element, options){
5350
+ this.element = $(element);
5351
+ this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; });
5352
+ delete options.inputs;
5353
+
5354
+ $(this.inputs)
5355
+ .datepicker(options)
5356
+ .bind('changeDate', $.proxy(this.dateUpdated, this));
5357
+
5358
+ this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); });
5359
+ this.updateDates();
5360
+ };
5361
+ DateRangePicker.prototype = {
5362
+ updateDates: function(){
5363
+ this.dates = $.map(this.pickers, function(i){ return i.date; });
5364
+ this.updateRanges();
5365
+ },
5366
+ updateRanges: function(){
5367
+ var range = $.map(this.dates, function(d){ return d.valueOf(); });
5368
+ $.each(this.pickers, function(i, p){
5369
+ p.setRange(range);
5370
+ });
5371
+ },
5372
+ dateUpdated: function(e){
5373
+ var dp = $(e.target).data('datepicker'),
5374
+ new_date = dp.getUTCDate(),
5375
+ i = $.inArray(e.target, this.inputs),
5376
+ l = this.inputs.length;
5377
+ if (i == -1) return;
5378
+
5379
+ if (new_date < this.dates[i]){
5380
+ // Date being moved earlier/left
5381
+ while (i>=0 && new_date < this.dates[i]){
5382
+ this.pickers[i--].setUTCDate(new_date);
5383
+ }
5384
+ }
5385
+ else if (new_date > this.dates[i]){
5386
+ // Date being moved later/right
5387
+ while (i<l && new_date > this.dates[i]){
5388
+ this.pickers[i++].setUTCDate(new_date);
5389
+ }
5390
+ }
5391
+ this.updateDates();
5392
+ },
5393
+ remove: function(){
5394
+ $.map(this.pickers, function(p){ p.remove(); });
5395
+ delete this.element.data().datepicker;
5396
+ }
5397
+ };
5398
+
5399
+ function opts_from_el(el, prefix){
5400
+ // Derive options from element data-attrs
5401
+ var data = $(el).data(),
5402
+ out = {}, inkey,
5403
+ replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'),
5404
+ prefix = new RegExp('^' + prefix.toLowerCase());
5405
+ for (var key in data)
5406
+ if (prefix.test(key)){
5407
+ inkey = key.replace(replace, function(_,a){ return a.toLowerCase(); });
5408
+ out[inkey] = data[key];
5409
+ }
5410
+ return out;
5411
+ }
5412
+
5413
+ function opts_from_locale(lang){
5414
+ // Derive options from locale plugins
5415
+ var out = {};
5416
+ // Check if "de-DE" style date is available, if not language should
5417
+ // fallback to 2 letter code eg "de"
5418
+ if (!dates[lang]) {
5419
+ lang = lang.split('-')[0]
5420
+ if (!dates[lang])
5421
+ return;
5422
+ }
5423
+ var d = dates[lang];
5424
+ $.each(locale_opts, function(i,k){
5425
+ if (k in d)
5426
+ out[k] = d[k];
5427
+ });
5428
+ return out;
5429
+ }
5430
+
5431
+ var old = $.fn.datepicker;
5432
+ var datepicker = $.fn.datepicker = function ( option ) {
5415
5433
  var args = Array.apply(null, arguments);
5416
5434
  args.shift();
5417
- return this.each(function () {
5435
+ var internal_return,
5436
+ this_return;
5437
+ this.each(function () {
5418
5438
  var $this = $(this),
5419
5439
  data = $this.data('datepicker'),
5420
5440
  options = typeof option == 'object' && option;
5421
5441
  if (!data) {
5422
- $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
5442
+ var elopts = opts_from_el(this, 'date'),
5443
+ // Preliminary otions
5444
+ xopts = $.extend({}, defaults, elopts, options),
5445
+ locopts = opts_from_locale(xopts.language),
5446
+ // Options priority: js args, data-attrs, locales, defaults
5447
+ opts = $.extend({}, defaults, locopts, elopts, options);
5448
+ if ($this.is('.input-daterange') || opts.inputs){
5449
+ var ropts = {
5450
+ inputs: opts.inputs || $this.find('input').toArray()
5451
+ };
5452
+ $this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts))));
5453
+ }
5454
+ else{
5455
+ $this.data('datepicker', (data = new Datepicker(this, opts)));
5456
+ }
5423
5457
  }
5424
5458
  if (typeof option == 'string' && typeof data[option] == 'function') {
5425
- data[option].apply(data, args);
5459
+ internal_return = data[option].apply(data, args);
5460
+ if (internal_return !== undefined)
5461
+ return false;
5426
5462
  }
5427
5463
  });
5464
+ if (internal_return !== undefined)
5465
+ return internal_return;
5466
+ else
5467
+ return this;
5428
5468
  };
5429
5469
 
5430
- $.fn.datepicker.defaults = {
5470
+ var defaults = $.fn.datepicker.defaults = {
5471
+ autoclose: false,
5472
+ beforeShowDay: $.noop,
5473
+ calendarWeeks: false,
5474
+ clearBtn: false,
5475
+ daysOfWeekDisabled: [],
5476
+ endDate: Infinity,
5477
+ forceParse: true,
5478
+ format: 'mm/dd/yyyy',
5479
+ keyboardNavigation: true,
5480
+ language: 'en',
5481
+ minViewMode: 0,
5482
+ rtl: false,
5483
+ startDate: -Infinity,
5484
+ startView: 0,
5485
+ todayBtn: false,
5486
+ todayHighlight: false,
5487
+ weekStart: 0
5431
5488
  };
5489
+ var locale_opts = $.fn.datepicker.locale_opts = [
5490
+ 'format',
5491
+ 'rtl',
5492
+ 'weekStart'
5493
+ ];
5432
5494
  $.fn.datepicker.Constructor = Datepicker;
5433
5495
  var dates = $.fn.datepicker.dates = {
5434
5496
  en: {
@@ -5437,7 +5499,8 @@ Automatically shown in inline mode.
5437
5499
  daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
5438
5500
  months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
5439
5501
  monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
5440
- today: "Today"
5502
+ today: "Today",
5503
+ clear: "Clear"
5441
5504
  }
5442
5505
  };
5443
5506
 
@@ -5478,6 +5541,8 @@ Automatically shown in inline mode.
5478
5541
  },
5479
5542
  parseDate: function(date, format, language) {
5480
5543
  if (date instanceof Date) return date;
5544
+ if (typeof format === 'string')
5545
+ format = DPGlobal.parseFormat(format);
5481
5546
  if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {
5482
5547
  var part_re = /([\-+]\d+)([dmwy])/,
5483
5548
  parts = date.match(/([\-+]\d+)([dmwy])/g),
@@ -5568,6 +5633,8 @@ Automatically shown in inline mode.
5568
5633
  return date;
5569
5634
  },
5570
5635
  formatDate: function(date, format, language){
5636
+ if (typeof format === 'string')
5637
+ format = DPGlobal.parseFormat(format);
5571
5638
  var val = {
5572
5639
  d: date.getUTCDate(),
5573
5640
  D: dates[language].daysShort[date.getUTCDay()],
@@ -5582,7 +5649,7 @@ Automatically shown in inline mode.
5582
5649
  val.mm = (val.m < 10 ? '0' : '') + val.m;
5583
5650
  var date = [],
5584
5651
  seps = $.extend([], format.separators);
5585
- for (var i=0, cnt = format.parts.length; i < cnt; i++) {
5652
+ for (var i=0, cnt = format.parts.length; i <= cnt; i++) {
5586
5653
  if (seps.length)
5587
5654
  date.push(seps.shift());
5588
5655
  date.push(val[format.parts[i]]);
@@ -5592,12 +5659,12 @@ Automatically shown in inline mode.
5592
5659
  headTemplate: '<thead>'+
5593
5660
  '<tr>'+
5594
5661
  '<th class="prev"><i class="icon-arrow-left"/></th>'+
5595
- '<th colspan="5" class="switch"></th>'+
5662
+ '<th colspan="5" class="datepicker-switch"></th>'+
5596
5663
  '<th class="next"><i class="icon-arrow-right"/></th>'+
5597
5664
  '</tr>'+
5598
5665
  '</thead>',
5599
5666
  contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
5600
- footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'
5667
+ footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>'
5601
5668
  };
5602
5669
  DPGlobal.template = '<div class="datepicker">'+
5603
5670
  '<div class="datepicker-days">'+
@@ -5623,10 +5690,348 @@ Automatically shown in inline mode.
5623
5690
  '</div>'+
5624
5691
  '</div>';
5625
5692
 
5626
- $.fn.datepicker.DPGlobal = DPGlobal;
5693
+ $.fn.datepicker.DPGlobal = DPGlobal;
5694
+
5695
+
5696
+ /* DATEPICKER NO CONFLICT
5697
+ * =================== */
5698
+
5699
+ $.fn.datepicker.noConflict = function(){
5700
+ $.fn.datepicker = old;
5701
+ return this;
5702
+ };
5703
+
5704
+
5705
+ /* DATEPICKER DATA-API
5706
+ * ================== */
5707
+
5708
+ $(document).on(
5709
+ 'focus.datepicker.data-api click.datepicker.data-api',
5710
+ '[data-provide="datepicker"]',
5711
+ function(e){
5712
+ var $this = $(this);
5713
+ if ($this.data('datepicker')) return;
5714
+ e.preventDefault();
5715
+ // component click requires us to explicitly show it
5716
+ datepicker.call($this, 'show');
5717
+ }
5718
+ );
5719
+ $(function(){
5720
+ //$('[data-provide="datepicker-inline"]').datepicker();
5721
+ //vit: changed to support noConflict()
5722
+ datepicker.call($('[data-provide="datepicker-inline"]'));
5723
+ });
5724
+
5725
+ }( window.jQuery ));
5726
+
5727
+ /**
5728
+ Bootstrap-datepicker.
5729
+ Description and examples: https://github.com/eternicode/bootstrap-datepicker.
5730
+ For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
5731
+ and set `language` option.
5732
+ Since 1.4.0 date has different appearance in **popup** and **inline** modes.
5733
+
5734
+ @class date
5735
+ @extends abstractinput
5736
+ @final
5737
+ @example
5738
+ <a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a>
5739
+ <script>
5740
+ $(function(){
5741
+ $('#dob').editable({
5742
+ format: 'yyyy-mm-dd',
5743
+ viewformat: 'dd/mm/yyyy',
5744
+ datepicker: {
5745
+ weekStart: 1
5746
+ }
5747
+ }
5748
+ });
5749
+ });
5750
+ </script>
5751
+ **/
5752
+ (function ($) {
5753
+ "use strict";
5754
+
5755
+ //store bootstrap-datepicker as bdateicker to exclude conflict with jQuery UI one
5756
+ $.fn.bdatepicker = $.fn.datepicker.noConflict();
5757
+ if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name
5758
+ $.fn.datepicker = $.fn.bdatepicker;
5759
+ }
5760
+
5761
+ var Date = function (options) {
5762
+ this.init('date', options, Date.defaults);
5763
+ this.initPicker(options, Date.defaults);
5764
+ };
5765
+
5766
+ $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
5767
+
5768
+ $.extend(Date.prototype, {
5769
+ initPicker: function(options, defaults) {
5770
+ //'format' is set directly from settings or data-* attributes
5771
+
5772
+ //by default viewformat equals to format
5773
+ if(!this.options.viewformat) {
5774
+ this.options.viewformat = this.options.format;
5775
+ }
5776
+
5777
+ //overriding datepicker config (as by default jQuery extend() is not recursive)
5778
+ //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
5779
+ this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
5780
+ format: this.options.viewformat
5781
+ });
5782
+
5783
+ //language
5784
+ this.options.datepicker.language = this.options.datepicker.language || 'en';
5785
+
5786
+ //store DPglobal
5787
+ this.dpg = $.fn.bdatepicker.DPGlobal;
5788
+
5789
+ //store parsed formats
5790
+ this.parsedFormat = this.dpg.parseFormat(this.options.format);
5791
+ this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
5792
+ },
5793
+
5794
+ render: function () {
5795
+ this.$input.bdatepicker(this.options.datepicker);
5796
+
5797
+ //"clear" link
5798
+ if(this.options.clear) {
5799
+ this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
5800
+ e.preventDefault();
5801
+ e.stopPropagation();
5802
+ this.clear();
5803
+ }, this));
5804
+
5805
+ this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
5806
+ }
5807
+ },
5808
+
5809
+ value2html: function(value, element) {
5810
+ var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
5811
+ Date.superclass.value2html(text, element);
5812
+ },
5813
+
5814
+ html2value: function(html) {
5815
+ return this.parseDate(html, this.parsedViewFormat);
5816
+ },
5817
+
5818
+ value2str: function(value) {
5819
+ return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
5820
+ },
5821
+
5822
+ str2value: function(str) {
5823
+ return this.parseDate(str, this.parsedFormat);
5824
+ },
5825
+
5826
+ value2submit: function(value) {
5827
+ return this.value2str(value);
5828
+ },
5829
+
5830
+ value2input: function(value) {
5831
+ this.$input.bdatepicker('update', value);
5832
+ },
5833
+
5834
+ input2value: function() {
5835
+ return this.$input.data('datepicker').date;
5836
+ },
5837
+
5838
+ activate: function() {
5839
+ },
5840
+
5841
+ clear: function() {
5842
+ this.$input.data('datepicker').date = null;
5843
+ this.$input.find('.active').removeClass('active');
5844
+ if(!this.options.showbuttons) {
5845
+ this.$input.closest('form').submit();
5846
+ }
5847
+ },
5848
+
5849
+ autosubmit: function() {
5850
+ this.$input.on('mouseup', '.day', function(e){
5851
+ if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) {
5852
+ return;
5853
+ }
5854
+ var $form = $(this).closest('form');
5855
+ setTimeout(function() {
5856
+ $form.submit();
5857
+ }, 200);
5858
+ });
5859
+ //changedate is not suitable as it triggered when showing datepicker. see #149
5860
+ /*
5861
+ this.$input.on('changeDate', function(e){
5862
+ var $form = $(this).closest('form');
5863
+ setTimeout(function() {
5864
+ $form.submit();
5865
+ }, 200);
5866
+ });
5867
+ */
5868
+ },
5869
+
5870
+ /*
5871
+ For incorrect date bootstrap-datepicker returns current date that is not suitable
5872
+ for datefield.
5873
+ This function returns null for incorrect date.
5874
+ */
5875
+ parseDate: function(str, format) {
5876
+ var date = null, formattedBack;
5877
+ if(str) {
5878
+ date = this.dpg.parseDate(str, format, this.options.datepicker.language);
5879
+ if(typeof str === 'string') {
5880
+ formattedBack = this.dpg.formatDate(date, format, this.options.datepicker.language);
5881
+ if(str !== formattedBack) {
5882
+ date = null;
5883
+ }
5884
+ }
5885
+ }
5886
+ return date;
5887
+ }
5888
+
5889
+ });
5890
+
5891
+ Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
5892
+ /**
5893
+ @property tpl
5894
+ @default <div></div>
5895
+ **/
5896
+ tpl:'<div class="editable-date well"></div>',
5897
+ /**
5898
+ @property inputclass
5899
+ @default null
5900
+ **/
5901
+ inputclass: null,
5902
+ /**
5903
+ Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
5904
+ Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>
5905
+
5906
+ @property format
5907
+ @type string
5908
+ @default yyyy-mm-dd
5909
+ **/
5910
+ format:'yyyy-mm-dd',
5911
+ /**
5912
+ Format used for displaying date. Also applied when converting date from element's text on init.
5913
+ If not specified equals to <code>format</code>
5914
+
5915
+ @property viewformat
5916
+ @type string
5917
+ @default null
5918
+ **/
5919
+ viewformat: null,
5920
+ /**
5921
+ Configuration of datepicker.
5922
+ Full list of options: http://vitalets.github.com/bootstrap-datepicker
5923
+
5924
+ @property datepicker
5925
+ @type object
5926
+ @default {
5927
+ weekStart: 0,
5928
+ startView: 0,
5929
+ minViewMode: 0,
5930
+ autoclose: false
5931
+ }
5932
+ **/
5933
+ datepicker:{
5934
+ weekStart: 0,
5935
+ startView: 0,
5936
+ minViewMode: 0,
5937
+ autoclose: false
5938
+ },
5939
+ /**
5940
+ Text shown as clear date button.
5941
+ If <code>false</code> clear button will not be rendered.
5942
+
5943
+ @property clear
5944
+ @type boolean|string
5945
+ @default 'x clear'
5946
+ **/
5947
+ clear: '&times; clear'
5948
+ });
5949
+
5950
+ $.fn.editabletypes.date = Date;
5951
+
5952
+ }(window.jQuery));
5953
+
5954
+ /**
5955
+ Bootstrap datefield input - modification for inline mode.
5956
+ Shows normal <input type="text"> and binds popup datepicker.
5957
+ Automatically shown in inline mode.
5958
+
5959
+ @class datefield
5960
+ @extends date
5961
+
5962
+ @since 1.4.0
5963
+ **/
5964
+ (function ($) {
5965
+ "use strict";
5966
+
5967
+ var DateField = function (options) {
5968
+ this.init('datefield', options, DateField.defaults);
5969
+ this.initPicker(options, DateField.defaults);
5970
+ };
5627
5971
 
5628
- }( window.jQuery );
5972
+ $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
5973
+
5974
+ $.extend(DateField.prototype, {
5975
+ render: function () {
5976
+ this.$input = this.$tpl.find('input');
5977
+ this.setClass();
5978
+ this.setAttr('placeholder');
5979
+
5980
+ //bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js)
5981
+ this.$tpl.bdatepicker(this.options.datepicker);
5982
+
5983
+ //need to disable original event handlers
5984
+ this.$input.off('focus keydown');
5985
+
5986
+ //update value of datepicker
5987
+ this.$input.keyup($.proxy(function(){
5988
+ this.$tpl.removeData('date');
5989
+ this.$tpl.bdatepicker('update');
5990
+ }, this));
5991
+
5992
+ },
5993
+
5994
+ value2input: function(value) {
5995
+ this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');
5996
+ this.$tpl.bdatepicker('update');
5997
+ },
5998
+
5999
+ input2value: function() {
6000
+ return this.html2value(this.$input.val());
6001
+ },
6002
+
6003
+ activate: function() {
6004
+ $.fn.editabletypes.text.prototype.activate.call(this);
6005
+ },
6006
+
6007
+ autosubmit: function() {
6008
+ //reset autosubmit to empty
6009
+ }
6010
+ });
6011
+
6012
+ DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {
6013
+ /**
6014
+ @property tpl
6015
+ **/
6016
+ tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
6017
+ /**
6018
+ @property inputclass
6019
+ @default 'input-small'
6020
+ **/
6021
+ inputclass: 'input-small',
6022
+
6023
+ /* datepicker config */
6024
+ datepicker: {
6025
+ weekStart: 0,
6026
+ startView: 0,
6027
+ minViewMode: 0,
6028
+ autoclose: true
6029
+ }
6030
+ });
6031
+
6032
+ $.fn.editabletypes.datefield = DateField;
5629
6033
 
6034
+ }(window.jQuery));
5630
6035
  /**
5631
6036
  Bootstrap-datetimepicker.
5632
6037
  Based on [smalot bootstrap-datetimepicker plugin](https://github.com/smalot/bootstrap-datetimepicker).
@@ -5634,7 +6039,7 @@ Before usage you should manually include dependent js and css:
5634
6039
 
5635
6040
  <link href="css/datetimepicker.css" rel="stylesheet" type="text/css"></link>
5636
6041
  <script src="js/bootstrap-datetimepicker.js"></script>
5637
-
6042
+
5638
6043
  For **i18n** you should include js file from here: https://github.com/smalot/bootstrap-datetimepicker/tree/master/js/locales
5639
6044
  and set `language` option.
5640
6045
 
@@ -5659,14 +6064,14 @@ $(function(){
5659
6064
  **/
5660
6065
  (function ($) {
5661
6066
  "use strict";
5662
-
6067
+
5663
6068
  var DateTime = function (options) {
5664
6069
  this.init('datetime', options, DateTime.defaults);
5665
6070
  this.initPicker(options, DateTime.defaults);
5666
6071
  };
5667
6072
 
5668
6073
  $.fn.editableutils.inherit(DateTime, $.fn.editabletypes.abstractinput);
5669
-
6074
+
5670
6075
  $.extend(DateTime.prototype, {
5671
6076
  initPicker: function(options, defaults) {
5672
6077
  //'format' is set directly from settings or data-* attributes
@@ -5675,13 +6080,13 @@ $(function(){
5675
6080
  if(!this.options.viewformat) {
5676
6081
  this.options.viewformat = this.options.format;
5677
6082
  }
5678
-
6083
+
5679
6084
  //overriding datetimepicker config (as by default jQuery extend() is not recursive)
5680
6085
  //since 1.4 datetimepicker internally uses viewformat instead of format. Format is for submit only
5681
6086
  this.options.datetimepicker = $.extend({}, defaults.datetimepicker, options.datetimepicker, {
5682
6087
  format: this.options.viewformat
5683
6088
  });
5684
-
6089
+
5685
6090
  //language
5686
6091
  this.options.datetimepicker.language = this.options.datetimepicker.language || 'en';
5687
6092
 
@@ -5691,16 +6096,21 @@ $(function(){
5691
6096
  //store parsed formats
5692
6097
  this.parsedFormat = this.dpg.parseFormat(this.options.format, this.options.formatType);
5693
6098
  this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat, this.options.formatType);
5694
-
5695
- //
5696
- this.options.datetimepicker.startView = this.options.startView;
5697
- this.options.datetimepicker.minView = this.options.minView;
5698
- this.options.datetimepicker.maxView = this.options.maxView;
5699
6099
  },
5700
-
6100
+
5701
6101
  render: function () {
5702
6102
  this.$input.datetimepicker(this.options.datetimepicker);
5703
-
6103
+
6104
+ //adjust container position when viewMode changes
6105
+ //see https://github.com/smalot/bootstrap-datetimepicker/pull/80
6106
+ this.$input.on('changeMode', function(e) {
6107
+ var f = $(this).closest('form').parent();
6108
+ //timeout here, otherwise container changes position before form has new size
6109
+ setTimeout(function(){
6110
+ f.triggerHandler('resize');
6111
+ }, 0);
6112
+ });
6113
+
5704
6114
  //"clear" link
5705
6115
  if(this.options.clear) {
5706
6116
  this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
@@ -5708,11 +6118,11 @@ $(function(){
5708
6118
  e.stopPropagation();
5709
6119
  this.clear();
5710
6120
  }, this));
5711
-
6121
+
5712
6122
  this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
5713
- }
6123
+ }
5714
6124
  },
5715
-
6125
+
5716
6126
  value2html: function(value, element) {
5717
6127
  //formatDate works with UTCDate!
5718
6128
  var text = value ? this.dpg.formatDate(this.toUTC(value), this.parsedViewFormat, this.options.datetimepicker.language, this.options.formatType) : '';
@@ -5720,45 +6130,45 @@ $(function(){
5720
6130
  DateTime.superclass.value2html(text, element);
5721
6131
  } else {
5722
6132
  return text;
5723
- }
6133
+ }
5724
6134
  },
5725
6135
 
5726
6136
  html2value: function(html) {
5727
6137
  //parseDate return utc date!
5728
- var value = html ? this.dpg.parseDate(html, this.parsedViewFormat, this.options.datetimepicker.language, this.options.formatType) : null;
6138
+ var value = this.parseDate(html, this.parsedViewFormat);
5729
6139
  return value ? this.fromUTC(value) : null;
5730
- },
5731
-
6140
+ },
6141
+
5732
6142
  value2str: function(value) {
5733
6143
  //formatDate works with UTCDate!
5734
6144
  return value ? this.dpg.formatDate(this.toUTC(value), this.parsedFormat, this.options.datetimepicker.language, this.options.formatType) : '';
5735
- },
5736
-
6145
+ },
6146
+
5737
6147
  str2value: function(str) {
5738
6148
  //parseDate return utc date!
5739
- var value = str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datetimepicker.language, this.options.formatType) : null;
6149
+ var value = this.parseDate(str, this.parsedFormat);
5740
6150
  return value ? this.fromUTC(value) : null;
5741
- },
5742
-
6151
+ },
6152
+
5743
6153
  value2submit: function(value) {
5744
6154
  return this.value2str(value);
5745
- },
6155
+ },
5746
6156
 
5747
6157
  value2input: function(value) {
5748
6158
  if(value) {
5749
6159
  this.$input.data('datetimepicker').setDate(value);
5750
6160
  }
5751
6161
  },
5752
-
6162
+
5753
6163
  input2value: function() {
5754
6164
  //date may be cleared, in that case getDate() triggers error
5755
6165
  var dt = this.$input.data('datetimepicker');
5756
6166
  return dt.date ? dt.getDate() : null;
5757
- },
5758
-
6167
+ },
6168
+
5759
6169
  activate: function() {
5760
6170
  },
5761
-
6171
+
5762
6172
  clear: function() {
5763
6173
  this.$input.data('datetimepicker').date = null;
5764
6174
  this.$input.find('.active').removeClass('active');
@@ -5766,7 +6176,7 @@ $(function(){
5766
6176
  this.$input.closest('form').submit();
5767
6177
  }
5768
6178
  },
5769
-
6179
+
5770
6180
  autosubmit: function() {
5771
6181
  this.$input.on('mouseup', '.minute', function(e){
5772
6182
  var $form = $(this).closest('form');
@@ -5775,19 +6185,38 @@ $(function(){
5775
6185
  }, 200);
5776
6186
  });
5777
6187
  },
5778
-
6188
+
5779
6189
  //convert date from local to utc
5780
6190
  toUTC: function(value) {
5781
6191
  return value ? new Date(value.valueOf() - value.getTimezoneOffset() * 60000) : value;
5782
6192
  },
5783
-
6193
+
5784
6194
  //convert date from utc to local
5785
6195
  fromUTC: function(value) {
5786
6196
  return value ? new Date(value.valueOf() + value.getTimezoneOffset() * 60000) : value;
6197
+ },
6198
+
6199
+ /*
6200
+ For incorrect date bootstrap-datetimepicker returns current date that is not suitable
6201
+ for datetimefield.
6202
+ This function returns null for incorrect date.
6203
+ */
6204
+ parseDate: function(str, format) {
6205
+ var date = null, formattedBack;
6206
+ if(str) {
6207
+ date = this.dpg.parseDate(str, format, this.options.datetimepicker.language, this.options.formatType);
6208
+ if(typeof str === 'string') {
6209
+ formattedBack = this.dpg.formatDate(date, format, this.options.datetimepicker.language, this.options.formatType);
6210
+ if(str !== formattedBack) {
6211
+ date = null;
6212
+ }
6213
+ }
6214
+ }
6215
+ return date;
5787
6216
  }
5788
6217
 
5789
6218
  });
5790
-
6219
+
5791
6220
  DateTime.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
5792
6221
  /**
5793
6222
  @property tpl
@@ -5797,7 +6226,7 @@ $(function(){
5797
6226
  /**
5798
6227
  @property inputclass
5799
6228
  @default null
5800
- **/
6229
+ **/
5801
6230
  inputclass: null,
5802
6231
  /**
5803
6232
  Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
@@ -5816,12 +6245,12 @@ $(function(){
5816
6245
  @property viewformat
5817
6246
  @type string
5818
6247
  @default null
5819
- **/
5820
- viewformat: null,
6248
+ **/
6249
+ viewformat: null,
5821
6250
  /**
5822
6251
  Configuration of datetimepicker.
5823
6252
  Full list of options: https://github.com/smalot/bootstrap-datetimepicker
5824
-
6253
+
5825
6254
  @property datetimepicker
5826
6255
  @type object
5827
6256
  @default { }
@@ -5833,13 +6262,13 @@ $(function(){
5833
6262
  /**
5834
6263
  Text shown as clear date button.
5835
6264
  If <code>false</code> clear button will not be rendered.
5836
-
6265
+
5837
6266
  @property clear
5838
6267
  @type boolean|string
5839
- @default 'x clear'
6268
+ @default 'x clear'
5840
6269
  **/
5841
6270
  clear: '&times; clear'
5842
- });
6271
+ });
5843
6272
 
5844
6273
  $.fn.editabletypes.datetime = DateTime;
5845
6274