x-editable-rails 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a6973ae156c26b7db1083c3215edbbd16c9acae2
4
- data.tar.gz: fc3914f001cbaa43658d451ee78ae126fa404e71
3
+ metadata.gz: 5efea27a1619957c2f3197189d9e17bb51a3b4ef
4
+ data.tar.gz: 8af1c69ce928792a5ac658b3b2b8d75bdc27b878
5
5
  SHA512:
6
- metadata.gz: e76220eafc1289c9c13d1a1a62d74dd639d060b100197759d6529f6101f2000f81e2772d77c8428ea891138cb961008ae44df3bd71ccb2fad347b849c5e3b2d6
7
- data.tar.gz: 9a2a5146c291a0603f6aea94299d1a875087722850925ae294ce3aa98d9e3f9cdf59acd908a5ab474c34033adab637ab5023f9e6a3b5939a479323e94cf3e29b
6
+ metadata.gz: 3f3de3e4bf6ea289cc325353c619c2479591589ec968c8bb2db9084d5e53753187616c6a6c4fd6aee43528cc1566bcccee8d52290a7ef29b189fa14dfb9988c1
7
+ data.tar.gz: f3bf084a7399627b4ab3d5a8315eab3c3d1ead2e233e2d402a4c820f8892cbffd3782622de4106ce77064e07bc9bb080fc62250a962887064f1ab2b5e834e6a5
@@ -1,7 +1,7 @@
1
1
  module X
2
2
  module Editable
3
3
  module Rails
4
- VERSION = "1.0.1"
4
+ VERSION = "1.0.2"
5
5
  end
6
6
  end
7
7
  end
@@ -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
+ });
5627
5949
 
5628
- }( window.jQuery );
5950
+ $.fn.editabletypes.date = Date;
5951
+
5952
+ }(window.jQuery));
5629
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
+ };
5971
+
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;
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