bootstrap-x-editable-rails 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # X-editable for Rails asset pipeline
1
+ # X-editable 1.4.1 for Rails
2
2
 
3
3
  [X-editable](https://github.com/vitalets/x-editable) is an in-place editing plugin with support for Twitter Bootstrap, jQuery UI or pure jQuery.
4
4
 
5
- The `bootstrap-x-editable-rails` gem integrates `X-editable` with the Rails asset pipeline. This gem only supports the Bootstrap part of X-editable.
5
+ The `bootstrap-x-editable-rails` gem integrates `X-editable` with Rails asset pipeline. This gem only supports the Bootstrap part of X-editable.
6
6
 
7
7
  ## Usage
8
8
 
@@ -31,3 +31,14 @@ Add to your `app/assets/javascripts/application.js`
31
31
  Add to your `app/assets/stylesheets/application.css`
32
32
 
33
33
  *= require bootstrap-editable
34
+
35
+ ## Updating the gem
36
+ There are two rake tasks designed to ease the maintenance of this gem.
37
+
38
+ The `update` task pulls the latest X-editable code from github and places images, stylesheets and javascripts in the correct gem paths. It also changes background-image properties in the stylesheet to asset pipeline equivalents.
39
+
40
+ rake update
41
+
42
+ The `build` task is a simple wrapper for `gem build`
43
+
44
+ rake build
@@ -0,0 +1,38 @@
1
+ desc "Update"
2
+ task :update do
3
+ src_path = "x-editable-src"
4
+ dist_path = "#{src_path}/dist/bootstrap-editable"
5
+
6
+ system("rm -rf #{src_path}")
7
+
8
+ system("git clone https://github.com/vitalets/x-editable #{src_path}")
9
+ system("cd #{src_path} && npm install")
10
+ system("cd #{src_path} && grunt build")
11
+
12
+ system("cp #{dist_path}/img/clear.png vendor/assets/images/")
13
+ system("cp #{dist_path}/img/loading.gif vendor/assets/images/")
14
+ system("cp #{dist_path}/css/bootstrap-editable.css vendor/assets/stylesheets/bootstrap-editable.scss")
15
+ system("cp #{dist_path}/js/bootstrap-editable.js vendor/assets/javascripts/")
16
+
17
+ fixes
18
+
19
+ system("rm -rf x-editable-src")
20
+ end
21
+
22
+ def fixes
23
+ replace_string_in_file("vendor/assets/stylesheets/bootstrap-editable.scss", "url('../img/loading.gif')", "image-url('loading.gif')")
24
+ replace_string_in_file("vendor/assets/stylesheets/bootstrap-editable.scss", "url('../img/clear.png')", "image-url('clear.png')")
25
+ end
26
+
27
+ def replace_string_in_file(file, find, replace)
28
+ file_content = File.read(file)
29
+
30
+ File.open(file, "w") do |f|
31
+ f.puts file_content.gsub!(find, replace)
32
+ end
33
+ end
34
+
35
+ desc "Build"
36
+ task "build" do
37
+ system("gem build bootstrap-x-editable-rails.gemspec")
38
+ end
@@ -1,5 +1,5 @@
1
1
  module BootstrapXEditableRails
2
2
  module Rails
3
- VERSION = "1.4.0"
3
+ VERSION = "1.4.1"
4
4
  end
5
5
  end
@@ -1,4 +1,4 @@
1
- /*! X-editable - v1.4.0
1
+ /*! X-editable - v1.4.1
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 */
@@ -16,7 +16,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
16
16
 
17
17
  var EditableForm = function (div, options) {
18
18
  this.options = $.extend({}, $.fn.editableform.defaults, options);
19
- this.$div = $(div); //div, containing form. Not form tag! Not editable-element.
19
+ this.$div = $(div); //div, containing form. Not form tag. Not editable-element.
20
20
  if(!this.options.scope) {
21
21
  this.options.scope = this;
22
22
  }
@@ -30,6 +30,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
30
30
  this.input = this.options.input;
31
31
 
32
32
  //set initial value
33
+ //todo: may be add check: typeof str === 'string' ?
33
34
  this.value = this.input.str2value(this.options.value);
34
35
  },
35
36
  initTemplate: function() {
@@ -232,6 +233,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
232
233
  }
233
234
 
234
235
  //if success callback returns object like {newValue: <something>} --> use that value instead of submitted
236
+ //it is usefull if you want to chnage value in url-function
235
237
  if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
236
238
  newValue = res.newValue;
237
239
  }
@@ -387,18 +389,25 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
387
389
  type: 'text',
388
390
  /**
389
391
  Url for submit, e.g. <code>'/post'</code>
390
- If function - it will be called instead of ajax. Function can return deferred object to run fail/done callbacks.
392
+ If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks.
391
393
 
392
394
  @property url
393
395
  @type string|function
394
396
  @default null
395
397
  @example
396
398
  url: function(params) {
399
+ var d = new $.Deferred;
397
400
  if(params.value === 'abc') {
398
- var d = new $.Deferred;
399
401
  return d.reject('error message'); //returning error via deferred object
400
402
  } else {
401
- someModel.set(params.name, params.value); //save data in some js model
403
+ //async saving data in js model
404
+ someModel.asyncSaveMethod({
405
+ ...,
406
+ success: function(){
407
+ d.resolve();
408
+ }
409
+ });
410
+ return d.promise();
402
411
  }
403
412
  }
404
413
  **/
@@ -689,21 +698,34 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
689
698
  /*
690
699
  returns array items from sourceData having value property equal or inArray of 'value'
691
700
  */
