[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/webroot/rsrc/externals/javelin/lib/ -> DOM.js (source)

   1  /**
   2   * @requires javelin-magical-init
   3   *           javelin-install
   4   *           javelin-util
   5   *           javelin-vector
   6   *           javelin-stratcom
   7   * @provides javelin-dom
   8   *
   9   * @javelin-installs JX.$
  10   * @javelin-installs JX.$N
  11   * @javelin-installs JX.$H
  12   *
  13   * @javelin
  14   */
  15  
  16  
  17  /**
  18   * Select an element by its "id" attribute, like ##document.getElementById()##.
  19   * For example:
  20   *
  21   *   var node = JX.$('some_id');
  22   *
  23   * This will select the node with the specified "id" attribute:
  24   *
  25   *   LANG=HTML
  26   *   <div id="some_id">...</div>
  27   *
  28   * If the specified node does not exist, @{JX.$()} will throw an exception.
  29   *
  30   * For other ways to select nodes from the document, see @{JX.DOM.scry()} and
  31   * @{JX.DOM.find()}.
  32   *
  33   * @param  string  "id" attribute to select from the document.
  34   * @return Node    Node with the specified "id" attribute.
  35   */
  36  JX.$ = function(id) {
  37  
  38    if (__DEV__) {
  39      if (!id) {
  40        JX.$E('Empty ID passed to JX.$()!');
  41      }
  42    }
  43  
  44    var node = document.getElementById(id);
  45    if (!node || (node.id != id)) {
  46      if (__DEV__) {
  47        if (node && (node.id != id)) {
  48          JX.$E(
  49            'JX.$("'+id+'"): '+
  50            'document.getElementById() returned an element without the '+
  51            'correct ID. This usually means that the element you are trying '+
  52            'to select is being masked by a form with the same value in its '+
  53            '"name" attribute.');
  54        }
  55      }
  56      JX.$E("JX.$('" + id + "') call matched no nodes.");
  57    }
  58  
  59    return node;
  60  };
  61  
  62  /**
  63   * Upcast a string into an HTML object so it is treated as markup instead of
  64   * plain text. See @{JX.$N} for discussion of Javelin's security model. Every
  65   * time you call this function you potentially open up a security hole. Avoid
  66   * its use wherever possible.
  67   *
  68   * This class intentionally supports only a subset of HTML because many browsers
  69   * named "Internet Explorer" have awkward restrictions around what they'll
  70   * accept for conversion to document fragments. Alter your datasource to emit
  71   * valid HTML within this subset if you run into an unsupported edge case. All
  72   * the edge cases are crazy and you should always be reasonably able to emit
  73   * a cohesive tag instead of an unappendable fragment.
  74   *
  75   * You may use @{JX.$H} as a shortcut for creating new JX.HTML instances:
  76   *
  77   *   JX.$N('div', {}, some_html_blob); // Treat as string (safe)
  78   *   JX.$N('div', {}, JX.$H(some_html_blob)); // Treat as HTML (unsafe!)
  79   *
  80   * @task build String into HTML
  81   * @task nodes HTML into Nodes
  82   */
  83  JX.install('HTML', {
  84  
  85    construct : function(str) {
  86      if (str instanceof JX.HTML) {
  87        this._content = str._content;
  88        return;
  89      }
  90  
  91      if (__DEV__) {
  92        if ((typeof str !== 'string') && (!str || !str.match)) {
  93          JX.$E(
  94            'new JX.HTML(<empty?>): ' +
  95            'call initializes an HTML object with an empty value.');
  96        }
  97  
  98        var tags = ['legend', 'thead', 'tbody', 'tfoot', 'column', 'colgroup',
  99                    'caption', 'tr', 'th', 'td', 'option'];
 100        var evil_stuff = new RegExp('^\\s*<(' + tags.join('|') + ')\\b', 'i');
 101        var match = str.match(evil_stuff);
 102        if (match) {
 103          JX.$E(
 104            'new JX.HTML("<' + match[1] + '>..."): ' +
 105            'call initializes an HTML object with an invalid partial fragment ' +
 106            'and can not be converted into DOM nodes. The enclosing tag of an ' +
 107            'HTML content string must be appendable to a document fragment. ' +
 108            'For example, <table> is allowed but <tr> or <tfoot> are not.');
 109        }
 110  
 111        var really_evil = /<script\b/;
 112        if (str.match(really_evil)) {
 113          JX.$E(
 114            'new JX.HTML("...<script>..."): ' +
 115            'call initializes an HTML object with an embedded script tag! ' +
 116            'Are you crazy?! Do NOT do this!!!');
 117        }
 118  
 119        var wont_work = /<object\b/;
 120        if (str.match(wont_work)) {
 121          JX.$E(
 122            'new JX.HTML("...<object>..."): ' +
 123            'call initializes an HTML object with an embedded <object> tag. IE ' +
 124            'will not do the right thing with this.');
 125        }
 126  
 127        // TODO(epriestley): May need to deny <option> more broadly, see
 128        // http://support.microsoft.com/kb/829907 and the whole mess in the
 129        // heavy stack. But I seem to have gotten away without cloning into the
 130        // documentFragment below, so this may be a nonissue.
 131      }
 132  
 133      this._content = str;
 134    },
 135  
 136    members : {
 137      _content : null,
 138      /**
 139       * Convert the raw HTML string into a DOM node tree.
 140       *
 141       * @task  nodes
 142       * @return DocumentFragment A document fragment which contains the nodes
 143       *                          corresponding to the HTML string you provided.
 144       */
 145      getFragment : function() {
 146        var wrapper = JX.$N('div');
 147        wrapper.innerHTML = this._content;
 148        var fragment = document.createDocumentFragment();
 149        while (wrapper.firstChild) {
 150          // TODO(epriestley): Do we need to do a bunch of cloning junk here?
 151          // See heavy stack. I'm disconnecting the nodes instead; this seems
 152          // to work but maybe my test case just isn't extensive enough.
 153          fragment.appendChild(wrapper.removeChild(wrapper.firstChild));
 154        }
 155        return fragment;
 156      },
 157  
 158      /**
 159       * Convert the raw HTML string into a single DOM node. This only works
 160       * if the element has a single top-level element. Otherwise, use
 161       * @{method:getFragment} to get a document fragment instead.
 162       *
 163       * @return Node Single node represented by the object.
 164       * @task nodes
 165       */
 166      getNode : function() {
 167        var fragment = this.getFragment();
 168        if (__DEV__) {
 169          if (fragment.childNodes.length < 1) {
 170            JX.$E('JX.HTML.getNode(): Markup has no root node!');
 171          }
 172          if (fragment.childNodes.length > 1) {
 173            JX.$E('JX.HTML.getNode(): Markup has more than one root node!');
 174          }
 175        }
 176        return fragment.firstChild;
 177      }
 178  
 179    }
 180  });
 181  
 182  
 183  /**
 184   * Build a new HTML object from a trustworthy string. JX.$H is a shortcut for
 185   * creating new JX.HTML instances.
 186   *
 187   * @task build
 188   * @param string A string which you want to be treated as HTML, because you
 189   *               know it is from a trusted source and any data in it has been
 190   *               properly escaped.
 191   * @return JX.HTML HTML object, suitable for use with @{JX.$N}.
 192   */
 193  JX.$H = function(str) {
 194    return new JX.HTML(str);
 195  };
 196  
 197  
 198  /**
 199   * Create a new DOM node with attributes and content.
 200   *
 201   *   var link = JX.$N('a');
 202   *
 203   * This creates a new, empty anchor tag without any attributes. The equivalent
 204   * markup would be:
 205   *
 206   *   LANG=HTML
 207   *   <a />
 208   *
 209   * You can also specify attributes by passing a dictionary:
 210   *
 211   *   JX.$N('a', {name: 'anchor'});
 212   *
 213   * This is equivalent to:
 214   *
 215   *   LANG=HTML
 216   *   <a name="anchor" />
 217   *
 218   * Additionally, you can specify content:
 219   *
 220   *   JX.$N(
 221   *     'a',
 222   *     {href: 'http://www.javelinjs.com'},
 223   *     'Visit the Javelin Homepage');
 224   *
 225   * This is equivalent to:
 226   *
 227   *   LANG=HTML
 228   *   <a href="http://www.javelinjs.com">Visit the Javelin Homepage</a>
 229   *
 230   * If you only want to specify content, you can omit the attribute parameter.
 231   * That is, these calls are equivalent:
 232   *
 233   *   JX.$N('div', {}, 'Lorem ipsum...'); // No attributes.
 234   *   JX.$N('div', 'Lorem ipsum...')      // Same as above.
 235   *
 236   * Both are equivalent to:
 237   *
 238   *   LANG=HTML
 239   *   <div>Lorem ipsum...</div>
 240   *
 241   * Note that the content is treated as plain text, not HTML. This means it is
 242   * safe to use untrusted strings:
 243   *
 244   *   JX.$N('div', '<script src="evil.com" />');
 245   *
 246   * This is equivalent to:
 247   *
 248   *   LANG=HTML
 249   *   <div>&lt;script src="evil.com" /&gt;</div>
 250   *
 251   * That is, the content will be properly escaped and will not create a
 252   * vulnerability. If you want to set HTML content, you can use @{JX.HTML}:
 253   *
 254   *   JX.$N('div', JX.$H(some_html));
 255   *
 256   * **This is potentially unsafe**, so make sure you understand what you're
 257   * doing. You should usually avoid passing HTML around in string form. See
 258   * @{JX.HTML} for discussion.
 259   *
 260   * You can create new nodes with a Javelin sigil (and, optionally, metadata) by
 261   * providing "sigil" and "meta" keys in the attribute dictionary.
 262   *
 263   * @param string                  Tag name, like 'a' or 'div'.
 264   * @param dict|string|@{JX.HTML}? Property dictionary, or content if you don't
 265   *                                want to specify any properties.
 266   * @param string|@{JX.HTML}?      Content string (interpreted as plain text)
 267   *                                or @{JX.HTML} object (interpreted as HTML,
 268   *                                which may be dangerous).
 269   * @return Node                   New node with whatever attributes and
 270   *                                content were specified.
 271   */
 272  JX.$N = function(tag, attr, content) {
 273    if (typeof content == 'undefined' &&
 274        (typeof attr != 'object' || attr instanceof JX.HTML)) {
 275      content = attr;
 276      attr = {};
 277    }
 278  
 279    if (__DEV__) {
 280      if (tag.toLowerCase() != tag) {
 281        JX.$E(
 282          '$N("'+tag+'", ...): '+
 283          'tag name must be in lower case; '+
 284          'use "'+tag.toLowerCase()+'", not "'+tag+'".');
 285      }
 286    }
 287  
 288    var node = document.createElement(tag);
 289  
 290    if (attr.style) {
 291      JX.copy(node.style, attr.style);
 292      delete attr.style;
 293    }
 294  
 295    if (attr.sigil) {
 296      JX.Stratcom.addSigil(node, attr.sigil);
 297      delete attr.sigil;
 298    }
 299  
 300    if (attr.meta) {
 301      JX.Stratcom.addData(node, attr.meta);
 302      delete attr.meta;
 303    }
 304  
 305    if (__DEV__) {
 306      if (('metadata' in attr) || ('data' in attr)) {
 307        JX.$E(
 308          '$N(' + tag + ', ...): ' +
 309          'use the key "meta" to specify metadata, not "data" or "metadata".');
 310      }
 311    }
 312  
 313    JX.copy(node, attr);
 314    if (content) {
 315      JX.DOM.setContent(node, content);
 316    }
 317    return node;
 318  };
 319  
 320  
 321  /**
 322   * Query and update the DOM. Everything here is static, this is essentially
 323   * a collection of common utility functions.
 324   *
 325   * @task stratcom Attaching Event Listeners
 326   * @task content Changing DOM Content
 327   * @task nodes Updating Nodes
 328   * @task serialize Serializing Forms
 329   * @task test Testing DOM Properties
 330   * @task convenience Convenience Methods
 331   * @task query Finding Nodes in the DOM
 332   * @task view Changing View State
 333   */
 334  JX.install('DOM', {
 335    statics : {
 336      _autoid : 0,
 337      _uniqid : 0,
 338      _metrics : {},
 339  
 340  
 341  /* -(  Changing DOM Content  )----------------------------------------------- */
 342  
 343  
 344      /**
 345       * Set the content of some node. This uses the same content semantics as
 346       * other Javelin content methods, see @{function:JX.$N} for a detailed
 347       * explanation. Previous content will be replaced: you can also
 348       * @{method:prependContent} or @{method:appendContent}.
 349       *
 350       * @param Node  Node to set content of.
 351       * @param mixed Content to set.
 352       * @return void
 353       * @task content
 354       */
 355      setContent : function(node, content) {
 356        if (__DEV__) {
 357          if (!JX.DOM.isNode(node)) {
 358            JX.$E(
 359              'JX.DOM.setContent(<yuck>, ...): '+
 360              'first argument must be a DOM node.');
 361          }
 362        }
 363  
 364        while (node.firstChild) {
 365          JX.DOM.remove(node.firstChild);
 366        }
 367        JX.DOM.appendContent(node, content);
 368      },
 369  
 370  
 371      /**
 372       * Prepend content to some node. This method uses the same content semantics
 373       * as other Javelin methods, see @{function:JX.$N} for an explanation. You
 374       * can also @{method:setContent} or @{method:appendContent}.
 375       *
 376       * @param Node  Node to prepend content to.
 377       * @param mixed Content to prepend.
 378       * @return void
 379       * @task content
 380       */
 381      prependContent : function(node, content) {
 382        if (__DEV__) {
 383          if (!JX.DOM.isNode(node)) {
 384            JX.$E(
 385              'JX.DOM.prependContent(<junk>, ...): '+
 386              'first argument must be a DOM node.');
 387          }
 388        }
 389  
 390        this._insertContent(node, content, this._mechanismPrepend, true);
 391      },
 392  
 393  
 394      /**
 395       * Append content to some node. This method uses the same content semantics
 396       * as other Javelin methods, see @{function:JX.$N} for an explanation. You
 397       * can also @{method:setContent} or @{method:prependContent}.
 398       *
 399       * @param Node Node to append the content of.
 400       * @param mixed Content to append.
 401       * @return void
 402       * @task content
 403       */
 404      appendContent : function(node, content) {
 405        if (__DEV__) {
 406          if (!JX.DOM.isNode(node)) {
 407            JX.$E(
 408              'JX.DOM.appendContent(<bleh>, ...): '+
 409              'first argument must be a DOM node.');
 410          }
 411        }
 412  
 413        this._insertContent(node, content, this._mechanismAppend);
 414      },
 415  
 416  
 417      /**
 418       * Internal, add content to a node by prepending.
 419       *
 420       * @param Node  Node to prepend content to.
 421       * @param Node  Node to prepend.
 422       * @return void
 423       * @task content
 424       */
 425      _mechanismPrepend : function(node, content) {
 426        node.insertBefore(content, node.firstChild);
 427      },
 428  
 429  
 430      /**
 431       * Internal, add content to a node by appending.
 432       *
 433       * @param Node  Node to append content to.
 434       * @param Node  Node to append.
 435       * @task content
 436       */
 437      _mechanismAppend : function(node, content) {
 438        node.appendChild(content);
 439      },
 440  
 441  
 442      /**
 443       * Internal, add content to a node using some specified mechanism.
 444       *
 445       * @param Node      Node to add content to.
 446       * @param mixed     Content to add.
 447       * @param function  Callback for actually adding the nodes.
 448       * @param bool      True if array elements should be passed to the mechanism
 449       *                  in reverse order, i.e. the mechanism prepends nodes.
 450       * @return void
 451       * @task content
 452       */
 453      _insertContent : function(parent, content, mechanism, reverse) {
 454        if (JX.isArray(content)) {
 455          if (reverse) {
 456            content = [].concat(content).reverse();
 457          }
 458          for (var ii = 0; ii < content.length; ii++) {
 459            JX.DOM._insertContent(parent, content[ii], mechanism, reverse);
 460          }
 461        } else {
 462          var type = typeof content;
 463          if (content instanceof JX.HTML) {
 464            content = content.getFragment();
 465          } else if (type == 'string' || type == 'number') {
 466            content = document.createTextNode(content);
 467          }
 468  
 469          if (__DEV__) {
 470            if (content && !content.nodeType) {
 471              JX.$E(
 472                'JX.DOM._insertContent(<node>, ...): '+
 473                'second argument must be a string, a number, ' +
 474                'a DOM node or a JX.HTML instance');
 475            }
 476          }
 477  
 478          content && mechanism(parent, content);
 479        }
 480      },
 481  
 482  
 483  /* -(  Updating Nodes  )----------------------------------------------------- */
 484  
 485  
 486      /**
 487       * Remove a node from its parent, so it is no longer a child of any other
 488       * node.
 489       *
 490       * @param Node Node to remove.
 491       * @return Node The node.
 492       * @task nodes
 493       */
 494      remove : function(node) {
 495        node.parentNode && JX.DOM.replace(node, null);
 496        return node;
 497      },
 498  
 499  
 500      /**
 501       * Replace a node with some other piece of content. This method obeys
 502       * Javelin content semantics, see @{function:JX.$N} for an explanation.
 503       * You can also @{method:setContent}, @{method:prependContent}, or
 504       * @{method:appendContent}.
 505       *
 506       * @param Node Node to replace.
 507       * @param mixed Content to replace it with.
 508       * @return Node the original node.
 509       * @task nodes
 510       */
 511      replace : function(node, replacement) {
 512        if (__DEV__) {
 513          if (!node.parentNode) {
 514            JX.$E(
 515              'JX.DOM.replace(<node>, ...): '+
 516              'node has no parent node, so it can not be replaced.');
 517          }
 518        }
 519  
 520        var mechanism;
 521        if (node.nextSibling) {
 522          mechanism = JX.bind(node.nextSibling, function(parent, content) {
 523            parent.insertBefore(content, this);
 524          });
 525        } else {
 526          mechanism = this._mechanismAppend;
 527        }
 528        var parent = node.parentNode;
 529        parent.removeChild(node);
 530        this._insertContent(parent, replacement, mechanism);
 531  
 532        return node;
 533      },
 534  
 535  
 536  /* -(  Serializing Forms  )-------------------------------------------------- */
 537  
 538  
 539      /**
 540       * Converts a form into a list of <name, value> pairs.
 541       *
 542       * Note: This function explicity does not match for submit inputs as there
 543       * could be multiple in a form. It's the caller's obligation to add the
 544       * submit input value if desired.
 545       *
 546       * @param   Node  The form element to convert into a list of pairs.
 547       * @return  List  A list of <name, value> pairs.
 548       * @task serialize
 549       */
 550      convertFormToListOfPairs : function(form) {
 551        var elements = form.getElementsByTagName('*');
 552        var data = [];
 553        for (var ii = 0; ii < elements.length; ++ii) {
 554          if (!elements[ii].name) {
 555            continue;
 556          }
 557          if (elements[ii].disabled) {
 558            continue;
 559          }
 560          var type = elements[ii].type;
 561          var tag  = elements[ii].tagName;
 562          if ((type in {radio: 1, checkbox: 1} && elements[ii].checked) ||
 563               type in {text: 1, hidden: 1, password: 1, email: 1, tel: 1,
 564                        number: 1} ||
 565               tag in {TEXTAREA: 1, SELECT: 1}) {
 566            data.push([elements[ii].name, elements[ii].value]);
 567          }
 568        }
 569        return data;
 570      },
 571  
 572  
 573      /**
 574       * Converts a form into a dictionary mapping input names to values. This
 575       * will overwrite duplicate inputs in an undefined way.
 576       *
 577       * @param   Node  The form element to convert into a dictionary.
 578       * @return  Dict  A dictionary of form values.
 579       * @task serialize
 580       */
 581      convertFormToDictionary : function(form) {
 582        var data = {};
 583        var pairs = JX.DOM.convertFormToListOfPairs(form);
 584        for (var ii = 0; ii < pairs.length; ii++) {
 585          data[pairs[ii][0]] = pairs[ii][1];
 586        }
 587        return data;
 588      },
 589  
 590  
 591  /* -(  Testing DOM Properties  )--------------------------------------------- */
 592  
 593  
 594      /**
 595       * Test if an object is a valid Node.
 596       *
 597       * @param wild Something which might be a Node.
 598       * @return bool True if the parameter is a DOM node.
 599       * @task test
 600       */
 601      isNode : function(node) {
 602        return !!(node && node.nodeName && (node !== window));
 603      },
 604  
 605  
 606      /**
 607       * Test if an object is a node of some specific (or one of several) types.
 608       * For example, this tests if the node is an ##<input />##, ##<select />##,
 609       * or ##<textarea />##.
 610       *
 611       *   JX.DOM.isType(node, ['input', 'select', 'textarea']);
 612       *
 613       * @param   wild        Something which might be a Node.
 614       * @param   string|list One or more tags which you want to test for.
 615       * @return  bool        True if the object is a node, and it's a node of one
 616       *                      of the provided types.
 617       * @task    test
 618       */
 619      isType : function(node, of_type) {
 620        node = ('' + (node.nodeName || '')).toUpperCase();
 621        of_type = JX.$AX(of_type);
 622        for (var ii = 0; ii < of_type.length; ++ii) {
 623          if (of_type[ii].toUpperCase() == node) {
 624            return true;
 625          }
 626        }
 627        return false;
 628      },
 629  
 630  
 631      /**
 632       * Listen for events occuring beneath a specific node in the DOM. This is
 633       * similar to @{JX.Stratcom.listen()}, but allows you to specify some node
 634       * which serves as a scope instead of the default scope (the whole document)
 635       * which you get if you install using @{JX.Stratcom.listen()} directly. For
 636       * example, to listen for clicks on nodes with the sigil 'menu-item' below
 637       * the root menu node:
 638       *
 639       *   var the_menu = getReferenceToTheMenuNodeSomehow();
 640       *   JX.DOM.listen(the_menu, 'click', 'menu-item', function(e) { ... });
 641       *
 642       * @task stratcom
 643       * @param Node        The node to listen for events underneath.
 644       * @param string|list One or more event types to listen for.
 645       * @param list?       A path to listen on, or a list of paths.
 646       * @param function    Callback to invoke when a matching event occurs.
 647       * @return object     A reference to the installed listener. You can later
 648       *                    remove the listener by calling this object's remove()
 649       *                    method.
 650       */
 651      listen : function(node, type, path, callback) {
 652        var auto_id = ['autoid:' + JX.DOM._getAutoID(node)];
 653        path = JX.$AX(path || []);
 654        if (!path.length) {
 655          path = auto_id;
 656        } else {
 657          for (var ii = 0; ii < path.length; ii++) {
 658            path[ii] = auto_id.concat(JX.$AX(path[ii]));
 659          }
 660        }
 661        return JX.Stratcom.listen(type, path, callback);
 662      },
 663  
 664  
 665      /**
 666       * Invoke a custom event on a node. This method is a companion to
 667       * @{method:JX.DOM.listen} and parallels @{method:JX.Stratcom.invoke} in
 668       * the same way that method parallels @{method:JX.Stratcom.listen}.
 669       *
 670       * This method can not be used to invoke native events (like 'click').
 671       *
 672       * @param Node      The node to invoke an event on.
 673       * @param string    Custom event type.
 674       * @param dict      Event data.
 675       * @return JX.Event The event object which was dispatched to listeners.
 676       *                  The main use of this is to test whether any
 677       *                  listeners prevented the event.
 678       */
 679      invoke : function(node, type, data) {
 680        if (__DEV__) {
 681          if (type in JX.__allowedEvents) {
 682            throw new Error(
 683              'JX.DOM.invoke(..., "' + type + '", ...): ' +
 684              'you cannot invoke with the same type as a native event.');
 685          }
 686        }
 687        return JX.Stratcom.dispatch({
 688          target: node,
 689          type: type,
 690          customData: data
 691        });
 692      },
 693  
 694  
 695      uniqID : function(node) {
 696        if (!node.getAttribute('id')) {
 697          node.setAttribute('id', 'uniqid_'+(++JX.DOM._uniqid));
 698        }
 699        return node.getAttribute('id');
 700      },
 701  
 702      alterClass : function(node, className, add) {
 703        if (__DEV__) {
 704          if (add !== false && add !== true) {
 705            JX.$E(
 706              'JX.DOM.alterClass(...): ' +
 707              'expects the third parameter to be Boolean: ' +
 708              add + ' was provided');
 709          }
 710        }
 711  
 712        var has = ((' '+node.className+' ').indexOf(' '+className+' ') > -1);
 713        if (add && !has) {
 714          node.className += ' '+className;
 715        } else if (has && !add) {
 716          node.className = node.className.replace(
 717            new RegExp('(^|\\s)' + className + '(?:\\s|$)', 'g'), ' ');
 718        }
 719      },
 720  
 721      htmlize : function(str) {
 722        return (''+str)
 723          .replace(/&/g, '&amp;')
 724          .replace(/"/g, '&quot;')
 725          .replace(/</g, '&lt;')
 726          .replace(/>/g, '&gt;');
 727      },
 728  
 729  
 730      /**
 731       * Show one or more elements, by removing their "display" style. This
 732       * assumes you have hidden them with @{method:hide}, or explicitly set
 733       * the style to `display: none;`.
 734       *
 735       * @task convenience
 736       * @param ... One or more nodes to remove "display" styles from.
 737       * @return void
 738       */
 739      show : function() {
 740        var ii;
 741  
 742        if (__DEV__) {
 743          for (ii = 0; ii < arguments.length; ++ii) {
 744            if (!arguments[ii]) {
 745              JX.$E(
 746                'JX.DOM.show(...): ' +
 747                'one or more arguments were null or empty.');
 748            }
 749          }
 750        }
 751  
 752        for (ii = 0; ii < arguments.length; ++ii) {
 753          arguments[ii].style.display = '';
 754        }
 755      },
 756  
 757  
 758      /**
 759       * Hide one or more elements, by setting `display: none;` on them. This is
 760       * a convenience method. See also @{method:show}.
 761       *
 762       * @task convenience
 763       * @param ... One or more nodes to set "display: none" on.
 764       * @return void
 765       */
 766      hide : function() {
 767        var ii;
 768  
 769        if (__DEV__) {
 770          for (ii = 0; ii < arguments.length; ++ii) {
 771            if (!arguments[ii]) {
 772              JX.$E(
 773                'JX.DOM.hide(...): ' +
 774                'one or more arguments were null or empty.');
 775            }
 776          }
 777        }
 778  
 779        for (ii = 0; ii < arguments.length; ++ii) {
 780          arguments[ii].style.display = 'none';
 781        }
 782      },
 783  
 784      textMetrics : function(node, pseudoclass, x) {
 785        if (!this._metrics[pseudoclass]) {
 786          var n = JX.$N(
 787            'var',
 788            {className: pseudoclass});
 789          this._metrics[pseudoclass] = n;
 790        }
 791        var proxy = this._metrics[pseudoclass];
 792        document.body.appendChild(proxy);
 793        proxy.style.width = x ? (x+'px') : '';
 794        JX.DOM.setContent(
 795          proxy,
 796          JX.$H(JX.DOM.htmlize(node.value).replace(/\n/g, '<br />')));
 797        var metrics = JX.Vector.getDim(proxy);
 798        document.body.removeChild(proxy);
 799        return metrics;
 800      },
 801  
 802  
 803      /**
 804       * Search the document for DOM nodes by providing a root node to look
 805       * beneath, a tag name, and (optionally) a sigil. Nodes which match all
 806       * specified conditions are returned.
 807       *
 808       * @task query
 809       *
 810       * @param  Node    Root node to search beneath.
 811       * @param  string  Tag name, like 'a' or 'textarea'.
 812       * @param  string  Optionally, a sigil which nodes are required to have.
 813       *
 814       * @return list    List of matching nodes, which may be empty.
 815       */
 816      scry : function(root, tagname, sigil) {
 817        if (__DEV__) {
 818          if (!JX.DOM.isNode(root)) {
 819            JX.$E(
 820              'JX.DOM.scry(<yuck>, ...): '+
 821              'first argument must be a DOM node.');
 822          }
 823        }
 824  
 825        var nodes = root.getElementsByTagName(tagname);
 826        if (!sigil) {
 827          return JX.$A(nodes);
 828        }
 829        var result = [];
 830        for (var ii = 0; ii < nodes.length; ii++) {
 831          if (JX.Stratcom.hasSigil(nodes[ii], sigil)) {
 832            result.push(nodes[ii]);
 833          }
 834        }
 835        return result;
 836      },
 837  
 838  
 839      /**
 840       * Select a node uniquely identified by a root, tagname and sigil. This
 841       * is similar to JX.DOM.scry() but expects exactly one result.
 842       *
 843       * @task query
 844       *
 845       * @param  Node    Root node to search beneath.
 846       * @param  string  Tag name, like 'a' or 'textarea'.
 847       * @param  string  Optionally, sigil which selected node must have.
 848       *
 849       * @return Node    Node uniquely identified by the criteria.
 850       */
 851      find : function(root, tagname, sigil) {
 852        if (__DEV__) {
 853          if (!JX.DOM.isNode(root)) {
 854            JX.$E(
 855              'JX.DOM.find(<glop>, "'+tagname+'", "'+sigil+'"): '+
 856              'first argument must be a DOM node.');
 857          }
 858        }
 859  
 860        var result = JX.DOM.scry(root, tagname, sigil);
 861  
 862        if (__DEV__) {
 863          if (result.length > 1) {
 864            JX.$E(
 865              'JX.DOM.find(<node>, "'+tagname+'", "'+sigil+'"): '+
 866              'matched more than one node.');
 867          }
 868        }
 869  
 870        if (!result.length) {
 871          JX.$E(
 872            'JX.DOM.find(<node>, "' + tagname + '", "' + sigil + '"): ' +
 873            'matched no nodes.');
 874        }
 875  
 876        return result[0];
 877      },
 878  
 879  
 880      /**
 881       * Select a node uniquely identified by an anchor, tagname, and sigil. This
 882       * is similar to JX.DOM.find() but walks up the DOM tree instead of down
 883       * it.
 884       *
 885       * @param   Node    Node to look above.
 886       * @param   string  Tag name, like 'a' or 'textarea'.
 887       * @param   string  Optionally, sigil which selected node must have.
 888       * @return  Node    Matching node.
 889       *
 890       * @task    query
 891       */
 892      findAbove : function(anchor, tagname, sigil) {
 893        if (__DEV__) {
 894          if (!JX.DOM.isNode(anchor)) {
 895            JX.$E(
 896              'JX.DOM.findAbove(<glop>, "' + tagname + '", "' + sigil + '"): ' +
 897              'first argument must be a DOM node.');
 898          }
 899        }
 900  
 901        var result = anchor.parentNode;
 902        while (true) {
 903          if (!result) {
 904            break;
 905          }
 906          if (JX.DOM.isType(result, tagname)) {
 907            if (!sigil || JX.Stratcom.hasSigil(result, sigil)) {
 908              break;
 909            }
 910          }
 911          result = result.parentNode;
 912        }
 913  
 914        if (!result) {
 915          JX.$E(
 916            'JX.DOM.findAbove(<node>, "' + tagname + '", "' + sigil + '"): ' +
 917            'no matching node.');
 918        }
 919  
 920        return result;
 921      },
 922  
 923  
 924      /**
 925       * Focus a node safely. This is just a convenience wrapper that allows you
 926       * to avoid IE's habit of throwing when nearly any focus operation is
 927       * invoked.
 928       *
 929       * @task convenience
 930       * @param Node Node to move cursor focus to, if possible.
 931       * @return void
 932       */
 933      focus : function(node) {
 934        try { node.focus(); } catch (lol_ie) {}
 935      },
 936  
 937  
 938      /**
 939       * Scroll to the position of an element in the document.
 940       * @task view
 941       * @param Node Node to move document scroll position to, if possible.
 942       * @return void
 943       */
 944      scrollTo : function(node) {
 945        window.scrollTo(0, JX.$V(node).y);
 946      },
 947  
 948      _getAutoID : function(node) {
 949        if (!node.getAttribute('data-autoid')) {
 950          node.setAttribute('data-autoid', 'autoid_'+(++JX.DOM._autoid));
 951        }
 952        return node.getAttribute('data-autoid');
 953      }
 954    }
 955  });


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