x-editable-rails 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- /*! X-editable - v1.4.4
1
+ /*! X-editable - v1.4.5
2
2
  * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
3
3
  * http://github.com/vitalets/x-editable
4
4
  * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
@@ -65,6 +65,10 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
65
65
  //show loading state
66
66
  this.showLoading();
67
67
 
68
+ //flag showing is form now saving value to server.
69
+ //It is needed to wait when closing form.
70
+ this.isSaving = false;
71
+
68
72
  /**
69
73
  Fired when rendering starts
70
74
  @event rendering
@@ -217,31 +221,38 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
217
221
  return;
218
222
  }
219
223
 
224
+ //convert value for submitting to server
225
+ var submitValue = this.input.value2submit(newValue);
226
+
227
+ this.isSaving = true;
228
+
220
229
  //sending data to server
221
- $.when(this.save(newValue))
230
+ $.when(this.save(submitValue))
222
231
  .done($.proxy(function(response) {
232
+ this.isSaving = false;
233
+
223
234
  //run success callback
224
235
  var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
225
-
236
+
226
237
  //if success callback returns false --> keep form open and do not activate input
227
238
  if(res === false) {
228
239
  this.error(false);
229
240
  this.showForm(false);
230
241
  return;
231
- }
232
-
242
+ }
243
+
233
244
  //if success callback returns string --> keep form open, show error and activate input
234
245
  if(typeof res === 'string') {
235
246
  this.error(res);
236
247
  this.showForm();
237
248
  return;
238
- }
239
-
249
+ }
250
+
240
251
  //if success callback returns object like {newValue: <something>} --> use that value instead of submitted
241
252
  //it is usefull if you want to chnage value in url-function
242
253
  if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
243
254
  newValue = res.newValue;
244
- }
255
+ }
245
256
 
246
257
  //clear error message
247
258
  this.error(false);
@@ -251,37 +262,42 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
251
262
  @event save
252
263
  @param {Object} event event object
253
264
  @param {Object} params additional params
254
- @param {mixed} params.newValue submitted value
265
+ @param {mixed} params.newValue raw new value
266
+ @param {mixed} params.submitValue submitted value as string
255
267
  @param {Object} params.response ajax response
256
268
 
257
269
  @example
258
270
  $('#form-div').on('save'), function(e, params){
259
271
  if(params.newValue === 'username') {...}
260
- });
261
- **/
262
- this.$div.triggerHandler('save', {newValue: newValue, response: response});
272
+ });
273
+ **/
274
+ this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response});
263
275
  }, this))
264
276
  .fail($.proxy(function(xhr) {
277
+ this.isSaving = false;
278
+
265
279
  var msg;
266
280
  if(typeof this.options.error === 'function') {
267
281
  msg = this.options.error.call(this.options.scope, xhr, newValue);
268
282
  } else {
269
283
  msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';
270
284
  }
271
-
285
+
272
286
  this.error(msg);
273
287
  this.showForm();
274
288
  }, this));
275
289
  },
276
290
 
277
- save: function(newValue) {
278
- //convert value for submitting to server
279
- var submitValue = this.input.value2submit(newValue);
280
-
291
+ save: function(submitValue) {
281
292
  //try parse composite pk defined as json string in data-pk
282
293
  this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true);
283
294
 
284
295
  var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
296
+ /*
297
+ send on server in following cases:
298
+ 1. url is function
299
+ 2. url is string AND (pk defined OR send option = always)
300
+ */
285
301
  send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),
286
302
  params;
287
303
 
@@ -816,6 +832,27 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
816
832
  $.error('Unknown type: '+ type);
817
833
  return false;
818
834
  }
835
+ },
836
+
837
+ //see http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr
838
+ supportsTransitions: function () {
839
+ var b = document.body || document.documentElement,
840
+ s = b.style,
841
+ p = 'transition',
842
+ v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
843
+
844
+ if(typeof s[p] === 'string') {
845
+ return true;
846
+ }
847
+
848
+ // Tests for vendor specific prop
849
+ p = p.charAt(0).toUpperCase() + p.substr(1);
850
+ for(var i=0; i<v.length; i++) {
851
+ if(typeof s[v[i] + p] === 'string') {
852
+ return true;
853
+ }
854
+ }
855
+ return false;
819
856
  }
820
857
 
821
858
  };
@@ -856,6 +893,9 @@ Applied as jQuery method.
856
893
  this.formOptions.scope = this.$element[0];
857
894
 
858
895
  this.initContainer();
896
+
897
+ //flag to hide container, when saving value will finish
898
+ this.delayedHide = false;
859
899
 
