7

edit 2 (solution)

Javascript

for a "possible" javascript way see @sabof answer below: https://stackoverflow.com/a/22172009/3309243 ..........

JQUERY

Events: I quickly made an example function getEvents(), but it seems to work :)

Attributes:

I found this somewhere on the internet, to return all attributes by using .attr()

Also I have just discovered about debugging in the browser and thus now know how to view an object. Might anyone wonder how: open your console (f12 atleast in FireFox) and type for example $("body") in the console command line (all the way at the bottom), click on the returned object and look at the right of the console :P

 //extending the attr function to return all attrs
(function($) {
      // duck-punching to make attr() return a map
      var _old = $.fn.attr;
      $.fn.attr = function() {
        var a, aLength, attributes, map;
        if (this[0] && arguments.length === 0) {
                map = {};
                attributes = this[0].attributes;
                aLength = attributes.length;
                for (a = 0; a < aLength; a++) {
                        map[attributes[a].name.toLowerCase()] = attributes[a].value;
                }
                return map;
        } else {
                return _old.apply(this, arguments);
        }
}
}(jQuery));

Thanks to the great example of @user3331198 I got the following (togheter with the code block above).

var href = "http://www.google.com/";
var text = "Google";

$( document ).ready(function() {    
   $("body").css({"background-color": "#000fff"})
       .append( $("<div>", {class: 'foo'}))
            .append($("<a>", { id: "anID", href: href, text: text }))
                     .append($("<span>").text("Foo").on("click", function(){ alert('!')}));
    loopDOM($("body"));   
 });




function loopDOM(obj,parentID=-1,thisID=0) {
   saveToDB({ 
     id: thisID,
     parent: parentID,
     tag: obj.prop("tagName"),
     style: obj.attr("style"),
     events: getEvents(obj),
     attribs: obj.attr()    
    });

    obj.children().each(function() {
       loopDOM($(this),thisID,++thisID)
    });
} 

function getEvents(obj) {   
    aEvents=[];
    if (typeof $._data( obj[0], 'events' )==="object") {
        $.each($._data( obj[0], 'events' ), function(i, event) {
           type=JSON.stringify(i);  
           aEvents[type]=[];
           $.each(event, function(j, h) {
                aEvents[type][aEvents[type].length]=h.handler;
            });
        });     
    }
    return aEvents;
}
function saveToDB(pass) {
    alert("id:"+pass["id"]+"\nparent:"+pass["parent"]+"\ntag:"+pass["tag"]+"\nstyle:"+pass["style"]);
    alert ("has the following attributes");
    for (var x in pass["attribs"])
         alert("attrib: "+x+" "+pass["attribs"][x]);
     alert ("has the following events");
    for (var x in pass["events"])
        alert("event: "+x+" "+pass["events"][x]);
 }

..........................................................................

Orginal post

I am toying around in my spare time to get a bit back into shape with coding.

For example I am building this DOM (for the practice with jQuery instead of plain HTML)

$(document).ready(function() {  
   $("body").css({"background-color": "#000fff"})
       .append( $("<div>", { class: "foo" })
            .append($("<a>", { href: href, text: text })
                .on("click", function(){ alert('!')}))
                   .append($("<span>").text("Foo")));
});

Now let's say I want to save the DOM in a DB table like this:

| ID | parentID | tag    | style      | events | atribs |
_________________________________________________________
| 0 | -1        | <body> | backgr...  | NULL   | NULL   |
| 1 |  0        | <div>  | NULL       | NULL   | class:.|
| 2 |  1        | <a>    | NULL       | click..| NULL   |
| 3 |  2        | <span> | NULL       | NULL   | NULL   |

For the purpose of retrieving and rebuilding the DOM with some JS function

To do this I would use for example some recursive loop like this: (sort of pseudo-JS)

function loopDOM(obj,parentID=-1,thisID=0) {
   saveToDB({ 
      id: thisID,
      parent: parentID,
      style: obj.getStyle,
      events: obj.getEvents,
      atribs: obj.getAtribs
   });

   obj.each(function() {
       loopDOM(child,thisID,++thisID)
   });
}

But I am totally stuck at every point here. I've been searching for at least 45 minutes but I could not answer even one of my following questions.

  1. How to loop through the $('body')? how do I retrieve its children from an element? I found .each, .find, .children but I can't find the proper object to pass like the <div> which is a child of <body>.
    I can't figure out how to recursively loop through the object.

  2. How to get ALL the (unknown) events, attributes and CSS of an element? I haven't been able to make any of the answers I've found work.

..........................................................................

edit 1 (outdated)

If I quickly put togheter the links @PellePenna posted I get this.. but its jquery and javascript mixed and It doenst work. I dont get the attribs nor the events. And I dont have an idea how to get an elements his position. I might be a bit retarted but I dont get the suggestions given. And also really like recursive loops :P

var el = this.getElementsByTagName("*");
for (var i = el.length; i--;) {
    //get atribs
    var nodes=[], values=[];
    if (el.attributes)
        for (var attr, i=0, attrs=el.attributes, l=attrs.length; i<l; i++){
            attr = attrs.item(i)
            nodes.push(attr.nodeName);
            values.push(attr.nodeValue);
        }
    //get events
    event=$._data(el, 'events');
}

