JavaScript Inline Events or Adding Events Afterwards - javascript

I have a question, which I can't seem to decide on my own so I'll ask here. The question is simple: whether to use inline JavaScript events or adding them afterwards? The theory in the background isn't that simple though:
I have a JS object that returns HTML. Whenever you create this object, the returned HTML will be used for another object's HTML. Therefore, adding events is not straight-forward. See:
secret.object = function() {
this.init = function() {
var html = '<div>and lots of other HTML content</div>';
return html;
};
}
This is a sample object that is created within this code:
for ( var i = 0; i < countObjects; i++) {
var obj = arguments[0].content[i];
generatedContent += spawnSecret(); /* The spawnSecret() is a method that initializes the object, and calls its init() method that returns the HTML.
}
and then later on I create a new object whose property "content" will be set to "generatedContent". It needs to add the events within the secret object I have, nowhere else. And since my system is built like this, I see only two ways around this: use inline events or build HTML using method calling instead of returning.
Hopefully, this wasn't too hard to understand.

If you created the elements using document.createElement() (but didn't append them to the DOM) and kept a reference to them, then you could populate them with the text content and attach event handlers to them, without having to use inline events.
When you are ready to reveal your 'secret' you could then append them to the DOM, rather than dumping in a text string of HTML tags and content.

I cant see it making much of a difference - if you just render your events using onclick etc. JavaScript event handlers they will be evaluated as soon as you append your generated HTML to the document, rather than you having to call attachEvent() or whatever.

Related

How to get a reference to an HTML element that only exists in memory

First, a little background on the question.
After I found this Spotify Community idea being throwed away, I've decided to check out the Web App's inner workings to figure out some way of changing the playback speed.
Turns out Spotify's Web Player uses a regular HTML5 video tag created by runtime JavaScript, which means I can tinker with it's playbackRate property. I'm able to reference it through the DevTools debugger and it all works.
But the element's reference is hidden behing uglified code and factory functions, so I can't grab it by regular user scripting. Also, the element never gets inserted into the DOM (parentElement is null), so document.getElementsByTagName() also won't work.
TL;DR: Is there a way of referencing a DOM Element that wasn't inserted into the page/only exists in memory?
I've made a simple working example here. Once you press the button, a video tag is created but not inserted into the page. You can't reference it.
I have solved my specific problem by replacing the document.createElement function with a wrapper function. Inside of this wrapper I check for a specific HTML tag and store it globally so I can access it later.
I'm using Tampermonkey on Chrome 81.0.4044.138.
Here's the relevant code:
const originalCreateElement = document.createElement;
document.createElement = function() {
const ret = originalCreateElement.apply(document, arguments)
if(arguments[0] == 'video'){
document.videotag = ret;
}
return ret;
}
By using this method one may store every single tag created on a global array.

Why is document.getElementById("item").value so hard to assign to a variable?

The way I see it, something like var thingy = document.getElementById("item").value should create a variable, put this element's value in it, and call the element's value whenever the variable is called.
I understand that it's not so simple, but I don't understand why.
I've read a lot of similar questions considering global variables and page onload.
The page unload seems to explain why linking the HTML page to an external javascript file, but I don't understand why there isn't a simple solution to that.
What makes assigning a variable to the element of an external HTML document so complicated that people commonly resort to jquery's simple $() assignment?
Keeping a "live" reference to an element
var thingy = document.getElementById("item").value is copying the value when the line is executed and storing it in thingy.
To keep a "live" copy of the value you would store a reference to the element
var thingy = document.getElementById("item")
Then you can call later
thingy.value to get the current value.
this is comparable to how it sounds like you have been using jQuery
var thingy = $("#item")
using var thingy = $("#item").val() would produce the same behavior as before of copying the current value and subsequent calls to thingy would not be the "live" value.
Having that reference be not null
Out of the box script tags are parsed synchronously, aka right when they are hit by the parser. Any elements after your script will not be loaded nor be available for querying.
I believe jQuery hooks into the DOMContentLoaded event and fires your code once the DOM tree is fully available(all the elements have been parsed). They give you some shorthands for hooking into this event.
$(function(){})
$(document).ready(function(){})
jQuery(function($){ });
The same is achievable in vanilla.js by hooking into the DOMContentLoaded event
document.addEventListener('DOMContentLoaded', function(){
/* DOM tree fully parsed and available here */
});
HTML 5 introduced the defer attribute to the script tag so we don't have to worry about it at all, our external script will be called after the DOM is parsed. Note that this only applies to external scripts and does not work with internal
<script src="mysite.js" defer></script>
....
/* inside mysite.js */
//DOM ready good to go
var thingy = document.getElementById("item");
Most likely you are facing this problem because you are trying to get the element before the dom is fully loaded, whilst jquery work inside function.ready so it waits for the page to be ready
When we type out var thingy = document.getElementById("item").value You're just getting the current value at that moment. That makes sense because it would be extremely hard to manipulate values and dom elements when the values always changing.. When you always want thingy to always return the current value you need a function.
var thingy = this.getValue();
getValue(){
return document.getElementById("item").value;
}
What makes assigning a variable to the element of an external HTML
document so complicated that people commonly resort to jquery's simple
$() assignment?
Nothing. It's just preference.
Edit: Check #Proffesor Allmans answer for more details to your problem. You have to post some code for a solution though.