860
900
  //bind 'destroyed' listener to destroy container when element is removed from dom
861
901
  this.$element.on('destroyed', $.proxy(function(){
@@ -960,7 +1000,14 @@ Applied as jQuery method.
960
1000
  save: $.proxy(this.save, this), //click on submit button (value changed)
961
1001
  nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)
962
1002
  cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button
963
- show: $.proxy(this.setPosition, this), //re-position container every time form is shown (occurs each time after loading state)
1003
+ show: $.proxy(function() {
1004
+ if(this.delayedHide) {
1005
+ this.hide(this.delayedHide.reason);
1006
+ this.delayedHide = false;
1007
+ } else {
1008
+ this.setPosition();
1009
+ }
1010
+ }, this), //re-position container every time form is shown (occurs each time after loading state)
964
1011
  rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
965
1012
  resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
966
1013
  rendered: $.proxy(function(){
@@ -1004,11 +1051,11 @@ Applied as jQuery method.
1004
1051
 
1005
1052
  /*
1006
1053
  Currently, form is re-rendered on every show.
1007
- The main reason is that we dont know, what container will do with content when closed:
1008
- remove(), detach() or just hide().
1054
+ The main reason is that we dont know, what will container do with content when closed:
1055
+ remove(), detach() or just hide() - it depends on container.
1009
1056
 
1010
1057
  Detaching form itself before hide and re-insert before show is good solution,
1011
- but visually it looks ugly, as container changes size before hide.
1058
+ but visually it looks ugly --> container changes size before hide.
1012
1059
  */
1013
1060
 
1014
1061
  //if form already exist - delete previous data
@@ -1041,10 +1088,18 @@ Applied as jQuery method.
1041
1088
  return;
1042
1089
  }
1043
1090
 
1091
+ //if form is saving value, schedule hide
1092
+ if(this.$form.data('editableform').isSaving) {
1093
+ this.delayedHide = {reason: reason};
1094
+ return;
1095
+ } else {
1096
+ this.delayedHide = false;
1097
+ }
1098
+
1044
1099
  this.$element.removeClass('editable-open');
1045
1100
  this.innerHide();
1046
-
1047
- /**
1101
+
1102
+ /**
1048
1103
  Fired when container was hidden. It occurs on both save or cancel.
1049
1104
  **Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.
1050
1105
  The workaround is to check `arguments.length` that is always `2` for x-editable.
@@ -1058,20 +1113,20 @@ Applied as jQuery method.
1058
1113
  //auto-open next editable
1059
1114
  $(this).closest('tr').next().find('.editable').editable('show');
1060
1115
  }
1061
- });
1062
- **/
1116
+ });
1117
+ **/
1063
1118
  this.$element.triggerHandler('hidden', reason || 'manual');
1064
1119
  },
1065
-
1120
+
1066
1121
  /* internal show method. To be overwritten in child classes */
1067
1122
  innerShow: function () {
1068
1123
 
1069
1124
  },
1070
-
1125
+
1071
1126
  /* internal hide method. To be overwritten in child classes */
1072
1127
  innerHide: function () {
1073
-
1074
- },
1128
+
1129
+ },
1075
1130
 
1076
1131
  /**
1077
1132
  Toggles container visibility (show / hide)
@@ -1116,7 +1171,7 @@ Applied as jQuery method.
1116
1171
  **/
1117
1172
  this.$element.triggerHandler('save', params);
1118
1173
 
1119
- //hide must be after trigger, as saving value may require methods od plugin, applied to input
1174
+ //hide must be after trigger, as saving value may require methods of plugin, applied to input
1120
1175
  this.hide('save');
1121
1176
  },
1122
1177
 
@@ -1276,7 +1331,7 @@ Applied as jQuery method.
1276
1331
  onblur: 'cancel',
1277
1332
 
