1.2.0

Back | Minified | Changelog
2012-01-19

v1.2 is now released! This release adds some cool new features that will take Sugar in a bit of a new direction.

Sugar is now extendable.

New in 1.2 is the extend method, which is available to all built-in Javascript classes. This method hooks into Sugar's internal system of mapping methods onto the prototype, and allows developers to add their own methods! There are a number of benefits to doing this. First, it will make use of the ES5 method defineProperty if available to prevent the method from appearing in for..in loops as an enumerable property. Additionally, when extending native objects you can have better control in the case of collisions. By default, Sugar will always defer to methods that already exist, as it does internally. However, passing true as the second property to extend will override any existing methods:

Number.extend({ toString: function() { return convertToRomanNumerals(this); } }, true);

Where it starts getting really interesting, however, is by passing a function as the override property. This will allow you to override methods but defer to them conditionally:

Number.extend({ toString: function() { return convertToRomanNumerals(this); } }, function(){ return this "ducks" 'octopus'.pluralize() > "octopi" 'crises'.singularize() > "crisis" 'user_data'.humanize() > "User data" 'a-tale-of-two-cities'.titleize() > "A Tale of Two Cities" 'Les Misérables'.parameterize() > "les-miserables" 'Garçons'.normalize() > "Garcons"

In addition to the methods themselves, the module also adds a new object String.Inflector to help augment the inflector itself:

String.Inflector.singular(/(quiz)zes$/gi, '$1'); String.Inflector.plural(/([ml])ouse$/gi, '$1ice'); String.Inflector.acronym('HTTP'); String.Inflector.irregular('person', 'people'); String.Inflector.uncountable('money'); String.Inflector.human(/_cnt$/i, '_count');

These replacements will be run when calling the appropriate method. uncountable and irregular apply to both singular and pluralize. Note also acronym, which is a new part of ActiveSupport that hasn't found its way to Rails yet. Also a point of personal pride is titleize which will properly not capitalize prepositions, articles, and the like.

Finally, String#namespace was added, which will find a global namespace or return undefined if none is found:

'Ext.Base.chart.Callout'.namespace()

If any of the namespaces are not defined along the way, for example Ext or Ext.Base, the method will return undefined instead of throwing an error.

Array fuzzy object finding

Sugar previously supported passing an object to array methods like findAll or any. This would recursively match every property of that object against elements in the array, allowing for identical objects whose references are different to match. Compare this to built-in indexOf, which does not match object properties:

var person = { name: 'joe' };
[person].indexOf(person)          > true
[person].indexOf({ name: 'joe' }) > false
[person].any(person)              > true
[person].any({ name: 'joe' })     > true

Although this would properly match, objects previously had to be identical. This behavior has changed and now all array methods are "fuzzy" by default when passing an object:

var person = { name: 'joe', age: 20 };
[person].any({ name: 'joe' })          > true
[person].any({ name: 'joe', age: 20 }) > true
[person].any({ name: 'joe', age: 22 }) > false

Where this gets even more interesting is that now any property of the search object passed can also be either a regex, which will match a string against the pattern, or a function which will match against a callback. Both of these were available previously to Sugar when directly passed, however now that they themselves are subject to recursive matching inside objects, some very cool patterns become possible:

var person = { name: 'joe', age: 20 };
[person].any({ name: /j(oe|erry)/ }); // true
[person].any({
  age: function(age) {
  return a > 10 &&; a;
}); //  true

This opens up a whole range of possibilties such as building up search filters that can be cached, and much, much more. This behavior is now the default for all Sugar array methods (when passing an object), which affects the following methods:

One of the more cool things is that this change is across the board, as those methods in the ES5 spec (every, some, and filter) do not natively support passing an object as an argument, so the default behavior is still preserved. Of course if you want to match identical objects, this is still perfectly possible:

var person = { name: 'joe', age: 20 };
[person].any(function(obj) {
  return Object.equal(obj, { name: 'joe' });
} > false

Or even simpler:

var person = { name: 'joe', age: 20 };
[person].any(Object.equal.fill({ name: 'joe' })); > false

If you can assure that your data all has the same number of properties, then you don't even have to bother with this:

var joe = { name: 'joe', age: 20 }; var sam = { name: 'sam', age: 45 };
[joe, sam].any(joe);  > true
[joe, sam].any(sam);  > true
[joe, sam].all(joe);  > false

Number abbreviations

Another new feature in v1.2 is abbreviation methods on the Number class:

(1000).abbr() > '1k' (10000).abbr() > '10k' (1000000).abbr() > '1m' ((10).pow(15)).abbr() > '1,000t'
(1245).abbr(1)    > '1k'
(1245).abbr(2)    > '1.2k'
(1245).abbr(3)    > '1.25k'

Number#abbr will abbreviate the number with an upper limit of t for "trillion", beyond which it will pretty format the numeral using Number#format. It can be passed a single number that will determine it's rounding precision. From here we get a bit crazy:

(1000).metric() > "1k" (1000000).metric() > "1,000k" (1677777).metric(1) > '1,677.8k' (1677777).metric(1, false) > '1.7M'
(1250).metric(2) + 'g'     > "1.25kg"
(1250).metric(2, 0) + 'L'  > "1,250L"
(0.025).metric() + 'm'     > "25mm"
(0.00025).metric() + 'm'   > "250μm"

Number#metric will render the number into the metric system, using standard unit suffixes. Like Number#abbr, the first argument is the rounding precision. The second parameter is the "limit" which caps the highest unit to be used. The default is 1, which is k ("kilo-"), however, this can be changed by passing a different value, or by passing false, in which case the only imposed limit is the highest unit, currently 6, or E ("exa-"). Number#metric can also convert extremely small numbers as well, down to the "nano" level.

(1000).bytes() > "1kB" (1000).bytes(2) > "0.98kB" ((10).pow(20)).bytes() > "90,949,470TB" ((10).pow(20)).bytes(0, false) > "87EB"

Finally, Number#bytes hooks into the same system to produce "bytes" abbreviations on a base 2 system. Arguments are identical, but the default limit is 4, or T ("tera-"). However, this default may eventually be subject to Moore's law :)

Other changes

The "sugar" method is now split into two:

Other: