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.
@@ -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