I have built a dom object Engine that has private/public fields/methods that I have simplified below:
function Engine(args){
this.display = args.display;
this.getDisplay = function(){return this.display;}
this.alertMsg = function(msg){
console.log(this.display);
alert(msg);
}
}
What I would like to do is build a custom event that would be triggered after the alert(msg) such as $(this.display).trigger("afterAlert");
function Engine(args){
this.display = args.display;
this.getDisplay = function(){return this.display;}
this.alertMsg = function(msg){
console.log(this.display);
alert(msg);
// trigger custom event here
$(this.display).trigger("afterAlert");
}
}
Now, this event could be empty or not. How would one or more objects declared later register to the "afterAlert" event? In my case, additional javascript files are loaded by the main file dynamically and could contain a code ressembling :
function new_obj(){
bind("afterAlert", function(){
alert("alert was called");
});
}
See my answer from this question...repeated for clarity
I will tackle the register, triggering and unbinding of custom events.
jQuery has all the tools you need to register, bind and unbind to custom events.
Below is an example of hooking up two divs to a custom event called customAjaxStart. I can then trigger this function and both handlers will get called.
Quick Demo Here - Have the firebug/ie8 console enabled.
e.g
$( function() {
$('#div1').bind('customAjaxStart', function(){
console.log('#div1 ajax start fired');
$(this).fadeTo('slow', 0.3);
});
$('#div2').bind('customAjaxStart', function(){
console.log('#div1 ajax start fired');
$(this).fadeTo('slow', 0.3);
});
//fire the custom event
$.event.trigger('customAjaxStart');
//unbind div1 from custom event
$('#div1').unbind('customAjaxStart');
//again trigger custom event - div1 handler will not fire this time
$.event.trigger('customAjaxStart');
});
Taking the above as an example I would trigger the customAjaxStart from the global ajaxStart. Any listeners would be triggered automatically whenever an xhr call is about to be made (ideal for disabling your widgets or showing a loading gif etc) e.g
$.ajaxStart( function(){
$.event.trigger('customAjaxStart');
});
I think what you are looking for is the Observer pattern. At least that's how I would implement it. The following code snippet uses different names but it does essentially what you want (allows registering for events, and even triggering):
Observable = {
addObserver: function(observer) {
if (!this.__observers) this.__observers = [];
this.__observers.push(observer);
},
addGlobalObserver: function(observer) {
if (!this.__global_observers) this.__global_observers = [];
this.__global_observers.push(observer);
},
removeObserver: function(observer) {
var newObservers = [];
var co;
while (co = this.__observers.pop()) {
if (co != observer) newObservers.push(co)
}
this.__observers = newObservers;
newObservers = [];
while (co = this.__global_observers.pop()) {
if (co != observer) newObservers.push(co)
}
this.__global_observers = newObservers;
},
notify: function(event) {
var allObservers = this.__global_observers.concat(this.__observers);
for (var i=0; i < allObservers.length; i++) {
var o = allObservers[i];
if (o[event]) {
var args = []
for (var j=1; j < arguments.length; j++) {
args.push(arguments[j])
};
o[event].apply(this, args);
}
};
},
__global_observers: [],
__initializer: function() {
this.__observers = [];
}
};
If you include this code into your class, you can register for events using addObserver() (addGlobalObserver() for "class level" events). Inside the object you trigger an event using notify().
Code taken from Coltrane.
Related
I'm using the Microsoft Translation Widget, which I'd like to use to automatically translate a webpage without user interaction.
The problem is, I can't get rid of the widget that keeps popping up or hide it on document.ready because the CSS and JS get loaded from Microsoft's own script in the widget!
Does anyone know a way around this? I've looked everywhere and cannot find a solutuion for this.
Whoa, after some time playing around with that, I've finally achieved what you want.
It's kindda ugly, because of some needed workarounds, but it works, take a look at the fiddle.
The steps were:
Firstly, we must override the default addEventListener behavior:
var addEvent = EventTarget.prototype.addEventListener;
var events = [];
EventTarget.prototype.addEventListener = function(type, listener) {
addEvent.apply(this, [].slice.call(arguments));
events.push({
element: this,
type: type,
listener: listener
});
}
Then, we create a helper function removeEvents. It removes all the event listeners of an element.
var removeEvents = function(el, type) {
var elEvents = events.filter(function(ev) {
return ev.element === el && (type ? ev.type === type : true);
});
for (var i = 0; i < elEvents.length; i++) {
el.removeEventListener(elEvents[i].type, elEvents[i].listener);
}
}
When creating the script tag, in the way Microsoft says:
var s = d.createElement('script');
s.type = 'text/javascript';
s.charset = 'UTF-8';
s.src = ((location && location.href && location.href.indexOf('https') == 0) ? 'https://ssl.microsofttranslator.com' : 'http://www.microsofttranslator.com') + '/ajax/v3/WidgetV3.ashx?siteData=ueOIGRSKkd965FeEGM5JtQ**&ctf=True&ui=true&settings=Manual&from=';
var p = d.getElementsByTagName('head')[0] || d.dElement;
p.insertBefore(s, p.firstChild);
We must add a load event listener to that script, and the code below is fully commented:
s.addEventListener('load', function() {
// when someone changes the translation, the plugin calls the method TranslateArray
// then, we save the original method in a variable, and we override it
var translate = Microsoft.Translator.TranslateArray;
Microsoft.Translator.TranslateArray = function() {
// we call the original method
translate.apply(this, [].slice.call(arguments));
// since the translation is not immediately available
// and we don't have control when it will be
// I've created a helper function to wait for it
waitForTranslation(function() {
// as soon as it is available
// we get all the elements with an attribute lang
[].forEach.call(d.querySelectorAll('[lang]'), function(item, i) {
// and we remove all the mouseover event listeners of them
removeEvents(item, 'mouseover');
});
});
}
// this is the helper function which waits for the translation
function waitForTranslation(cb) {
// since we don't have control over the translation callback
// the workaround was to see if the Translating label is visible
// we keep calling the function, until it's hidden again
// and then we call our callback
var visible = d.getElementById('FloaterProgressBar').style.visibility;
if (visible === 'visible') {
setTimeout(function() {
waitForTranslation(cb);
}, 0);
return;
}
cb();
}
});
Update 1
After re-reading your question, it seems you want to hide all the widgets at all.
So, you must add the following code as soon as the translation is got:
waitForTranslation(function() {
document.getElementById('MicrosoftTranslatorWidget').style.display = 'none';
document.getElementById('WidgetLauncher').style.display = 'none';
document.getElementById('LauncherTranslatePhrase').style.display = 'none';
document.getElementById('TranslateSpan').style.display = 'none';
document.getElementById('LauncherLogo').style.display = 'none';
document.getElementById('WidgetFloaterPanels').style.display = 'none';
// rest of the code
});
I've created another fiddle for you, showing that new behavior.
Update 2
You can prevent the widget showing at all by adding the following CSS code:
#MicrosoftTranslatorWidget, #WidgetLauncher, #LauncherTranslatePhrase, #TranslateSpan, #LauncherLogo, #WidgetFloaterPanels {
opacity: 0!important;
}
And you can even prevent the before-translated text being showed, by hiding the document.body by default, and then showing it when the page is fully translated:
(function(w, d) {
document.body.style.display = 'none';
/* (...) */
s.addEventListener('load', function() {
var translate = Microsoft.Translator.TranslateArray;
Microsoft.Translator.TranslateArray = function() {
translate.apply(this, [].slice.call(arguments));
waitForTranslation(function() {
/* (...) */
document.body.style.display = 'block';
});
}
});
});
Take a look at the final fiddle I've created.
For me, this was the solution:
on your < style > section add this class
.LTRStyle { display: none !important }
Also, if you are invoking the translation widget this way:
Microsoft.Translator.Widget.Translate('en', lang, null, null, TranslationDone, null, 3000);
then add this to your callback (in this example is TranslationDone) function:
function TranslationDone() {
Microsoft.Translator.Widget.domTranslator.showHighlight = false;
Microsoft.Translator.Widget.domTranslator.showTooltips = false;
document.getElementById('WidgetFloaterPanels').style.display = 'none';
};
is there any way, how can I globally (in service) disable and enable all ng-click and ng-submit events?
For example when user is offline I want to disable all actions till he gets connection back..
I tried to bind all elements with an onClick event which will call stopImmediatePropagation but it didn't work..
$('*[ng-click]').click(function( event ) {
event.stopImmediatePropagation();
});
Also this question is a little bit different from this one:
Disable ng-click on certain conditions of application for all types of element
I'd like to disable/enable all events in APP globally from service, I'm not able to modify all ng-* calls on all elements in the APP..
Try including a return false too:
$('*[ng-click]').click(function( event ) {
event.stopImmediatePropagation();
return false;
});
Snippet
The below snippet demonstrates that multiple event handlers attached to a single <a> works too.
$(function () {
$("a").click(function () {
alert("Hello!");
return false;
});
$("a").click(function () {
alert("Bye!");
return false;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
Click Me
So finally I end up with temporarily disabling all events on the page using jquery..
I got inspired from this plugin http://ignitersworld.com/lab/eventPause.html which for some reason did not work (without any error)
So I took main parts and put it to this class which is working now using jquery v2.1.1:
var EventManager = function() {
var self = this;
var nullFun=function(){};
var getIndex = function(array,value){
for(var i=0; i< array.length; i++){
if(array[i]==value){
return i;
}
}
return -1;
};
this.pauseEvent = function(elm,eventAry){
var events = $._data(elm, "events");
if (events) {
$.each(events, function(type, definition) {
if((getIndex(eventAry,type)!=-1)||(eventAry=='')){
$.each(definition, function(index, event) {
if (event.handler.toString() != nullFun.toString()){
if(!$._iwEventPause) $._iwEventPause = {};
$._iwEventPause["iw-event" + event.guid] = event.handler;
event.handler = nullFun;
}
})
}
})
}
};
this.activeEvent = function(elm,eventAry){
var events = $._data(elm, "events");
if (events) {
$.each(events, function(type, definition) {
if((getIndex(eventAry,type)!=-1)||(eventAry=='')){
$.each(definition, function(index, event) {
if (event.handler.toString() == nullFun.toString()){
event.handler = $._iwEventPause["iw-event" + event.guid];
}
})
}
})
}
};
this.disableAll = function(el) {
el = el || $('*');
el.each(function() {
self.pauseEvent($(this)[0], '');
});
self.pauseEvent($(window)[0], '');
};
this.enableAll = function(el) {
el = el || $('*');
el.each(function() {
self.activeEvent($(this)[0], '');
});
self.activeEvent($(window)[0], '');
};
return this;
};
var eManager = new EventManager();
eManager.disableAll();
eManager.enableAll();
This will go through window object and all elements on the page, move their event handlers away to _iwEventPause object and replace handlers with dummy function.. When enabling, it will move handlers back so they get normally called..
This solution does not handle event handlers added after disabling..
I have a local page to help in HTML and JavaScript that helps me with some basic tasks at work. I've been going back over my code and rewriting it to use best practices, since it helps me learn, and recently I've been trying to study namespacing and put it to use by rewriting the common page functions and event listeners.
window.onload = (function() {
var automationPageWrapper = (function() {
var self = {}
self.evntListeners = {
btnTextChange: function() {
// Code that changes button text when clicked
},
btnColorChange: function(formID) {
// Code that iterates through buttons with a certain name
// and makes them all the same default color
}
}
self.listeners = {
btnListeners: function() {
// Add all event listeners having to do with buttons here
}
}
return self;
});
automationPageWrapper.listeners.btnListeners();
});
Why isn't this attaching the event listeners?
Is there a better way to be formatting/calling this?
Is this a professional method for setting up JavaScript code?
I tested the event listeners by taking the functions and posting them into the Chrome console, so I think they work.
The full text, since some people like reading through all of it:
// Global namespace for the Page Functions
window.addEventListener("onload", function() {
var automationPageWrapper = (function() {
var self = {};
// Namespace for event listeners
self.evtListeners = {
// Function to change the color of a selected button
btnColorChange: function(formName) {
var elementsByName = document.getElementsByName(formName);
for (var i = 0; i < elementsByName.length; i++) {
if (elementsByName[i].className == "active") {
elementsByName[i].className = "inactive";
break;
}
}
},
// Add the event listeners
listeners: {
btnListeners: (function () {
document.getElementById('sidebar').addEventListener("click", function(e){
self.evtListeners.btnColorChange('sidebuttons');
e.target.className = "active";
});
})()
}
}
return self;
})();
automationPageWrapper.listeners.btnColorChange();
});
I'm writing a silly page where I have 5 audio elements named myAudio1 thru myAudio5, and I want to display hello/goodbye messages when each of these audios are played/ended.
My current (certainly not the best) approach is having a JavaScript snippet for each of my audio tags: (this is for the first one)
var aud1 = document.getElementById("myAudio1");
aud1.onplay = function() {
console.log("myAudio1 says hello");
};
aud1.onended = function() {
console.log("myAudio1 says goodbye");
};
so I have five of these snippets with their own identifiers, all the way to myAudio5.
Apparently it's a long and cumbersome approach, so I tried to simplify it and came up with this:
var audList = document.getElementsByTagName("audio");
console.log(audList.length);
for (var i = 0; i < audList.length; i++) {
audList[i].addEventListener("load", audCheck, false);
}
function audCheck(e) {
var aud = e.target;
var audID = e.target.id;
aud.onplay = function() {
console.log(audID+" says hello");
};
aud.onended = function() {
console.log(audID+" says goodbye");
};
}
For whatever reason it's not working (Help! http://jsfiddle.net/8176ccnk/); if it did, I would wonder if creating multiple event listeners is a necessity in these kind of scenarios, where the event handler itself sort of acts like an event listener. (I don't think having one event handler that handles all child audio DOM events at the parent DOM level works...)
In general, what's the best way of interacting with DOMs during these events?
There is no load event in the media events so your handler audCheck is not getting called.
You can directly add the start/stop/ended listeners
var audList = document.getElementsByTagName("audio");
console.log(audList.length); //prints out how many audio elements there are
for (var i = 0; i < audList.length; i++) {
audList[i].addEventListener("play", onPlay, false);
audList[i].addEventListener("ended", onEnded, false);
}
function onPlay(e) {
console.log(e.target.id + " says hello");
};
function onEnded(e) {
console.log(e.target.id + " says goodbye");
};
Demo: Fiddle
I'm writing a jQuery plugin where the events which start/stop the plugin are customisable, so potentially the same event could both start and stop the plugin (e.g. click to start and click to stop).
What's an elegant way, ideally not involving timeouts or unbinding and rebinding of listeners (and not too many "isPlaying" "isBeingStarted" flags etc..) to make sure the correct callback is called
(Note: When I posted this answer, the question had a typo in it which made it seem like binding/unbinding would be okay as long as timeouts weren't involved.)
I don't see any need for timeouts, just bind/unbind as appropriate:
this.bind(startEvent, start);
function start() {
$(this).unbind(startEvent).bind(stopEvent, stop);
}
function stop() {
$(this).unbind(stopEvent).bind(startEvent, start);
}
In the above, I assume that startEvent is the configured start event name (and I'd probably add a namespace to it, e.g. the user passes in "click" but you add ".niftyplugin" to it resulting in startEvent containing "click.niftyplugin" so you can bind/unbind at will), and stopEvent is the configured stop event name (with namespace).
Here's a full example, with namespaces and using data to remember the options (you could use a closure if you prefer) - live copy:
// Plugin stuff
(function($) {
$.fn.niftyPlugin = niftyPlugin;
function niftyPlugin(options) {
var data;
data = {
startEvent: (options && options.startEvent || "click") + ".niftyplugin",
stopEvent: (options && options.stopEvent || "click") + ".niftyplugin"
};
this.data("niftyPlugin", data).bind(data.startEvent, start);
return this;
}
function start() {
var $this = $(this),
data = $this.data("niftyPlugin");
$this.unbind(data.startEvent).bind(data.stopEvent, stop);
display("Start");
}
function stop() {
var $this = $(this),
data = $this.data("niftyPlugin");
$this.unbind(data.stopEvent).bind(data.startEvent, start);
display("Stop");
}
function display(msg) {
$("<p>").html(msg).appendTo(document.body);
}
})(jQuery);
// Use
jQuery(function($) {
$("#theButton").click(function() {
$("<p>Non-plugin hook fired</p>").appendTo(document.body);
}).niftyPlugin({
startEvent: "click"
});
});
The only other alternative I see is stopImmediatePropagation - live example:
// Plugin stuff
(function($) {
$.fn.niftyPlugin = niftyPlugin;
function niftyPlugin(options) {
var startEvent, stopEvent, running = false;
startEvent = (options && options.startEvent || "click") + ".niftyplugin";
stopEvent = (options && options.stopEvent || "click") + ".niftyplugin";
this.bind(startEvent, start).bind(stopEvent, stop);
return this;
function start(event) {
if (running) {
return;
}
running = true;
display("Start");
event.stopImmediatePropagation();
}
function stop(event) {
if (!running) {
return;
}
running = false;
display("Stop");
event.stopImmediatePropagation();
}
}
function display(msg) {
$("<p>").html(msg).appendTo(document.body);
}
})(jQuery);
// Use
jQuery(function($) {
$("#theButton").click(function() {
$("<p>Non-plugin hook fired</p>").appendTo(document.body);
}).niftyPlugin({
startEvent: "click"
});
});
I don't like it, though, because it interferes with other handlers for the event. For instance, in the above, if I change the use to this:
// Use
jQuery(function($) {
$("#theButton").niftyPlugin({
startEvent: "click"
}).click(function() {
$("<p>Non-plugin hook fired</p>").appendTo(document.body);
});
});
...so the plug-in grabs the events before the non-plug-in code, boom, the non-plug-in code never sees the event (example).
So despite the overhead, I suspect bind/unbind are your friends here.
It may be overkill, but an elegant way to not have to maintain a bunch of flags (e.g. "isPlaying") is to use a Finite State Machine.
Here's a jQuery implementation: https://github.com/DukeLeNoir/jquery-machine
The eventual solution I've gone for is to do a quick uniqueness test for events used for stopping and starting and if there are any events used for both stopping and starting then a different listener (which does an isPlaying check) is attached to these. There's a small performance hit on loading the plugin, but after that the event handling code is about as efficient as can be.
function processEvents() {
var tempStart = opts.startEvent.split(" ").sort(),
tempStop = opts.stopEvent.split(" ").sort();
startEventLoop: for(var i=0, il = tempStart.length;i<il;i++) {
for(var j=0, jl = tempStop.length;j<jl;j++) {
if(tempStart[i] == tempStop[j]) {
stopStartEvents.push(tempStart[i])
tempStop.splice(j,1);
continue startEventLoop;
}
}
startEvents.push(tempStart[i])
}
startEvents = startEvents.join(" ");
stopEvents = tempStop.join(" ");
stopStartEvents = stopStartEvents.join(" ");
}
$this.on(stopEvents, function() {
$this.trigger("stop.flickBook");
}).on(startEvents, function() {
$this.trigger("start.flickBook");
}).on(stopStartEvents, function() {
playing ? $this.trigger("stop.flickBook") : $this.trigger("start.flickBook");
});