Community
  • 1
  • 1
058WistWol
  • 89
  • 7
  • What if you call "*"? .. i.e. `document.getElementsByTagName("*");` .. Will that give you what you want? – Asons Mar 04 '14 at 11:34
  • It might, but how do get its childeren to loop? And on a plus how do I get its events, style, atribs? – 058WistWol Mar 04 '14 at 11:37
  • About looping elements: http://stackoverflow.com/questions/8747086/most-efficient-way-to-iterate-over-all-dom-elements – Asons Mar 04 '14 at 11:39
  • About attrib's http://stackoverflow.com/questions/2048720/get-all-attributes-from-a-html-element-with-javascript-jquery and events http://stackoverflow.com/questions/2008592/can-i-find-events-bound-on-an-element-with-jquery – Asons Mar 04 '14 at 11:42

3 Answers3

2

Try this :--

function loopDOM(obj,parentID,thisID) {
   saveToDB({ 
      id: thisID,
      parent: parentID,
      style: obj.attr("style"),
      events: Object.keys($._data(obj[0],'events')),
      atribs: obj.attr()
   });

   obj.children().each(function() {
       loopDOM($(this),thisID,++thisID)
   });
}

First time call function loopDOM($('body'),-1,0)

user3331198
  • 184
  • 7
2

Here is my entry. I'm not doing events, since AFAIK, it's not possible to do it reliably with the current DOM api.

var makeId = (function() {
  var counter = 0;
  return function () { 
    return ++counter;
  }
}());

function getAttributes(elem) {
  if (! elem.attributes) {
    return {};
  }
  var result = {};
  Array.prototype.forEach.call(
    elem.attributes, 
    function (elem) {
      result[elem.nodeName] = elem.value;
    });
  return result;
}

function traverse(parentId, elem) {
  if (! parentId && parentId !== 0) {
    parentId = -1;
  }
  if (! elem) {
    elem = document.documentElement;
  }
  var id = makeId();
  var me = {
    id: id, 
    name: elem.nodeName, 
    parentId: parentId, 
    attributes: getAttributes(elem), 
  };
  if (elem instanceof Text) {
    me.text = elem.wholeText;
  }
  var children =  Array.prototype.map.call(
    elem.childNodes, 
    traverse.bind(null, id)
  ); 
  return Array.prototype.concat.apply(
    [me], children
  );
}
sabof
  • 8,062
  • 4
  • 28
  • 52
  • This looks really nice. Allthough I cant get it working, but that is due to my total lack of javascript and DOM knowledge. I dont know how to pass a starting element to traverse(parentId, elem). Its not ($('body')) since that is jquery but I wouldnt know what it would be in javascript. (I might google it, but now I am going to work further with the jquery exmaple posted by @user3331198). Thanks for your effort and nice code. Allthough I dont have it working I will select you as an answer aswell. Since it seems to me as a javascript solution. Instead of the jquery Iam using – 058WistWol Mar 04 '14 at 12:51
  • To get "everything", you call it like this `traverse()` – sabof Mar 04 '14 at 12:53
  • This version still looses the contents of text nodes, I'm in the process of updating it. – sabof Mar 04 '14 at 12:53
  • Just a note for anyone reading this (and I am really not a pro coder or anything, but..) I belief DOM and DATA should be divided and thus I belief the contents of the text node of the DOM should really not be a part of the dom. In my head I see a use like this: `createDOM()` , `fillDOMwithData()` `getDOM()`. Thus what I a trying to say is that the contents of the text nodes in the DOM is not part of the DOM at creation or saving.. allthough if you are making some live editor you would want to get the data.. – 058WistWol Mar 04 '14 at 13:00
  • I've fixed it. If you don't like text nodes, just replace `childNodes` with `children`. – sabof Mar 04 '14 at 13:03
  • I thought I could select two answers. For the step in my right direction seems to be the jquery way, so I have selected the other answer as answer. Also because the topic of the post (is been edited by an admin?) to only question about looping the DOM. But this post as an answer aswell! – 058WistWol Mar 04 '14 at 13:11
1

You can pass * to getElementsByTagName() so that it will return all elements in a page:

var all = document.getElementsByTagName("*");

for (var i=0, max=all.length; i < max; i++) {
     //here do somthing
}

Note that you could use querySelectorAll(), if it's available, to just find elements with a particular class.

if (document.querySelectorAll)
    var clsElements = document.querySelectorAll(".mySpeshalClass");
else
    // loop through all elements instead

or



$('body *').each(function() {
    // do stuff
});

If you have inline <script> tags and the like, you can add a :not selector to exclude those:

$('body *:not(script, style, noscript)').each(function() {
    // do stuff
});

As pointed out in the comments and the jQuery all selector docs, this probably won't perform very well. I haven't done any empirical testing, but this might perform better:

$('body').find('*').not('script, style, noscript').each(...);

Mohammad Kermani
  • 5,188
  • 7
  • 37
  • 61