About saving DOM elements in object properties

I want to make an UI object that saves references to DOM objects and also some UI strings, so I can use it all over my code and can handle name changes easily. Kind of like this:
var UI = {
DOMelem0: document.getElementById('imUnique'),
DOMelem1: document.getElementById('imSpecial')
};
However, I think that everytime I would access DOMelem0 (by calling UI.DOMelem0), for instance, I'd be calling the getElementById() function, is that what happens?
Or is it no different than storing the elem in a scoped variable? (Like so: var elem = document.getElementById('cow');)
I'm worried about any performance issues this might cause if I were to have lots of UI elements, although I guess they'd be minimal. Either way, I wouldn't want to be calling the DOM method all the time.
Thanks.
Calling UI.DOMelem0; will not call document.getElementById('imUnique').
document.getElementById('imUnique') is only called when you first create the UI object.

Can I create event attributes?

Can I create my own attributes on HTML elements that are treated as JavaScript code, and able to be executed when my code so desires?
For example, say I want to create a custom event onpagerefresh, and at the same time, allow the person who writes the HTML to add attributes that handle this event.
<body onpagerefresh="updateContents()">
I am able to parse the HTML to get the value of this new onpagerefresh attribute, but how do I tell JavaScript to treat anything in this attribute as JavaScript code rather than a string?
You can use new Function(), the function constructor, to get a function object from your string and then assign it to an attribute of your object. Ex:
var divElement = document.getElementById('mydiv');
divElement.onpagerefresh = new Function("èvent", "alert();");
Whether that's better than eval() in any meaningful way is debatable (though it does give you a distinct scope), but that would be how you do it. Possibly a better option in your case would be to allow the person implementing the HTML to use a preexisting function name (onpagerefresh="updateContents") and then simply execute that one as e.g. window["updateContents"](). That way you don't let any old DOM insertion write random javascript into your code. But of course that limits the functionality.
In case you want to create an actual event accompanied with support for listeners, this Mozilla article describes a way to do so:
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
In your case:
var event = new Event('onpagerefresh');
// Listen for the event.
elem.addEventListener('onpagerefresh', function (e) {
updateContents();
}, false);
// Dispatch the event.
elem.dispatchEvent(event);
Though, be wary of cross-browser support.

Trying to write a Javascript class to handle dynamically adding more data to my HTML. Need some guidance