692
- itemsByValue: function(value, sourceData) {
701
+ itemsByValue: function(value, sourceData, valueProp) {
693
702
  if(!sourceData || value === null) {
694
703
  return [];
695
704
  }
696
705
 
697
- //convert to array
698
- if(!$.isArray(value)) {
699
- value = [value];
700
- }
706
+ valueProp = valueProp || 'value';
701
707
 
702
- /*jslint eqeq: true*/
703
- var result = $.grep(sourceData, function(o){
704
- return $.grep(value, function(v){ return v == o.value; }).length;
708
+ var isValArray = $.isArray(value),
709
+ result = [],
710
+ that = this;
711
+
712
+ $.each(sourceData, function(i, o) {
713
+ if(o.children) {
714
+ result = result.concat(that.itemsByValue(value, o.children));
715
+ } else {
716
+ /*jslint eqeq: true*/
717
+ if(isValArray) {
718
+ if($.grep(value, function(v){ return v == (o && typeof o === 'object' ? o[valueProp] : o); }).length) {
719
+ result.push(o);
720
+ }
721
+ } else {
722
+ if(value == (o && typeof o === 'object' ? o[valueProp] : o)) {
723
+ result.push(o);
724
+ }
725
+ }
726
+ /*jslint eqeq: false*/
727
+ }
705
728
  });
706
- /*jslint eqeq: false*/
707
729
 
708
730
  return result;
709
731
  },
@@ -785,8 +807,8 @@ Applied as jQuery method.
785
807
  innerCss: null, //tbd in child class
786
808
  init: function(element, options) {
787
809
  this.$element = $(element);
788
- //todo: what is in priority: data or js?
789
- this.options = $.extend({}, $.fn.editableContainer.defaults, $.fn.editableutils.getConfigData(this.$element), options);
810
+ //since 1.4.1 container do not use data-* directly as they already merged into options.
811
+ this.options = $.extend({}, $.fn.editableContainer.defaults, options);
790
812
  this.splitOptions();
791
813
 
792
814
  //set scope of form callbacks to element
@@ -878,6 +900,7 @@ Applied as jQuery method.
878
900
  cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button
879
901
  show: $.proxy(this.setPosition, this), //re-position container every time form is shown (occurs each time after loading state)
880
902
  rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
903
+ resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
881
904
  rendered: $.proxy(function(){
882
905
  /**
883
906
  Fired when container is shown and form is rendered (for select will wait for loading dropdown options)
@@ -1004,7 +1027,6 @@ Applied as jQuery method.
1004
1027
  },
1005
1028
 
1006
1029
  save: function(e, params) {
1007
- this.hide('save');
1008
1030
  /**
1009
1031
  Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
1010
1032
 
@@ -1025,6 +1047,9 @@ Applied as jQuery method.
1025
1047
  });
1026
1048
  **/
1027
1049
  this.$element.triggerHandler('save', params);
1050
+
1051
+ //hide must be after trigger, as saving value may require methods od plugin, applied to input
1052
+ this.hide('save');
1028
1053
  },
1029
1054
 
1030
1055
  /**
@@ -1255,12 +1280,14 @@ Applied as jQuery method.
1255
1280
  innerHide: function () {
1256
1281
  this.$tip.hide(this.options.anim, $.proxy(function() {
1257
1282
  this.$element.show();
1258
- this.tip().empty().remove();
1283
+ this.innerDestroy();
1259
1284
  }, this));
1260
1285
  },
1261
1286
 
1262
1287
  innerDestroy: function() {
1263
- this.tip().remove();
1288
+ if(this.tip()) {
1289
+ this.tip().empty().remove();
1290
+ }
1264
1291
  }
1265
1292
  });
1266
1293
 
@@ -1275,8 +1302,13 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1275
1302
 
1276
1303
  var Editable = function (element, options) {
1277
1304
  this.$element = $(element);
1278
- this.options = $.extend({}, $.fn.editable.defaults, $.fn.editableutils.getConfigData(this.$element), options);
1279
- this.init();
1305
+ //data-* has more priority over js options: because dynamically created elements may change data-*
1306
+ this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element));
1307
+ if(this.options.selector) {
1308
+ this.initLive();
1309
+ } else {
1310
+ this.init();
1311
+ }
1280
1312
  };
1281
1313
 
1282
1314
  Editable.prototype = {
@@ -1319,8 +1351,10 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1319
1351
  if(this.options.toggle !== 'manual') {
1320
1352
  this.$element.addClass('editable-click');
1321
1353
  this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
1354
+ //prevent following link
1322
1355
  e.preventDefault();
1323
- //stop propagation not required anymore because in document click handler it checks event target
1356
+
1357
+ //stop propagation not required because in document click handler it checks event target
1324
1358
  //e.stopPropagation();
1325
1359
 
1326
1360
  if(this.options.toggle === 'mouseenter') {
@@ -1362,6 +1396,24 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1362
1396
  }, this));
1363
1397
  },
1364
1398
 
1399
+ /*
1400
+ Initializes parent element for live editables
1401
+ */
1402
+ initLive: function() {
1403
+ //store selector
1404
+ var selector = this.options.selector;
1405
+ //modify options for child elements
1406
+ this.options.selector = false;
1407
+ this.options.autotext = 'never';
1408
+ //listen toggle events
1409
+ this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){
1410
+ var $target = $(e.target);
1411
+ if(!$target.data('editable')) {
1412
+ $target.editable(this.options).trigger(e);
1413
+ }
1414
+ }, this));
1415
+ },
1416
+
1365
1417
  /*
1366
1418
  Renders value into element's text.
1367
1419
  Can call custom display method from options.
@@ -1375,8 +1427,8 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1375
1427
  return;
1376
1428
  }
1377
1429
 
1378
- //if it is input with source, we pass callback in third param to be called when source is loaded
1379
- if(this.input.options.hasOwnProperty('source')) {
1430
+ //if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded
1431
+ if(this.input.value2htmlFinal) {
1380
1432
  return this.input.value2html(this.value, this.$element[0], this.options.display, response);
1381
1433
  //if display method defined --> use it
1382
1434
  } else if(typeof this.options.display === 'function') {
@@ -1394,7 +1446,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1394
1446
  enable: function() {
1395
1447
  this.options.disabled = false;
1396
1448
  this.$element.removeClass('editable-disabled');
1397
- this.handleEmpty();
1449
+ this.handleEmpty(this.isEmpty);
1398
1450
  if(this.options.toggle !== 'manual') {
1399
1451
  if(this.$element.attr('tabindex') === '-1') {
1400
1452
  this.$element.removeAttr('tabindex');
@@ -1410,7 +1462,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1410
1462
  this.options.disabled = true;
1411
1463
  this.hide();
1412
1464
  this.$element.addClass('editable-disabled');
1413
- this.handleEmpty();
1465
+ this.handleEmpty(this.isEmpty);
1414
1466
  //do not stop focus on this element
1415
1467
  this.$element.attr('tabindex', -1);
1416
1468
  },
@@ -1471,27 +1523,33 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1471
1523
  },
1472
1524
 
1473
1525
  /*
1474
- * set emptytext if element is empty (reverse: remove emptytext if needed)
1526
+ * set emptytext if element is empty
1475
1527
  */
1476
- handleEmpty: function () {
1528
+ handleEmpty: function (isEmpty) {
1477
1529
  //do not handle empty if we do not display anything
1478
1530
  if(this.options.display === false) {
1479
1531
  return;
1480
1532
  }
1481
1533
 
1482
- var emptyClass = 'editable-empty';
1534
+ this.isEmpty = isEmpty !== undefined ? isEmpty : $.trim(this.$element.text()) === '';
1535
+
1483
1536
  //emptytext shown only for enabled
1484
1537
  if(!this.options.disabled) {
1485
- if ($.trim(this.$element.text()) === '') {
1486
- this.$element.addClass(emptyClass).text(this.options.emptytext);
1487
- } else {
1488
- this.$element.removeClass(emptyClass);
1538
+ if (this.isEmpty) {
1539
+ this.$element.text(this.options.emptytext);
1540
+ if(this.options.emptyclass) {
1541
+ this.$element.addClass(this.options.emptyclass);
1542
+ }
1543
+ } else if(this.options.emptyclass) {
1544
+ this.$element.removeClass(this.options.emptyclass);
1489
1545
  }
1490
1546
  } else {
1491
1547
  //below required if element disable property was changed
1492
- if(this.$element.hasClass(emptyClass)) {
1548
+ if(this.isEmpty) {
1493
1549
  this.$element.empty();
1494
- this.$element.removeClass(emptyClass);
1550
+ if(this.options.emptyclass) {
1551
+ this.$element.removeClass(this.options.emptyclass);
1552
+ }
1495
1553
  }
1496
1554
  }
1497
1555
  },
@@ -1513,6 +1571,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1513
1571
  input: this.input //pass input to form (as it is already created)
1514
1572
  });
1515
1573
  this.$element.editableContainer(containerOptions);
1574
+ //listen `save` event
1516
1575
  this.$element.on("save.internal", $.proxy(this.save, this));
1517
1576
  this.container = this.$element.data('editableContainer');
1518
1577
  } else if(this.container.tip().is(':visible')) {
@@ -1550,13 +1609,29 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1550
1609
  * called when form was submitted
1551
1610
  */
1552
1611
  save: function(e, params) {
1553
- //if url is not user's function and value was not sent to server and value changed --> mark element with unsaved css.
1554
- if(typeof this.options.url !== 'function' && this.options.display !== false && params.response === undefined && this.input.value2str(this.value) !== this.input.value2str(params.newValue)) {
1555
- this.$element.addClass('editable-unsaved');
1556
- } else {
1557
- this.$element.removeClass('editable-unsaved');
1612
+ //mark element with unsaved class if needed
1613
+ if(this.options.unsavedclass) {
1614
+ /*
1615
+ Add unsaved css to element if:
1616
+ - url is not user's function
1617
+ - value was not sent to server
1618
+ - params.response === undefined, that means data was not sent
1619
+ - value changed
1620
+ */
1621
+ var sent = false;
1622
+ sent = sent || typeof this.options.url === 'function';
1623
+ sent = sent || this.options.display === false;
1624
+ sent = sent || params.response !== undefined;
1625
+ sent = sent || (this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(params.newValue));
1626
+
1627
+ if(sent) {
1628
+ this.$element.removeClass(this.options.unsavedclass);
1629
+ } else {
1630
+ this.$element.addClass(this.options.unsavedclass);
1631
+ }
1558
1632
  }
1559
1633
 
1634
+ //set new value
1560
1635
  this.setValue(params.newValue, false, params.response);
1561
1636
 
1562
1637
  /**
@@ -1648,7 +1723,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1648
1723
  url: '/post',
1649
1724
  pk: 1
1650
1725
  });
1651
- **/
1726
+ **/
1652
1727
  $.fn.editable = function (option) {
1653
1728
  //special API methods returning non-jquery object
1654
1729
  var result = {}, args = arguments, datakey = 'editable';
@@ -1665,7 +1740,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1665
1740
  username: "username is required",
1666
1741
  fullname: "fullname should be minimum 3 letters length"
1667
1742
  }
1668
- **/
1743
+ **/
1669
1744
  case 'validate':
1670
1745
  this.each(function () {
1671
1746
  var $this = $(this), data = $this.data(datakey), error;
@@ -1686,7 +1761,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1686
1761
  username: "superuser",
1687
1762
  fullname: "John"
1688
1763
  }
1689
- **/
1764
+ **/
1690
1765
  case 'getValue':
1691
1766
  this.each(function () {
1692
1767
  var $this = $(this), data = $this.data(datakey);
@@ -1705,11 +1780,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1705
1780
  @param {object} options
1706
1781
  @param {object} options.url url to submit data
1707
1782
  @param {object} options.data additional data to submit
1708
- @param {object} options.ajaxOptions additional ajax options
1783
+ @param {object} options.ajaxOptions additional ajax options
1709
1784
  @param {function} options.error(obj) error handler
1710
1785
  @param {function} options.success(obj,config) success handler
1711
1786
  @returns {Object} jQuery object
1712
- **/
1787
+ **/
1713
1788
  case 'submit': //collects value, validate and submit to server for creating new record
1714
1789
  var config = arguments[1] || {},
1715
1790
  $elems = this,
@@ -1860,7 +1935,51 @@ Makes editable any HTML element on the page. Applied as jQuery method.
1860
1935
  }
1861
1936
  }
1862
1937
  **/
1863
- display: null
1938
+ display: null,
1939
+ /**
1940
+ Css class applied when editable text is empty.
1941
+
1942
+ @property emptyclass
1943
+ @type string
1944
+ @since 1.4.1
1945
+ @default editable-empty
1946
+ **/
1947
+ emptyclass: 'editable-empty',
1948
+ /**
1949
+ Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`).
1950
+ You may set it to `null` if you work with editables locally and submit them together.
1951
+
1952
+ @property unsavedclass
1953
+ @type string
1954
+ @since 1.4.1
1955
+ @default editable-unsaved
1956
+ **/
1957
+ unsavedclass: 'editable-unsaved',
1958
+ /**
1959
+ If a css selector is provided, editable will be delegated to the specified targets.
1960
+ Usefull for dynamically generated DOM elements.
1961
+ **Please note**, that delegated targets can't use `emptytext` and `autotext` options,
1962
+ as they are initialized after first click.
1963
+
1964
+ @property selector
1965
+ @type string
1966
+ @since 1.4.1
1967
+ @default null
1968
+ @example
1969
+ <div id="user">
1970
+ <a href="#" data-name="username" data-type="text" title="Username">awesome</a>
1971
+ <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" title="Group">Operator</a>
1972
+ </div>
1973
+
1974
+ <script>
1975
+ $('#user').editable({
1976
+ selector: 'a',
1977
+ url: '/post',
1978
+ pk: 1
1979
+ });
1980
+ </script>
1981
+ **/
1982
+ selector: null
1864
1983
  };
1865
1984
 
1866
1985
  }(window.jQuery));
@@ -2053,15 +2172,7 @@ To create your own input you can inherit from this class.
2053
2172
  @type string
2054
2173
  @default input-medium
2055
2174
  **/
2056
- inputclass: 'input-medium',
2057
- /**
2058
- Name attribute of input
2059
-
2060
- @property name
2061
- @type string
2062
- @default null
2063
- **/
2064
- name: null
2175
+ inputclass: 'input-medium'
2065
2176
  };
2066
2177
 
2067
2178
  $.extend($.fn.editabletypes, {abstractinput: AbstractInput});
@@ -2214,7 +2325,7 @@ List - abstract class for inputs that have source option loaded from js array or
2214
2325
  }, this)
