This project is archived and is in readonly mode.

#639 ✓invalid
Chris the Developer

String.toElement() proposal

Reported by Chris the Developer | April 20th, 2009 @ 08:53 AM | in 2.0 (closed)

Recently I needed to do quite a bit of DOM building and JSON substitution, so instead of 4 new Element() calls I did a string substitute and then converted the resulting string into an element, using the following code:


String.implement({
	toElement: function() {
		var injector = new Element('div', {
			'html': this,
			'styles': {
				'position': 'absolute',
				'left': -1000000
			}
		}).inject(document.body);
		var children = injector.getChildren();
		injector.destroy();
		
		return children;
	}
});

This might be a useful addition to String.

Comments and changes to this ticket

  • Chris the Developer

    Chris the Developer April 20th, 2009 @ 08:54 AM

    I'm not even sure the injection is required (and if not the Element constructor could obviously also be shortened), but for the sake of our dear old friend, I thought it best to add.

  • Chris the Developer

    Chris the Developer April 20th, 2009 @ 10:13 AM

    After some testing, it seems you need to clone the elements or the destroy() will break stuff. I've also tested in IE6, and we don't need to inject the element to get its children.

    
    String.implement({
    	toElement: function() {		
    		var injector = new Element('div', {
    			'html': this
    		});
    		var children = injector.getChildren().clone();
    		injector.destroy();
    		
    		return children;
    	}
    });
    
  • Sebastian Markbåge

    Sebastian Markbåge April 20th, 2009 @ 02:08 PM

    Yea this is a useful way to create new elements. Not sure if it should be a String extension though.

    I think this is a very useful syntax:

    $('

    My Element
    '); // element $$('
    first
    second
    '); // array of elements

    That way you could pass HTML to any plugin expecting an element or multiple elements.

    It's a bit confusing if you include text nodes though:

    $$('

    first
    text node
    second
    '); // Ignores text node?
  • Sebastian Markbåge

    Sebastian Markbåge April 20th, 2009 @ 02:10 PM

    Yea this is a useful way to create new elements. Not sure if it should be a String extension though.

    I think this is a very useful syntax:

    
    
    $('<div id="element1">My Element</div>'); // element
    $$('<div id="el1">first</div><div id="el2">second</div>'); // array of elements
    
    

    That way you could pass HTML to any plugin expecting an element or multiple elements.

    It's a bit confusing if you include text nodes though:

    
    
    $$('<div id="el1">first</div> text node <div id="el2">second</div>'); // Ignores text node?
    
    
  • Chris the Developer

    Chris the Developer April 20th, 2009 @ 02:25 PM

    I wasn't aware you could use $ and $$ to return elements from an HTML string, but if this is the case; it's certainly not mentioned in the docs. :(

    Smells like jQuery actually...

  • Sebastian Markbåge

    Sebastian Markbåge April 20th, 2009 @ 02:27 PM

    Well you can't... yet. But it was a suggestion that we could use that syntax instead. jQuery has that syntax, yea.

  • Chris the Developer

    Chris the Developer April 20th, 2009 @ 04:35 PM

    I'm not sure $ and $$ are the best fit though. The natural behavior of $ is to query the DOM and return elements when found, or null when not. This way, $ would have to query for an element, then for an id and then try and build some elements on what could have been an incorrect id.

    I think it makes more sense to isolate this functionality, and certainly not to create ambiguity where $ and $$ are concerned.

  • Sebastian Markbåge

    Sebastian Markbåge April 20th, 2009 @ 05:05 PM

    $ and $$ takes other types than selectors though. Such as Objects with toElement methods (Swiff for example) or array of elements.

    $ doesn't have to check if an id exists. It could build the element if it starts with <A-Z ...>, assuming nobody uses HTML as IDs which nobody does.

    The beauty lies in how $(...) is already used to extend or select elements.

    
      initialize: function(element){
        this.element = $(element) || new Element('div', { html: 'default element' });
        this.element.inject(document.body);
      }
    

    The above class expects an element that it can use to inject somewhere. Let's say for a ToolTip, OverText, Popup, Lightbox or whatever.

    The first parameter now accepts: 1) An element object in the document. 2) An element ID string of an element in the document. 3) An element object that hasn't yet been inserted in the document. 4) An previously uncreated Element in the form of a HTML string. 5) Undefined, in which case the default implementation is used.

    I see what you mean that it could be confusing if you assume that it is a DOM query. But it's a very useful syntax.

  • Scott Kyle

    Scott Kyle April 20th, 2009 @ 07:27 PM

    I personally think that writing HTML in JavaScript is unnatural and invites bugs. I had a colleague using jQuery to create blog comments from HTML strings spend ALL day tracking down an IE bug. I noticed his HTML wasn't properly formed somewhere, and the parser for innerHTML on IE wasn't accepting it but other browsers were.

    I wrote about using a limited selector to create elements on my blog here, I think this is a safer (but admittedly more limited) approach to this problem.

  • Sebastian Markbåge

    Sebastian Markbåge April 20th, 2009 @ 07:58 PM

    I'm not a big fan of "protecting developers" from themselves. That syntax seems designed to limit the functionality to protect developers from abusing it. It's use seems very narrow. Maybe if you're adding many single elements with only class or id props.

    But it doesn't add much benefit if you need something with a more complex hierarchy of children. Then HTML is much cleaner and it's also faster than adding elements using the DOM.

    But of course everything could be abused if it's used where it's not appropriate.

  • Chris the Developer

    Chris the Developer April 20th, 2009 @ 08:57 PM

    @Scott - I hear you, but in an environment where you need to do lots of DOM changes/creates based on remote data, it is probably better to send less http data and write better HTML.

    The specific situation in which I needed this code is one where I'm fetching an array of records and building quite a complex list. I can either send all the HTML over, with each request, or I can send a tiny JSON object and build the list with Javascript. I tend towards the second, despite it making my life harder, since it makes the users' easier.

    It also happened to circumvent 17 calls to new Element().

  • Scott Kyle

    Scott Kyle April 20th, 2009 @ 11:04 PM

    Right, I totally agree with the need to sometimes create complex HTML on the fly and my method would not lend itself well to that. However, I'm not sure that it should be officially condoned with a toElement() method because that just invites abuse.

    Secondly, clone() and destroy() are expensive operations that shouldn't be needed for this method.

  • Chris the Developer

    Chris the Developer April 21st, 2009 @ 06:24 AM

    The original, you'll notice, didn't include clone(). It was needed with the inclusion of destroy(). If you can find a non-destructive way to unset the injector, while still maintaining the unaffected structure created by toElement(), then please share.

    Unfortunately, abuse comes down to developer actions. Any framework, or parts thereof, are open to abuse - but a competent developer will know when not to abuse...

  • Sebastian Markbåge

    Sebastian Markbåge April 21st, 2009 @ 09:14 AM

    
    var div = document.createElement('div');
    div.innerHTML = this;
    return $$(div.childNodes);
    

    You don't need to extend the parent element with Element nor should you need to inject it into the document so you don't need clone nor destroy.

  • Chris the Developer

    Chris the Developer April 21st, 2009 @ 09:18 AM

    How do you garbage collect the div you are creating and filling with a potentially limitless amount of elements?

  • Chris the Developer

    Chris the Developer April 21st, 2009 @ 09:22 AM

    Perhaps you're confusing Element.dispose() with Element.destroy(). I'm not cloning/destroying because it's in the DOM, but I want to do garbage collection on the injector.

  • Sebastian Markbåge

    Sebastian Markbåge April 21st, 2009 @ 09:27 AM

    An Element only needs an explicit call to .destroy in Internet Explorer if it has been extended with the Element methods for example with $(...) or new Element. Otherwise it is garbage collected fine using the regular mark-and-sweep method like any other object in JavaScript.

    Since we create it using document.createElement and skip extending it, we don't need to call .destroy(). We shouldn't call destroy on children neither since we're going to use them.

  • Chris the Developer

    Chris the Developer April 21st, 2009 @ 09:33 AM

    Thank you for your feedback. Following your suggestion, the implementation would look like this:

    
    String.implement({
    	toElement: function() {		
    		var div = document.createElement('div');
    		div.innerHTML = this;
    		return $$(div.childNodes);
    	}
    });
    

    Can you verify this working in the common smattering of browsers?

  • Scott Kyle

    Scott Kyle April 21st, 2009 @ 04:41 PM

    I believe it is partially a framework's responsibility to discourage abuse by inexperienced developers through the encouragement of using good programming design patterns.

    Rather than encouraging something like this:

    
    var html = '';
    response.comments.each(function(comment){
    	html += '<div class="comment">';
    	html += ... // bunch of html string creation
    	html += '</div>'
    });
    comments.adopt(html.toElement());
    

    MooTools should nudge the developer more in this direction:

    
    response.comments.each(function(comment){
    	new Element('div', {
    		'class': 'comment',
    		'html': ... // bunch of html string creation	
    	}).inject(comments);
    });
    

    But if I'm really the only one who thinks this, then as for the latest implementation, I like it. It looks nice and doesn't use destroy() or clone(). I was worried about it not using Element.Properties.html.set but it looks like that method is only useful for translating html for IE that is being injected into a table or select element.

    I'll shut up now about this proposal :-)

  • Sebastian Markbåge

    Sebastian Markbåge April 21st, 2009 @ 05:38 PM

    Yea, I'm also a big fan of using proper DOM manipulation instead of HTML strings. I've just exactly this design choice in my Editable (WYSIWYG) class.

    Interesting side note though: Your first code would get much better performance than the later as it scales. At least in current browsers.

  • Scott Kyle

    Scott Kyle April 21st, 2009 @ 07:34 PM

    Hmmm, I lied because I said I'd shut up, but your comment on performance made me think. I used Firebug to profile the first and second method I used in my example with varying sized loops (100-1000).

    I found that using the toElement() method was twice as fast and made one third the function calls, which surprised me a bit because I thought it would be faster, but running $$(div.childNodes) is the culprit.

    If you filled the "comments" element with set('html') then it's about six times faster (and a tiny fraction of the function calls), but using toElement lets you append HTML strings to "comments." So I asked myself what toElement() is really supposed to accomplish here, and it seems to me that appending HTML strings is where it's most useful, since "new Element.set('html')" can become cumbersome and slow as you scale (thought not that much slower).

    I feel like this method is really what we're after and it's about 20% faster:

    
    Element.implement({
    	appendHTML: function(){
    		var div = document.createElement('div');
    		div.innerHTML = Array.flatten(arguments).join('');
    		return this.adopt(div.childNodes);
    	}
    });
    

    I'm sorry I didn't shut up like I said I would, but I had to chime in again!

  • Chris the Developer

    Chris the Developer April 21st, 2009 @ 08:38 PM

    While I'm over the moon that this thread is generating so much interest, and applaud your contributions, the original idea was to be able to turn a flat string into an array of extended elements.

    Element.appendHTML is a great outcome of the thread, and I'm sure deserves consideration for inclusion to core (I'd vote!), but I think we also have a thing going for some sort of implementation of String.makeMeSomeElements()...

    :)

  • Scott Kyle

    Scott Kyle April 21st, 2009 @ 11:49 PM

    O well if you name it "makeMeSomeElements" then all of my objections would be voided!

    Speaking of naming...since the method returns Elements and not an Element, shouldn't it be "toElements"? :-)

  • Christoph Pojer

    Christoph Pojer April 22nd, 2009 @ 09:59 AM

    • State changed from “new” to “invalid”

    toElement is not a wise choice as it works with $ for objects. If a class has a method toElement you can use $(instance) to return an element. As you certainly can't do that with a string it would create confusion.

    A nicer and faster approach to appendHTML would be: http://paste.mootools.net/f1043ca53 It probably fills the niche to be implemented into MooTools-More but for MooTools-Core its too specific. For the next major release of MooTools a syntax like new Element('<div>html</div>') is being discussed.

    Please note that this is a bugtracker and not a discussion board. Feel free to join #mootools on Freenode to talk about this stuff, We'd greatly appreciate that.

  • Scott Kyle

    Scott Kyle April 22nd, 2009 @ 05:03 PM

    I like your implementation of appendHTML, but I reject the notion that it is too niche for Core. Core has set('text'), set('html'), and appendText. It almost seems incomplete to leave out appendHTML, especially considering it is just six lines of code that many users may find useful. I know I've scratched my head before wondering why I could set text, append text, set HTML, but not append HTML.

    I understand that this is a bugtracker and not a discussion board, but IRC is not a viable alternative for these sorts of discussions. This was a conversation that took place over the course of a couple days, each participant able to participate on their own time schedule without scheduling a meet up time on IRC.

    A proposal was made, and perhaps this isn't the best place for that, but of course there is going to be discussion on it. Chastising the participants is really not the right move here. It's not as if one of us were going to reply saying that we shouldn't discuss this here, let's talk on IRC at 8pm PST. Unfortunately, Google groups and the unofficial forums aren't exactly hotspots for discussion on MooTools features either.

  • Christoph Pojer

    Christoph Pojer April 22nd, 2009 @ 05:27 PM

    You are actually right considering that appendText exists. I will try to get this feature into core as it really is only a few lines, I don't promise anything though.

    Actually this really is a place for either IRC or Google Groups to dicuss. I'm going to send you a Mail regarding that :) Thank you for your input and contributions to MooTools.

  • Chris the Developer

    Chris the Developer October 22nd, 2009 @ 11:13 PM

    Guess this did get some love after all, ala Elements.From! Good job everyone! :)

  • ESSIE26WOOD

    ESSIE26WOOD September 3rd, 2011 @ 02:11 AM

    • Milestone order changed from “0” to “0”

    All people deserve wealthy life and loans or bank loan will make it much better. Just because freedom is grounded on money.

  • WarnerSandra21

    WarnerSandra21 September 16th, 2011 @ 06:30 PM

    In this moment the students purchase custom writing and essays for sale opting for writing service but really frequently the students buy custom essay papers about this topic. It’s really professional knowledge, thanks for this!

  • Ariel11

    Ariel11 December 1st, 2011 @ 03:09 PM

    This is such a great development 'Recently I needed to do quite a bit of DOM building and JSON substitution, so instead of 4 new Element() calls I did a string substitute and then converted the resulting string into an element, using the following code"surely after some background check more developments will be coming..

  • Marcus Aurelius

    Marcus Aurelius January 10th, 2012 @ 03:41 PM

    Hi, I realize this is a bit out of date, but I'm quite intrigued. I'm new to DOM and haven't really scratched the surface of JSON.
    best air purifier

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

Shared Ticket Bins

Pages