I'm creating a small tooltip application and I'm having trouble. I'm trying to add an event to the document, but am having trouble referencing the function that needs to be executed. Here is the code:
var Note, note;
(function () {
'use strict';
// Helper functions
function addEvent(to, type, fn) {
if (to.addEventListener) {
to.addEventListener(type, fn, false);
} else if (to.attachEvent) {
to.attachEvent('on' + type, fn);
} else {
to['on' + type] = fn;
}
}
// Temporary constructor
function Temp() {
this.dragging = false;
return this;
}
Temp.prototype = {
listen: function () {
this.dragging = true;
},
drag: function () {
alert('hi 1');
if (!this.dragging) return;
alert('hi 2');
},
create: function () {
// unimportant code ...
addEvent(document, 'mousedown', this.drag);
// unimportant code ...
}
};
window.Note = Temp;
}());
note = new Note();
note.create(); // the note is created as planned
note.listen(); // alert(note.dragging) yields true
If there are small mistakes in the code I don't think those are the problem, the code on my system passes JSLint (I know that doesn't guarantee correctness). Neither of the alerts alert their arguments; I suspect, though, that the problem is assigning 'this.drag' as the function reference to the event handler. Are there any workarounds for this?
Thank you all for your time!
Try next:
(function () {
'use strict';
// Helper functions
function addEvent(to, type, fn) {
if (to.addEventListener) to.addEventListener(type, fn, false);
else if (to.attachEvent) to.attachEvent('on' + type, fn);
else to['on' + type] = fn; // this is bad. this do not support multiple handlers
}
// Temporary constructor
function Temp() {
this.dragging = false;
}
Temp.prototype = {
constructor: Temp, // needed because it is removed when used Temp.prototype={...}
listen: function () {
this.dragging = true;
},
drag: function () {
alert('hi 1');
if (!this.dragging) return;
alert('hi 2');
},
create: function () {
//addEvent(document, 'mousedown', this.drag);
addEvent(document, 'mousedown', this.drag.bind(this));
// or in older maner (when .bind() not implemented):
//var self=this;
//addEvent(document, 'mousedown', function(){self.drag();});
}
};
window.Note = Temp;
})();
var note = new Note();
note.create(); // the note is created as planned
note.listen(); // alert(note.dragging) yields true
Related
I have the following JavaScript code:
var MyGlobalRef = (function () {
function init(obj1, obj2) {
prepareEvents(obj1);
prepareEvents(obj2);
function prepareEvents(obj) {
var handleMouseUp = function (evt) {
// do work with obj
};
obj.addEventListener('mouseup', handleMouseUp);
}
}
return {
init: init
}
})();
In my main page, I'm frequently calling the init function like this:
function moveNext(){
MyGlobalRef.init(getNewObj1(), getNewObj2());
}
My problem is that on that moveNext() function, I tend to replace the existing objects, meaning that there are only 2 objects in the page at all times. However, the more I call moveNext the more event listeners get generated. So by the time I moveNext 3 times, the mouse up event fires 3 times per one mouse up. The obvious solution is to call addEventListener only the first time. However, the problem with this is that obj (that is used in the mouse up event) does not update and still references the original obj. Also, I failed to use removeEventListener because I don't have a reference to the obj once it's initiated.
To expand on my comment. Here is what I envision. You return a destroy function as a result of init execution
Solution #1
var MyGlobalRef = (function () {
function init(obj1, obj2) {
prepareEvents(obj1);
prepareEvents(obj2);
function prepareEvents(obj) {
var handleMouseUp = function (evt) {
// do work with obj
};
obj.addEventListener('mouseup', handleMouseUp);
}
return function() {
obj1.removeEventListener('mouseup');
obj2.removeEventListener('mouseup');
};
}
return {
init: init
}
})();
// cleanup is a variable stored in your code to run a cleanup on obj1 and obj2 later on
function moveNext(){
if (cleanup) {
cleanup();
}
cleanup = MyGlobalRef.init(GetNewObj1A(), GetNewObj1A());
}
Solution #2
Keep reference to obj1 and obj2 in closure.
var MyGlobalRef = (function () {
var _obj1;
var _obj2;
function init(obj1, obj2) {
prepareEvents(obj1);
prepareEvents(obj2);
function prepareEvents(obj) {
var handleMouseUp = function (evt) {
// do work with obj
};
obj.addEventListener('mouseup', handleMouseUp);
}
_obj1 = obj1;
_obj2 = obj2;
}
function cleanup() {
if (_obj1) {
_obj1.removeEventListener('mouseup');
}
if (_obj2) {
_obj2.removeEventListener('mouseup');
}
}
return {
init: init,
cleanup: cleanup
}
})();
function moveNext(){
MyGlobalRef.cleanup();
MyGlobalRef.init(GetNewObj1A(), GetNewObj1A());
}
You call prepareEvents before the function, put function prepareEvents(obj) before your init function. Try it.
I have the following module
var m=(function() {
// setup variables
return {
center: function() {
// do stuff
},
open: function(settings) {
// do open stuff
// setups the handler for the close...
$close.on("click",function(e) {
e.preventDefault();
m.close();
});
},
close: function() {
// do close stuff
// ** need to add code here
//to perform a callback function on close
// EDIT
if(this.hasOwnProperty("callback"))
callback();
},
//** EDITED
addCallback: function(func) {
this.callback=func;
}
};
}());
// open
m.open();
The close in the triggered automatically by a click event. I want to somehow insert a callback into the close() to be performed at the time of closing... I thought about adding a function like
addCallback(callback) {
this.callback=callback;
}
but i'm not sure how to call that within the close().
** EDIT **
Thanks to user3297291 for the below response and Barmar you were right I do actually only need one callback not an array. I have edited the above code by adding a addCallback(). Then the code to run would be:
m.open()
function check() {
alert("hello");
}
m.addCallback(check);
But this doesn't work I'm trying to understand why and I new to javascript OO..
You'll have to keep track of an array of callbacks (or just one). The simplest implementation could be something like this:
var m = (function() {
var onCloseCallbacks = [];
return {
addOnCloseCallback: function(cb) {
onCloseCallbacks.push(cb);
},
close: function() {
console.log("Closing");
onCloseCallbacks.forEach(function(cb) {
cb();
});
}
};
}());
m.addOnCloseCallback(function() {
console.log("After close");
});
m.close();
Note that the array of callbacks is defined inside the closure.
For a more advanced solution, you'd want to be able to dispose the callback from outside of the m module. Here's an example of how this could be added:
var m = (function() {
var onCloseCallbacks = [];
return {
addOnCloseCallback: function(cb) {
onCloseCallbacks.push(cb);
return {
dispose: function() {
var i = onCloseCallbacks.indexOf(cb);
onCloseCallbacks = onCloseCallbacks
.slice(0, i)
.concat(onCloseCallbacks.slice(i + 1));
}
};
},
close: function() {
console.log("Closing");
onCloseCallbacks.forEach(function(cb) {
cb();
});
}
};
}());
var myCallback = m.addOnCloseCallback(function() {
console.log("After close");
});
m.close(); // Does trigger cb
myCallback.dispose();
m.close(); // Doesn't trigger cb
I created the following plugin that mostly works. It takes xeditable, but allows for jQueryUI's autocomplete to select the value. It mostly works, however, I am struggling on how to pass the returned id from jQueryUI Autocomplete to the success callback.
How do I pass a value from the jQueryUI autocomplete select callback to the xeditable success callback?
EDIT. I got it working, but think it is a bit of a kludge. What is the proper way to do so?
EDIT #2. See https://jsfiddle.net/fndnu5m0/5/ for a demo.
$('#targetID').xeditableAutoSource({
source: 'getSource.php',
success: function(response, newValue) {
console.log($(this).data('uid')); //This is the value I want!
}
});
(function($){
var defaults = {
source: [], //Replace with URL
placement: 'right',
title: 'XEditable Title',
success: function(response, newValue) {} //id will be $(this).data('uid')
};
var methods = {
init : function (options) {
var settings = $.extend({},defaults, options || {});
this.each(function () {
var $this=$(this).editable({
//send:'never',
placement:settings.placement,
title:settings.title,
success: settings.success
})
.on('shown', function(e, editable) {
var $input=editable.input.$input.val('');
var $button=$input.parent().next().find('button.editable-submit').css('opacity', 0.3)
.bind('click.prevent', function() {return false;});
$input.focus(function() {
$button.css('opacity', 0.3).bind('click.prevent', function() {return false;});
})
.autocomplete({
source: settings.source,
select: function(e, ui) {
$input.blur();
$button.css('opacity', 1).unbind('click.prevent');
$this.data('uid',ui.item.id); //This is the value I need in the success callback!
}
})
.autocomplete('widget').click(function() {return false;});
});
})
},
destroy : function () {
return this.each(function () {
return this.each(function () {});
});
}
};
$.fn.xeditableAutoSource = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.xeditableAutoSource');
}
};
}(jQuery));
Too much code... As far as I understood you want to pass to a function not just a callback but also its parameter. You can try the following technique:
var f1 = function(callback) { //we will pass a callback to this function
callback()
}
var f2= function(param1) { //that will be our callback
console.log(param1)
}
var p = 'Callback will always be called with this parameter'
f1( function() { f2(p) } ) //here we pass f2 with p as a callback to f1
Callback and it's parameter are wrapped into anonymous function. 'f1' and 'f2' are the functions from you libraries, you don't have to modify them, they are needed to illustrate the technique.
With some minor changes you can make it work if a function that accepts a callback passes some other parameters to it:
var f1 = function(callback) {
var p1 = 'Parameter set by f1'
callback(p1)
}
var f2= function(param1, param2) {
console.log(param1 +' '+ param2)
}
var p = 'Constant parameter'
f1( function(p1) { f2(p1, p) } )
If this is the equivalent to the jquery bind method, what would it be for the trigger method.
function bind( scope, fn ) {
return function () {
fn.apply( scope, arguments );
};
}
the code above is from another post and it looks the same as a proxy method
you can comment on this too
I have to take the jquery part out off this framework, - this is just the relevant part
if (selector === '') {
this.el.bind(eventName, method);
} else {
this.el.delegate(selector, eventName, method);
}
}
}
});
if (includes) result.include(includes);
return result;
};
exports.Controller = mod;
})($, window);
var exports = this;
var Events = {
bind: function(){
if ( !this.o ) this.o = $({});
this.o.bind.apply(this.o, arguments);
},
trigger: function(){
if ( !this.o ) this.o = $({});
this.o.trigger.apply(this.o, arguments);
}
};
thanks
It depends on the type of event you wish to trigger. If it's a custom event:
var event = new Event('build');
elem.dispatchEvent(event);
If it's a native event:
var event = new MouseEvent('click');
elem.dispatchEvent(event);
This is of course meant to simulate a mouse event. Other events have their own type.
Once I crossed this site How to Manually Trigger Events in JavaScript
// Here is a VERY basic generic trigger method
function triggerEvent(el, type)
{
if ((el[type] || false) && typeof el[type] == 'function')
{
el[type](el);
}
}
// We could call this on multiple objects at any time
function resetFields()
{
triggerEvent(document.getElementById('has-email'), 'onchange');
triggerEvent(document.getElementById('other-field'), 'onclick');
triggerEvent(document.getEleemntById('another-one'), 'onblur');
}
I need to launch custom events from CLASS. I know to do this with DOM objects and jquery, using triggerHandler, like $(object)..triggerHandler("inputChange", {param:X});
The problem is when i try this with a Class, like this:
var MyClass = (function(){
var static_var = 1;
var MyClass = function () {
var privateVar;
var privateFn = function(){ alert('Im private!'); };
this.someProperty = 5;
this.someFunction = function () {
alert('Im public!');
};
this.say = function() {
alert('Num ' + this.someProperty);
$(this).triggerHandler("eventCustom");
}
this.alter = function() {
this.someProperty ++;
}
};
return MyClass;
})();
TheClass = new MyClass();
$(TheClass).on('eventCustom', function() {
alert('Event!');
});
TheClass.say();
This doesn't launch warnings or errors, but the events listener is not working (or event is not dispatched). I think the jQuery event system doesn't work with not DOM object, correct?
Any other way (I need events, not callbacks for my specific case) to launch the events?
Thanks a lot!
I wrote an ES6 event class for nowadays in under 100 lines of code without using JQuery. If you don't want to use DOM-events you can extend your class, which should deal with Events.
For listening to events, you can use on, once, onReady, onceReady. On is execute the callbackfunction every time the label is trigger. Once only one time. The "ready"-functions execute the callback, if the label had been already triggerd before.
For triggering an event, use a trigger. To remove an eventhandler, use off.
I hope the example makes it clear:
class ClassEventsES6 {
constructor() {
this.listeners = new Map();
this.onceListeners = new Map();
this.triggerdLabels = new Map();
}
// help-function for onReady and onceReady
// the callbackfunction will execute,
// if the label has already been triggerd with the last called parameters
_fCheckPast(label, callback) {
if (this.triggerdLabels.has(label)) {
callback(this.triggerdLabels.get(label));
return true;
} else {
return false;
}
}
// execute the callback everytime the label is trigger
on(label, callback, checkPast = false) {
this.listeners.has(label) || this.listeners.set(label, []);
this.listeners.get(label).push(callback);
if (checkPast)
this._fCheckPast(label, callback);
}
// execute the callback everytime the label is trigger
// check if the label had been already called
// and if so excute the callback immediately
onReady(label, callback) {
this.on(label, callback, true);
}
// execute the callback onetime the label is trigger
once(label, callback, checkPast = false) {
this.onceListeners.has(label) || this.onceListeners.set(label, []);
if (!(checkPast && this._fCheckPast(label, callback))) {
// label wurde nocht nicht aufgerufen und
// der callback in _fCheckPast nicht ausgeführt
this.onceListeners.get(label).push(callback);
}
}
// execute the callback onetime the label is trigger
// or execute the callback if the label had been called already
onceReady(label, callback) {
this.once(label, callback, true);
}
// remove the callback for a label
off(label, callback = true) {
if (callback === true) {
// remove listeners for all callbackfunctions
this.listeners.delete(label);
this.onceListeners.delete(label);
} else {
// remove listeners only with match callbackfunctions
let _off = (inListener) => {
let listeners = inListener.get(label);
if (listeners) {
inListener.set(label, listeners.filter((value) => !(value === callback)));
}
};
_off(this.listeners);
_off(this.onceListeners);
}
}
// trigger the event with the label
trigger(label, ...args) {
let res = false;
this.triggerdLabels.set(label, ...args); // save all triggerd labels for onready and onceready
let _trigger = (inListener, label, ...args) => {
let listeners = inListener.get(label);
if (listeners && listeners.length) {
listeners.forEach((listener) => {
listener(...args);
});
res = true;
}
};
_trigger(this.onceListeners, label, ...args);
_trigger(this.listeners, label, ...args);
this.onceListeners.delete(label); // callback for once executed, so delete it.
return res;
}
}
// +++ here starts the example +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class TestClassEvents extends ClassEventsES6 {
constructor() {
super();
this.once('sayHallo', this.fStartToTalk);
this.on('sayHallo', this.fSayHallo);
}
fStartToTalk() {
console.log('I start to talk... ');
}
fSayHallo(name = 'Nobody') {
console.log('Hallo ' + name);
}
}
let testClassEvents = new TestClassEvents();
testClassEvents.trigger('sayHallo', 'Tony');
testClassEvents.trigger('sayHallo', 'Tim');
testClassEvents.onReady('sayHallo', e => console.log('I already said hello to ' + e));
testClassEvents.trigger('sayHallo', 'Angie');
testClassEvents.off('sayHallo');
testClassEvents.trigger('sayHallo', 'Peter');
console.log('I dont say hallo to Peter, because the event is off!')
Your understanding of how javascript works is limited since you are approaching it from a traditional OOP point of view. Take a look at this fiddle http://jsfiddle.net/9pCmh/ & you will see that you can actually pass functions as variables to other functions. There are no classes in javascript, only functions which can be closures which can be made to emulate traditional classes:
var MyClass = (function(){
var static_var = 1;
var MyClass = function ( callback ) {
var privateVar;
var privateFn = function(){ alert('Im private!'); };
this.someProperty = 5;
this.someFunction = function () {
alert('Im public!');
};
this.say = function() {
alert('Num ' + this.someProperty);
callback();
}
this.alter = function() {
this.someProperty ++;
}
};
return MyClass;
})();
TheClass = new MyClass(function() {
alert('Event!');
});
TheClass.say();
Alternatively you could create a function in your "class" to configure the callback/trigger instead of passing it into the constructor.
Have a look at this as a start for your further reading on this concept... How do JavaScript closures work?
Edit
To appease those critics looking for an eventQueue here is an updated jsfiddle :)
http://jsfiddle.net/Qxtnd/9/
var events = new function() {
var _triggers = {};
this.on = function(event,callback) {
if(!_triggers[event])
_triggers[event] = [];
_triggers[event].push( callback );
}
this.triggerHandler = function(event,params) {
if( _triggers[event] ) {
for( i in _triggers[event] )
_triggers[event][i](params);
}
}
};
var MyClass = (function(){
var MyClass = function () {
this.say = function() {
alert('Num ' + this.someProperty);
events.triggerHandler('eventCustom');
}
};
return MyClass;
})();
TheClass = new MyClass();
events.on('eventCustom', function() {
alert('Event!');
});
events.on('eventCustom', function() {
alert('Another Event!');
});
TheClass.say();