2215
2326
  });
2216
2327
  } else { //options as json/array/function
2217
- if (typeof this.options.source === 'function') {
2328
+ if ($.isFunction(this.options.source)) {
2218
2329
  this.sourceData = this.makeArray(this.options.source());
2219
2330
  } else {
2220
2331
  this.sourceData = this.makeArray(this.options.source);
@@ -2270,35 +2381,45 @@ List - abstract class for inputs that have source option loaded from js array or
2270
2381
  * convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}]
2271
2382
  */
2272
2383
  makeArray: function(data) {
2273
- var count, obj, result = [], iterateEl;
2384
+ var count, obj, result = [], item, iterateItem;
2274
2385
  if(!data || typeof data === 'string') {
2275
2386
  return null;
2276
2387
  }
2277
2388
 
2278
2389
  if($.isArray(data)) { //array
2279
- iterateEl = function (k, v) {
2390
+ /*
2391
+ function to iterate inside item of array if item is object.
2392
+ Caclulates count of keys in item and store in obj.
2393
+ */
2394
+ iterateItem = function (k, v) {
2280
2395
  obj = {value: k, text: v};
2281
2396
  if(count++ >= 2) {
2282
- return false;// exit each if object has more than one value
2397
+ return false;// exit from `each` if item has more than one key.
2283
2398
  }
2284
2399
  };
2285
2400
 
2286
2401
  for(var i = 0; i < data.length; i++) {
2287
- if(typeof data[i] === 'object') {
2288
- count = 0;
2289
- $.each(data[i], iterateEl);
2290
- if(count === 1) {
2402
+ item = data[i];
2403
+ if(typeof item === 'object') {
2404
+ count = 0; //count of keys inside item
2405
+ $.each(item, iterateItem);
2406
+ //case: [{val1: 'text1'}, {val2: 'text2} ...]
2407
+ if(count === 1) {
2291
2408
  result.push(obj);
2292
- } else if(count > 1 && data[i].hasOwnProperty('value') && data[i].hasOwnProperty('text')) {
2293
- result.push(data[i]);
2294
- } else {
2295
- //data contains incorrect objects
2409
+ //case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...]
2410
+ } else if(count > 1) {
2411
+ //removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text')
2412
+ if(item.children) {
2413
+ item.children = this.makeArray(item.children);
2414
+ }
2415
+ result.push(item);
2296
2416
  }
2297
2417
  } else {
2298
- result.push({value: data[i], text: data[i]});
2418
+ //case: ['text1', 'text2' ...]
2419
+ result.push({value: item, text: item});
2299
2420
  }
2300
2421
  }
2301
- } else { //object
2422
+ } else { //case: {val1: 'text1', val2: 'text2, ...}
2302
2423
  $.each(data, function (k, v) {
2303
2424
  result.push({value: k, text: v});
2304
2425
  });
@@ -2321,12 +2442,16 @@ List - abstract class for inputs that have source option loaded from js array or
2321
2442
  List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
2322
2443
  /**
2323
2444
  Source data for list.
2324
- If **array** - it should be in format: `[{value: 1, text: "text1"}, {...}]`
2445
+ If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`
2325
2446
  For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order.
2326
2447
 
2327
2448
  If **string** - considered ajax url to load items. In that case results will be cached for fields with the same source and name. See also `sourceCache` option.
2328
2449
 
2329
2450
  If **function**, it should return data in format above (since 1.4.0).
2451
+
2452
+ Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only).
2453
+ `[{text: "group1", children: [{value: 1, text: "text1"}, {value: 2, text: "text2"}]}, ...]`
2454
+
2330
2455
 
2331
2456
  @property source
2332
2457
  @type string | array | object | function
@@ -2350,8 +2475,8 @@ List - abstract class for inputs that have source option loaded from js array or
2350
2475
  **/
2351
2476
  sourceError: 'Error when loading list',
2352
2477
  /**
2353
- if <code>true</code> and source is **string url** - results will be cached for fields with the same source and name.
2354
- Usefull for editable grids.
2478
+ if <code>true</code> and source is **string url** - results will be cached for fields with the same source.
2479
+ Usefull for editable column in grid to prevent extra requests.
2355
2480
 
2356
2481
  @property sourceCache
2357
2482
  @type boolean
@@ -2415,10 +2540,7 @@ $(function(){
2415
2540
  .keyup($.proxy(this.toggleClear, this))
2416
2541
  .parent().css('position', 'relative');
2417
2542
 
2418
- this.$clear.click($.proxy(function(){
2419
- this.$clear.hide();
2420
- this.$input.val('').focus();
2421
- }, this));
2543
+ this.$clear.click($.proxy(this.clear, this));
2422
2544
  }
2423
2545
  },
2424
2546
 
@@ -2443,12 +2565,17 @@ $(function(){
2443
2565
  return;
2444
2566
  }
2445
2567
 
2446
- if(this.$input.val()) {
2568
+ if(this.$input.val().length) {
2447
2569
  this.$clear.show();
2448
2570
  } else {
2449
2571
  this.$clear.hide();
2450
2572
  }
2451
- }
2573
+ },
2574
+
2575
+ clear: function() {
2576
+ this.$clear.hide();
2577
+ this.$input.val('').focus();
2578
+ }
2452
2579
  });
2453
2580
 
2454
2581
  Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
@@ -2622,14 +2749,21 @@ $(function(){
2622
2749
  $.extend(Select.prototype, {
2623
2750
  renderList: function() {
2624
2751
  this.$input.empty();
2625
-
2626
- if(!$.isArray(this.sourceData)) {
2627
- return;
2628
- }
2629
2752
 
2630
- for(var i=0; i<this.sourceData.length; i++) {
2631
- this.$input.append($('<option>', {value: this.sourceData[i].value}).text(this.sourceData[i].text));
2632
- }
2753
+ var fillItems = function($el, data) {
2754
+ if($.isArray(data)) {
2755
+ for(var i=0; i<data.length; i++) {
2756
+ if(data[i].children) {
2757
+ $el.append(fillItems($('<optgroup>', {label: data[i].text}), data[i].children));
2758
+ } else {
2759
+ $el.append($('<option>', {value: data[i].value}).text(data[i].text));
2760
+ }
2761
+ }
2762
+ }
2763
+ return $el;
2764
+ };
2765
+
2766
+ fillItems(this.$input, this.sourceData);
2633
2767
 
2634
2768
  this.setClass();
2635
2769
 
@@ -2714,8 +2848,7 @@ $(function(){
2714
2848
  for(var i=0; i<this.sourceData.length; i++) {
2715
2849
  $label = $('<label>').append($('<input>', {
2716
2850
  type: 'checkbox',
2717
- value: this.sourceData[i].value,
2718
- name: this.options.name
2851
+ value: this.sourceData[i].value
2719
2852
  }))
2720
2853
  .append($('<span>').text(' '+this.sourceData[i].text));
2721
2854
 
@@ -2744,7 +2877,7 @@ $(function(){
2744
2877
 
2745
2878
  //set checked on required checkboxes
2746
2879
  value2input: function(value) {
2747
- this.$input.removeAttr('checked');
2880
+ this.$input.prop('checked', false);
2748
2881
  if($.isArray(value) && value.length) {
2749
2882
  this.$input.each(function(i, el) {
2750
2883
  var $el = $(el);
@@ -2753,7 +2886,7 @@ $(function(){
2753
2886
  /*jslint eqeq: true*/
2754
2887
  if($el.val() == val) {
2755
2888
  /*jslint eqeq: false*/
2756
- $el.attr('checked', 'checked');
2889
+ $el.prop('checked', true);
2757
2890
  }
2758
2891
  });
2759
2892
  });
@@ -2992,6 +3125,199 @@ Range (inherit from number)
2992
3125
  $.fn.editabletypes.range = Range;
2993
3126
  }(window.jQuery));