1278
1333
  /**
1279
- Animation speed (inline mode)
1334
+ Animation speed (inline mode only)
1280
1335
  @property anim
1281
1336
  @type string
1282
1337
  @default false
@@ -1380,6 +1435,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1380
1435
  } else {
1381
1436
  this.init();
1382
1437
  }
1438
+
1439
+ //check for transition support
1440
+ if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
1441
+ this.options.highlight = false;
1442
+ }
1383
1443
  };
1384
1444
 
1385
1445
  Editable.prototype = {
@@ -1424,25 +1484,33 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1424
1484
  if(this.options.toggle !== 'manual') {
1425
1485
  this.$element.addClass('editable-click');
1426
1486
  this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
1427
- //prevent following link
1428
- e.preventDefault();
1487
+ //prevent following link if editable enabled
1488
+ if(!this.options.disabled) {
1489
+ e.preventDefault();
1490
+ }
1429
1491
 
1430
1492
  //stop propagation not required because in document click handler it checks event target
1431
1493
  //e.stopPropagation();
1432
1494
 
1433
1495
  if(this.options.toggle === 'mouseenter') {
1434
1496
  //for hover only show container
1435
- this.show();
1497
+ this.show();
1436
1498
  } else {
1437
1499
  //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
1438
1500
  var closeAll = (this.options.toggle !== 'click');
1439
1501
  this.toggle(closeAll);
1440
- }
1502
+ }
1441
1503
  }, this));
1442
1504
  } else {
1443
1505
  this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
1444
1506
  }
1445
1507
 
1508
+ //if display is function it's far more convinient to have autotext = always to render correctly on init
1509
+ //see https://github.com/vitalets/x-editable-yii/issues/34
1510
+ if(typeof this.options.display === 'function') {
1511
+ this.options.autotext = 'always';
1512
+ }
1513
+
1446
1514
  //check conditions for autotext:
1447
1515
  switch(this.options.autotext) {
1448
1516
  case 'always':
@@ -1621,12 +1689,29 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1621
1689
  return;
1622
1690
  }
1623
1691
 
1624
- this.isEmpty = isEmpty !== undefined ? isEmpty : $.trim(this.$element.text()) === '';
1692
+ /*
1693
+ isEmpty may be set directly as param of method.
1694
+ It is required when we enable/disable field and can't rely on content
1695
+ as node content is text: "Empty" that is not empty %)
1696
+ */
1697
+ if(isEmpty !== undefined) {
1698
+ this.isEmpty = isEmpty;
1699
+ } else {
1700
+ //detect empty
1701
+ if($.trim(this.$element.html()) === '') {
1702
+ this.isEmpty = true;
1703
+ } else if($.trim(this.$element.text()) !== '') {
1704
+ this.isEmpty = false;
1705
+ } else {
1706
+ //e.g. '<img>'
1707
+ this.isEmpty = !this.$element.height() || !this.$element.width();
1708
+ }
1709
+ }
1625
1710
 
1626
1711
  //emptytext shown only for enabled
1627
1712
  if(!this.options.disabled) {
1628
1713
  if (this.isEmpty) {
1629
- this.$element.text(this.options.emptytext);
1714
+ this.$element.html(this.options.emptytext);
1630
1715
  if(this.options.emptyclass) {
1631
1716
  this.$element.addClass(this.options.emptyclass);
1632
1717
  }
@@ -1721,6 +1806,21 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1721
1806
  }
1722
1807
  }
1723
1808
 
1809
+ //highlight when saving
1810
+ if(this.options.highlight) {
1811
+ var $e = this.$element,
1812
+ $bgColor = $e.css('background-color');
1813
+
1814
+ $e.css('background-color', this.options.highlight);
1815
+ setTimeout(function(){
1816
+ $e.css('background-color', $bgColor);
1817
+ $e.addClass('editable-bg-transition');
1818
+ setTimeout(function(){
1819
+ $e.removeClass('editable-bg-transition');
1820
+ }, 1700);
1821
+ }, 0);
1822
+ }
1823
+
1724
1824
  //set new value
1725
1825
  this.setValue(params.newValue, false, params.response);
1726
1826
 
@@ -1787,6 +1887,8 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1787
1887
  if(this.container) {
1788
1888
  this.container.destroy();
1789
1889
  }
1890
+
1891
+ this.input.destroy();
1790
1892
 
1791
1893
  if(this.options.toggle !== 'manual') {
1792
1894
  this.$element.removeClass('editable-click');
@@ -1844,28 +1946,37 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1844
1946
  /**
1845
1947
  Returns current values of editable elements.
1846
1948
  Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.
1847
- If value of some editable is `null` or `undefined` it is excluded from result object.
1949
+ If value of some editable is `null` or `undefined` it is excluded from result object.
1950
+ When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object.
1848
1951
 
1849
1952
  @method getValue()
1953
+ @param {bool} isSingle whether to return just value of single element
1850
1954
  @returns {Object} object of element names and values
1851
1955
  @example
1852
1956
  $('#username, #fullname').editable('getValue');
1853
- // possible result:
1957
+ //result:
1854
1958
  {
1855
1959
  username: "superuser",
1856
1960
  fullname: "John"
1857
1961
  }
1962
+ //isSingle = true
1963
+ $('#username').editable('getValue', true);
1964
+ //result "superuser"
1858
1965
  **/
1859
1966
  case 'getValue':
1860
- this.each(function () {
1861
- var $this = $(this), data = $this.data(datakey);
1862
- if (data && data.value !== undefined && data.value !== null) {
1863
- result[data.options.name] = data.input.value2submit(data.value);
1864
- }
1865
- });
1967
+ if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
1968
+ result = this.eq(0).data(datakey).value;
1969
+ } else {
1970
+ this.each(function () {
1971
+ var $this = $(this), data = $this.data(datakey);
1972
+ if (data && data.value !== undefined && data.value !== null) {
1973
+ result[data.options.name] = data.input.value2submit(data.value);
1974
+ }
1975
+ });
1976
+ }
1866
1977
  return result;