Here's what I'm aiming to achieve:
HTML
<fieldset id="addmore">
<p>blah</p>
<a class="remove">remove me</a>
</fieldset>
<a class="add">add more fieldsets</a>
Javascript
var addmore = new AddMore($('fieldset'));
addmore.buildCache(/*this will pull the innerHTML of the fieldset*/);
// bind the buttons
addmore.bind('add', $('a.add'));
addmore.bind('remove', $('a.remove'));
I've found myself having a lot more 'addmore' stuff in my HTML lately so I've been trying to build a class that will do all the leg work for me that I can just reuse in all my projects. The above code will, hopefully, be all I have to add each time and then the rest is done for me.
I've been winging this thing so, off the top of my head, here's what the class has to do:
Apply the jQuery bindings to the supplied 'button' objects so we can add/remove fieldsets
When a new fieldset is added, we have to recall the bind function so the new fieldset's 'a.add' button will work (I've found jQuery's .live() function to be buggy, for whatever reason, and try to avoid it)
It will hopefully do this with no memory leaks :}
Javascript Class
/*
Class to handle adding more data to the form array
Initialise the class by passing in the elements you want to add more of
Then bind 'add' and 'remove' buttons to the functions and the class will do the rest
*/
/*
Pass the jQuery object you want to 'addmore' of
Ex: var x = new AddMore($('fieldset.addmore'));
*/
function AddMore($element)
{
if (!$element || typeof($element) != 'object')
throw 'Constructor requires a jQuery object';
this.element = $element; // this is a jQuery object
this.cache = null;
}
/*
Supply clean HTML to this function and it will be cached
since the cached data will be used when 'adding more', you'll want the inputs to be emptied,
selects to have their first option selected and any other data removed you don't want readded to the page
*/
AddMore.prototype.buildCache = function(fieldset)
{
if (!fieldset)
throw 'No data supplied to cache';
this.cache = fieldset;
}
/*
use this to create the initial bindings rather than jQuery
the reason? I find .live() to be buggy. it doesn't always work. this usually means having to use a standard .bind()
and then re-bind when we add in the new set
that's what this class helps with. when it adds in the new data, it rebinds for you. nice and easy.
*/
AddMore.prototype.bind = function(type, $button)
{
if (!type || !$button && (type != 'add' && type != 'remove'))
throw 'Invalid paramaters';
// don't reapply the bindings to old elements...
if ($button.hasClass('addmore-binded'))
return;
// jQuery overwrites 'this' within it's scope
var _this = this;
if (type == 'add')
{
$button.bind('click', function()
{
_this.element.after(_this.cache);
});
}
}
I was going to have the .bind() method (in my class) call itself upon adding the new fieldset to reapply the binding but lost confidence with efficiency (speed/memory).
How should I tackle this? Do you have any pointers? Can you recommend improvements?
Thanks for the help.
In the most simplest form, you can do something like this:
var html = '{put html to add each time here}';
$('.add').click(function() {
$(html).insertAfter($('fieldset').last());
return false;
});
$('.remove').live('click', function() {
$(this).parent().remove();
return false;
});
You may need to tweak it based on your exact needs, but this should accomplish what you described in your example.
Update: sorry, remove should use the live method.
For creation of the new DOM elements, allow the specification/parameters to be any of the following:
simple HTML as a string (like the example above),
a function returning either a DOM element or HTML text. You can skip bind() or live() issues by adding
the onclick element when creating the HTML/element in the function. Although doing it in the AddMore() scope would be more tedious
if it's not a DOM element that gets returned.
inputs to a helper/factory method (maybe a template and name/value pairs) - postpone this unless you know enough patterns already.
Option #1 seems almost useless, but #3 might be hard unless you have extra time now.
Notes:
You might want to use $(theNewDomElement).insertBefore(_this.button); rather than _this.element.after(theNewDomElement); so that new items are append to the end of the list.
Actually, for insertBefore() you might just use this rather than _this.button since presumably the button (or anchor) is this, but then that limits the functionality - just make it some sort of DOM element (or a jQuery object that equates to one).
In most cases, I'd presume your DOM elements represent data that you'll want to save/send/transmit to your server, so provide a before-removal function, too. Even allow the function to skip removal -- like after a confirm().
Good luck.

Categories