Reduce the DOM search frequency in Javascript - 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).

Related

Efficient OOP way to generate DOM elements dynamically with 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);
}
}

TypeError: undefined is not a function jQuery

I'm new to jQuery and I can get it to sometimes work, however, for some reason, when I try to call a function, it gives me the title error, but if I do it in developer tools, it works fine.
http://jsfiddle.net/otanan/pmzzLo3e/#&togetherjs=AezijhfBrj
It seems to work fine when retrieving the classes from the DOM, but not when I call a function such as
.click(function() {});
Here's the code:
var downloads = $(".info"),
className = "info_clicked";
for(var i in downloads)
{
downloads[i].click(function()
{
if(!this.hasClass(className))
this.addClass(className);
else
this.removeClass(className);
});
}
When you access a jQuery collection as an array, it returns the DOM elements, not jQuery objects. You should use .each() rather than for (i in downloads):
downloads.each(function() {
$(this).click(function() {
if (!$(this).hasClass(className)) {
$(this).addClass(className);
} else {
$(this).removeClass(className);
}
});
});
You could also simplify the whole thing to:
downloads.click(function() {
$(this).toggleClass(className);
});
Most jQuery methods automatically iterate over all the elements in a collection if it makes sense to do so (the notable exceptions are methods that return information from the element, like .text() or .val() -- they just use the first element). So you generally only have to iterate explicitly if you need to do different things for each element. This is one of the great conveniences of using jQuery rather than plain JS: you rarely have to write explicit iterations.
I think the issue is that you're attempting to call a jQuery function on an object that is no longer a jQuery object.
For example you're saying $(".info"). Which retrieves a single jQuery object. As soon as you index that object downloads[i] it is no longer a jQuery object, it is a plain HTML element and does not have a click function available.
What you really need to do is get the jQuery object for the indexed item:
var downloads = $(".info"),
className = "info_clicked";
for(var i = 0; i < downloads.length; i++)
{
$(downloads[i]).click(function()
{
if(!this.hasClass(className))
this.addClass(className);
else
this.removeClass(className);
});
}
try it:
$(downloads[i]).click(function(){ //...

Binding multiple events to elements stored in variable

I know that puting reference of HTML element into the variable is a good practice if I need to reference to this element many times. But I run into the problem with this while making my project. How can I bind multiple and the same events to the elements which are stored into the variable?
For now I deal with it this way:
var producerEl = $("#js-producer");
var brandEl = $("#js-brand");
var seriesEl = $("#js-series");
bind(seriesEl);
bind(brandEl);
bind(seriesEl);
function bind($el) {
$el.on("keypress", function () {
// some code..
});
}
I need something like $(producerEl, brandEl, seriesEl).on...
var producerEl = $("#js-producer");
var brandEl = $("#js-brand");
var seriesEl = $("#js-series");
producerEl.add(brandEl).add(seriesEl).on("click", function () {
alert('hello');
});
If you are trying to keep your code readable, might I suggest this approach?
$("#js-producer, #js-brand, #js-series").on('keypress', function () { });
Hmm. If you're using these selectors only one, don't care about "I know it is good to". The best solution is the one provided by David Smith.
Anyway, jQuery is using the sizzle selector engine, who has it's own cache. You can ask for
$("#js-producer, #js-brand, #js-series")
the result would be cached and reused.

Using javascript:function syntax versus jQuery selector to make Ajax calls

I do front-end dev only like 10% of the time and am curious which is the better way to handle making ajax calls. These calls are just posting data to a web app that specifies an action name and an id.
<a href='javascript:addToList({'action':'set-default-time-zone','id':23})'>set default timezone</a>
<div class='add-to-list action-set-default-time-zone id-23'>set default timezone</div>
I have used both over the years but am not sure which one is preferred. It seems like they get to the same point in the end. Would you consider these to be the two best alternatives and is one better than the other?
I've implemented the div method as follows:
$(document).ready(function(){
$('.add-to-list').click(function(){
var id=getId($(this).attr("class"));
var action=getAction($(this).attr("class"));
$.post('/api/' + action,function(data){
...
},'json')
});
});
function getAction(str){
var parts=str.split(' ');
var phrase='action-';
for(i=0; i<parts.length; i++){
var val=parts[i].match(phrase);
if(val!=null){
var action=parts[i].split('action-');
return action[1];
}
}
}
function getId(piece){
var parts=piece.split('id-');
var frag_id=parts[parts.length-1];
var part_id=frag_id.split('-');
var id=part_id[part_id.length-1];
return id;
}
The link method would seem straightforward.
thx
Well the second approach is what you would call Unobtrusive JavaScript. It is believed to be a more robust approach (I'll avoid the term better here.)
However, your implementation is a bit over-complicated. It could be tuned down to:
HTML:
<div class="add-to-list" data-action="set-default-time-zone" data-id="23">
set default timezone
</div>
JavaScript:
$(document).ready(function () {
$('.add-to-list').click(function () {
var id = $(this).attr("data-id");
var action = $(this).attr("data-action");
$.post('/api/' + action, function(data) {
// ...
}, 'json')
});
});
The HTML5 specification allows for attributes starting with data- to be carrying user-defined data. And it's also backward compatible (will work with older browsers.)
Method 1:
<a href='javascript:addToList({'action':'set-default-time-zone','id':23})'>set default timezone</a>
Method 2:
<div class='add-to-list action-set-default-time-zone id-23'>set default timezone</div>
Method 2 is preferred because you would be practicing unobtrusive style of coding with a much clearer separation of your markup and your scripting code. It is alot easier to read and debug, and there for more maintainable. Also, i would propose instead of using CSS classes to pass data, to use the jQuery.data() method to store data on elements.

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