Storing functions in array. Is it a good practice? - javascript

var sing = function(name) {
console.log(name + " is SINGING");
}
var cry = function(name) {
console.log(name + " is CRYING");
}
var list = [sing, cry];
for(var func of list) {
func('foo');
}
This is exactly what I want in my code. But I am not sure if its a good practice.

Yes, in some situations, it is the preferable design to store functions in an array.
Imagine the following EventEmitter class. You can register event listeners using a method called on and dispatch an event using emit. The functions are stored in an array:
var EventEmitter = function () {
this._events = {};
};
EventEmitter.prototype.on = function (event, listener) {
if (!this._events.hasOwnProperty(event)) {
this._events[event] = []; // stores the listeners bound to the event's name
}
this._events[event].push(listener); // add the listener to the event's listeners array
};
EventEmitter.prototype.emit = function (event) {
var args = Array.slice(arguments, 1);
if (this._events.hasOwnProperty(event)) {
this._events[event].forEach(function (listener) {
listener.apply(null, args);
});
}
}
var emitter = new EventEmitter();
// push the first function to the array
emitter.on('event-a', function (data) {
console.log('Event A was fired!');
});
// push the second function to the array
emitter.on('event-a', function (data) {
console.log('Second listener to event A');
});
emitter.on('event-b', function (a, b, c) {
console.log('Event B:', a + b + c);
});
emitter.emit('event-a');
setTimeout(function () {
emitter.emit('event-b', 2, 3, 4);
}, 1500);
This is why I would use it in some situations. I don't consider it bad practice.

var functionName = function(name, action) {
console.log(name + " is "+ action );
}
functionName("Aditya", "Programing");
Aditya, I think you should choose this format. It is always good practice to keep a single function for similar kind of tasks to perform.

Related

Can't remove event listener for windows object

