Giving DOM Nodes Javascript Functionality - javascript

I'm working on a Single Page App and quite a few of the DOM node construction functions return a reference to the object they've created. Here's an example of what I mean:
if (!document.getElementById('playlistHeader')) {
appView.buildKit.playlist.headerWrap();
}
// construct element
var secondaryBtnsWrap = document.createElement("div");
secondaryBtnsWrap.id = "playlistSecondaryBtnWrap";
secondaryBtnsWrap.className = "clearright right";
// attach it
document.getElementById('playlistHeader').appendChild(secondaryBtnsWrap);
// return reference to dom node
return secondaryBtnsWrap;
I figured it would be redundant to destroy and recreate the node when changing between views, so I started working towards being able to wipe the content of some nodes (subsections of the site) by giving them a custom function that handles the wiping.
// build wipe function
secondaryBtnsWrap.wipe = function(){
// do custom wiping here
}
The idea is to "reset" parts of the UI and rebuild the differences between the views. For example, if there was a button that we'd need regardless, the wipe function won't delete it. That way it eliminates the extra legwork of creating the same element.
In some cases it's a lot easier to get a reference to a node and trigger the custom function attached, but I was wondering if its actually a good idea or if it just SEEMS like a good idea (but isn't).
TL;DR VERSION
Is it a good idea to give DOM nodes Javascript functions?

DOM Nodes are first-class objects. Nothing wrong with assigning them your own properties. Just don't try to overwrite any built-ins (except perhaps event handlers, but you should really be using attachEvent/addEventListener). In my opinion, it's one of the most convenient features of javascript in terms of user-interface development.
You could also build your page with a standard object which generates its own DOM nodes. You can attach event listeners which invoke functions on said object for interactivity. Use closures to reference the owner object. For instance:
function Foo() {
this.element = undefined;
this.createTextbox();
}
Foo.prototype.createTextbox = function() {
var $self = this;
this.element = document.createElement('input');
this.element.type = 'text';
this.element.addEventListener('change', function() {
$self.onChange.apply($self, arguments);
});
}
Foo.prototype.onChange = function() {
console.log(this.element.value);
}
Foo.prototype.Render = function(target) {
target = target || document.body;
target.appendChild(this.element);
}
var foo = new Foo();
foo.Render();
Both methods are equally valid, but I'd say the latter is probably more convenient when either the design or functionality needs to be significantly modified (rather than changing ids, classes, and layouts in both places, you only have to change them in one).

Related

Pure Javascript plugin development