2994
3127
  /**
3128
+ Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
3129
+ Please see [original docs](http://ivaynberg.github.com/select2) for detailed description and options.
3130
+ You should manually include select2 distributive:
3131
+
3132
+ <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
3133
+ <script src="select2/select2.js"></script>
3134
+
3135
+ @class select2
3136
+ @extends abstractinput
3137
+ @since 1.4.1
3138
+ @final
3139
+ @example
3140
+ <a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-original-title="Select country"></a>
3141
+ <script>
3142
+ $(function(){
3143
+ $('#country').editable({
3144
+ source: [
3145
+ {id: 'gb', text: 'Great Britain'},
3146
+ {id: 'us', text: 'United States'},
3147
+ {id: 'ru', text: 'Russia'}
3148
+ ],
3149
+ select2: {
3150
+ multiple: true
3151
+ }
3152
+ });
3153
+ });
3154
+ </script>
3155
+ **/
3156
+ (function ($) {
3157
+
3158
+ var Constructor = function (options) {
3159
+ this.init('select2', options, Constructor.defaults);
3160
+
3161
+ options.select2 = options.select2 || {};
3162
+
3163
+ var that = this,
3164
+ mixin = {
3165
+ placeholder: options.placeholder
3166
+ };
3167
+
3168
+ //detect whether it is multi-valued
3169
+ this.isMultiple = options.select2.tags || options.select2.multiple;
3170
+
3171
+ //if not `tags` mode, we need define init set data from source
3172
+ if(!options.select2.tags) {
3173
+ if(options.source) {
3174
+ mixin.data = options.source;
3175
+ }
3176
+
3177
+ //this function can be defaulted in seletc2. See https://github.com/ivaynberg/select2/issues/710
3178
+ mixin.initSelection = function (element, callback) {
3179
+ var val = that.str2value(element.val()),
3180
+ data = $.fn.editableutils.itemsByValue(val, mixin.data, 'id');
3181
+
3182
+ //for single-valued mode should not use array. Take first element instead.
3183
+ if($.isArray(data) && data.length && !that.isMultiple) {
3184
+ data = data[0];
3185
+ }
3186
+
3187
+ callback(data);
3188
+ };
3189
+ }
3190
+
3191
+ //overriding objects in config (as by default jQuery extend() is not recursive)
3192
+ this.options.select2 = $.extend({}, Constructor.defaults.select2, mixin, options.select2);
3193
+ };
3194
+
3195
+ $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
3196
+
3197
+ $.extend(Constructor.prototype, {
3198
+ render: function() {
3199
+ this.setClass();
3200
+ //apply select2
3201
+ this.$input.select2(this.options.select2);
3202
+
3203
+ //trigger resize of editableform to re-position container in multi-valued mode
3204
+ if(this.isMultiple) {
3205
+ this.$input.on('change', function() {
3206
+ $(this).closest('form').parent().triggerHandler('resize');
3207
+ });
3208
+ }
3209
+
3210
+ },
3211
+
3212
+ value2html: function(value, element) {
3213
+ var text = '', data;
3214
+ if(this.$input) { //when submitting form
3215
+ data = this.$input.select2('data');
3216
+ } else { //on init (autotext)
3217
+ //here select2 instance not created yet and data may be even not loaded.
3218
+ //we can check data/tags property of select config and if exist lookup text
3219
+ if(this.options.select2.tags) {
3220
+ data = value;
3221
+ } else if(this.options.select2.data) {
3222
+ data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id');
3223
+ }
3224
+ }
3225
+
3226
+ if($.isArray(data)) {
3227
+ //collect selected data and show with separator
3228
+ text = [];
3229
+ $.each(data, function(k, v){
3230
+ text.push(v && typeof v === 'object' ? v.text : v);
3231
+ });
3232
+ } else if(data) {
3233
+ text = data.text;
3234
+ }
3235
+
3236
+ text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
3237
+
3238
+ $(element).text(text);
3239
+ },
3240
+
3241
+ html2value: function(html) {
3242
+ return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
3243
+ },
3244
+
3245
+ value2input: function(value) {
3246
+ this.$input.val(value).trigger('change');
3247
+ },
3248
+
3249
+ input2value: function() {
3250
+ return this.$input.select2('val');
3251
+ },
3252
+
3253
+ str2value: function(str, separator) {
3254
+ if(typeof str !== 'string' || !this.isMultiple) {
3255
+ return str;
3256
+ }
3257
+
3258
+ separator = separator || this.options.select2.separator || $.fn.select2.defaults.separator;
3259
+
3260
+ var val, i, l;
3261
+
3262
+ if (str === null || str.length < 1) {
3263
+ return null;
3264
+ }
3265
+ val = str.split(separator);
3266
+ for (i = 0, l = val.length; i < l; i = i + 1) {
3267
+ val[i] = $.trim(val[i]);
3268
+ }
3269
+
3270
+ return val;
3271
+ }
3272
+
3273
+ });
3274
+
3275
+ Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
3276
+ /**
3277
+ @property tpl
3278
+ @default <input type="hidden">
3279
+ **/
3280
+ tpl:'<input type="hidden">',
3281
+ /**
3282
+ Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
3283
+
3284
+ @property select2
3285
+ @type object
3286
+ @default null
3287
+ **/
3288
+ select2: null,
3289
+ /**
3290
+ Placeholder attribute of select
3291
+
3292
+ @property placeholder
3293
+ @type string
3294
+ @default null
3295
+ **/
3296
+ placeholder: null,
3297
+ /**
3298
+ Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.
3299
+ Please note, that format is different from simple `select` input: use 'id' instead of 'value'.
3300
+ E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.
3301
+
3302
+ @property source
3303
+ @type array
3304
+ @default null
3305
+ **/
3306
+ source: null,
3307
+ /**
3308
+ Separator used to display tags.
3309
+
3310
+ @property viewseparator
3311
+ @type string
3312
+ @default ', '
3313
+ **/
3314
+ viewseparator: ', '
3315
+ });
3316
+
3317
+ $.fn.editabletypes.select2 = Constructor;
3318
+
3319
+ }(window.jQuery));
3320
+ /**
2995
3321
  * Combodate - 1.0.1
2996
3322
  * Dropdown date and time picker.
2997
3323
  * Converts text input into dropdowns to pick day, month, year, hour, minute and second.
@@ -4934,3 +5260,225 @@ Automatically shown in inline mode.
4934
5260
  $.fn.datepicker.DPGlobal = DPGlobal;
4935
5261
 
4936
5262
  }( window.jQuery );
5263
+
5264
+ /**
5265
+ Typeahead input (bootstrap only). Based on Twitter Bootstrap [typeahead](http://twitter.github.com/bootstrap/javascript.html#typeahead).
5266
+ Depending on `source` format typeahead operates in two modes:
5267
+
5268
+ * **strings**:
5269
+ When `source` defined as array of strings, e.g. `['text1', 'text2', 'text3' ...]`.
5270
+ User can submit one of these strings or any text entered in input (even if it is not matching source).
5271
+
5272
+ * **objects**:
5273
+ When `source` defined as array of objects, e.g. `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`.
5274
+ User can submit only values that are in source (otherwise `null` is submitted). This is more like *dropdown* behavior.
5275
+
5276
+ @class typeahead
5277
+ @extends list
5278
+ @since 1.4.1
5279
+ @final
5280
+ @example
5281
+ <a href="#" id="country" data-type="typeahead" data-pk="1" data-url="/post" data-original-title="Input country"></a>
5282
+ <script>
5283
+ $(function(){
5284
+ $('#country').editable({
5285
+ value: 'ru',
5286
+ source: [
5287
+ {value: 'gb', text: 'Great Britain'},
5288
+ {value: 'us', text: 'United States'},
5289
+ {value: 'ru', text: 'Russia'}
5290
+ ]
5291
+ }
5292
+ });
5293
+ });
5294
+ </script>
5295
+ **/
5296
+ (function ($) {
5297
+
5298
+ var Constructor = function (options) {
5299
+ this.init('typeahead', options, Constructor.defaults);
5300
+
5301
+ //overriding objects in config (as by default jQuery extend() is not recursive)
5302
+ this.options.typeahead = $.extend({}, Constructor.defaults.typeahead, {
5303
+ //set default methods for typeahead to work with objects
5304
+ matcher: this.matcher,
5305
+ sorter: this.sorter,
5306
+ highlighter: this.highlighter,
5307
+ updater: this.updater
5308
+ }, options.typeahead);
5309
+ };
5310
+
5311
+ $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.list);
5312
+
5313
+ $.extend(Constructor.prototype, {
5314
+ renderList: function() {
5315
+ this.$input = this.$tpl.is('input') ? this.$tpl : this.$tpl.find('input[type="text"]');
5316
+
5317
+ //set source of typeahead
5318
+ this.options.typeahead.source = this.sourceData;
5319
+
5320
+ //apply typeahead
5321
+ this.$input.typeahead(this.options.typeahead);
5322
+
5323
+ //attach own render method
5324
+ this.$input.data('typeahead').render = $.proxy(this.typeaheadRender, this.$input.data('typeahead'));
5325
+
5326
+ this.renderClear();
5327
+ this.setClass();
5328
+ this.setAttr('placeholder');
5329
+ },
5330
+
5331
+ value2htmlFinal: function(value, element) {
5332
+ if(this.getIsObjects()) {
5333
+ var items = $.fn.editableutils.itemsByValue(value, this.sourceData);
5334
+ $(element).text(items.length ? items[0].text : '');
5335
+ } else {
5336
+ $(element).text(value);
5337
+ }
5338
+ },
5339
+
5340
+ html2value: function (html) {
5341
+ return html ? html : null;
5342
+ },
5343
+
5344
+ value2input: function(value) {
5345
+ if(this.getIsObjects()) {
5346
+ var items = $.fn.editableutils.itemsByValue(value, this.sourceData);
5347
+ this.$input.data('value', value).val(items.length ? items[0].text : '');
5348
+ } else {
5349
+ this.$input.val(value);
5350
+ }
5351
+ },
5352
+
5353
+ input2value: function() {
5354
+ if(this.getIsObjects()) {
5355
+ var value = this.$input.data('value'),
5356
+ items = $.fn.editableutils.itemsByValue(value, this.sourceData);
5357
+
5358
+ if(items.length && items[0].text.toLowerCase() === this.$input.val().toLowerCase()) {
5359
+ return value;
5360
+ } else {
5361
+ return null; //entered string not found in source
5362
+ }
5363
+ } else {
5364
+ return this.$input.val();
5365
+ }
5366
+ },
5367
+
5368
+ /*
5369
+ if in sourceData values <> texts, typeahead in "objects" mode:
5370
+ user must pick some value from list, otherwise `null` returned.
5371
+ if all values == texts put typeahead in "strings" mode:
5372
+ anything what entered is submited.
5373
+ */
5374
+ getIsObjects: function() {
5375
+ if(this.isObjects === undefined) {
5376
+ this.isObjects = false;
5377
+ for(var i=0; i<this.sourceData.length; i++) {
5378
+ if(this.sourceData[i].value !== this.sourceData[i].text) {
5379
+ this.isObjects = true;
5380
+ break;
5381
+ }
5382
+ }
5383
+ }
5384
+ return this.isObjects;
5385
+ },
5386
+
5387
+ /*
5388
+ Methods borrowed from text input
5389
+ */
5390
+ activate: $.fn.editabletypes.text.prototype.activate,
5391
+ renderClear: $.fn.editabletypes.text.prototype.renderClear,
5392
+ postrender: $.fn.editabletypes.text.prototype.postrender,
5393
+ toggleClear: $.fn.editabletypes.text.prototype.toggleClear,
5394
+ clear: function() {
5395
+ $.fn.editabletypes.text.prototype.clear.call(this);
5396
+ this.$input.data('value', '');
5397
+ },
5398
+
5399
+
5400
+ /*
5401
+ Typeahead option methods used as defaults
5402
+ */
5403
+ /*jshint eqeqeq:false, curly: false, laxcomma: true*/
5404
+ matcher: function (item) {
5405
+ return $.fn.typeahead.Constructor.prototype.matcher.call(this, item.text);
5406
+ },
5407
+ sorter: function (items) {
5408
+ var beginswith = []
5409
+ , caseSensitive = []
5410
+ , caseInsensitive = []
5411
+ , item
5412
+ , text;
5413
+
5414
+ while (item = items.shift()) {
5415
+ text = item.text;
5416
+ if (!text.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item);
5417
+ else if (~text.indexOf(this.query)) caseSensitive.push(item);
5418
+ else caseInsensitive.push(item);
5419
+ }
5420
+
5421
+ return beginswith.concat(caseSensitive, caseInsensitive);
5422
+ },
5423
+ highlighter: function (item) {
5424
+ return $.fn.typeahead.Constructor.prototype.highlighter.call(this, item.text);
5425
+ },
5426
+ updater: function (item) {
5427
+ item = this.$menu.find('.active').data('item');
5428
+ this.$element.data('value', item.value);
5429
+ return item.text;
5430
+ },
5431
+
5432
+
5433
+ /*
5434
+ Overwrite typeahead's render method to store objects.
5435
+ There are a lot of disscussion in bootstrap repo on this point and still no result.
5436
+ See https://github.com/twitter/bootstrap/issues/5967
5437
+
5438
+ This function just store item in via jQuery data() method instead of attr('data-value')
5439
+ */
5440
+ typeaheadRender: function (items) {
5441
+ var that = this;
5442
+
5443
+ items = $(items).map(function (i, item) {
5444
+ // i = $(that.options.item).attr('data-value', item)
5445
+ i = $(that.options.item).data('item', item);
5446
+ i.find('a').html(that.highlighter(item));
5447
+ return i[0];
5448
+ });
5449
+
5450
+ items.first().addClass('active');
5451
+ this.$menu.html(items);
5452
+ return this;
5453
+ }
5454
+ /*jshint eqeqeq: true, curly: true, laxcomma: false*/
5455
+
5456
+ });
5457
+
5458
+ Constructor.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
5459
+ /**
5460
+ @property tpl
5461
+ @default <input type="text">
5462
+ **/
5463
+ tpl:'<input type="text">',
5464
+ /**
5465
+ Configuration of typeahead. [Full list of options](http://twitter.github.com/bootstrap/javascript.html#typeahead).
5466
+
5467
+ @property typeahead
5468
+ @type object
5469
+ @default null
5470
+ **/
5471
+ typeahead: null,
5472
+ /**
5473
+ Whether to show `clear` button
5474
+
5475
+ @property clear
5476
+ @type boolean
5477
+ @default true
5478
+ **/
5479
+ clear: true
5480
+ });
5481
+
5482
+ $.fn.editabletypes.typeahead = Constructor;
5483
+
5484
+ }(window.jQuery));
@@ -1,4 +1,4 @@
1
- /*! X-editable - v1.4.0
1
+ /*! X-editable - v1.4.1
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 */
@@ -116,21 +116,6 @@
116
116
  .editable-clear-x:hover {
117
117
  opacity: 1;
118
118
  }
