[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/webroot/rsrc/externals/javelin/core/ -> util.js (source)

   1  /**
   2   * Javelin utility functions.
   3   *
   4   * @provides javelin-util
   5   *
   6   * @javelin-installs JX.$E
   7   * @javelin-installs JX.$A
   8   * @javelin-installs JX.$AX
   9   * @javelin-installs JX.isArray
  10   * @javelin-installs JX.copy
  11   * @javelin-installs JX.bind
  12   * @javelin-installs JX.bag
  13   * @javelin-installs JX.keys
  14   * @javelin-installs JX.log
  15   * @javelin-installs JX.id
  16   * @javelin-installs JX.now
  17   *
  18   * @javelin
  19   */
  20  
  21  /**
  22   * Throw an exception and attach the caller data in the exception.
  23   *
  24   * @param  string  Exception message.
  25   */
  26  JX.$E = function(message) {
  27    var e = new Error(message);
  28    var caller_fn = JX.$E.caller;
  29    if (caller_fn) {
  30      e.caller_fn = caller_fn.caller;
  31    }
  32    throw e;
  33  };
  34  
  35  
  36  /**
  37   * Convert an array-like object (usually ##arguments##) into a real Array. An
  38   * "array-like object" is something with a ##length## property and numerical
  39   * keys. The most common use for this is to let you call Array functions on the
  40   * magical ##arguments## object.
  41   *
  42   *   JX.$A(arguments).slice(1);
  43   *
  44   * @param  obj     Array, or array-like object.
  45   * @return Array   Actual array.
  46   */
  47  JX.$A = function(object) {
  48    // IE8 throws "JScript object expected" when trying to call
  49    // Array.prototype.slice on a NodeList, so just copy items one by one here.
  50    var r = [];
  51    for (var ii = 0; ii < object.length; ii++) {
  52      r.push(object[ii]);
  53    }
  54    return r;
  55  };
  56  
  57  
  58  /**
  59   * Cast a value into an array, by wrapping scalars into singletons. If the
  60   * argument is an array, it is returned unmodified. If it is a scalar, an array
  61   * with a single element is returned. For example:
  62   *
  63   *   JX.$AX([3]); // Returns [3].
  64   *   JX.$AX(3);   // Returns [3].
  65   *
  66   * Note that this function uses a @{function:JX.isArray} check whether or not
  67   * the argument is an array, so you may need to convert array-like objects (such
  68   * as ##arguments##) into real arrays with @{function:JX.$A}.
  69   *
  70   * This function is mostly useful to create methods which accept either a
  71   * value or a list of values.
  72   *
  73   * @param  wild    Scalar or Array.
  74   * @return Array   If the argument was a scalar, an Array with the argument as
  75   *                 its only element. Otherwise, the original Array.
  76   */
  77  JX.$AX = function(maybe_scalar) {
  78    return JX.isArray(maybe_scalar) ? maybe_scalar : [maybe_scalar];
  79  };
  80  
  81  
  82  /**
  83   * Checks whether a value is an array.
  84   *
  85   *   JX.isArray(['an', 'array']); // Returns true.
  86   *   JX.isArray('Not an Array');  // Returns false.
  87   *
  88   * @param  wild     Any value.
  89   * @return bool     true if the argument is an array, false otherwise.
  90   */
  91  JX.isArray = Array.isArray || function(maybe_array) {
  92    return Object.prototype.toString.call(maybe_array) == '[object Array]';
  93  };
  94  
  95  
  96  /**
  97   * Copy properties from one object to another. If properties already exist, they
  98   * are overwritten.
  99   *
 100   *   var cat  = {
 101   *     ears: 'clean',
 102   *     paws: 'clean',
 103   *     nose: 'DIRTY OH NOES'
 104   *   };
 105   *   var more = {
 106   *     nose: 'clean',
 107   *     tail: 'clean'
 108   *   };
 109   *
 110   *   JX.copy(cat, more);
 111   *
 112   *   // cat is now:
 113   *   //  {
 114   *   //    ears: 'clean',
 115   *   //    paws: 'clean',
 116   *   //    nose: 'clean',
 117   *   //    tail: 'clean'
 118   *   //  }
 119   *
 120   * NOTE: This function does not copy the ##toString## property or anything else
 121   * which isn't enumerable or is somehow magic or just doesn't work. But it's
 122   * usually what you want.
 123   *
 124   * @param  obj Destination object, which properties should be copied to.
 125   * @param  obj Source object, which properties should be copied from.
 126   * @return obj Modified destination object.
 127   */
 128  JX.copy = function(copy_dst, copy_src) {
 129    for (var k in copy_src) {
 130      copy_dst[k] = copy_src[k];
 131    }
 132    return copy_dst;
 133  };
 134  
 135  
 136  /**
 137   * Create a function which invokes another function with a bound context and
 138   * arguments (i.e., partial function application) when called; king of all
 139   * functions.
 140   *
 141   * Bind performs context binding (letting you select what the value of ##this##
 142   * will be when a function is invoked) and partial function application (letting
 143   * you create some function which calls another one with bound arguments).
 144   *
 145   * = Context Binding =
 146   *
 147   * Normally, when you call ##obj.method()##, the magic ##this## object will be
 148   * the ##obj## you invoked the method from. This can be undesirable when you
 149   * need to pass a callback to another function. For instance:
 150   *
 151   *   COUNTEREXAMPLE
 152   *   var dog = new JX.Dog();
 153   *   dog.barkNow(); // Makes the dog bark.
 154   *
 155   *   JX.Stratcom.listen('click', 'bark', dog.barkNow); // Does not work!
 156   *
 157   * This doesn't work because ##this## is ##window## when the function is
 158   * later invoked; @{method:JX.Stratcom.listen} does not know about the context
 159   * object ##dog##. The solution is to pass a function with a bound context
 160   * object:
 161   *
 162   *   var dog = new JX.Dog();
 163   *   var bound_function = JX.bind(dog, dog.barkNow);
 164   *
 165   *   JX.Stratcom.listen('click', 'bark', bound_function);
 166   *
 167   * ##bound_function## is a function with ##dog## bound as ##this##; ##this##
 168   * will always be ##dog## when the function is called, no matter what
 169   * property chain it is invoked from.
 170   *
 171   * You can also pass ##null## as the context argument to implicitly bind
 172   * ##window##.
 173   *
 174   * = Partial Function Application =
 175   *
 176   * @{function:JX.bind} also performs partial function application, which allows
 177   * you to bind one or more arguments to a function. For instance, if we have a
 178   * simple function which adds two numbers:
 179   *
 180   *   function add(a, b) { return a + b; }
 181   *   add(3, 4); // 7
 182   *
 183   * Suppose we want a new function, like this:
 184   *
 185   *   function add3(b) { return 3 + b; }
 186   *   add3(4); // 7
 187   *
 188   * Instead of doing this, we can define ##add3()## in terms of ##add()## by
 189   * binding the value ##3## to the ##a## argument:
 190   *
 191   *   var add3_bound = JX.bind(null, add, 3);
 192   *   add3_bound(4); // 7
 193   *
 194   * Zero or more arguments may be bound in this way. This is particularly useful
 195   * when using closures in a loop:
 196   *
 197   *   COUNTEREXAMPLE
 198   *   for (var ii = 0; ii < button_list.length; ii++) {
 199   *     button_list[ii].onclick = function() {
 200   *       JX.log('You clicked button number '+ii+'!'); // Fails!
 201   *     };
 202   *   }
 203   *
 204   * This doesn't work; all the buttons report the highest number when clicked.
 205   * This is because the local ##ii## is captured by the closure. Instead, bind
 206   * the current value of ##ii##:
 207   *
 208   *   var func = function(button_num) {
 209   *     JX.log('You clicked button number '+button_num+'!');
 210   *   }
 211   *   for (var ii = 0; ii < button_list.length; ii++) {
 212   *     button_list[ii].onclick = JX.bind(null, func, ii);
 213   *   }
 214   *
 215   * @param  obj|null  Context object to bind as ##this##.
 216   * @param  function  Function to bind context and arguments to.
 217   * @param  ...       Zero or more arguments to bind.
 218   * @return function  New function which invokes the original function with
 219   *                   bound context and arguments when called.
 220   */
 221  JX.bind = function(context, func, more) {
 222    if (__DEV__) {
 223      if (typeof func != 'function') {
 224        JX.$E(
 225          'JX.bind(context, <yuck>, ...): '+
 226          'Attempting to bind something that is not a function.');
 227      }
 228    }
 229  
 230    var bound = JX.$A(arguments).slice(2);
 231    if (func.bind) {
 232      return func.bind.apply(func, [context].concat(bound));
 233    }
 234  
 235    return function() {
 236      return func.apply(context || window, bound.concat(JX.$A(arguments)));
 237    };
 238  };
 239  
 240  
 241  /**
 242   * "Bag of holding"; function that does nothing. Primarily, it's used as a
 243   * placeholder when you want something to be callable but don't want it to
 244   * actually have an effect.
 245   *
 246   * @return void
 247   */
 248  JX.bag = function() {
 249    // \o\ \o/ /o/ woo dance party
 250  };
 251  
 252  
 253  /**
 254   * Convert an object's keys into a list. For example:
 255   *
 256   *   JX.keys({sun: 1, moon: 1, stars: 1}); // Returns: ['sun', 'moon', 'stars']
 257   *
 258   * @param  obj    Object to retrieve keys from.
 259   * @return list   List of keys.
 260   */
 261  JX.keys = Object.keys || function(obj) {
 262    var r = [];
 263    for (var k in obj) {
 264      r.push(k);
 265    }
 266    return r;
 267  };
 268  
 269  
 270  /**
 271   * Identity function; returns the argument unmodified. This is primarily useful
 272   * as a placeholder for some callback which may transform its argument.
 273   *
 274   * @param   wild  Any value.
 275   * @return  wild  The passed argument.
 276   */
 277  JX.id = function(any) {
 278    return any;
 279  };
 280  
 281  
 282  if (!window.console || !window.console.log) {
 283    if (window.opera && window.opera.postError) {
 284      window.console = {log: function(m) { window.opera.postError(m); }};
 285    } else {
 286      window.console = {log: function(m) { }};
 287    }
 288  }
 289  
 290  
 291  /**
 292   * Print a message to the browser debugging console (like Firebug).
 293   *
 294   * @param  string Message to print to the browser debugging console.
 295   * @return void
 296   */
 297  JX.log = function(message) {
 298    window.console.log(message);
 299  };
 300  
 301  
 302  if (__DEV__) {
 303    window.alert = (function(native_alert) {
 304      var recent_alerts = [];
 305      var in_alert = false;
 306      return function(msg) {
 307        if (in_alert) {
 308          JX.log(
 309            'alert(...): '+
 310            'discarded reentrant alert.');
 311          return;
 312        }
 313        in_alert = true;
 314        recent_alerts.push(JX.now());
 315  
 316        if (recent_alerts.length > 3) {
 317          recent_alerts.splice(0, recent_alerts.length - 3);
 318        }
 319  
 320        if (recent_alerts.length >= 3 &&
 321            (recent_alerts[recent_alerts.length - 1] - recent_alerts[0]) < 5000) {
 322          if (confirm(msg + "\n\nLots of alert()s recently. Kill them?")) {
 323            window.alert = JX.bag;
 324          }
 325        } else {
 326          //  Note that we can't .apply() the IE6 version of this "function".
 327          native_alert(msg);
 328        }
 329        in_alert = false;
 330      };
 331    })(window.alert);
 332  }
 333  
 334  /**
 335   * Date.now is the fastest timestamp function, but isn't supported by every
 336   * browser. This gives the fastest version the environment can support.
 337   * The wrapper function makes the getTime call even slower, but benchmarking
 338   * shows it to be a marginal perf loss. Considering how small of a perf
 339   * difference this makes overall, it's not really a big deal. The primary
 340   * reason for this is to avoid hacky "just think of the byte savings" JS
 341   * like +new Date() that has an unclear outcome for the unexposed.
 342   *
 343   * @return Int A Unix timestamp of the current time on the local machine
 344   */
 345  JX.now = (Date.now || function() { return new Date().getTime(); });


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1