How can I do that?
It seems that you can have multiple jQuery's ready() functions, and they will all run when the DOM is loaded.
So how can I create my own ready()-like function? :)
function _addEvent(e, evt, handler){
if(evt == "ready")
evt = "DOMContentLoaded";
if(typeof handler !== 'function')return;
if (e.addEventListener)
e.addEventListener(evt, handler, false);
else if (e.attachEvent)
e.attachEvent("on" + evt, handler);
else
{
var oldHandler = e["on" + evt];
function newHandler(event){
handler.call(e, event);
if(typeof oldhandler === 'function')oldhandler.call(e, event);
}
}
}
var _events = ["ready", "click", "mousedown"]; //...
var _myLib = function(item){
function eventWorker(item, event){
this.add = function(handler){
_addEvent(item, event, handler);
};
}
for(var i=0;i<_events.length;i++)
this[_events[i]] = (new eventWorker(item, _events[i])).add;
};
var MyLib = function(item){
return new _myLib(item);
};
MyLib(document).ready(function(){alert("I am ready!");});
Test =>
http://jsfiddle.net/vgraN/
First, you need to identify what it is you need the function for - is it to respond to a particular browser event?
jQuery's $(document).ready(fn) uses an array internally to hold the functions to execute when the DOM has loaded. Adding a new ready(fn) call appends the function fn to the array. When the DOM has loaded (which is detected in various ways according to which browser the code is executing within), each function in turn in the array is executed. Any functions added using ready(fn) after the DOM has loaded are executed immediately.
In summary, you can use an array to store the functions to execute whenever it is that you need to execute them.
Take a look at domready, a standalone port of the ready(fn) function from jQuery to get some ideas about how to go about it.
It sounds like you want to make an array of functions and append new callbacks to it.
It's not easy to do cross browser.
If you assume the DOMContentLoaded event exists then you can just make
var domready = (function () {
var cbs = [];
document.addEventListener("DOMContentLoaded", function () {
cbs.forEach(function (f) {
f();
});
});
return function (cb) {
cbs.push(cb);
};
})();
You can use other fallbacks like window.onload and a hackish scroll check like jQuery does.
I'd recommend either using domready or reading the source.
Do you want to create a function which when passed a function will call that function at a particular time? (Also, it can be called multiple times.) If so this is how I would do it it. (Based on jQuery code.)
var funcQueue = (function () {
var funcList = [];
function runAll() {
var len = funcList.length,
index = 0;
for (; index < len; index++)
funcList[index].call(); // you can pass in a "this" parameter here.
}
function add(inFunc) {
funcList.push(inFunc);
}
})();
To use:
funcQueue.add(function () { alert("one"); });
funcQueue.add(function () { alert("two"); });
funcQueue.runAll(); // should alert twice.
Related
Here is my JS simple script:
var Chat = function() {
console.log("init");
this.debug = function (txt) {
console.log(txt);
}
document.getElementById('shoutBoxInput').addEventListener("keypress", keyPressedFunction, false);
this.keyPressedFunction = function(e){
console.log("keyPressed");
}
this.sendText = function() {
var texte = document.getElementById('shoutBoxInput').value;
if (texte=="") return;
document.getElementById('shoutBoxInput').value =""
this.debug("sendTexte:"+texte);
}
this.receiveText = function(username, texte) {
}
}
var chat = new Chat();
My problem comes from:
document.getElementById('shoutBoxInput').addEventListener("keypress", keyPressedFunction, false);
this.keyPressedFunction = function(e){
Error Uncaught ReferenceError: keyPressedFunction is not defined
If I use:
document.getElementById('shoutBoxInput').addEventListener("keydown", this.keyPressedFunction, true);
then keyPressedFunction is never called.
Fiddle: http://jsfiddle.net/ghLfhb6z/
Let's start with the problem, and then move to what's dangerous about your code.
The problem is that when you call addEventListener, this.keyPressedEvent doesn't yet exist:
// this.keyPressedFunction doesn't exist...so you are registering a 'keypress'
// event to undefined.
document.getElementById('shoutBoxInput').addEventListener("keypress",
keyPressedFunction, false);
// now you define this.keyPressedFunction
this.keyPressedFunction = function(e){
console.log("keyPressed");
}
// so this is where you should be attaching it to the event
You may be thinking about JavaScript's hosting mechanism, and thinking "ah, the this.keyPressedFunction definition is being hoisted to the top of this function, so it's available for assigment." But hoisting only applies to variable and function definitions; what you're doing is assigning an anonymous function to a member property, so hoisting does not apply.
Now on to the dangerous:
When you use a method (a function property of an object) for a callback, the meaning of this is lost when that callback is invoked. (I know you aren't currently using this in your callback, but you probably will eventually!) In other words, when a key is pressed, and keyPressedFunction is called, the value of this won't be what you expect. The upshot of this is you have to be very careful assigning methods to callbacks or events. If you want to do it, you'll have to use Function.prototype.bind. Here's your code re-written in the correct order, and using bind:
this.keyPressedFunction = function(e){
console.log("keyPressed");
}
document.getElementById('shoutBoxInput').addEventListener("keypress",
this.keyPressedFunction.bind(this), false);
place your function before you use its referenc...then use this.keyPressedFunction...then is 'keypress' a valid native js event ?
http://jsfiddle.net/ghLfhb6z/4/
yes there was the errors I told, in fact most important is to place your event handlers at the end, check the right event, and use this if the function is on this :
var Chat = function() {
console.log("init");
this.debug = function (txt) {
console.log(txt);
}
this.keyPressedFunction = function(e){
console.log("keyPressed");
}
this.sendText = function() {
var texte = document.getElementById('shoutBoxInput').value;
if (texte=="") return;
document.getElementById('shoutBoxInput').value =""
this.debug("sendTexte:"+texte);
}
this.receiveText = function(username, texte) {
}
// place this at the end
document.getElementById('shoutBoxInput').addEventListener("keydown", this.keyPressedFunction, false);
}
var chat = new Chat();
#dmidz has provided a correct answer that will solve your problem, but if your keyPressedFunction only needs to be referred to code inside your Chat() module, then you don't need to make them properties of this (Chat):
var Chat = function() {
console.log("init");
function debug(txt) {
console.log(txt);
}
function keyPressedFunction(e){
console.log("keyPressed");
}
this.sendText = function() {
var texte = document.getElementById('shoutBoxInput').value;
if (texte=="") return;
document.getElementById('shoutBoxInput').value ="";
debug("sendTexte:"+texte);
}
this.receiveText = function(username, texte) {
}
document.getElementById('shoutBoxInput')
.addEventListener("keypress", keyPressedFunction, false);
}
If you do this, then you don't necessarily have to declare your functions before you use them, but it would be good style to do so nonetheless.
The problem is simple. I have a massive javascript application. And there are lot of times in the app where I use code which looks something like this -
$('#treat').html(new_data);
....
....
$('#cool').html(some_html_data);
....
....
$('#not_cool').html(ajax_data);
So what I want to do is, everytime this html() function is called I want to execute a set of functions.
function do_these_things_every_time_data_is_loaded_into_a_div()
{
$('select').customSelect();
$('input').changeStyle();
etc.
}
How do I do this? Thank you.
You can use the custom event handlers for that:
$('#treat').html(new_data);
// Trigger the custom event after html change
$('#treat').trigger('custom');
// Custom event handler
$('#treat').on('custom', function( event) {
// do_these_things_every_time_data_is_loaded_into_a_div
alert('Html had changed!');
});
UPDATE
Based on answer over here and with some modifications you can do this:
// create a reference to the old `.html()` function
$.fn.htmlOriginal = $.fn.html;
// redefine the `.html()` function to accept a callback
$.fn.html = function (html, callback) {
// run the old `.html()` function with the first parameter
this.htmlOriginal(html);
// run the callback (if it is defined)
if (typeof callback == "function") {
callback();
}
}
$("#treat").html(new_data, function () {
do_these_things_every_time_data_is_loaded_into_a_div();
});
$("#cool").html(new_data, function () {
do_these_things_every_time_data_is_loaded_into_a_div();
});
Easily maintainable and less code as per your requirements.
You can overwrite the jQuery.fn.html() method, as described in Override jQuery functions
For example, use this:
var oHtml = jQuery.fn.html;
jQuery.fn.html = function(value) {
if(typeof value !== "undefined")
{
jQuery('select').customSelect();
jQuery('input').changeStyle();
}
// Now go back to jQuery's original html()
return oHtml.apply(this, value);
};
When html() is called it usually make the DOM object changes, so you can look for DOM change event handler, it is called whenever your HTML of main page change. I found
Is there a JavaScript/jQuery DOM change listener?
if this help your cause.
You can replace the html function with your own function and then call the function html:
$.fn.html = (function(oldHtml) {
var _oldHtml = oldHtml;
return function(param) {
// your code
alert(param);
return _oldHtml.apply(this, [param]);
};
})($.fn.html);
I have a little script for you. Insert that into your javascript:
//#Author Karl-André Gagnon
$.hook = function(){
$.each(arguments, function(){
var fn = this
if(!$.fn['hooked'+fn]){
$.fn['hooked'+fn] = $.fn[fn];
$.fn[fn] = function(){
var r = $.fn['hooked'+fn].apply(this, arguments);
$(this).trigger(fn, arguments);
return r
}
}
})
}
This allow you to "hook" jQuery function and trigger an event when you call it.
Here how you use it, you first bind the function you want to trigger. In your case, it will be .html():
$.hook('html');
Then you add an event listener with .on. It there is no dynamicly added element, you can use direct binding, else, delegated evets work :
$(document).on('html', '#threat, #cool, #not_cool',function(){
alert('B');
})
The function will launch everytime #threat, #cool or #not_cool are calling .html.
The $.hook plugin is not fully texted, some bug may be here but for your HTML, it work.
Example : http://jsfiddle.net/5svVQ/
Here jsFiddle to test sample
I'm currently writing a jQuery snippet to handle any html content change in DOM 'triggered' by any jQuery domManip function (extending some functions). Not sure it's the best way to do it, so any advice will be welcome.
This snippet works as expected if bound to document. However, if I try to bind it to a specific element, I'm facing problem which some function as .remove(). Maybe it's due to custom event not using normal propagation behaviour but I'm really not sure.
This is a working sample, I bind contentChange event to document, works cross-browser as I can test it: {Firefox, IE9, Chrome and Safari under Win7}
;
(function ($) {
$.fn.contentChange = function (types, data, fn) {
return this.on('contentChange', types, null, data, fn);
};
var oDomManip = $.fn.domManip,
oHtml = $.fn.html,
oEmpty = $.fn.empty,
oRemove = $.fn.remove,
extendFct = function (oFct, sender, args) {
return oFct.apply(sender, args), $.event.trigger('contentChange');
//=>if testing specific element (#test) use instead following line
//return oFct.apply(sender, args), $(sender).trigger('contentChange');
};
$.fn.domManip = function () {
extendFct(oDomManip, this, arguments)
};
$.fn.html = function () {
extendFct(oHtml, this, arguments)
};
$.fn.empty = function () {
extendFct(oEmpty, this, arguments)
};
$.fn.remove = function () {
extendFct(oRemove, this, arguments)
};
})(jQuery);
I use: $.event.trigger('contentChange') to trigger custom event.
Called like it:
$(document).contentChange(function () {
console.log("onContentChange")
});
However, if I use:
$('#test').contentChange(function () {
console.log("onContentChange")
});
The custom event is not triggered.
So, to trigger a custom event on a specific element, I can triggered it like this:
$(sender).trigger('contentChange');
But now, call to remove() method on self or children doesn't triggered my custom event.
I can understand that event callback function won't be called if I remove the element, but why isn't it called when removing children (while it's working if bound to document!)?
I was expecting this line to make custom event bubbles to '#test':
$('#test').find('div:first').remove();
Is there any way to triggered this custom event bound to a specific element when manipulating this element and/or its children?
You need to trigger the event on the element that was modified.
http://jsfiddle.net/Gw4Lj/2/
return oFct.apply(sender, args), sender.trigger('contentChange');
however, with that change, you will no longer catch the event that was triggered on an element that isn't connected to the DOM because it isn't a descendant of that document, which is ok in my opinion because it isn't associated to that DOM, it's in a DOM Fragment.
I come with slightly modified version wich seems to work fine for the purpose i reach.
Need optimization for the .on() method extend, so please feel free to share your feedbacks.
Inspired from here: https://groups.google.com/forum/?fromgroups=#!topic/jquery-dev/ZaMw2XB6wyM
Thanks to Wil Stuckey
Here jsFiddle
;(function ($) {
var fctsToObserve = {
append: [$.fn.append, 'self'],
prepend: [$.fn.prepend, 'self'],
remove: [$.fn.remove, 'parent'],
before: [$.fn.before, 'parent'],
after: [$.fn.after, 'parent']
}, fctsObserveKeys = '';
$.each(fctsToObserve, function (key, element) {
fctsObserveKeys += "hasChanged." + key + " ";
});
var oOn = $.fn.on;
$.fn.on = function () {
if (arguments[0].indexOf('hasChanged') != -1) arguments[0] += " " + fctsObserveKeys;
return oOn.apply(this, arguments);
};
$.fn.hasChanged = function (types, data, fn) {
return this.on(fctsObserveKeys, types, null, data, fn);
};
$.extend($, {
observeMethods: function (namespace) {
var namespace = namespace ? "." + namespace : "";
var _len = $.fn.length;
delete $.fn.length;
$.each(fctsToObserve, function (key) {
var _pre = this;
$.fn[key] = function () {
var target = _pre[1] === 'self' ? this : this.parent(),
ret = _pre[0].apply(this, arguments);
target.trigger("hasChanged." + key + namespace, arguments);
return ret;
};
});
$.fn.length = _len;
}
});
$.observeMethods()
})(jQuery);
I know that having the value of this being changed to the element receiving the event in event handling functions is pretty useful. However, I'd like to make my functions always be called in my application context, and not in an element context. This way, I can use them as event handlers and in other ways such as in setTimeout calls.
So, code like this:
window.app = (function () {
var that = {
millerTime: function () {},
changeEl: function (el) {
el = el || this;
// rest of code...
that.millerTime();
}
};
return that;
}());
could just be like this:
window.app = (function () {
return {
millerTime: function () {},
changeEl: function (el) {
// rest of code...
this.millerTime();
}
};
}());
The first way just looks confusing to me. Is there a good easy way to pass the element receiving the event as the first argument (preferably a jQuery-wrapped element) to my event handling function and call within the context of app? Let's say I bind a bunch of event handlers using jQuery. I don't want to have to include anonymous functions all the time:
$('body').on('click', function (event) {
app.changeEl.call(app, $(this), event); // would be nice to get event too
});
I need a single function that will take care of this all for me. At this point I feel like there's no getting around passing an anonymous function, but I just want to see if someone might have a solution.
My attempt at it:
function overrideContext (event, fn) {
if (!(this instanceof HTMLElement) ||
typeof event === 'undefined'
) {
return overrideContext;
}
// at this point we know jQuery called this function // ??
var el = $(this);
fn.call(app, el, event);
}
$('body').on('click', overrideContext(undefined, app.changeEl));
Using Function.prototype.bind (which I am new to), I still can't get the element:
window.app = (function () {
return {
millerTime: function () {},
changeEl: function (el) {
// rest of code...
console.log(this); // app
this.millerTime();
}
};
}());
function overrideContext (evt, fn) {
var el = $(this); // $(Window)
console.log(arguments); // [undefined, app.changeEl, p.Event]
fn.call(app, el, event);
}
$('body').on('click', overrideContext.bind(null, undefined, app.changeEl));
Using $('body').on('click', overrideContext.bind(app.changeEl)); instead, this points to my app.changeEl function and my arguments length is 1 and contains only p.Event. I still can't get the element in either instance.
Defining a function like this should give you what you want:
function wrap(func) {
// Return the function which is passed to `on()`, which does the hard work.
return function () {
// This gets called when the event is fired. Call the handler
// specified, with it's context set to `window.app`, and pass
// the jQuery element (`$(this)`) as it's first parameter.
func.call(window.app, $(this) /*, other parameters (e?)*/);
}
}
You'd then use it like so;
$('body').on('click', wrap(app.changeEl));
For more info, see Function.call()
Additionally, I'd like to recommend against this approach. Well versed JavaScript programmers expect the context to change in timeouts and event handlers. Taking this fundamental away from them is like me dropping you in the Sahara with no compass.
How can you decorate a DOM node so that you add an event handler, but within the new handler you can call the previous handler?
I assume that you are binding events in the way element.onclick = function () {};.
Yes, you can make a function that wraps previous event handlers and execute them sequentially, e.g.:
function addEvent(el, event, handler) {
var oldEvent = el['on'+event];
if (typeof oldEvent != 'function') {
el['on'+event] = handler;
} else {
el['on'+event] = function() {
oldEvent();
handler();
}
}
}
var el = document.getElementById('el');
addEvent(el, 'click', function () { alert('1'); });
addEvent(el, 'click', function () { alert('2'); });
Check the above example here.
It depends on how you're adding your handlers, of course, but here's one old-fashioned way:
function addClickHandler(nodeId, handler) {
var node = document.getElementById(nodeId), old = node.onclick;
node.onclick = function() {
handler(old);
}
}
Your handler function would check it's parameter to see if it's not null.
You could of course get as fancy as you wanted to here. Note that most Javascript frameworks actually don't give your event handlers much information about other handlers. Generally it's a fragile pattern to work with that sort of relationship, but I suppose if you set out with a design that regularizes tthe handler setup, it could work fine.