DOC on java for manipulating DOM (General java doc in "DOCjava".) ===================================================== The root of the object (and DOM?) tree is: window -> document -> (html -> head, body) ... In document, HTML is the root: doc->html-> head, body These are defined fields: window.document document.body [gets body OR outermost frameset] document.url document.element ?? 'this' refers to window (except in event-catching). var 'window' refers to window: is actually a var/attribute of window that self-refers. So: if I just JS to create a new (popup) window, this is not a child of my doc. Of course, can do this: just a new tree of objects. Can have ptrs to that tree or parts of it. ?Are all these DOM objects with parent, child etc.? ?does getElementById() work from window? or only from doc? onload() should be body.onload. (If it must be win.onload then have this set body.onload() var. ) I.e. body won't be created when win is loading. JScript global vars are fields/properties of window. Iff they are declared globally with var, then they cannot be deleted. window.x = ''; similarly creates global JS var x. But implicit creation (no var) means on some browsers that you can't delete it, nor see it by enumeration. With Var then the symbol and so window.x field is created on a prepass of the code, not when the line with var is exec. SOME BASIC WAYS OF GETTING ALL ELEMENTS OF SOME TYPE var arr; arr = document.getElementById("aaa").getAttribute("href"); arr = document.getElementsByClassName("abc xx"); // gets elems that are members of both those classes. arr = myform.getElementsByTagName("input"); // But will these got objects that are NOT displayed? ===================================================== A1] JAVA VIEW OF WEB PAGES -- DOM. General points. JScript can address all aspects of the HTML objects (see below). But it seems to act as if JS were creating those attributes as necessary, rather than letting you tell if those attributes were set in HTML. But can test this e.g. if(TDptr.hasAttribute("colspan")) tells me whether or not colspan was set while: if(isNaN(TDptr.colSpan) || TDptr.colSpan > 1) always behaves as if colSpan already was set to some definite value (1 as default). (Can test, create, delete attributes of any node using jscript functions. hasAttribute(), getAttribute(), setAttribute(), removeAttribute() getAttributeNode() etc. same but take a node ptr rather than a name as arg. ) BUT: can change an attribute also by this: TDptr.colSpan = 3; TDptr.colSpan.specified -- is true or false. [Is this right? and does it test value not prop?] obj.hasOwnProperty(): because JS is all about object/inheritance, the JS way to do this is ask if a given node has colspan as its own, or as inherited. if(TDptr.hasAttribute("colspan")) row--; Works if("colspan" in TDptr) row--; Don't work if(TDptr.hasOwnProperty("colspan") row--; Don't work. if(TDptr.attributes[].colspan.specified) row--; NOT work, but could loop over array of attrs and test for one named 'colspan' (and with .specified true? or is that just saying it has a value too?). SO: I don't fully understand why the getAttr fns test the HTML specs; but the simpler more direct JS acts as if all attrs are predefined. AA] JAVA CHANGES TO WEB PAGES -- DOM. General intro. points. Can get at HTML structures in java extensively, at least in current browser; and both get and set values of vars. E.g. < input type=SUBMIT name=qwer value=1 onclick="javascript:document.bigform.method='POST'; document.bigform.btnname.value = document.bigform.input.value; document.bigform.action = '< ?=$url1?>'; "> In general, the HTML is already parsed into a DOM (doc. object model), allowing java to get at any bit of it. Can actually create new structures: e.g. have a skel of a table in HTML; and use java to create the whole middle: by creating a node (e.g. TR), assigning to its attributes, then linking it into the DOM. See (e.g.) http://www.htmlgoodies.com/primers/jsp/article.php/3594621/Javascript-Basics-Part-6.htm var newnode = document.createTextNode('My new text.'); newnode.nodeValue = "My changed text?"; var tr = document.createElement('TR'); [Key tool for use with this: a DOM traversal script. see testjava.html] Text is the value/contents of a text node, which is a child of the enclosing HTML tag=node e.g. , , , etc. Text can be assigned (or read/fetched) e.g.: ptr.nodeValue = "My new text"; ptr.setAttribute("src", "x.html"); ptr.src='x' may work, but setAttribute has less bugs. To get to the right place (get the value for ptr), generally some mixture of these 2 methods (or in extremes, just one or the other method): a) Jump to somewhere nearby using a tag's ID: e.g. ptr = document.getElementById("myspan1"); getElementsByTagName(); b) Navigate the children. e.g. ptr = ptr.childNodes[1].childNodes[0]; Snags: i) browsers insert a tag , for instance inside ii) May be text nodes you aren't expecting even if never rendered e.g. a LF after a means its first child(0) is a textnode. The snags mean I need to find ways of testing node properties: while (TDptr) { // (see mystudents.php) if (TDptr.nodeName == 'TD') break; if (TDptr.nodeType == 3) ; // text node found TDptr = TDptr.nextSibling; } I.e. skip over other node types (esp. unwanted text nodes) Generally speaking when navigating the DOM by hand: Allow for textnodes any/everywhere Test for ptr=null May have to dive down a level you didn't expect. Some things e.g.
  • items may go on far beyond/below your intuitive idea of the end of the item if no other
  • or terminates it. ptr.innerHTML = "my new text"; ptr.style.backgroundColor = 'yellow'; This works in Safari, not always in Firefox. Because actually color isn't a node attribute (style is). So define css in and use java to switch them in and out. action="javascript:myptr.setAttribute('ID', 'myyellow');" B0] JAVA CHANGING the DOM: best tactics summary (Justification emerges below) 1) If creating nodes in Java, create whole subtree out of the DOM, and do only one attach operation: cheaper that way. 2) Just store pointers to target nodes: easy to get parent etc. from these. 2.2) Orphan subtrees always remembered (if ptr); but locations lost from src. 3) Only need one pass/traverse of DOM: can store in effect (ptrs to) both nodes and locations. Won't need to traverse again while inserting. 4) Clones (copies of subtrees and nodes). Deep clones (whole subtrees) could be expensive. "Shallow" clones of the node hardly ever useful. Can use any node type as a placeholder / location mark (not dup. node). 5) Can store location in various ways but best is a node (e.g. a textnode) 6) But in swap algos, can often / usually arrange it so need no clones and only one extra marker node. 7) Use replaceChild() as much as possible (it does the deletes automatically). B] DOM code algorithm STRATEGY / STAGES For programs to change the DOM e.g. swap nodes around, recommend this strategy. 0. Just save ptrs to nodes, not worry about the rest. Can get parent, child, sibs immediately from a node. 1. Collect (copies of ptrs to) the nodes you will be concerned with e.g. simply by getElementsByTag(), or by navigating the DOM with own code. Store ptrs to nodes; won't have to traverse DOM again. 2. Calculate the mappings you will need. 2.1 Will need to end up with, before stage 4, a)Mapping from old node to the (node in the) new position it is going to E.g. use reindex[] array to rep. this Actually, reindex is a fn. of newpos->oldpos of its new contents b) Map from old node to its replacement 2.2 There are two calc. stages to this: a) Go over data to calc params e.g. maxcols to limit cols to be rotated in a table. b) Calc the rotation or randomisation of order. 3. For some algos, need to a) Make a clone for each node to be shuffled. b) Finish making all clones before start to replace nodes. 4. Loop over nodes to be changed and do it e.g. by this line: newTD = newarr[reindex[cols]]; TRptr.replaceChild(newTD, old); C] JAVA CHANGES TO WEB PAGES -- DOM (new) To maintain the integrity of the tree, all of the rm,insert,replace ops must not only change one or more fields of the node-object itself e.g. to point to its nbours, but must go to its nbours (parent, sibs, children) and edit their fields to point back. Objects cannot be assigned or zeroed in themselves. The forbidding of "child[i] = newnode" makes this salient rather than obscuring it. One consequence of this is that moving a node which is already in the tree must delete it from its former position, and heal over the gap in the pointers there. I.e. not only its own pointers, but its former nbours who used to point to it are joined together over the gap. The consequence in turn of that is that moving an object is easy: (a single call of replacenode(new, old) will delete new from its old position as well as overwriting old and losing any children it might have had in the tree). On the other hand, simply swapping two nodes is not so obvious: given pointers to the two nodes then: SWAP: general. SWAP can't mean anything sensible if one of the nodes is a descedent of any other. The following algos will fail if this is true OR you can start each with a check. I have check code in "testjava.html" function "isagchild()" SWAP1a: Mk and store deep clones of all ptrs. Loop over rest and do replace(ptrn, ptrn+1); => N deep clones; 0 locations stored. No constraint on sequence of replaces. SWAP1b: [Given ptr1,2,3,4; and they are not descendents of each other] ptr0 = clone(ptr1); replace(ptr0, ptr2); replace(ptr2, ptr3); replace(ptr3, ptr4); replace(ptr4, ptr1); => 1 clone; 0 locations stored; constrained to chain sequence of action. SWAP2a: [Given ptr1,2,3,4; and they are not descendents of each other] parent0 = ptr1.parent; ptr0 = ptr1.nextSibling; replace(ptr1, ptr2); replace(ptr2, ptr3); replace(ptr3, ptr4); parent0.insertBefore(ptr4, ptr0); => 0 clone; 1 locations stored; constrained to chain sequence of action. Loc stored by parentPtr, ptr to next // Don't work if in same peer[] arr. SWAP2b: [Given ptr1,2,3,4; and they are not descendents of each other] parent0 = ptr1.parent; //ptr0 = ptr1.nextSibling; var i0, x = ptr1.childNodes; for (i0 = 0; i< x.length; i++) { if (x[i] == ptr1) break;} replace(ptr1, ptr2); replace(ptr2, ptr3); replace(ptr3, ptr4); //parent0.insertBefore(ptr4, ptr0); parent0.insertBefore(ptr3, parent0.childNodes[i0]); Loc stored by parentPtr, child-index. => better way to store loc: works within a peer arr[] (If used last, and only one loc stored.) SWAP3: // Best loc store is a node: any node will do. // No deep clone costs, just 1-node creat. // If pay this for all nodes, then no sequence constraint. SWAP3a: [Given ptr1,2,3,4; and they are not descendents of each other] var t = document.createTextNode('Marker'); foreach ptr[i] myinsertBefore(document.createTextNode('Marker'), ptr[i]); foreach ptr[i] replace(ptr[rand], ptr[i].previousSibling); => N new nodes; 0/N locations stored; No constraint to sequence of action. SWAP3b: [Given ptr1,2,3,4; and they are not descendents of each other] myinsertBefore((ptr0 = document.createTextNode('Marker')), ptr[1]); replace(ptr1, ptr2); replace(ptr2, ptr3); replace(ptr3, ptr4); replace(ptr4, ptr0); SWAP3c: [Given ptr1,2,3,4; and they are not descendents of each other] foreach ptr[i] src[i] = dest[i] = ptr[i]; Loop over all i->j pairs to replace if (dest[i]) { //if dest[i] hasn't been used yet (but will be destroyed) var t = document.createTextNode('Marker'); myinsertBefore(t, dest[i]); dest[i] = t; } replace(i, j); dest[j] = null; => Safe in all cases, but cheap if sequence observed. 1 new nodes; constrained to chain sequence of action. N new nodes; No constraint to sequence of action. Any tactics for DOM edits, other than isolated insert or remove, depend on the final actions to be taken (at heart of loop). Orphan subtrees always remembered; but locations lost from src. Remembering orphaned subtrees is no problem. Remembering locations is neatest if rep. by an (old, dest) node. But to get freedom of action sequence, just store all locations: only 2 slots per node extra storage: cheapest possible shallow clone.. There seem 3 basic alternatives: 1) Delete then insert at each location. 1.1 Del then insert doesn't keep location very conveniently. 2) Insert then delete at each location // Location stored exactly in DOM. // Need clone to insert // InsertBefore([new] clone(ptr1), ptr2); rmChild(ptr2); 3) Use replaceChild (del and insert in a bundle). 4) Delete all nodes; then insert all from store. // Needs no store for content; but locations / sequence must somehow be stored by code. May be most efficient for table-rotate. D] Patterns / chunks: Approx. analogy to Unix (MV, CP, RM): rm: removeChild(node); mv out of DOM: dest = removeChild(src); mv [src-file to dest-file] destr within /into DOM: replaceChild(src, dest); mv [src-file to dest-dir] constr within /into DOM: dest.appendChild(src); [dest.insertBefore(src, dest.firstChild);] cp constr. out of DOM: dest = src.cloneNode(true); cp constr into DOM: myappend(src.cloneNode(true), dest-parent); cp destr. into DOM: replaceChild(src.cloneNode(true), dest); swap: see above SWAP. E] JAVA CHANGES TO WEB PAGES -- DOM (core funcs' actual behav) Document fragment: a pseudonode to hold children for insertion; allows what I thought shallow clone was for. Use for building out-of-DOM subtrees then do insert only once. If moving, mv out of tree, and use head node to hold the subtree. var fragment = document.createDocumentFragment(); for ( var e = 0; e < elems.length; e++ ) fragment.appendChild( elems[e] ); for ( var i = 0; i < div.length; i++ ) div[i].appendChild( fragment.cloneNode(true) ); E2) DOC ON FNS TO CHANGE DOM STRUCTURE For each arg to each of these fns, there are 5 cases to document: NULL; node is currently in DOM, node is not in DOM (i.e. in storage), children are currently in DOM, children are not in DOM (i.e. in storage). parent.removeChild(nodeptr); nodeptr == null: error Inside DOM: OK, and deletes the subtree. Returns the deleted child, which can be restored elsewhere.? parent.replaceChild(new, old); parent.insertBefore(new, achild); E3) DOC ON the DOM core functions. insertBefore() insertBefore(newnode, nextnode) (->newnode) if nextnode == null, appends child function myinsertb(src, dest) // Only if dest is a node { dest.parentNode.insertBefore(src, dest.nextSibling); } appendChild() dest-parent. appendChild(new) (->newnode) function myappend(src, destparent) { destparent.appendChild(src); } function myprepend(src, destparent) { destparent.insertBefore(src, destparent.firstChild); } removeChild() removeChild(node) -> oldnode function myrmnode(node) { node.parentNode.removeChild(node); } replaceChild() replaceChild(new, old) -> oldnode; (Error if old=null) If new is docFrag, then old replaced by all its children. (it says). It installs the node "new" (same record/object) in the place of old. It uses (and partly overwrites) the actual node record of new. It deletes old(dest) i.e. orphans it from DOM (and returns it). It deletes new(src) from its former position in DOM, if any. function myreplace(src, dest) { dest.parentNode.replaceChild(src, dest); } // if you want dest allowed to be NULL, then function myreplace2(src, dest, destparent) { destparent.replaceChild(src, dest); } cloneNode() node.cloneNode(true/false) Creates a new node of same type and fields. But "false" i.e. shallow clone copies NONE of the DOM ptr fields: it has no children (or sibs or parents). "True" copies whole subtree i.e. recurses down the children. However CSS props like offsets are NOT copied. => so what is wanted is a shallow clone that does copy children but not the whole subtree: docFrags? Well, not really. replacechild() moves it direct w/o clone; and leaves the orphaned dest intact for use w/o cloning. So no automatic necessity for cloning. JAVA SMALLS for DOM java Cannot use action="javascript:shiftdata(this);" for a FORM to pass a ptr to the form, because action is an attribute of the FORM. Actually this seems a problem: not being able to get a self-ref into the function called as action for the form. But then, the basic idea of the action is a URL not a java function. But in many cases there is only one FORM per page, so not a problem then? If only because java will let you go from doc-> forms[] -> elementsOfForm. (Actually: 'this' is set to window or event. Events are tied to DOM objects .... and so work as wanted.) Sol: use onclick="javascript:fn(this)" etc. for form or btn. i.e. do the action in a click handler, not in the form action. BUT must have an action for the form, even when not useful. SO: action="javascript:" seems to be the only null action. document.getElementsByTagName('*') will return a flat list of all DOM nodes; no recursion. LOCAL PERSISTENT STORAGE ACROSS RELOADS See DOCcssJAVA for my doc on local storage of vars across reloads -- to preserve state purely in JS (not PHP and arg strings). sessionStorage, and localStorage. SOME CODE Here's some expert Java code I don't u.; but which works I believe. It moves around nodes between unrelated parts of the DOM. // WARNING: I haven't yet tested this // - It's likely that it will strip events upon shuffling function shuffle(elems) { allElems = (function(){ var ret = [], l = elems.length; while (l--) { ret[ret.length] = elems[l]; } return ret; })(); var shuffled = (function(){ var l = allElems.length, ret = []; while (l--) { var random = Math.floor(Math.random() * allElems.length), randEl = allElems[random].cloneNode(true); allElems.splice(random, 1); ret[ret.length] = randEl; } return ret; })(), l = elems.length; while (l--) { elems[l].parentNode.insertBefore(shuffled[l], elems[l].nextSibling); elems[l].parentNode.removeChild(elems[l]); } } // Usage: shuffle( document.getElementsByTagName('li')); document.getElementById('divid').innerHTML = 'BAD'; This works on say
    , changing the enclosed HTML of the first DIV with that ID.