Efficient OOP way to generate DOM elements dynamically with JavaScript? - javascript

I'm tinkering with writing a more efficient methodology in the creation of dynamically generated DOM elements via JavaScript. This is something I intend to add into my own JS framework later on. Looking for other OOP devs that could help better refine what I do have.
Here's a link to the working CodePen:
http://codepen.io/DaneTheory/pen/yeLvmm/
Here's the JS:
function CreateDOMEl() {};
CreateDOMEl.prototype.uiFrag = document.createDocumentFragment();
CreateDOMEl.prototype.elParent = function(elParent, index) {
this.elParent = document.getElementsByTagName(elParent)[index];
}
CreateDOMEl.prototype.elType = function(type) {
newEl = document.createElement(type);
this.uiFrag.appendChild(newEl);
}
CreateDOMEl.prototype.elContent = function(elContent) {
this.elContent = elContent;
newEl.textContent = elContent;
}
CreateDOMEl.prototype.buildEl = function() {
this.elParent.appendChild(this.uiFrag);
}
var div = new CreateDOMEl();
div.elParent('body', 0);
div.elType('DIV');
div.elContent('OK');
div.buildEl();
console.log(div);
var bttn = new CreateDOMEl();
bttn.elParent('body', 0);
bttn.elType('BUTTON');
bttn.elContent('SUBMIT');
bttn.buildEl();
console.log(bttn);
And some CSS to get elements to appear on page:
div {
width:100px;
height:100px;
border: 1px solid red;
}
My thoughts:
For performance, using the prototype to build methods versus placing all the logic in the constructor.
Rather than directly appending elements to the page, append to a single Document Fragment. Once the element is built out as a Doc Frag, appending the Doc Frag to to the DOM. I like this method for performance, but would like to improve upon it. Any useful implementations of requestnimationFrame, or using range and other versions of the document fragment method?
Silly, but I think for debugging it'd be nice to see the generated Element type within the Object property's on console log. As of right now, console logging a created element will show the elements parent and text content. It'd be great to show the elements type as well.
Creating more than one element at a time is another piece of functionality I'd like to offer as an option. For instance, creating a div element creates one div element. What's a good way to add another optional method to create multiple instances of div's.
div.elType('DIV');
// After calling the elType method, do something like this:
div.elCount(20);
// This would create 20 of the same divs
Lastly, a nice clean way to optionally add attributes (i.e: classes, an ID, value, a placeholder, custom attributes, data-* attributes, etc.). I've got a nice helper function I use that adds multiple attributes to an element in an object literal syntax looking way. Adding this as a method of the constructor would be ideal. Here's that function:
function setAttributes(el, attrs) {
for(var key in attrs) {
el.setAttribute(key, attrs[key]);
}
}
// A use case using the above
// function would be:
var anInputElement = document.createElement("TEXTAREA");
setAttributes(anInputElement, {
"type": "text",
"id": "awesomeID",
"name": "coolName",
"placeholder": "Hey I'm some placeholder example text",
"class": "awesome"
});
// Which creates the following HTML snippet:
<textarea type="text" id="awesomeID" name="coolName" placeholder="Hey I'm some placeholder example text" class="awesome">
As a side note, realizing now that the above helper function needs rewritten so that multiple classes could be created.

Respectfully, I believe you may be overthinking it. Just use the tools available in JavaScript and get 'er done. In terms of performance, computers are so fast at running your JavaScript that you (and me) are unable to perceive, or even comprehend, the speed. Here's how I add a link to an MDL nav menu, for example. It's just vanilla JS. Don't forget to add event listeners.
function navMenuAdd(type,text){
var newAnchor = doc.createElement("anchor");
newAnchor.classList.add('mdl-navigation__link');
newAnchor.classList.add(type);
newAnchor.href = "javascript:void(0)";
var anchorContent = doc.createTextNode(text);
newAnchor.appendChild(anchorContent);
newAnchor.addEventListener('click', navMenuClickHandler, false);
//newAnchor.style.display = 'none';
if (type === 'Thingy A'){
//insertAfter(newAnchor, navMenuCredentials);
navMenuCredentialsPanel.appendChild(newAnchor);
} else if (type === 'Thingy B'){
//insertAfter(newAnchor, navMenuDevices);
navMenuDevicesPanel.appendChild(newAnchor);
}
}

Related

Getting javascript object back from DOM element

Say I create a javascript class as follows:
class DivClass {
constructor () {
this.div1 = document.createElement('div')
this.div1.id = 'div1'
}
}
Later I instantiate the class as follows:
var divObject = new DivClass()
parentDiv.appendChild(divObject.div1)
and the DIV eventually appears in the DOM.
If I was to locate the 'div1' element within the DOM, say via getElementById() for argument sake, is there anyway of getting back to the javascript 'divObject' responsible for its creation?
From what little I've learned about javascript, I kind of get the impression that the translation from javascript API to DOM is a one way trip and this just simply isn't possible.
Apologies in advance if I've gotten any of the terminology wrong, but I'm kind of new to javascript and still don't fully understand the DOM/API relationship.
Any advice would be greatly appreciated.
You can add a reference to the object to the DIV element.
class DivClass {
constructor () {
this.div1 = document.createElement('div')
this.div1.id = 'div1'
this.div1.divClass = this;
}
}
Then you can use document.getElementById("div1").divClass to get the object.

Reduce the DOM search frequency in Javascript