I need to develop a pure javascript plugin wich can be accessed like jquery typed plugins ( $('.pluginWrapper').pluginInit();
However i need to to use pure javascript and i was thinking maybe about 2 supported formats:
document.getElementById('pluginWrapper').pluginInit();
pluginInit(document.getElementById('pluginWrapper'));
I know that you have to do an IIFE to wrap it and call it via object methods but i do not know how i can bind that to an element.
I am mentioning that i am a begginer so please can someone please explain something about this. Cheers!
You would be safer developing a plugin interface that simply exposes plugins as functions which take an element as an argument.
function MyPlugin(element) {
// do stuff with element
element.setAttribute('data-plugin-id', 'pluginName');
}
The other approach involves extending Element.prototype. Which could potentially be a dangerous action in production software.
However, it is still possible.
Element.prototype.pluginInit = function() {
// the element is accessible as `this`
this.setAttribute('data-plugin-id', 'pluginName');
}
A function is easy for everyone to understand. Plugin writers don't have to understand any interfaces for creating and registering plugins, they just need to know that they should write functions that take elements as arguments.
There's a great talk from Rich Hickey (the creator of Clojure) called Simplicity Matters in which he stresses that the worst thing you can do is add additional complexity when simple solutions will do.
In this case, you don't need anything more complex than a function which takes an element as an argument.
If it is completely essential that you have control of the function, you could write a simple interface for registering and initiating plugins.
function Plugin(element) {
if(element === null) {
throw new TypeError("Element must not be null!");
}
// get all the plugin names from the store
var pluginNames = Object.keys(Plugin.store);
// make sure `this` is set to the element for each plugin
var availablePlugins = pluginNames.reduce(function(plugins, name) {
plugins[name] = Plugin.store[name].bind(element);
return plugins;
}, {});
// return an object containing all plugins
return availablePlugins;
}
// we'll store the plugins in this object
Plugin.store = {};
// we can register new plugins with this method
Plugin.register = function(name, pluginFn) {
Plugin.store[name] = pluginFn;
};
Which you could use like this.
Plugin.register('myPlugin', function() {
this.setAttribute('data-plugin-id', 'myPlugin');
});
Plugin(document.getElementById('pluginWrapper')).myPlugin();
If you want the plugin function to take a selector, the same way as jQuery, then you can use document.querySelectorAll inside your definition for Plugin.
function Plugin(selector) {
var element = document.querySelectorAll(selector);
if(element === null) {
throw new TypeError("Element must not be null!");
}
// get all the plugin names from the store
var pluginNames = Object.keys(Plugin.store);
// make sure `this` is set to the element for each plugin
var availablePlugins = pluginNames.reduce(function(plugins, name) {
plugins[name] = Plugin.store[name].bind(element);
return plugins;
}, {});
// return an object containing all plugins
return availablePlugins;
}
Then you would use it like this instead.
Plugin.register('myPlugin', function() {
this.setAttribute('data-plugin-id', 'myPlugin');
});
Plugin('#pluginWrapper').myPlugin();

scope Issue seeing object methods

I have tried searching through a lot of S.O. pages but nothing has touched EXACTLY on this top while also NOT USING JQUERY.... I am trying to stick to pure JavaScript as I want to learn it 115% before advancing my current knowledge of JQuery.
I have an object called ScreenResizeTool like this...
function ScreenResizeTool(currImg) {
window.addEventHandler('resize', function() {
listen(currImg);
}, true);
}
and a method like this...
ScreenResizeTool.prototype.listen = function(currImg) {
//Random Code For Resizing
};
My trouble is probably obvious to an experienced JavaScript user but I am having trouble not making this into a messy dirty awful OOP set. I have done various tests to show and prove to myself that the this inside the addEventHandler changes when it becomes bound to the window. This much I assumed before testing but I was able to see that once window.resize event happens the listen method is gone and not a part of the global window variable....
I have also tried adding a this capture such as this.me = this inside the object constructor however it also couldn't see the me variable once it ran. Once the window took the function over it no longer knew anything about the me variable or any reference to my class methods....
I am aware that I could separate this differently but my goal here is to learn how to fully encapsulate and use as many clean OOP structures as possible as I just came from the .NET world and I need it in my life.
I am also aware that I could make messy calls and or store this object or access to the methods inside the window variable but that seems outright wrong to me. I should be able to fully encapsulate this object and have its events and methods all implemented in this class structure.
I also know that the currImg variable is not going to be seen either but lets start small here. I assume once I figure out my incorrect train of thought on scope for JavaScript I should be fine to figure out the currImg problem.
I know there's 1000 JavaScript programmers out there waiting to rip me a new one over asking this simple question but I gotta know...
Thoughts anyone?
this inside a function bound to a DOM Object (like window) will always refer to that object.
this inside a constructor function will always refer to the prototype.
A common practice to circumvent the this issue, as you mentioned, is to cache it in a variable, often called self. Now you want the variables and properties of your object available after instantiation, so what you need is the return keyword, more specifically to return the parent object itself. Let's put that together:
function ScreenResizeTool() {
var self = this;
// method to instantiate the code is often stored in init property
this.init = function() {
window.addEventListener('resize', function() {
self.listen(); // self will refer to the prototype, not the window!
}, true);
};
return this;
}
ScreenResizeTool.prototype.listen = function() { // Dummy function
var h = window.innerHeight, w = window.innerWidth;
console.log('Resized to ' + w + ' x ' + h + '!');
};
Pretty easy huh? So we have our prototype now, but prototypes can't do anything if there's not an instance. So we create an instance of ScreenResizeTool and instantiate it with its init method:
var tool = new ScreenResizeTool();
tool.init();
// every time you resize the window now, a result will be logged!
You could also simply store the listen & init methods as private functions inside your constructor, and return them in an anonymous object:
function ScreenResizeTool() {
var listen = function() { ... };
var init = function() { ... };
// in this.init you can now simply call listen() instead of this.listen()
return {
listen: listen,
init: init
}
}
Check out the fiddle and make sure to open your console. Note that in this case I'd rather use the first function than the second (it does exactly the same) because prototypes are only useful if you have multiple instances or subclasses
The whole concept of this in JavaScript is a nightmare for beginners and in my code I usually try to avoid it as it gets confusing fast and makes code unreadable (IMHO). Also, many people new to JavaScript but experienced in object-oriented programming languages try to get into the whole this and prototype stuff directly though the don't actually need to (google JS patterns like IIFE for example as alternatives).
So looking at your original code:
function ScreenResizeTool(currImg) {
window.addEventHandler('resize', function() {
listen(currImg); // global function listen?
}, true);
}
ScreenResizeTool.prototype.listen = function(currImg) {
//Random Code For Resizing
};
First off, you probably mean addEventListener instead. In its callback you refer to listen but as a global variable which would look for it as window.listen - which doesn't exit. So you could think to do this:
function ScreenResizeTool(currImg) {
window.addEventHandler('resize', function() {
this.listen(currImg); // what's this?
}, true);
}
As you want to use the prototype.listen function of ScreenResizeTool. But this won't work either as the event listener's callback function is called with a different this and not the this that is your function scope.
This is where something comes in which makes most programmers cringe, you have to cache this, examples from code I've seen:
var _this = this;
var that = this;
var _self = this;
Let's just use the latter to be able to refer to the function within the event callback:
function ScreenResizeTool(currImg) {
var _self = this;
window.addEventListener('resize', function() {
_self.listen();
}, true);
}
Now this will actually work and do what you want to achieve: invoke the prototype.listen function of ScreenResizeTool.
See this JSFiddle for a working example: http://jsfiddle.net/KNw6R/ (check the console for output)
As a last word, this problem did not have anything to do with using jQuery or not. It's a general problem of JS. And especially when having to deal with different browser implementations you should be using jQuery (or another such library) to make your own code clean and neat and not fiddle around with multiple if statements to find out what feature is supported in what way.

Simple way to access current object

I'm trying to find a simple way to refer to the current instance of an object from the object methods itself (similar to this keyword in any decent language).
I've tried many variations of "storing a pointer for itself" (like window.window.window do), but always something go wrong. For example:
function ClassA() {
var mySelf = this;
this.myMethod = function() {
console.log(mySelf); //Once you extend this class, mySelf will not be available
}
}
function ClassB() {
this.mySelf = this; //Ok, now you can extend this and mySelf will be there
this.myMethod = function() {
console.log(mySelf);//"mySelf is not defined" because it's a property now
console.log(this.mySelf);//Value of 'this' and 'self' will vary
console.log(RandomContainer.ClassBInstance.mySelf);//I can't use a defined path
}
}
Since everything about OOP in JavaScript is hackish, I have to ask...
Is there any magic to refer to the current object that a method is being called from?
EDIT
A lot of possible solutions in the comments, thanks guys!
But I still need to improve my question. So I'll add some piece of code with my failed attempts, and then try out the proposed solutions...
function BaseController()
{
var myPriv = 42;
return {
getAnswer: function() {
return myPriv;
}
};
}
function SomeController()
{
return {
showAnswer: function()
{
var answer;
answer = myPriv; //I need some way to access this
answer = getAnswer(); //Also a way to refer to my own methods
//(I don't even find a way to call 'showAnswer' from this context)
console.log('The answer is ' + answer);
}
};
}
//That's how I was extending my classes so far...
var someControllerInstance = jQuery.extend(
new BaseController(),
new SomeController()
);
someControllerInstance.getAnswer(); //Works!
someControllerInstance.showAnswer(); //Reference errors...
take time to learn the idiosyncrasies of js, be warned though, it's like marmite.
If you'll allow me to be blunt for a moment, you are approaching JavaScript with the wrong frame of mind. It is not designed for classical inheritance, nor for protected properties or methods, and there is no benefit in bending it that way. To be honest I find towering stacks of inheritance a pain to read and navigate, unless you have a singing-all-dancing IDE that may take a week to load. The closer to flat and open you can achieve — whilst still keeping things flexible — the better you are at coding, and the more other coders that may take over your work will thank you. (obviously that is opinion)
For more information on prototype inheritance read the following informative post:
http://davidwalsh.name/javascript-objects-deconstruction
Below is an example of prototype inheritance, it should be noted that Object.create and isPrototypeOf are relatively new and do not exist for older JavaScript interpreters. Approximate polyfills can be used in most cases however.
Put simply JavaScript becomes much more powerful when you think in terms of objects borrowing methods from wherever they may be found, and not instances rigidly inheriting from slightly less specific instances; or worse, constructors that keep rebuilding the same functions again and again.
The following is just an example, and across my 16 years of coding ECMAScript I have barely ever needed anything that approaches classical inheritance, nor have I needed objects that heavily inherit on the prototype chain either. More often than not my js is basically just a number of newly created objects, properly name-spaced, that borrow methods from fixed pools of functions; any type detections are duck-typed, and I'm careful to keep everything as local as possible.
Anyway, here's something I don't often use:
var Make = function(construct){
return Object.create(construct);
};
var Extend = function(proto, props){
var instance = Object.create(proto);
for ( var i in props ) { instance[i] = props[i]; }
instance.super = proto; // this should never be needed really
return instance;
};
var Animal = {
keepBreathing: function(){
console.log('animal', this);
}
};
var Monkey = Extend(Animal, {
climbTree: function(){
console.log('monkey', this);
console.log('monkey', this.super);
}
});
var KeyserSoze = Make(Monkey);
KeyserSoze.keepBreathing(); /// animal, instance of KeyserSoze
KeyserSoze.climbTree(); /// monkey, instance of KeyserSoze
console.log('an instance of monkey', KeyserSoze);
console.log('has animal as proto', Animal.isPrototypeOf(KeyserSoze)); // true
console.log('has monkey as proto', Monkey.isPrototypeOf(KeyserSoze)); // true
Whilst the above does follow a kind of classical layout i.e. Monkey inherits methods from Animal, you could approach things in a different way. The following is more open to dynamic changes, in the fact you could switch out the behaviours object for another interface entirely. Again, this is just an illustration that you don't have to construct things in a fixed way.
Something I'm more likely to use:
var AnimalBehaviours = {
keepBreathing: function(){
(this.breathCount === undefined) && (this.breathCount = 0);
this.breathCount++;
}
};
var ApeLikeDescendant = (function( behaviours ){
return {
create: function( config ){
return Object.create(this).prep(config);
},
prep: function( config ){
/// do your instance specific set up here
return this;
},
climbTree: function(){
console.log('ape-like', this);
},
keepBreathing: function(){
return behaviours.keepBreathing.apply(this, arguments);
},
switchBehaviours: function(newBehaviours){
behaviours = newBehaviours;
}
};
})(AnimalBehaviours);
var DouglasAdams = ApeLikeDescendant.create({});
You could adapt the above to behave more in a manner similar to mixins i.e. you'd take the behaviours, step through them and merge them to the ApeLike object... it's really quite open to whatever your goal is.
Something that I use regularly:
var ElementEdgeCases = {
makeWorkOnNetscape47: function(){
this.target; /// Intentionally left almost blank.
}
};
var ElementFinagler = (function(){
var finagle = {
target: null,
prep: function( element ){
this.target = element;
return this;
},
addClass: function(){
var c = this.target.getAttribute('className'); /// and so on ...
return this;
},
elaborate: function( mixin ){
for ( var i in mixin ) {
if ( mixin.hasOwnProperty(i) ) {
this[i] = mixin[i];
}
}
return this;
}
};
return function( element ){
return Object.create(finagle).prep(element);
};
})();
var elm = document.getElementsByTagName('body')[0];
ElementFinagler(elm)
.elaborate(ElementEdgeCases) // extend the object if we need
.addClass('hello world')
;
The main thing to keep in mind with JavaScript is that no function is owned by anything, not really. Every time you execute a function, the function's context is implied by the way you call that function — the context is computed at call time. This allows a great deal of flexibility, and whilst you mention that calling function.apply(context, args) or function.call(context, arg, arg, ...) every time is cumbersome; it is very easy to codify your own system for hiding that repetition away.
Oh, and before I forget, one other thing to take away from the code above is that there is no duplication of function creation. Every instance shares the same functions in memory. This is an important thing to bear in mind if you are planning to create large scale applications, as you can quickly eat up memory with multiple instances of functions.
So just to recap:
Forget inheritance, it's rarely required for many js projects.
Create flexible objects that can be extended when required.
Borrow methods when you need them from other objects, using apply or call to change context.
Keep your code open, there is no need for private; open is more extensible.
Private no, but having properties not enumerable is a different story, see defineProperty.
Make sure you do not duplicate functions — unless you have to — instead create them once and reference.
this referrers to the dom node that an event originated from, or the javascript object it is a child of. In that order.
this.parentNode referrers the dom node's parent (Not sure if it works in object context).
When I write javascript I'll usually try to leverage the html's dom itself, nesting things mindful of how the JS needs to reference them.

How can I add a namespace function to onclick in Javascript?

After discovering about Javascript namespaces, I tried to implement them but I run into a problem while trying to attach a namespace method to an element's onclick.
I used this method to wrap up my functions/methods/classes (a simplified concept, not my actual code):
;(function(window, undefined) {
//my namespace
var NS = {};
NS.test = {
f : function(param) {
alert(param);
}
}
NS.test.('test 2');
})(window);
Inside, everything works fine and "test 2" is prompted.
However, when I try to attach that function to a click event, by doing something like this:
<a href-"#" onclick="NS.test.f('test');">Click me!</a>
it doesn't work, just like it doesn't work when I call that function after the })(window); part.
I tried it calling it window.NS.test.f('test'); but with no effect.
How can I make an onclick event call my function?
I could attach an event listener inside my wrapper, like I do for other html elements with no difficulty, but it would be problematic in this case since I'm generating the links with javascript and I find it easier and simpler to just add onclick="doSomething" for all my links, instead of creating them, then cache them and add event listeners.
Call me lazy, but in this particular case I prefer to do
someDiv.innerHTML = my_Generated_Html_Code_With_OnClick;
instead of
//demo code, ignore the flaws and the fact it won't work on IE
someDiv.innerHTML = my_generated_Html_code;
myLink = document.getElementById(id);
myLink.addEventListener('mousedown', NS.test.f('test'));
I do not use any framework nor do I wish to, since I'm trying to get a better understanding of the so-called vanilla javascript first.
I set up a jsfiddle here.
P.S. I must admit I didn't understand namespaces completely so if I'm doing something wrong here or applying the concept in a way I am not supposed to, I would appreciate any tips or corrections
That's because NS is declared inside and hence only exists inside the function:
function(window, undefined) {
var NS = {};
// NS exists here ...
}
// ... but not here
If you want to make it available to the rest of the page, then you can do:
function(window, undefined) {
var NS = window.NS = {};
// NS and window.NS exist here ...
}
// ... and window.NS exists here.

