1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require('system/locale');
  9 
 10 // These are basic enhancements to the string class used throughout
 11 // SproutCore.
 12 /** @private */
 13 SC.STRING_TITLEIZE_REGEXP = (/([\s|\-|\_|\n])([^\s|\-|\_|\n]?)/g);
 14 SC.STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g);
 15 SC.STRING_DASHERIZE_REGEXP = (/[ _]/g);
 16 SC.STRING_DASHERIZE_CACHE = {};
 17 SC.STRING_TRIM_LEFT_REGEXP = (/^\s+/g);
 18 SC.STRING_TRIM_RIGHT_REGEXP = (/\s+$/g);
 19 SC.STRING_CSS_ESCAPED_REGEXP = (/(:|\.|\[|\])/g);
 20 
 21 /**
 22   @namespace
 23 
 24   SproutCore implements a variety of enhancements to the built-in String
 25   object that make it easy to perform common substitutions and conversions.
 26 
 27   Most of the utility methods defined here mirror those found in Prototype
 28   1.6.
 29 
 30   @since SproutCore 1.0
 31   @lends String.prototype
 32 */
 33 SC.mixin(SC.String, {
 34 
 35   /**
 36     Capitalizes a string.
 37 
 38     ## Examples
 39 
 40         capitalize('my favorite items') // 'My favorite items'
 41         capitalize('css-class-name')    // 'Css-class-name'
 42         capitalize('action_name')       // 'Action_name'
 43         capitalize('innerHTML')         // 'InnerHTML'
 44 
 45     @return {String} capitalized string
 46   */
 47   capitalize: function(str) {
 48     return str.charAt(0).toUpperCase() + str.slice(1);
 49   },
 50 
 51   /**
 52     Camelizes a string.  This will take any words separated by spaces, dashes
 53     or underscores and convert them into camelCase.
 54 
 55     ## Examples
 56 
 57         camelize('my favorite items') // 'myFavoriteItems'
 58         camelize('css-class-name')    // 'cssClassName'
 59         camelize('action_name')       // 'actionName'
 60         camelize('innerHTML')         // 'innerHTML'
 61 
 62     @returns {String} camelized string
 63   */
 64   camelize: function(str) {
 65     var ret = str.replace(SC.STRING_TITLEIZE_REGEXP, function(str, separater, character) {
 66       return character ? character.toUpperCase() : '';
 67     });
 68 
 69     var first = ret.charAt(0),
 70         lower = first.toLowerCase();
 71 
 72     return first !== lower ? lower + ret.slice(1) : ret;
 73   },
 74 
 75   /**
 76     Converts a camelized string into all lower case separated by underscores.
 77 
 78     ## Examples
 79 
 80     decamelize('my favorite items') // 'my favorite items'
 81     decamelize('css-class-name')    // 'css-class-name'
 82     decamelize('action_name')       // 'action_name'
 83     decamelize('innerHTML')         // 'inner_html'
 84 
 85     @returns {String} the decamelized string.
 86   */
 87   decamelize: function(str) {
 88     return str.replace(SC.STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();
 89   },
 90 
 91   /**
 92     Converts a camelized string or a string with spaces or underscores into
 93     a string with components separated by dashes.
 94 
 95     ## Examples
 96 
 97     | *Input String* | *Output String* |
 98     dasherize('my favorite items') // 'my-favorite-items'
 99     dasherize('css-class-name')    // 'css-class-name'
100     dasherize('action_name')       // 'action-name'
101     dasherize('innerHTML')         // 'inner-html'
102 
103     @returns {String} the dasherized string.
104   */
105   dasherize: function(str) {
106     var cache = SC.STRING_DASHERIZE_CACHE,
107         ret   = cache[str];
108 
109     if (ret) {
110       return ret;
111     } else {
112       ret = SC.String.decamelize(str).replace(SC.STRING_DASHERIZE_REGEXP,'-');
113       cache[str] = ret;
114     }
115 
116     return ret;
117   },
118 
119   /**
120     Escapes the given string to make it safe to use as a jQuery selector.
121     jQuery will interpret '.' and ':' as class and pseudo-class indicators.
122 
123     @see http://learn.jquery.com/using-jquery-core/faq/how-do-i-select-an-element-by-an-id-that-has-characters-used-in-css-notation/
124 
125     @param {String} str the string to escape
126     @returns {String} the escaped string
127   */
128   escapeCssIdForSelector: function (str) {
129     return str.replace(SC.STRING_CSS_ESCAPED_REGEXP, '\\$1');
130   },
131 
132   /**
133     Localizes the string.  This will look up the receiver string as a key
134     in the current Strings hash.  If the key matches, the loc'd value will be
135     used.  The resulting string will also be passed through fmt() to insert
136     any variables.
137 
138     @param str {String} String to localize
139     @param args {Object...} optional arguments to interpolate also
140     @returns {String} the localized and formatted string.
141   */
142   loc: function(str) {
143     // NB: This could be implemented as a wrapper to locWithDefault() but
144     // it would add some overhead to deal with the arguments and adds stack
145     // frames, so we are keeping the implementation separate.
146     if (!SC.Locale.currentLocale) { SC.Locale.createCurrentLocale(); }
147 
148     var localized = SC.Locale.currentLocale.locWithDefault(str);
149     if (SC.typeOf(localized) !== SC.T_STRING) { localized = str; }
150 
151     var args = SC.$A(arguments);
152     args.shift(); // remove str param
153     //to extend String.prototype
154     if (args.length > 0 && args[0] && args[0].isSCArray) { args = args[0]; }
155 
156     // I looked up the performance of try/catch. IE and FF do not care so
157     // long as the catch never happens. Safari and Chrome are affected rather
158     // severely (10x), but this is a one-time cost per loc (the code being
159     // executed is likely as expensive as this try/catch cost).
160     //
161     // Also, .loc() is not called SO much to begin with. So, the error handling
162     // that this gives us is worth it.
163     try {
164       return SC.String.fmt(localized, args);      
165     } catch (e) {
166       SC.error("Error processing string with key: " + str);
167       SC.error("Localized String: " + localized);
168       SC.error("Error: " + e);
169     }
170 
171   },
172 
173   /**
174     Returns the localized metric value for the specified key.  A metric is a
175     single value intended to be used in your interface’s layout, such as
176     "Button.Confirm.Width" = 100.
177 
178     If you would like to return a set of metrics for use in a layout hash, you
179     may prefer to use the locLayout() method instead.
180 
181     @param str {String} key
182     @returns {Number} the localized metric
183   */
184   locMetric: function(key) {
185     var K             = SC.Locale,
186         currentLocale = K.currentLocale;
187 
188     if (!currentLocale) {
189       K.createCurrentLocale();
190       currentLocale = K.currentLocale;
191     }
192     return currentLocale.locMetric(key);
193   },
194 
195   /**
196     Creates and returns a new hash suitable for use as an SC.View’s 'layout'
197     hash.  This hash will be created by looking for localized metrics following
198     a pattern based on the “base key” you specify.
199 
200     For example, if you specify "Button.Confirm", the following metrics will be
201     used if they are defined:
202 
203       Button.Confirm.left
204       Button.Confirm.top
205       Button.Confirm.right
206       Button.Confirm.bottom
207       Button.Confirm.width
208       Button.Confirm.height
209       Button.Confirm.midWidth
210       Button.Confirm.minHeight
211       Button.Confirm.centerX
212       Button.Confirm.centerY
213 
214     Additionally, you can optionally specify a hash which will be merged on top
215     of the returned hash.  For example, if you wish to allow a button’s width
216     to be configurable per-locale, but always wish for it to be centered
217     vertically and horizontally, you can call:
218 
219       locLayout("Button.Confirm", {centerX:0, centerY:0})
220 
221     …so that you can combine both localized and non-localized elements in the
222     returned hash.  (An exception will be thrown if there is a locale-specific
223     key that matches a key specific in this hash.)
224 
225 
226     For example, if your locale defines:
227 
228       Button.Confirm.left
229       Button.Confirm.top
230       Button.Confirm.right
231       Button.Confirm.bottom
232 
233 
234     …then these two code snippets will produce the same result:
235 
236       layout: {
237         left:   "Button.Confirm.left".locMetric(),
238         top:    "Button.Confirm.top".locMetric(),
239         right:  "Button.Confirm.right".locMetric(),
240         bottom: "Button.Confirm.bottom".locMetric()
241       }
242 
243       layout: "Button.Confirm".locLayout()
244 
245     The former is slightly more efficient because it doesn’t have to iterate
246     through the possible localized layout keys, but in virtually all situations
247     you will likely wish to use the latter.
248 
249     @param str {String} key
250     @param {str} (optional) additionalHash
251     @param {String} (optional) additionalHash
252     @returns {Number} the localized metric
253   */
254   locLayout: function(key, additionalHash) {
255     var K             = SC.Locale,
256         currentLocale = K.currentLocale;
257 
258     if (!currentLocale) {
259       K.createCurrentLocale();
260       currentLocale = K.currentLocale;
261     }
262     return currentLocale.locLayout(key, additionalHash);
263   },
264 
265   /**
266     Works just like loc() except that it will return the passed default
267     string if a matching key is not found.
268 
269     @param {String} str the string to localize
270     @param {String} def the default to return
271     @param {Object...} args optional formatting arguments
272     @returns {String} localized and formatted string
273   */
274   locWithDefault: function(str, def) {
275     if (!SC.Locale.currentLocale) { SC.Locale.createCurrentLocale(); }
276 
277     var localized = SC.Locale.currentLocale.locWithDefault(str, def);
278     if (SC.typeOf(localized) !== SC.T_STRING) { localized = str; }
279 
280     var args = SC.$A(arguments);
281     args.shift(); // remove str param
282     args.shift(); // remove def param
283 
284     return SC.String.fmt(localized, args);
285   },
286 
287   /**
288    Removes any extra whitespace from the edges of the string. This method is
289    also aliased as strip().
290 
291    @returns {String} the trimmed string
292   */
293   trim: jQuery.trim,
294 
295   /**
296    Removes any extra whitespace from the left edge of the string.
297 
298    @returns {String} the trimmed string
299   */
300   trimLeft: function (str) {
301     return str.replace(SC.STRING_TRIM_LEFT_REGEXP,"");
302   },
303 
304   /**
305    Removes any extra whitespace from the right edge of the string.
306 
307    @returns {String} the trimmed string
308   */
309   trimRight: function (str) {
310     return str.replace(SC.STRING_TRIM_RIGHT_REGEXP,"");
311   },
312   
313   /**
314     Mulitplies a given string. For instance if you have a string "xyz"
315     and multiply it by 2 the result is "xyzxyz".
316     
317     @param {String} str the string to multiply
318     @param {Number} value the number of times to multiply the string
319     @returns {String} the mulitiplied string
320   */
321   mult: function(str, value) {
322     if (SC.typeOf(value) !== SC.T_NUMBER || value < 1) return null;
323     
324     var ret = "";
325     for (var i = 0; i < value; i += 1) {
326       ret += str;
327     }
328     
329     return ret;
330   }
331   
332 });
333 
334 
335 // IE doesn't support string trimming
336 if(String.prototype.trim) {
337   SC.supplement(String.prototype,
338   /** @scope String.prototype */ {
339 
340     trim: function() {
341       return SC.String.trim(this, arguments);
342     },
343 
344     trimLeft: function() {
345       return SC.String.trimLeft(this, arguments);
346     },
347 
348     trimRight: function() {
349       return SC.String.trimRight(this, arguments);
350     }
351   });
352 }
353 
354 // We want the version defined here, not in Runtime
355 SC.mixin(String.prototype,
356 /** @scope String.prototype */ {
357 
358   loc: function() {
359     return SC.String.loc(this.toString(), SC.$A(arguments));
360   },
361 
362   locMetric: function() {
363     return SC.String.locMetric(this.toString());
364   },
365 
366   locLayout: function(additionalHash) {
367     return SC.String.locLayout(this.toString(), additionalHash);
368   }
369 
370 });
371 
372