I'm in the process of getting rid of Jquery from a small project and re-writing the script with vanilla js. In the current code there's a jquery implementation to search a DOM element and then use jquery 'find'to search specific elements within the element.
var ImageCapture ={
cacheDom : function(){
this.form = $('#drawingBoard');
this.saveBtn = this.form.find('#saveBtn');
this.image = this.form.find('#image');
this.results = this.form.find('#results');
}
}
I've converted the above Jquery code into vanilla js like below.
var ImageCapture ={
cacheDom: function () {
this.form = document.getElementById('drawingBoard');
this.saveBtn = this.form.querySelector('#saveBtn');
this.image = this.form.querySelector('#image');
this.results = this.form.querySelector('#results');
}
}
The new implementation seem to be working fine but I wanted to be sure if it's the correct way of replacing the Jquery implementation using vanilla JS?
Thanks in advance.
I've made two jsfiddles to show you a subtle difference in behavior between plain js querySelectorAll and jQuery selectors. You would naturally expect them to behave the same but they do not.
Here is the plain js version: https://jsfiddle.net/a81e2do3/
Here is jQuery: https://jsfiddle.net/a81e2do3/1/
In short, if you have this html:
div#a > div#b > div#c
if you have a plain JS element node object of #b, you can do b.querySelector('#a #c') and successfully select div#c, but you cannot do that in jQuery (which imo makes more sense the jQuery way).

How to temporarily house arbitrary dom nodes

I'm doing a bit of advanced work in KnockoutJS, whereby I generate some html outside of the KO process, apply bindings to them, and then insert them in my page.
The problem is housing the new html. My html is a couple of table rows, and when I do
var div = document.createElement('div');
div.innerHTML = template(viewModel);
the div strips out all the table content (my tr and td tags), presumably since divs can't contain table rows.
My cheesy workaround for the moment is below: use a tbody. But I'd like something a bit more generalized. I thought to use a document fragment, but that doesn't seem to have an innerHTML property to set.
What's the preferred way to handle this?
var div = document.createElement('tbody');
div.innerHTML = template(viewModel);
ko.applyBindingsToDescendants(bindingContext, div);
$(element).after($(div).contents());
As a workaround you could fetch the type of the parent node, create an empty detached new node of that type to house your contents, and later fetch the items from there.
It might look something like this, assuming you don't mind inserting the content before element, rather than after it:
var container = document.createElement(element.parentNode.tagName),
frag = document.createDocumentFragment();
container.innerHTML = template(viewModel);
ko.applyBindingsToDescendants(bindingContext, container);
while (container.childNodes.length){
frag.appendChild(container.childNodes[0]);
}
element.parentNode.insertBefore(frag, element);
But it'd be better to figure out why your contents are stripped to begin with.
I think you will get this problem only with table parts.
Hence you can do this:
var templ = template(viewModel);
var newElement = document.createElement(
$(templ).is("tr, tbody, thead") ? 'table' : 'div'
);
newElement.innerHTML(templ);
Nit's answer works but its still a bit of a hack, I would use a custom template source instead.
First you need to create a engine that uses strings as source, like
var stringTemplateSource = function (template) {
this.template = template;
};
stringTemplateSource.prototype.text = function () {
return this.template;
};
var stringTemplateEngine = new ko.nativeTemplateEngine();
stringTemplateEngine.makeTemplateSource = function (template) {
return new stringTemplateSource(template);
};
Then you can use it from a custom binding like
ko.renderTemplate(template, bindingContext.createChildContext(data), { templateEngine: stringTemplateEngine }, element, "replaceChildren");
Were template is a string containing the actual html

adding a new div after another one

I am trying to add a new div right after a div with the class of dtext01 but its not working.
Here is what the div looks like with the class <div class="dtext01"></div> and I would like the final code to look like this <div class="dtext01"></div><div id="rewards">Testing!!!</div>
Here is the code I have tried with no success.
<script type="text/javascript">
var stickyNode = document.createElement("div");
stickyNode.innerHTML = 'Testing!!!';
stickyNode.id = "rewards";
var referenceNode = document.getElementByClass("dtext01");
referenceNode.parentNode.insertBefore(stickyNode, referenceNode);
</script>
There is no document.getElementByClass(). There are document.getElementsByClassName() and querySelectorAll() which can be used for fetching elements by className, but they are not standard across browsers.
You can always import a cross browser getElementsByClassName from a reliable source, such as this one, or write your own - or just use IDs instead of classes if you have a choice.
The function you are looking for is document.getElementsByClassName (note the plural on Elements). It is a fairly recent addition to the HTMLElement interface, so you should test for it and provide a fallback if it isn't supported.
To append your div just after the first node returned, use:
var referenceNode;
if (document.getElementsByClassName) {
referenceNode = document.getElementsByClassName("dtext01")[0];
} else {
// fallback to some other function
}
// Make sure you got a node before trying to call methods on it
if (referenceNode) {
referenceNode.parentNode.insertBefore(stickyNode, referenceNode);
}

Problem with dynamically generated JavaScript click events

I am trying to generate several divs which each should have a separate onclick handler. However, when I create multiple items in my application, all of the previous items have the same onclick event as the first one. Below is the code that generates the html based on an array or "Project" objects. How can I change this code to retain all individual click events?
var t1 = new Tracker(function(projects) {
var tempElem;
projectList.innerHTML = "";
for(i in projects) {
tempElem = document.createElement("div");
tempElem.setAttribute("id", projects[i].id);
tempElem.setAttribute("class", "project");
tempElem.innerHTML = projects[i].name;
projectList.appendChild(tempElem);
document.getElementById(projects[i].id).onclick = function() {
t1.setActiveProject(t1.getProjectById(projects[i].id)); };
}
}, setActiveProject);
Using jQuery will save you lots of time and headaches. I would recommend using it for what you are trying to accomplish.
http://docs.jquery.com/Main_Page
I need to accomplish this task without the use of frameworks. It is for a class that I am taking.

Categories