Customising a JQuery Element

Is it inadvisable to add methods to a JQuery element?
eg:
var b = $("#uniqueID");
b.someMethod = function(){};
Update
Just to clarify, I am working on a JS-driven app that is binding JSON data to local JS objects that encapsulate the business logic for manipulating the actual underlying DOM elements. The objects currently store a reference to their associated HTML element/s. I was thinking that I could, in effect, merge a specific instance of a jquery element with it's logic by taking that reference add adding the methods required.
Well, there's nothing inherently wrong with it. It is, however, pretty pointless. For example:
$('body').someMethod = function(){};
console.log($('body').someMethod); // undefined
You are attaching the new function only to that selection, not to all selections of that element.
What you should do instead is to add a new function to jQuery.fn, which is a shortcut for jQuery.prototype:
jQuery.fn.someMethod = function() {
if (this[0].nodeName == 'body') {
// do your function
}
return this; // preserve chaining
};
The problem is that your function would be quite transient. A further requery and it will be gone. You can extend the jQuery object itself by $.fn.someMethod = function() {} and this method will be available for all queries.
$.fn.someMethod = function() {}
var b = $("body");
b.someMethod();
Or you can create a jQuery plugin. You can define a plugin this way:
$.fn.someMethod = function(options) {
# ...
});
Call it using $('body').someMethod();

Categories

Resources