Path: blob/master/webroot/rsrc/externals/javelin/lib/DOM.js
12242 views
/**1* @requires javelin-magical-init2* javelin-install3* javelin-util4* javelin-vector5* javelin-stratcom6* @provides javelin-dom7*8* @javelin-installs JX.$9* @javelin-installs JX.$N10* @javelin-installs JX.$H11*12* @javelin13*/141516/**17* Select an element by its "id" attribute, like ##document.getElementById()##.18* For example:19*20* var node = JX.$('some_id');21*22* This will select the node with the specified "id" attribute:23*24* LANG=HTML25* <div id="some_id">...</div>26*27* If the specified node does not exist, @{JX.$()} will throw an exception.28*29* For other ways to select nodes from the document, see @{JX.DOM.scry()} and30* @{JX.DOM.find()}.31*32* @param string "id" attribute to select from the document.33* @return Node Node with the specified "id" attribute.34*/35JX.$ = function(id) {3637if (__DEV__) {38if (!id) {39JX.$E('Empty ID passed to JX.$()!');40}41}4243var node = document.getElementById(id);44if (!node || (node.id != id)) {45if (__DEV__) {46if (node && (node.id != id)) {47JX.$E(48'JX.$(\''+id+'\'): '+49'document.getElementById() returned an element without the '+50'correct ID. This usually means that the element you are trying '+51'to select is being masked by a form with the same value in its '+52'"name" attribute.');53}54}55JX.$E('JX.$(\'' + id + '\') call matched no nodes.');56}5758return node;59};6061/**62* Upcast a string into an HTML object so it is treated as markup instead of63* plain text. See @{JX.$N} for discussion of Javelin's security model. Every64* time you call this function you potentially open up a security hole. Avoid65* its use wherever possible.66*67* This class intentionally supports only a subset of HTML because many browsers68* named "Internet Explorer" have awkward restrictions around what they'll69* accept for conversion to document fragments. Alter your datasource to emit70* valid HTML within this subset if you run into an unsupported edge case. All71* the edge cases are crazy and you should always be reasonably able to emit72* a cohesive tag instead of an unappendable fragment.73*74* You may use @{JX.$H} as a shortcut for creating new JX.HTML instances:75*76* JX.$N('div', {}, some_html_blob); // Treat as string (safe)77* JX.$N('div', {}, JX.$H(some_html_blob)); // Treat as HTML (unsafe!)78*79* @task build String into HTML80* @task nodes HTML into Nodes81*/82JX.install('HTML', {8384construct : function(str) {85if (str instanceof JX.HTML) {86this._content = str._content;87return;88}8990if (__DEV__) {91if ((typeof str !== 'string') && (!str || !str.match)) {92JX.$E(93'new JX.HTML(<empty?>): ' +94'call initializes an HTML object with an empty value.');95}9697var tags = ['legend', 'thead', 'tbody', 'tfoot', 'column', 'colgroup',98'caption', 'tr', 'th', 'td', 'option'];99var evil_stuff = new RegExp('^\\s*<(' + tags.join('|') + ')\\b', 'i');100var match = str.match(evil_stuff);101if (match) {102JX.$E(103'new JX.HTML("<' + match[1] + '>..."): ' +104'call initializes an HTML object with an invalid partial fragment ' +105'and can not be converted into DOM nodes. The enclosing tag of an ' +106'HTML content string must be appendable to a document fragment. ' +107'For example, <table> is allowed but <tr> or <tfoot> are not.');108}109110var really_evil = /<script\b/;111if (str.match(really_evil)) {112JX.$E(113'new JX.HTML("...<script>..."): ' +114'call initializes an HTML object with an embedded script tag! ' +115'Are you crazy?! Do NOT do this!!!');116}117118var wont_work = /<object\b/;119if (str.match(wont_work)) {120JX.$E(121'new JX.HTML("...<object>..."): ' +122'call initializes an HTML object with an embedded <object> tag. IE ' +123'will not do the right thing with this.');124}125126// TODO: May need to deny <option> more broadly, see127// http://support.microsoft.com/kb/829907 and the whole mess in the128// heavy stack. But I seem to have gotten away without cloning into the129// documentFragment below, so this may be a nonissue.130}131132this._content = str;133},134135members : {136_content : null,137/**138* Convert the raw HTML string into a DOM node tree.139*140* @task nodes141* @return DocumentFragment A document fragment which contains the nodes142* corresponding to the HTML string you provided.143*/144getFragment : function() {145var wrapper = JX.$N('div');146wrapper.innerHTML = this._content;147var fragment = document.createDocumentFragment();148while (wrapper.firstChild) {149// TODO: Do we need to do a bunch of cloning junk here?150// See heavy stack. I'm disconnecting the nodes instead; this seems151// to work but maybe my test case just isn't extensive enough.152fragment.appendChild(wrapper.removeChild(wrapper.firstChild));153}154return fragment;155},156157/**158* Convert the raw HTML string into a single DOM node. This only works159* if the element has a single top-level element. Otherwise, use160* @{method:getFragment} to get a document fragment instead.161*162* @return Node Single node represented by the object.163* @task nodes164*/165getNode : function() {166var fragment = this.getFragment();167if (__DEV__) {168if (fragment.childNodes.length < 1) {169JX.$E('JX.HTML.getNode(): Markup has no root node!');170}171if (fragment.childNodes.length > 1) {172JX.$E('JX.HTML.getNode(): Markup has more than one root node!');173}174}175return fragment.firstChild;176}177178}179});180181182/**183* Build a new HTML object from a trustworthy string. JX.$H is a shortcut for184* creating new JX.HTML instances.185*186* @task build187* @param string A string which you want to be treated as HTML, because you188* know it is from a trusted source and any data in it has been189* properly escaped.190* @return JX.HTML HTML object, suitable for use with @{JX.$N}.191*/192JX.$H = function(str) {193return new JX.HTML(str);194};195196197/**198* Create a new DOM node with attributes and content.199*200* var link = JX.$N('a');201*202* This creates a new, empty anchor tag without any attributes. The equivalent203* markup would be:204*205* LANG=HTML206* <a />207*208* You can also specify attributes by passing a dictionary:209*210* JX.$N('a', {name: 'anchor'});211*212* This is equivalent to:213*214* LANG=HTML215* <a name="anchor" />216*217* Additionally, you can specify content:218*219* JX.$N(220* 'a',221* {href: 'http://www.javelinjs.com'},222* 'Visit the Javelin Homepage');223*224* This is equivalent to:225*226* LANG=HTML227* <a href="http://www.javelinjs.com">Visit the Javelin Homepage</a>228*229* If you only want to specify content, you can omit the attribute parameter.230* That is, these calls are equivalent:231*232* JX.$N('div', {}, 'Lorem ipsum...'); // No attributes.233* JX.$N('div', 'Lorem ipsum...') // Same as above.234*235* Both are equivalent to:236*237* LANG=HTML238* <div>Lorem ipsum...</div>239*240* Note that the content is treated as plain text, not HTML. This means it is241* safe to use untrusted strings:242*243* JX.$N('div', '<script src="evil.com" />');244*245* This is equivalent to:246*247* LANG=HTML248* <div><script src="evil.com" /></div>249*250* That is, the content will be properly escaped and will not create a251* vulnerability. If you want to set HTML content, you can use @{JX.HTML}:252*253* JX.$N('div', JX.$H(some_html));254*255* **This is potentially unsafe**, so make sure you understand what you're256* doing. You should usually avoid passing HTML around in string form. See257* @{JX.HTML} for discussion.258*259* You can create new nodes with a Javelin sigil (and, optionally, metadata) by260* providing "sigil" and "meta" keys in the attribute dictionary.261*262* @param string Tag name, like 'a' or 'div'.263* @param dict|string|@{JX.HTML}? Property dictionary, or content if you don't264* want to specify any properties.265* @param string|@{JX.HTML}? Content string (interpreted as plain text)266* or @{JX.HTML} object (interpreted as HTML,267* which may be dangerous).268* @return Node New node with whatever attributes and269* content were specified.270*/271JX.$N = function(tag, attr, content) {272if (typeof content == 'undefined' &&273(typeof attr != 'object' || attr instanceof JX.HTML)) {274content = attr;275attr = {};276}277278if (__DEV__) {279if (tag.toLowerCase() != tag) {280JX.$E(281'$N("'+tag+'", ...): '+282'tag name must be in lower case; '+283'use "'+tag.toLowerCase()+'", not "'+tag+'".');284}285}286287var node = document.createElement(tag);288289if (attr.style) {290JX.copy(node.style, attr.style);291delete attr.style;292}293294if (attr.sigil) {295JX.Stratcom.addSigil(node, attr.sigil);296delete attr.sigil;297}298299if (attr.meta) {300JX.Stratcom.addData(node, attr.meta);301delete attr.meta;302}303304if (__DEV__) {305if (('metadata' in attr) || ('data' in attr)) {306JX.$E(307'$N(' + tag + ', ...): ' +308'use the key "meta" to specify metadata, not "data" or "metadata".');309}310}311312for (var k in attr) {313if (attr[k] === null) {314continue;315}316node[k] = attr[k];317}318319if (content) {320JX.DOM.setContent(node, content);321}322return node;323};324325326/**327* Query and update the DOM. Everything here is static, this is essentially328* a collection of common utility functions.329*330* @task stratcom Attaching Event Listeners331* @task content Changing DOM Content332* @task nodes Updating Nodes333* @task serialize Serializing Forms334* @task test Testing DOM Properties335* @task convenience Convenience Methods336* @task query Finding Nodes in the DOM337* @task view Changing View State338*/339JX.install('DOM', {340statics : {341_autoid : 0,342_uniqid : 0,343_metrics : {},344_frameNode: null,345_contentNode: null,346347348/* -( Changing DOM Content )----------------------------------------------- */349350351/**352* Set the content of some node. This uses the same content semantics as353* other Javelin content methods, see @{function:JX.$N} for a detailed354* explanation. Previous content will be replaced: you can also355* @{method:prependContent} or @{method:appendContent}.356*357* @param Node Node to set content of.358* @param mixed Content to set.359* @return void360* @task content361*/362setContent : function(node, content) {363if (__DEV__) {364if (!JX.DOM.isNode(node)) {365JX.$E(366'JX.DOM.setContent(<yuck>, ...): '+367'first argument must be a DOM node.');368}369}370371while (node.firstChild) {372JX.DOM.remove(node.firstChild);373}374JX.DOM.appendContent(node, content);375},376377378/**379* Prepend content to some node. This method uses the same content semantics380* as other Javelin methods, see @{function:JX.$N} for an explanation. You381* can also @{method:setContent} or @{method:appendContent}.382*383* @param Node Node to prepend content to.384* @param mixed Content to prepend.385* @return void386* @task content387*/388prependContent : function(node, content) {389if (__DEV__) {390if (!JX.DOM.isNode(node)) {391JX.$E(392'JX.DOM.prependContent(<junk>, ...): '+393'first argument must be a DOM node.');394}395}396397this._insertContent(node, content, this._mechanismPrepend, true);398},399400401/**402* Append content to some node. This method uses the same content semantics403* as other Javelin methods, see @{function:JX.$N} for an explanation. You404* can also @{method:setContent} or @{method:prependContent}.405*406* @param Node Node to append the content of.407* @param mixed Content to append.408* @return void409* @task content410*/411appendContent : function(node, content) {412if (__DEV__) {413if (!JX.DOM.isNode(node)) {414JX.$E(415'JX.DOM.appendContent(<bleh>, ...): '+416'first argument must be a DOM node.');417}418}419420this._insertContent(node, content, this._mechanismAppend);421},422423424/**425* Internal, add content to a node by prepending.426*427* @param Node Node to prepend content to.428* @param Node Node to prepend.429* @return void430* @task content431*/432_mechanismPrepend : function(node, content) {433node.insertBefore(content, node.firstChild);434},435436437/**438* Internal, add content to a node by appending.439*440* @param Node Node to append content to.441* @param Node Node to append.442* @task content443*/444_mechanismAppend : function(node, content) {445node.appendChild(content);446},447448449/**450* Internal, add content to a node using some specified mechanism.451*452* @param Node Node to add content to.453* @param mixed Content to add.454* @param function Callback for actually adding the nodes.455* @param bool True if array elements should be passed to the mechanism456* in reverse order, i.e. the mechanism prepends nodes.457* @return void458* @task content459*/460_insertContent : function(parent, content, mechanism, reverse) {461if (JX.isArray(content)) {462if (reverse) {463content = [].concat(content).reverse();464}465for (var ii = 0; ii < content.length; ii++) {466JX.DOM._insertContent(parent, content[ii], mechanism, reverse);467}468} else {469var type = typeof content;470if (content instanceof JX.HTML) {471content = content.getFragment();472} else if (type == 'string' || type == 'number') {473content = document.createTextNode(content);474}475476if (__DEV__) {477if (content && !content.nodeType) {478JX.$E(479'JX.DOM._insertContent(<node>, ...): '+480'second argument must be a string, a number, ' +481'a DOM node or a JX.HTML instance');482}483}484485content && mechanism(parent, content);486}487},488489490/* -( Updating Nodes )----------------------------------------------------- */491492493/**494* Remove a node from its parent, so it is no longer a child of any other495* node.496*497* @param Node Node to remove.498* @return Node The node.499* @task nodes500*/501remove : function(node) {502node.parentNode && JX.DOM.replace(node, null);503return node;504},505506507/**508* Replace a node with some other piece of content. This method obeys509* Javelin content semantics, see @{function:JX.$N} for an explanation.510* You can also @{method:setContent}, @{method:prependContent}, or511* @{method:appendContent}.512*513* @param Node Node to replace.514* @param mixed Content to replace it with.515* @return Node the original node.516* @task nodes517*/518replace : function(node, replacement) {519if (__DEV__) {520if (!node.parentNode) {521JX.$E(522'JX.DOM.replace(<node>, ...): '+523'node has no parent node, so it can not be replaced.');524}525}526527var mechanism;528if (node.nextSibling) {529mechanism = JX.bind(node.nextSibling, function(parent, content) {530parent.insertBefore(content, this);531});532} else {533mechanism = this._mechanismAppend;534}535var parent = node.parentNode;536parent.removeChild(node);537this._insertContent(parent, replacement, mechanism);538539return node;540},541542543/* -( Serializing Forms )-------------------------------------------------- */544545546/**547* Converts a form into a list of <name, value> pairs.548*549* Note: This function explicity does not match for submit inputs as there550* could be multiple in a form. It's the caller's obligation to add the551* submit input value if desired.552*553* @param Node The form element to convert into a list of pairs.554* @return List A list of <name, value> pairs.555* @task serialize556*/557convertFormToListOfPairs : function(form) {558var elements = form.getElementsByTagName('*');559var data = [];560for (var ii = 0; ii < elements.length; ++ii) {561if (!elements[ii].name) {562continue;563}564if (elements[ii].disabled) {565continue;566}567var type = elements[ii].type;568var tag = elements[ii].tagName;569if ((type in {radio: 1, checkbox: 1} && elements[ii].checked) ||570type in {text: 1, hidden: 1, password: 1, email: 1, tel: 1,571number: 1} ||572tag in {TEXTAREA: 1, SELECT: 1}) {573data.push([elements[ii].name, elements[ii].value]);574}575}576return data;577},578579580/**581* Converts a form into a dictionary mapping input names to values. This582* will overwrite duplicate inputs in an undefined way.583*584* @param Node The form element to convert into a dictionary.585* @return Dict A dictionary of form values.586* @task serialize587*/588convertFormToDictionary : function(form) {589var data = {};590var pairs = JX.DOM.convertFormToListOfPairs(form);591for (var ii = 0; ii < pairs.length; ii++) {592data[pairs[ii][0]] = pairs[ii][1];593}594return data;595},596597598/* -( Testing DOM Properties )--------------------------------------------- */599600601/**602* Test if an object is a valid Node.603*604* @param wild Something which might be a Node.605* @return bool True if the parameter is a DOM node.606* @task test607*/608isNode : function(node) {609return !!(node && node.nodeName && (node !== window));610},611612613/**614* Test if an object is a node of some specific (or one of several) types.615* For example, this tests if the node is an ##<input />##, ##<select />##,616* or ##<textarea />##.617*618* JX.DOM.isType(node, ['input', 'select', 'textarea']);619*620* @param wild Something which might be a Node.621* @param string|list One or more tags which you want to test for.622* @return bool True if the object is a node, and it's a node of one623* of the provided types.624* @task test625*/626isType : function(node, of_type) {627node = ('' + (node.nodeName || '')).toUpperCase();628of_type = JX.$AX(of_type);629for (var ii = 0; ii < of_type.length; ++ii) {630if (of_type[ii].toUpperCase() == node) {631return true;632}633}634return false;635},636637638/**639* Listen for events occuring beneath a specific node in the DOM. This is640* similar to @{JX.Stratcom.listen()}, but allows you to specify some node641* which serves as a scope instead of the default scope (the whole document)642* which you get if you install using @{JX.Stratcom.listen()} directly. For643* example, to listen for clicks on nodes with the sigil 'menu-item' below644* the root menu node:645*646* var the_menu = getReferenceToTheMenuNodeSomehow();647* JX.DOM.listen(the_menu, 'click', 'menu-item', function(e) { ... });648*649* @task stratcom650* @param Node The node to listen for events underneath.651* @param string|list One or more event types to listen for.652* @param list? A path to listen on, or a list of paths.653* @param function Callback to invoke when a matching event occurs.654* @return object A reference to the installed listener. You can later655* remove the listener by calling this object's remove()656* method.657*/658listen : function(node, type, path, callback) {659var auto_id = ['autoid:' + JX.DOM._getAutoID(node)];660path = JX.$AX(path || []);661if (!path.length) {662path = auto_id;663} else {664for (var ii = 0; ii < path.length; ii++) {665path[ii] = auto_id.concat(JX.$AX(path[ii]));666}667}668return JX.Stratcom.listen(type, path, callback);669},670671672/**673* Invoke a custom event on a node. This method is a companion to674* @{method:JX.DOM.listen} and parallels @{method:JX.Stratcom.invoke} in675* the same way that method parallels @{method:JX.Stratcom.listen}.676*677* This method can not be used to invoke native events (like 'click').678*679* @param Node The node to invoke an event on.680* @param string Custom event type.681* @param dict Event data.682* @return JX.Event The event object which was dispatched to listeners.683* The main use of this is to test whether any684* listeners prevented the event.685*/686invoke : function(node, type, data) {687if (__DEV__) {688if (type in JX.__allowedEvents) {689throw new Error(690'JX.DOM.invoke(..., "' + type + '", ...): ' +691'you cannot invoke with the same type as a native event.');692}693}694return JX.Stratcom.dispatch({695target: node,696type: type,697customData: data698});699},700701702uniqID : function(node) {703if (!node.getAttribute('id')) {704node.setAttribute('id', 'uniqid_'+(++JX.DOM._uniqid));705}706return node.getAttribute('id');707},708709alterClass : function(node, className, add) {710if (__DEV__) {711if (add !== false && add !== true) {712JX.$E(713'JX.DOM.alterClass(...): ' +714'expects the third parameter to be Boolean: ' +715add + ' was provided');716}717}718719var has = ((' '+node.className+' ').indexOf(' '+className+' ') > -1);720if (add && !has) {721node.className += ' '+className;722} else if (has && !add) {723node.className = node.className.replace(724new RegExp('(^|\\s)' + className + '(?:\\s|$)', 'g'), ' ').trim();725}726},727728htmlize : function(str) {729return (''+str)730.replace(/&/g, '&')731.replace(/"/g, '"')732.replace(/</g, '<')733.replace(/>/g, '>');734},735736737/**738* Show one or more elements, by removing their "display" style. This739* assumes you have hidden them with @{method:hide}, or explicitly set740* the style to `display: none;`.741*742* @task convenience743* @param ... One or more nodes to remove "display" styles from.744* @return void745*/746show : function() {747var ii;748749if (__DEV__) {750for (ii = 0; ii < arguments.length; ++ii) {751if (!arguments[ii]) {752JX.$E(753'JX.DOM.show(...): ' +754'one or more arguments were null or empty.');755}756}757}758759for (ii = 0; ii < arguments.length; ++ii) {760arguments[ii].style.display = '';761}762},763764765/**766* Hide one or more elements, by setting `display: none;` on them. This is767* a convenience method. See also @{method:show}.768*769* @task convenience770* @param ... One or more nodes to set "display: none" on.771* @return void772*/773hide : function() {774var ii;775776if (__DEV__) {777for (ii = 0; ii < arguments.length; ++ii) {778if (!arguments[ii]) {779JX.$E(780'JX.DOM.hide(...): ' +781'one or more arguments were null or empty.');782}783}784}785786for (ii = 0; ii < arguments.length; ++ii) {787arguments[ii].style.display = 'none';788}789},790791textMetrics : function(node, pseudoclass, x) {792if (!this._metrics[pseudoclass]) {793var n = JX.$N(794'var',795{className: pseudoclass});796this._metrics[pseudoclass] = n;797}798var proxy = this._metrics[pseudoclass];799document.body.appendChild(proxy);800proxy.style.width = x ? (x+'px') : '';801JX.DOM.setContent(802proxy,803JX.$H(JX.DOM.htmlize(node.value).replace(/\n/g, '<br />')));804var metrics = JX.Vector.getDim(proxy);805document.body.removeChild(proxy);806return metrics;807},808809810/**811* Search the document for DOM nodes by providing a root node to look812* beneath, a tag name, and (optionally) a sigil. Nodes which match all813* specified conditions are returned.814*815* @task query816*817* @param Node Root node to search beneath.818* @param string Tag name, like 'a' or 'textarea'.819* @param string Optionally, a sigil which nodes are required to have.820*821* @return list List of matching nodes, which may be empty.822*/823scry : function(root, tagname, sigil) {824if (__DEV__) {825if (!JX.DOM.isNode(root)) {826JX.$E(827'JX.DOM.scry(<yuck>, ...): '+828'first argument must be a DOM node.');829}830}831832var nodes = root.getElementsByTagName(tagname);833if (!sigil) {834return JX.$A(nodes);835}836var result = [];837for (var ii = 0; ii < nodes.length; ii++) {838if (JX.Stratcom.hasSigil(nodes[ii], sigil)) {839result.push(nodes[ii]);840}841}842return result;843},844845846/**847* Select a node uniquely identified by a root, tagname and sigil. This848* is similar to JX.DOM.scry() but expects exactly one result.849*850* @task query851*852* @param Node Root node to search beneath.853* @param string Tag name, like 'a' or 'textarea'.854* @param string Optionally, sigil which selected node must have.855*856* @return Node Node uniquely identified by the criteria.857*/858find : function(root, tagname, sigil) {859if (__DEV__) {860if (!JX.DOM.isNode(root)) {861JX.$E(862'JX.DOM.find(<glop>, "'+tagname+'", "'+sigil+'"): '+863'first argument must be a DOM node.');864}865}866867var result = JX.DOM.scry(root, tagname, sigil);868869if (__DEV__) {870if (result.length > 1) {871JX.$E(872'JX.DOM.find(<node>, "'+tagname+'", "'+sigil+'"): '+873'matched more than one node.');874}875}876877if (!result.length) {878JX.$E(879'JX.DOM.find(<node>, "' + tagname + '", "' + sigil + '"): ' +880'matched no nodes.');881}882883return result[0];884},885886887/**888* Select a node uniquely identified by an anchor, tagname, and sigil. This889* is similar to JX.DOM.find() but walks up the DOM tree instead of down890* it.891*892* @param Node Node to look above.893* @param string Optional tag name, like 'a' or 'textarea'.894* @param string Optionally, sigil which selected node must have.895* @return Node Matching node.896*897* @task query898*/899findAbove : function(anchor, tagname, sigil) {900if (__DEV__) {901if (!JX.DOM.isNode(anchor)) {902JX.$E(903'JX.DOM.findAbove(<glop>, "' + tagname + '", "' + sigil + '"): ' +904'first argument must be a DOM node.');905}906}907908var result = anchor.parentNode;909while (true) {910if (!result) {911break;912}913if (!tagname || JX.DOM.isType(result, tagname)) {914if (!sigil || JX.Stratcom.hasSigil(result, sigil)) {915break;916}917}918result = result.parentNode;919}920921if (!result) {922JX.$E(923'JX.DOM.findAbove(<node>, "' + tagname + '", "' + sigil + '"): ' +924'no matching node.');925}926927return result;928},929930931/**932* Focus a node safely. This is just a convenience wrapper that allows you933* to avoid IE's habit of throwing when nearly any focus operation is934* invoked.935*936* @task convenience937* @param Node Node to move cursor focus to, if possible.938* @return void939*/940focus : function(node) {941try { node.focus(); } catch (lol_ie) {}942},943944945/**946* Set specific nodes as content and frame nodes for the document.947*948* This will cause @{method:scrollTo} and @{method:scrollToPosition} to949* affect the given frame node instead of the window. This is useful if the950* page content is broken into multiple panels which scroll independently.951*952* Normally, both nodes are the document body.953*954* @task view955* @param Node Node to set as the scroll frame.956* @param Node Node to set as the content frame.957* @return void958*/959setContentFrame: function(frame_node, content_node) {960JX.DOM._frameNode = frame_node;961JX.DOM._contentNode = content_node;962},963964965/**966* Get the current content frame, or `document.body` if one has not been967* set.968*969* @task view970* @return Node The node which frames the main page content.971* @return void972*/973getContentFrame: function() {974return JX.DOM._contentNode || document.body;975},976977/**978* Scroll to the position of an element in the document.979*980* If @{method:setContentFrame} has been used to set a frame, that node is981* scrolled.982*983* @task view984* @param Node Node to move document scroll position to, if possible.985* @return void986*/987scrollTo : function(node) {988var pos = JX.Vector.getPosWithScroll(node);989JX.DOM.scrollToPosition(0, pos.y);990},991992/**993* Scroll to a specific position in the document.994*995* If @{method:setContentFrame} has been used to set a frame, that node is996* scrolled.997*998* @task view999* @param int X position, in pixels.1000* @param int Y position, in pixels.1001* @return void1002*/1003scrollToPosition: function(x, y) {1004var self = JX.DOM;1005if (self._frameNode) {1006self._frameNode.scrollLeft = x;1007self._frameNode.scrollTop = y;1008} else {1009window.scrollTo(x, y);1010}1011},10121013_getAutoID : function(node) {1014if (!node.getAttribute('data-autoid')) {1015node.setAttribute('data-autoid', 'autoid_'+(++JX.DOM._autoid));1016}1017return node.getAttribute('data-autoid');1018}1019}1020});102110221023