119
-
120
- /*
121
- .editable-clear-x1 {
122
- background: image-url('clear.png') center center no-repeat;
123
- display: inline-block;
124
- zoom: 1;
125
- *display: inline;
126
- width: 13px;
127
- height: 13px;
128
- vertical-align: middle;
129
- position: relative;
130
- margin-left: -20px;
131
- }
132
- */
133
-
134
119
  .editable-container {
135
120
  max-width: none !important; /* without this rule poshytip/tooltip does not stretch */
136
121
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootstrap-x-editable-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.4.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-12 00:00:00.000000000 Z
12
+ date: 2013-01-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: railties
@@ -69,6 +69,7 @@ files:
69
69
  - .gitignore
70
70
  - Gemfile
71
71
  - README.md
72
+ - Rakefile
72
73
  - X-EDITABLE-LICENSE-MIT
73
74
  - bootstrap-x-editable-rails.gemspec
74
75
  - bootstrap-x-editable-rails.sublime-project
@@ -76,6 +77,7 @@ files:
76
77
  - lib/bootstrap-x-editable-rails/engine.rb
77
78
  - lib/bootstrap-x-editable-rails/railtie.rb
78
79
  - lib/bootstrap-x-editable-rails/version.rb
80
+ - vendor/assets/images/clear.png
79
81
  - vendor/assets/images/loading.gif
80
82
  - vendor/assets/javascripts/bootstrap-editable-inline.js
81
83
  - vendor/assets/javascripts/bootstrap-editable.js
@@ -94,7 +96,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
96
  version: '0'
95
97
  segments:
96
98
  - 0
97
- hash: 3701022052104564382
99
+ hash: 1222445192061243716
98
100
  required_rubygems_version: !ruby/object:Gem::Requirement
99
101
  none: false
100
102
  requirements:
@@ -103,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
105
  version: '0'
104
106
  segments:
105
107
  - 0
106
- hash: 3701022052104564382
108
+ hash: 1222445192061243716
107
109
  requirements: []
108
110
  rubyforge_project:
109
111
  rubygems_version: 1.8.24