1867
1978
 
1868
- /**
1979
+ /**
1869
1980
  This method collects values from several editable elements and submit them all to server.
1870
1981
  Internally it runs client-side validation for all fields and submits only in case of success.
1871
1982
  See <a href="#newrecord">creating new records</a> for details.
@@ -2089,7 +2200,16 @@ Makes editable any HTML element on the page. Applied as jQuery method.
2089
2200
  });
2090
2201
  </script>
2091
2202
  **/
2092
- selector: null
2203
+ selector: null,
2204
+ /**
2205
+ Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers.
2206
+
2207
+ @property highlight
2208
+ @type string|boolean
2209
+ @since 1.4.5
2210
+ @default #FFFF80
2211
+ **/
2212
+ highlight: '#FFFF80'
2093
2213
  };
2094
2214
 
2095
2215
  }(window.jQuery));
@@ -2103,23 +2223,23 @@ To create your own input you can inherit from this class.
2103
2223
  **/
2104
2224
  (function ($) {
2105
2225
  "use strict";
2106
-
2226
+
2107
2227
  //types
2108
2228
  $.fn.editabletypes = {};
2109
-
2229
+
2110
2230
  var AbstractInput = function () { };
2111
2231
 
2112
2232
  AbstractInput.prototype = {
2113
2233
  /**
2114
2234
  Initializes input
2115
-
2235
+
2116
2236
  @method init()
2117
2237
  **/
2118
2238
  init: function(type, options, defaults) {
2119
2239
  this.type = type;
2120
2240
  this.options = $.extend({}, defaults, options);
2121
2241
  },
2122
-
2242
+
2123
2243
  /*
2124
2244
  this method called before render to init $tpl that is inserted in DOM
2125
2245
  */
@@ -2133,107 +2253,107 @@ To create your own input you can inherit from this class.
2133
2253
  /**
2134
2254
  Renders input from tpl. Can return jQuery deferred object.
2135
2255
  Can be overwritten in child objects
2136
-
2137
- @method render()
2138
- **/
2256
+
2257
+ @method render()
2258
+ **/
2139
2259
  render: function() {
2140
2260
 
2141
2261
  },
2142
2262
 
2143
2263
  /**
2144
2264
  Sets element's html by value.
2145
-
2146
- @method value2html(value, element)
2265
+
2266
+ @method value2html(value, element)
2147
2267
  @param {mixed} value
2148
2268
  @param {DOMElement} element
2149
- **/
2269
+ **/
2150
2270
  value2html: function(value, element) {
2151
- $(element).text(value);
2271
+ $(element).text($.trim(value));
2152
2272
  },
2153
-
2273
+
2154
2274
  /**
2155
2275
  Converts element's html to value
2156
-
2157
- @method html2value(html)
2276
+
2277
+ @method html2value(html)
2158
2278
  @param {string} html
2159
2279
  @returns {mixed}
2160
- **/
2280
+ **/
2161
2281
  html2value: function(html) {
2162
2282
  return $('<div>').html(html).text();
2163
2283
  },
2164
-
2284
+
2165
2285
  /**
2166
2286
  Converts value to string (for internal compare). For submitting to server used value2submit().
2167
-
2287
+
2168
2288
  @method value2str(value)
2169
2289
  @param {mixed} value
2170
2290
  @returns {string}
2171
- **/
2291
+ **/
2172
2292
  value2str: function(value) {
2173
2293
  return value;
2174
2294
  },
2175
-
2295
+
2176
2296
  /**
2177
2297
  Converts string received from server into value. Usually from `data-value` attribute.
2178
-
2179
- @method str2value(str)
2298
+
2299
+ @method str2value(str)
2180
2300
  @param {string} str
2181
2301
  @returns {mixed}
2182
- **/
2302
+ **/
2183
2303
  str2value: function(str) {
2184
2304
  return str;
2185
2305
  },
2186
2306
 
2187
2307
  /**
2188
2308
  Converts value for submitting to server. Result can be string or object.
2189
-
2309
+
2190
2310
  @method value2submit(value)
2191
2311
  @param {mixed} value
2192
2312
  @returns {mixed}
2193
- **/
2313
+ **/
2194
2314
  value2submit: function(value) {
2195
2315
  return value;
2196
- },
2197
-
2316
+ },
2317
+
2198
2318
  /**
2199
2319
  Sets value of input.
2200
-
2320
+
2201
2321
  @method value2input(value)
2202
2322
  @param {mixed} value
2203
- **/
2323
+ **/
2204
2324
  value2input: function(value) {
2205
2325
  this.$input.val(value);
2206
2326
  },
2207
-
2327
+
2208
2328
  /**
2209
2329
  Returns value of input. Value can be object (e.g. datepicker)
2210
-
2330
+
2211
2331
  @method input2value()
2212
- **/
2332
+ **/
2213
2333
  input2value: function() {
2214
2334
  return this.$input.val();
2215
2335
  },
2216
2336
 
2217
2337
  /**
2218
2338
  Activates input. For text it sets focus.
2219
-
2339
+
2220
2340
  @method activate()
2221
- **/
2341
+ **/
2222
2342
  activate: function() {
2223
2343
  if(this.$input.is(':visible')) {
2224
2344
  this.$input.focus();
2225
2345
  }
2226
2346
  },
2227
-
2347
+
2228
2348
  /**
2229
2349
  Creates input.
2230
-
2350
+
2231
2351
  @method clear()
2232
2352
  **/
2233
2353
  clear: function() {
2234
2354
  this.$input.val(null);
2235
2355
  },
2236
-
2356
+
2237
2357
  /**
2238
2358
  method to escape html.
2239
2359
  **/
@@ -2243,18 +2363,24 @@ To create your own input you can inherit from this class.
2243
2363
 
2244
2364
  /**
2245
2365
  attach handler to automatically submit form when value changed (useful when buttons not shown)
2246
- **/
2366
+ **/
2247
2367
  autosubmit: function() {
2248
2368
 
2249
2369
  },
2250
2370
 
2371
+ /**
2372
+ Additional actions when destroying element
2373
+ **/
2374
+ destroy: function() {
2375
+ },
2376
+
2251
2377
  // -------- helper functions --------
2252
2378
  setClass: function() {
2253
2379
  if(this.options.inputclass) {
2254
2380
  this.$input.addClass(this.options.inputclass);
2255
2381
  }
2256
2382
  },
2257
-
2383
+
2258
2384
  setAttr: function(attr) {
2259
2385
  if (this.options[attr] !== undefined && this.options[attr] !== null) {
2260
2386
  this.$input.attr(attr, this.options[attr]);
@@ -2356,30 +2482,33 @@ List - abstract class for inputs that have source option loaded from js array or
2356
2482
  // ------------- additional functions ------------
2357
2483
 
2358
2484
  onSourceReady: function (success, error) {
2485
+ //run source if it function
2486
+ var source;
2487
+ if ($.isFunction(this.options.source)) {
2488
+ source = this.options.source.call(this.options.scope);
2489
+ this.sourceData = null;
2490
+ //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed
2491
+ } else {
2492
+ source = this.options.source;
2493
+ }
2494
+
2359
2495
  //if allready loaded just call success
2360
- if($.isArray(this.sourceData)) {
2496
+ if(this.options.sourceCache && $.isArray(this.sourceData)) {
2361
2497
  success.call(this);
2362
2498
  return;
2363
2499
  }
2364
2500
 
2365
- // try parse json in single quotes (for double quotes jquery does automatically)
2501
+ //try parse json in single quotes (for double quotes jquery does automatically)
2366
2502
  try {
2367
- this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false);
2503
+ source = $.fn.editableutils.tryParseJson(source, false);
2368
2504
  } catch (e) {
2369
2505
  error.call(this);
2370
2506
  return;
2371
2507
  }
2372
-
2373
- var source = this.options.source;
2374
-
2375
- //run source if it function
2376
- if ($.isFunction(source)) {
2377
- source = source.call(this.options.scope);
2378
- }
2379
2508
 
2380
2509
  //loading from url
2381
2510
  if (typeof source === 'string') {
2382
- //try to get from cache
2511
+ //try to get sourceData from cache
2383
2512
  if(this.options.sourceCache) {
2384
2513
  var cacheID = source,
2385
2514
  cache;
@@ -3299,25 +3428,29 @@ Range (inherit from number)
3299
3428
  }(window.jQuery));
3300
3429
  /**
3301
3430
  Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
3302
- Please see [original docs](http://ivaynberg.github.com/select2) for detailed description and options.
3303
- You should manually include select2 distributive:
3431
+ Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
3432
+ Compatible **select2 version is 3.4.1**!
3433
+ You should manually download and include select2 distributive:
3304
3434
 
3305
3435
  <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
3306
3436
  <script src="select2/select2.js"></script>
3307
3437
 
3308
- For make it **Bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
3438
+ To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
3309
3439
 
3310
3440
  <link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>
3311
3441
 
3312
- **Note:** currently `ajax` source for select2 is not supported, as it's not possible to load it in closed select2 state.
3313
- The solution is to load source manually and assign statically.
3442
+ **Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.
3443
+ You need initially put both `data-value` and element's text youself:
3444
+
3445
+ <a href="#" data-type="select2" data-value="1">Text1</a>
3446
+
3314
3447
 
3315
3448
  @class select2
3316
3449
  @extends abstractinput
3317
3450
  @since 1.4.1
3318
3451
  @final
3319
3452
  @example
3320
- <a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-original-title="Select country"></a>
3453
+ <a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>
3321
3454
  <script>
3322
3455
  $(function(){
3323
3456
  $('#country').editable({
@@ -3338,56 +3471,47 @@ $(function(){
3338
3471
 
3339
3472
  var Constructor = function (options) {
3340
3473
  this.init('select2', options, Constructor.defaults);
3341
-
3474
+
3342
3475
  options.select2 = options.select2 || {};
3476
+
3477
+ this.sourceData = null;
3343
3478
 
3344
- var that = this,
3345
- mixin = { //mixin to select2 options
3346
- placeholder: options.placeholder
3347
- };
3348
-
3349
- //detect whether it is multi-valued
3350
- this.isMultiple = options.select2.tags || options.select2.multiple;
3479
+ //placeholder
3480
+ if(options.placeholder) {
3481
+ options.select2.placeholder = options.placeholder;
3482
+ }
3351
3483
 
3352
- //if not `tags` mode, we need define initSelection to set data from source
3353
- if(!options.select2.tags) {
3354
- if(options.source) {
3355
- mixin.data = options.source;
3356
- }
3484
+ //if not `tags` mode, use source
3485
+ if(!options.select2.tags && options.source) {
3486
+ var source = options.source;
3487
+ //if source is function, call it (once!)
3488
+ if ($.isFunction(options.source)) {
3489
+ source = options.source.call(options.scope);
3490
+ }
3357
3491
 
3358
- //this function can be defaulted in seletc2. See https://github.com/ivaynberg/select2/issues/710
3359
- mixin.initSelection = function (element, callback) {
3360
- //temp: try update results
3361
- /*
3362
- if(options.select2 && options.select2.ajax) {
3363
- console.log('attached');
3364
- var original = $(element).data('select2').postprocessResults;
3365
- console.log(original);
3366
- $(element).data('select2').postprocessResults = function(data, initial) {
3367
- console.log('postprocess');
3368
- // this.element.triggerHandler('loaded', [data]);
3369
- original.apply(this, arguments);
3370
- }
3371
-
3372
- // $(element).on('loaded', function(){console.log('loaded');});
3373
- $(element).data('select2').updateResults(true);
3492
+ if (typeof source === 'string') {
3493
+ options.select2.ajax = options.select2.ajax || {};
3494
+ //some default ajax params
3495
+ if(!options.select2.ajax.data) {
3496
+ options.select2.ajax.data = function(term) {return { query:term };};
3374
3497
  }
3375
- */
3376
-
3377
- var val = that.str2value(element.val()),
3378
- data = $.fn.editableutils.itemsByValue(val, mixin.data, 'id');
3379
-
3380
- //for single-valued mode should not use array. Take first element instead.
3381
- if($.isArray(data) && data.length && !that.isMultiple) {
3382
- data = data[0];
3498
+ if(!options.select2.ajax.results) {
3499
+ options.select2.ajax.results = function(data) { return {results:data };};
3383
3500
  }
3384
-
3385
- callback(data);
3386
- };
3387
- }
3501
+ options.select2.ajax.url = source;
3502
+ } else {
3503
+ //check format and convert x-editable format to select2 format (if needed)
3504
+ this.sourceData = this.convertSource(source);
3505
+ options.select2.data = this.sourceData;
3506
+ }
3507
+ }
3388
3508
 
3389
3509
  //overriding objects in config (as by default jQuery extend() is not recursive)
3390
- this.options.select2 = $.extend({}, Constructor.defaults.select2, mixin, options.select2);
3510
+ this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
3511
+
3512
+ //detect whether it is multi-valued
3513
+ this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
3514
+ this.isRemote = ('ajax' in this.options.select2);
3391
3515
  };
3392
3516
 
3393
3517
  $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
@@ -3395,21 +3519,17 @@ $(function(){
3395
3519
  $.extend(Constructor.prototype, {
3396
3520
  render: function() {
3397
3521
  this.setClass();
3522
+
3398
3523
  //apply select2
3399
3524
  this.$input.select2(this.options.select2);
3400
3525
 
3401
- //when data is loaded via ajax, we need to know when it's done
3402
- if('ajax' in this.options.select2) {
3403
- /*
3404
- console.log('attached');
3405
- var original = this.$input.data('select2').postprocessResults;
3406
- this.$input.data('select2').postprocessResults = function(data, initial) {
3407
- this.element.triggerHandler('loaded', [data]);
3408
- original.apply(this, arguments);
3409
- }
3410
- */
3526
+ //when data is loaded via ajax, we need to know when it's done to populate listData
3527
+ if(this.isRemote) {
3528
+ //listen to loaded event to populate data
3529
+ this.$input.on('select2-loaded', $.proxy(function(e) {
3530
+ this.sourceData = e.items.results;
3531
+ }, this));
3411
3532
  }
3412
-
3413
3533
 
3414
3534
  //trigger resize of editableform to re-position container in multi-valued mode
3415
3535
  if(this.isMultiple) {
@@ -3421,20 +3541,16 @@ $(function(){
3421
3541
 
3422
3542
  value2html: function(value, element) {
3423
3543
  var text = '', data;
3424
- if(this.$input) { //called when submitting form and select2 already exists
3425
- data = this.$input.select2('data');
3426
- } else { //on init (autotext)
3427
- //here select2 instance not created yet and data may be even not loaded.
3428
- //we can check data/tags property of select config and if exist lookup text
3429
- if(this.options.select2.tags) {
3430
- data = value;
3431
- } else if(this.options.select2.data) {
3432
- data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id');
3433
- } else {
3434
- //if('ajax' in this.options.select2) {
3435
- }
3544
+
3545
+ if(this.options.select2.tags) { //in tags mode just assign value
3546
+ data = value;
3547
+ } else if(this.sourceData) {
3548
+ data = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
3549
+ } else {
3550
+ //can not get list of possible values (e.g. autotext for select2 with ajax source)
3436
3551
  }
3437
3552
 
3553
+ //data may be array (when multiple values allowed)
3438
3554
  if($.isArray(data)) {
3439
3555
  //collect selected data and show with separator
3440
3556
  text = [];
@@ -3455,7 +3571,26 @@ $(function(){
3455
3571
  },
3456
3572
 
3457
3573
  value2input: function(value) {
3458
- this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
3574
+ //for remote source .val() is not working, need to look in sourceData
3575
+ if(this.isRemote) {
3576
+ //todo: check value for array
3577
+ var item, items;
3578
+ //if sourceData loaded, use it to get text for display
3579
+ if(this.sourceData) {
3580
+ items = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
3581
+ if(items.length) {
3582
+ item = items[0];
3583
+ }
3584
+ }
3585
+ //if item not found by sourceData, use element text (e.g. for the first show)
3586
+ if(!item) {
3587
+ item = {id: value, text: $(this.options.scope).text()};
3588
+ }
3589
+ //select2('data', ...) allows to set both id and text --> usefull for initial show when items are not loaded
3590
+ this.$input.select2('data', item).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
3591
+ } else {
3592
+ this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
3593
+ }
3459
3594
  },
3460
3595
 
3461
3596
  input2value: function() {
@@ -3488,6 +3623,22 @@ $(function(){
3488
3623
  $(this).closest('form').submit();
3489
3624
  }
3490
3625
  });
3626
+ },
3627
+
3628
+ /*
3629
+ Converts source from x-editable format: {value: 1, text: "1"} to
3630
+ select2 format: {id: 1, text: "1"}
3631
+ */
3632
+ convertSource: function(source) {
3633
+ if($.isArray(source) && source.length && source[0].value !== undefined) {
3634
+ for(var i = 0; i<source.length; i++) {
3635
+ if(source[i].value !== undefined) {
3636
+ source[i].id = source[i].value;
3637
+ delete source[i].value;
3638
+ }
3639
+ }
3640
+ }
3641
+ return source;
3491
3642
  }
3492
3643
 
3493
3644
  });
@@ -3539,12 +3690,22 @@ $(function(){
3539
3690
  }(window.jQuery));
3540
3691
 
3541
3692
  /**
3542
- * Combodate - 1.0.3
3693
+ * Combodate - 1.0.4
3543
3694
  * Dropdown date and time picker.
3544
3695
  * Converts text input into dropdowns to pick day, month, year, hour, minute and second.
3545
3696
  * Uses momentjs as datetime library http://momentjs.com.
3546
3697
  * For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang
3547
3698
  *
3699
+ * Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight
3700
+ * In combodate:
3701
+ * 12:00 pm --> 12:00 (24-h format, midday)
3702
+ * 12:00 am --> 00:00 (24-h format, midnight, start of day)
3703
+ *
3704
+ * Differs from momentjs parse rules:
3705
+ * 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change)
3706
+ * 00:00 am, 12:00 am --> 00:00 (24-h format, day not change)
3707
+ *
3708
+ *
3548
3709
  * Author: Vitaliy Potapov
3549
3710
  * Project page: http://github.com/vitalets/combodate
3550
3711
  * Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
@@ -3694,9 +3855,10 @@ $(function(){
3694
3855
 
3695
3856
  for(i=0; i<=11; i++) {
3696
3857
  if(longNames) {
3697
- name = moment().month(i).format('MMMM');
3858
+ //see https://github.com/timrwood/momentjs.com/pull/36
3859
+ name = moment().date(1).month(i).format('MMMM');
3698
3860
  } else if(shortNames) {
3699
- name = moment().month(i).format('MMM');
3861
+ name = moment().date(1).month(i).format('MMM');
3700
3862
  } else if(twoDigit) {
3701
3863
  name = this.leadZero(i+1);
3702
3864
  } else {
@@ -3732,9 +3894,10 @@ $(function(){
3732
3894
  h12 = this.options.template.indexOf('h') !== -1,
3733
3895
  h24 = this.options.template.indexOf('H') !== -1,
3734
3896
  twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
3897
+ min = h12 ? 1 : 0,
3735
3898
  max = h12 ? 12 : 23;
3736
3899
 
3737
- for(i=0; i<=max; i++) {
3900
+ for(i=min; i<=max; i++) {
3738
3901
  name = twoDigit ? this.leadZero(i) : i;
3739
3902
  items.push([i, name]);
3740
3903
  }
@@ -3783,7 +3946,7 @@ $(function(){
3783
3946
  },
3784
3947
 
3785
3948
  /*
3786
- Returns current date value.
3949
+ Returns current date value from combos.
3787
3950
  If format not specified - `options.format` used.
3788
3951
  If format = `null` - Moment object returned.
3789
3952
  */
@@ -3812,12 +3975,14 @@ $(function(){
3812
3975
  return '';
3813
3976
  }
3814
3977
 
3815
- //convert hours if 12h format
3978
+ //convert hours 12h --> 24h
3816
3979
  if(this.$ampm) {
3817
- values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
3818
- if(values.hour === 24) {
3819
- values.hour = 0;
3820
- }
3980
+ //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
3981
+ if(values.hour === 12) {
3982
+ values.hour = this.$ampm.val() === 'am' ? 0 : 12;
3983
+ } else {
3984
+ values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
3985
+ }
3821
3986
  }
3822
3987
 
3823
3988
  dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
@@ -3868,11 +4033,17 @@ $(function(){
3868
4033
  });
3869
4034
 
3870
4035
  if(this.$ampm) {
3871
- if(values.hour > 12) {
3872
- values.hour -= 12;
4036
+ //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
4037
+ if(values.hour >= 12) {
3873
4038
  values.ampm = 'pm';
4039
+ if(values.hour > 12) {
4040
+ values.hour -= 12;
4041
+ }
3874
4042
  } else {
3875
- values.ampm = 'am';
4043
+ values.ampm = 'am';
4044
+ if(values.hour === 0) {
4045
+ values.hour = 12;
4046
+ }
3876
4047
  }
3877
4048
  }
3878
4049
 
@@ -4206,7 +4377,7 @@ $(function(){
4206
4377
  this.call('update', $content);
4207
4378
  this.call('show');
4208
4379
 
4209
- this.tip().addClass('editable-container');
4380
+ this.tip().addClass(this.containerClass);
4210
4381
  this.$form.data('editableform').input.activate();
4211
4382
  },
4212
4383