I am having a lot of trouble trying to remove an event listener.
I have created a website that relies on JavaScript quite heavily. When you navigate on the website it is basically loading in elements dynamically without a page refresh with template literals.
I have to sometimes load in content and add infinite scroll but also be able to remove that event again.
This is the code I use to handle scroll events:
var start = 30;
var active = true;
function yHandler(elem)
{
var oHeight = selectElems('content_main', 'i').offsetHeight;
var yOffset = window.pageYOffset;
var hLimit = yOffset + window.innerHeight;
if (hLimit >= oHeight - 500 && active === true)
{
active = false;
new requestContent({
page: GET.page,
type: returnContentType(GET.page),
scroll: true,
start: start
}, (results) => {
if(results){
setTimeout(()=>{
active = true;
start = start + 30;;
}, 400);
new ContentActive();
}
});
}
}
var scrollRoute =
{
contentScroll: () =>{
yHandler();
}
};
var scrollHandler = function(options)
{
var func = options.func.name;
var funcOptions = options.func.options;
var elem = options.elem;
var flag = options.flag;
this.events = () => {
addEvent(elem, 'scroll', ()=>{
scrollRoute[func](elem, funcOptions);
}, flag);
}
this.clear = () => {
elem.removeEventListener('scroll', scrollRoute[func](), flag);
}
}
I am using this function to set events
function addEvent(obj, type, fn, flag = false) {
if (obj.addEventListener) {
obj.addEventListener(type, fn, flag);
} else if (obj.attachEvent) {
obj["e" + type + fn] = fn;
obj[type + fn] = function () {
obj["e" + type + fn](window.event);
};
obj.attachEvent("on" + type, obj[type + fn]);
} else {
obj["on" + type] = obj["e" + type + fn];
}
}
I am calling this code from whatever code when I need to set the infinite scroll event
new scrollHandler({
func: {
'name':'contentScroll',
},
elem: window,
flag: true,
}).events();
I am calling this code from whatever code when I need to remove the infinite scroll event but without any luck
new scrollHandler({
func: {
'name':'contentScroll',
},
elem: window,
flag: true,
}).clear();
How do I successfully remove the event listener? I can't just name the instances, that will be so messy in the long run when setting and removing the scroll events from various different places.
Two problems:
You have to pass the same function to removeEventListener as you passed to addEventListener. (Similarly, you have to pass the same function to detachEvent as you passed to attachEvent using Microsoft's proprietary stuff — but unless you really have to support IE8 and earlier, you can ditch all that.) Your code isn't doing that.
When trying to remove the handler, you're calling scrollRoute[func]() and passing its return value into removeEventListener. As far as I can tell, that's passing undefined into removeEventListener, which won't do anything useful.
Here's the code I'm referring to above:
this.events = () => {
addEvent(elem, 'scroll', ()=>{ // *** Arrow function you don't
scrollRoute[func](elem, funcOptions); // *** save anywhere
}, flag); // ***
}
this.clear = () => {
elem.removeEventListener('scroll', scrollRoute[func](), flag);
// Calling rather than passing func −−−^^^^^^^^^^^^^^^^^^^
}
Notice that the function you're passing addEvent (which will pass it to addEventListener) is an anonymous arrow function you don't save anywhere, but the function you're passing removeEventListener is the result of calling scrollRoute[func]().
You'll need to keep a reference to the function you pass addEvent and then pass that same function to a function that will undo what addEvent did (removeEvent, perhaps?). Or, again, ditch all that, don't support IE8, and use addEventListener directly.
So for instance:
var scrollHandler = function(options) {
var func = options.func.name;
var funcOptions = options.func.options;
var elem = options.elem;
var flag = options.flag;
var handler = () => {
scrollRoute[func](elem, funcOptions);
};
this.events = () => {
elem.addEventListener('scroll', handler, flag);
};
this.clear = () => {
elem.removeEventListener('scroll', handler, flag);
};
};
(Notice I added a couple of missing semicolons, since you seem to be using them elsewhere, and consistent curly brace positioning.)
Or using more features of ES2015 (since you're using arrow functions already):
var scrollHandler = function(options) {
const {elem, flag, func: {name, options}} = options;
const handler = () => {
scrollRoute[name](elem, options);
};
this.events = () => {
elem.addEventListener('scroll', handler, flag);
};
this.clear = () => {
elem.removeEventListener('scroll', handler, flag);
};
};

Binding events to functions within objects

I'm running into a peculiar issue with jQuery/JS in general.
I'm working on a JS file that let me create portlets for a client site. I'm using SignalR to communicate changes to the users.
The following piece of code gives me a bit of a headache as to why it won't work.
Eenheden: function (poPortlet) {
this.moPortlet = poPortlet;
this.Init = function () {
$(this.moPortlet).find('.portlet-title').text($(this.moPortlet).attr('data-cpv-type') + ' Eenheden')
};
this.BindHub = function () {
CPV.moHub.on('onEenheidUpdate', this.Events.Update);
};
this.Events = {
Update: function (pnId, psStatus) {
CPV.Log('Error', 'Update: ' + pnId + ' ' + psStatus);
}
};
}
I am trying to bind the function this.Events.Update on the SignalR event onEenheidUpdate. Instances of these Eenheiden objects are not unique on the pages. The idea is that they contain the same data, but can get filtered, creating a different portlet depending on some configs.
My problem is that the onEenheidUpdate function doesn't trigger the proper event. I want to do it like this so I can use references I set for the unique object, such as the jQuery object I set on initialization.
Problem
Your problem is that when jQuery or javascript in general triggers an event callback, the "context" (value of this) is changed. For example
var MyModule = {
init: function () {
$('.amazing-element').on('click', function () {
// This breaks because `
// `this` is not MyModule
// When the callback is triggered
// `this` is `<p class="amazing-element"></p>`
this.onClick()
})
},
onClick: function () {
console.log('wee, I was clicked')
}
}
Function.prototype.bind to the rescue!
var MyModule = {
init: function () {
$('.amazing-element').on('click', function () {
this.onClick()
}.bind(this))
// ^ here we bind the context of the callback to the
// correct version of `this`.
},
onClick: function () {
console.log('wee, I was clicked')
}
}
So your exact example would look like:
Eenheden: function (poPortlet) {
this.moPortlet = poPortlet;
this.Init = function () {
$(this.moPortlet).find('.portlet-title').text($(this.moPortlet).attr('data-cpv-type') + ' Eenheden')
};
this.BindHub = function () {
CPV.moHub.on('onEenheidUpdate', this.Events.Update.bind(this));
// ^ change here
};
this.Events = {
Update: function (pnId, psStatus) {
CPV.Log('Error', 'Update: ' + pnId + ' ' + psStatus);
}
};
}

Using addEventListener on Prototype

i am trying to runt the results of Prototype via a click through addEventListener but the results are showing even without clicking.
<div id="box">Your Item:</div>
<button id="myBtn">Buy</button>
<script type="text/javascript">
function Machine(n,p){
this.name = n;
this.price = p;
}
Machine.prototype.Dispatch = function(){
var container = document.getElementById('box');
container.innerHTML = this.name + " " + this.price;
}
var Drink = new Machine("coke",80);
var Handle = document.getElementById('myBtn');
Handle.addEventListener("click",Drink.Dispatch(),false);
</script>
http://jsfiddle.net/9trqK/
You have to pass a function reference to addEventListener, instead of calling the function (and passing its return value) as you're doing:
Handle.addEventListener("click", Drink.Dispatch, false);
When you do that, your handler will only be fired on click. But you will face a different problem: this inside the handler will be the clicked element, not your Machine instance. To solve that, you can use Function.bind (see the linked docs for a polyfill for older browsers):
var drink = new Machine("coke",80);
var handle = document.getElementById('myBtn');
handle.addEventListener("click", drink.Dispatch.bind(drink), false);
Note: It's common practice to only use uppercase initials for constructor function names, so you can tell them apart on a quick glance. That's why I renamed Drink and Handle to drink and handle.
You need to add your call between a function() {} closure for your listener. Or remove the () after your function name.
This should work: http://jsfiddle.net/9trqK/1/
function Machine(n, p) {
this.name = n;
this.price = p;
}
var handle = document.getElementById('myBtn');
Machine.prototype.Dispatch = function () {
var container = document.getElementById('box');
container.innerHTML = this.name + " " + this.price;
}
var Drink = new Machine("coke", 80);
handle.addEventListener("click", function() { Drink.Dispatch() }, false);
To do custom events on object I use this simple script to extend my objects:
/* Simple Custom event prototype for objects
Extend your object like this:
<OBJECT>.prototype = new EventEmitter;
Then you can use :
<OBJECT>.on("test",function() { alert("Test event triggered"); })
<OBJECT>.on("test",function() { alert("Test event 2 triggered"); })
<OBJECT>.trigger("test");
*/
function EventEmitter() {
var listeners = Object(); //Matrix of event/callbacks
//Add listener
this.on = function(evt, callback) {
//console.log("Listener added: " + evt);
if (!listeners.hasOwnProperty(evt))
listeners[evt] = Array();
listeners[evt].push(callback);
}
//Call listeners
this.trigger = function(evt, params) {
//console.log("trigger called " + evt);
//console.dir(listeners);
if (evt in listeners) {
callbacks = listeners[evt];
//Call all callbacks with the params
for (var x in callbacks){
callbacks[x](params);
}
} else {
console.log("No listeners found for " + evt);
}
}
}

Dynamic event building in JavaScript

I'd like to create an event for each function in a script, then inject the event trigger into the end of the function.
This way I can see exactly when each function has completed and use the events like hooks for other functions
If I can do it dynamically, I can add as many new functions as I like without having to create and append these events.
Here's a basic example of what I'm trying to do, this won't actually work, but it gives you an idea. I have been using jQuery, but I'll accept any JavaScript framework at all, and any method.
var obj = {};
(function()
{
this.init = function()
{
// loop through every function
$.each(this, function(k, v)
{
// create an event for every function
$('body').bind(v, function()
{
console.log('Event: ' + v + ' Finished');
});
// Add a event trigger into each specific function in the loop
this[v].call($('body').trigger(v));
});
}
this.another_function = function()
{
// do something
}
this.some_function = function()
{
/do something
}
}).apply(obj);
obj.init();
(edit) The script itself basically renders a Calendar, but there are a lot of callbacks, ajax requests, buttons. etc... If I could tie each feature down to an event, it would make my life easier when extending it, adding new features etc...
Loop through every function, replace it with new one, which calls original function on the same object and triggers event on body.
var obj = { };
(function()
{
this.init = function()
{
var self = this;
foreach(var name in this) {
if (typeof k !== 'Function') continue;
if (name ==='init') continue;
var original = this[name];
var newFunc = function() {
original.apply(self, arguments);
$('body').trigger(name);
}
this[name] = newFunc;
}
}
this.another_function = function()
{
// do something
}
this.some_function = function()
{
/do something
}
}).apply(obj);
obj.init();

removeEventListener() with a callback with a different context

I'm writing a mobile app in PhoneGap, but there seems to be an issue with Webkit and its ability to remove event listeners from its event list when there's a scope context change on the callback. Below is an example:
Function.prototype.bind = function(scope) {
var fn = this;
return function () {
fn.apply(scope, arguments);
};
};
a = function(){};
a.prototype.tmp = function(e){
var tmp = ddd.q('#tmp');
tmp.className = 'active';
tmp.addEventListener('webkitAnimationEnd',this.tmp2.bind([this,tmp]),false);
}
a.prototype.tmp2 = function(e){
this[1].removeEventListener('webkitAnimationEnd',this[0].tmp2.bind([this[0],this[1]]),false);
this[1].className = 'inactive;
var t2 = ddd.q('#tmp2');
t2.className = 'active';
t2.addEventListener('webkitAnimationEnd',this[0].setStart.bind([this,t2]),false);
};
Now, in the above code, the event listeners never peel off, and whenever the callback gets invoked, the event listener list becomes rather large -- as demonstrated in Web Inspector. Any ideas on how to remove event listeners when they're done using callbacks that change function scope?
Can you use something like this jsfiddle example? this is the object on which the click event is fired. self is the A object.
Function.prototype.bind = Function.prototype.bind || function(scope) {
var fn = this;
return function () {
fn.apply(scope, arguments);
};
};
A = function() {};
A.prototype.click = function (el) {
var self = this;
var onClick = function () {
el.removeEventListener('click', onClick, false);
alert("this=" + this + "\nself=" + self + "\nel=" + el + "\nclicked");
}
el.addEventListener('click', onClick, false);
}
A.prototype.toString = function () {
return "I am an A!";
}
a = new A();
a.click(document.getElementById("a1"));
a.click(document.getElementById("a2"));
Update 1 - second example is here. Major differences below.
function createOnClickHandler (scope, outerThis, el) {
var onClick = (function (evt) {
el.removeEventListener('click', onClick, false);
alert("this=" + this + "\nouterThis=" + outerThis + ", \nel=" + el + "\nclicked");
}).bind(scope);
return onClick;
}
A = function() {};
A.prototype.click = function (el) {
var ob = {
toString: function () {
return "I am an ob!";
}
};
el.addEventListener('click', createOnClickHandler(ob, this, el), false);
}
Update 2 - general example of a one-time event handler that binds your event handler to a particular scope, calls that handler, and unregisters the listener.
function createOneTimeHandler (evtName, fn, scope, el) {
var bound = fn.bind(scope);
var onEvent = function (evt) {
el.removeEventListener(evtName, onEvent, false);
bound(evt);
};
el.addEventListener(evtName, onEvent, false);
}

Categories