{"id":17,"date":"2011-06-10T19:20:42","date_gmt":"2011-06-10T23:20:42","guid":{"rendered":"http:\/\/www.defectivestudios.com\/devblog\/?p=17"},"modified":"2012-08-10T22:21:00","modified_gmt":"2012-08-11T02:21:00","slug":"a-nifty-way-to-create-html-from-javascript","status":"publish","type":"post","link":"https:\/\/www.defectivestudios.com\/devblog\/a-nifty-way-to-create-html-from-javascript\/","title":{"rendered":"A nifty way to create HTML from JavaScript"},"content":{"rendered":"<p>In rewriting the folderlist for AssetCloud, I came across the need to generate quite a bit of HTML elements via JavaScript, and what I think is a pretty nifty way to do it. \u00a0Here&#8217;s the backstory:<\/p>\n<p>The AC folderlist, at time of writing, is generated as most PHP pages are, a server-side PHP script which spits out an HTML string based on data from the database. \u00a0This works great for most content, since access to the database is very quick, and string data is pretty cheap in terms of bandwidth. \u00a0However, a sufficently complex project will have a <strong>lot <\/strong>of assets, which will create a folderlist described by a <strong>lot<\/strong> of html. \u00a0At time of writing, the Platformer folderlist comes in at around 3.2MB (this is just HTML, mind you, no images or anything), and the master folder list (which will crash Chrome if you don&#8217;t wait for it) is 5.82MB. \u00a0To put those numbers in context, the Platformer list takes 3 full seconds to download, and the master list (for whatever reason) takes a whopping 1.6 minutes to download! \u00a0That&#8217;s pretty unacceptable by pretty much everyone&#8217;s standards. \u00a0Read more to see my solution.<\/p>\n<p><!--more--><\/p>\n<p>So, the solution? \u00a0Do it in JavaScript! \u00a0That is, use an AJAX query to get the data behind the folder list in JSON (I guess then shouldn&#8217;t it be AJAJ? \u00a0Blech, that&#8217;s a terrible acronym) and render the page itself via JS. \u00a0Sounds simple enough, but creating HTML elements in JavaScript is a major pain. \u00a0For reference, this is the code to create the following confirmation dialog:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.defectivestudios.com\/devblog\/images\/confirm.PNG\" alt=\"\" \/><\/p>\n<pre class=\"syntax {JavaScript}\">\/\/ -*- mode: javascript; -*-\r\nvar q = options['q'];\r\nif(q == null)\tq = \"Are you sure you want to do this?\";\r\nvar confirmText = options['confirmText'];\r\nif(confirmText == null)\tconfirmText = \"Yes\";\r\nvar denyText = options['denyText'];\r\nif(denyText == null)\tdenyText = \"No\";\r\nvar confirm = options['confirm'];\r\n\r\n$(\"#confirm_modal\").remove();\r\nvar cont = document.createElement('div');\r\nvar confirmModal = document.createElement('div');\r\nconfirmModal.className = \"rounded box\";\r\n\r\nvar head = document.createElement('div');\r\nhead.className = \"top-rounded whitetext gradient box_head drag_head\";\r\nhead.innerHTML = q;\r\nconfirmModal.appendChild(head);\r\n\r\nvar body = document.createElement('div');\r\nbody.className = \"modal_content\";\r\n\r\nvar buttoncontainer = document.createElement('div');\r\nbuttoncontainer.className =\"button_container\";\r\nvar confirmButton = document.createElement('div');\r\nconfirmButton.className = \"center w85 button text-center\";\r\nconfirmButton.id = \"confirm_button\";\r\nconfirmButton.innerHTML = confirmText;\r\nbuttoncontainer.appendChild(confirmButton);\r\n\r\nvar spacer = document.createElement('div');\r\nspacer.style.width = \"30px\";\r\nbuttoncontainer.appendChild(spacer);\r\n\r\nvar denyButton = document.createElement('div');\r\ndenyButton.className = \"center w85 button text-center\";\r\ndenyButton.innerHTML = denyText;\r\ndenyButton.id = \"deny_button\";\r\nbuttoncontainer.appendChild(denyButton);\r\nbody.appendChild(buttoncontainer);\r\n\r\nconfirmModal.appendChild(body);\r\ncont.appendChild(confirmModal);<\/pre>\n<p>That&#8217;s about 45 lines for 7 elements (plus some crap at the top to determine the text). \u00a0Yuck! \u00a0I wasn&#8217;t about to do this crap for the folderlist, which is made up of quite a few elements, not 7. \u00a0So, in comes a helper function. \u00a0This actually turned out to be much much easier than I thought it would be. \u00a0As a note, I struggled with this for a while on account of the innerHTML property of DOM elements. \u00a0I&#8217;m not exactly sure how or why (and thanks to molgrew on the freenode javascript channel for pointing this out) but setting this property on an element that you&#8217;ve already added an event (onclick in this case) to will remove that event. \u00a0So, I no longer use innerHTML and add textNodes among the element&#8217;s children. \u00a0The code for the function is as follows:<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"syntax {JavaScript}\">\/\/ -*- mode: javascript; -*-\r\nfunction Elm(type, attributes, children, properties){\t\t\/\/Short for \"Element\"\r\n\t\/\/Create element of type \"type\"\r\n\tvar el = document.createElement(type);\r\n\t\/\/Set any attributes given by the attributes object\r\n\tfor(var option in attributes)\r\n\t\tel.setAttribute(option, attributes[option]);\r\n\t\/\/Add children from children array.  If the child is text, add it as a text node\r\n\tfor(var child in children){\r\n\t\tif(typeof(children[child]) == \"string\")\r\n\t\t\tel.appendChild(document.createTextNode(children[child]));\r\n\t\telse\r\n\t\t\tel.appendChild(children[child]);\r\n\t}\r\n\t\/\/Add any extra properties supplied\r\n\tfor(var prop in properties){\r\n\t\tel[prop] = properties[prop];\r\n\t}\r\n\treturn el;\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>All of the parameters but &#8220;type&#8221; are optional, and the fourth, &#8220;properties&#8221;, is there for the special purpose of keeping metadata along with the element.  In my case, there&#8217;s a FolderListNode object which contains references to particular elements in the row, and some functions.  Please excuse the presentation of the code above (the column is just not wide enough \ud83d\ude41 ), and see &#8220;raw code&#8221; to view the formatting correctly.  I&#8217;m sure that I&#8217;m breaking some standards and this won&#8217;t work on every browser under the sun, but it simplifies the process greatly.  As I come across issues, I&#8217;ll expand on the function to implement the necessary workarounds.  Let&#8217;s rewrite that confirm dialog with the Elm function:<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"syntax {JavaScript}\">\/\/ -*- mode: javascript; -*-\r\nvar q = options['q'];\r\nif(q == null)\tq = \"Are you sure you want to do this?\";\r\nvar confirmText = options['confirmText'];\r\nif(confirmText == null)\tconfirmText = \"Yes\";\r\nvar denyText = options['denyText'];\r\nif(denyText == null)\tdenyText = \"No\";\r\nvar confirm = options['confirm'];\r\n\r\n$(\"#confirm_modal\").remove();\r\n\r\nvar cont =\tElm('div', {},\r\n\/*Box*\/\t\t[ Elm('div', { class: \"rounded box\" },\r\n\/*Head*\/\t\t\t[ Elm('div', { class: \"top-rounded whitetext gradient box_head drag_head\" }, [q]),\r\n\/*Body*\/\t\t\t  Elm('div', { class: \"modal_content\" },\r\n\/*ButtonContainer*\/\t\t[ Elm('div', { class: \"button_container\" },\r\n\/*ConfirmButton*\/\t\t\t[ Elm('div', {class: \"center w85 button text-center\", id: \"confirm_button\"}, [confirmText]),\r\n\/*Spacer*\/\t\t\t\t\t  Elm('div', {style: \"width:30px\"}),\r\n\/*DenyButton*\/\t\t\t\t  Elm('div', {class: \"center w85 button text-center\", id: \"deny_button\"}, [denyText])\r\n\t\t\t\t\t\t])\r\n\t\t\t\t\t])\r\n\t\t\t\t])\r\n\t\t\t]);\r\nnew_modal(\"confirm_modal\", $(cont).html());<\/pre>\n<p>&nbsp;<\/p>\n<p>My appologies for the ugly presentation. \u00a0Once Jono gets up and I can login to the admin portion of the blog I&#8217;ll get a code highlighter installed. \u00a0Anyway, you can see that the code is not <em>much shorter<\/em> but more importantly <em>readable<\/em>. \u00a0It would take me a little while to read through the above example and figure out which elements were children of which, and what was actually going on. \u00a0I can read the above as pretty much straight HTML. \u00a0Formatting the code gets a little hairy (eclipse wanted to add an extra tab on top of what you see above, as if it were a normal broken statement) but I think if you stick to the convention I displayed above (one tab after variable declaration, and a new tab for each level of nesting) I think you&#8217;ll be able to keep from tearing your hair out.<\/p>\n<p>&nbsp;<\/p>\n<p>I literally just came up with this last night, so by no means do I consider it standard or conventional in the least, but I&#8217;d like to hear it torn apart by some JavaScript snobs, so have at it!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In rewriting the folderlist for AssetCloud, I came across the need to generate quite a bit of HTML elements via JavaScript, and what I think is a pretty nifty way to do it. \u00a0Here&#8217;s the backstory: The AC folderlist, at time of writing, is generated as most PHP pages are, a server-side PHP script which [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,8,6],"tags":[],"_links":{"self":[{"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/posts\/17"}],"collection":[{"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/comments?post=17"}],"version-history":[{"count":2,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/posts\/17\/revisions"}],"predecessor-version":[{"id":18,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/posts\/17\/revisions\/18"}],"wp:attachment":[{"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/media?parent=17"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/categories?post=17"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/tags?post=17"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}