Greasemonkey: take from ajaxrespond for later use - ajax

The site http://project-gc.com/Statistics/TopFavWilson views data depending on a set filter via ajax.
I can see this data as bargraphs.(I'm logged in and authenticated)
But I would like to save this data for import into e.g. a spreadsheet program.
I need the Fav%Wilson points (also in the ajaxresponde), I can't get them in a different way.
I thought about using a greasemonkey script to fetch the responddata from the after the ajaxcall.
Maybe with "waitForKeyElements"?
Can anybody suggest a sollution or give me a hint how to solve the problem?

If you only want to monitor the responses without any interaction, you can replace the XHR constructor with a wrapper function and add an event listener to each newly created instance.
// ==UserScript==
// #name ajaxobserver
// #namespace http://example.com
// #description observes ajax responses
// #include http://project-gc.com/Statistics/TopFavWilson
// #include http://project-gc.com/Statistics/TopFavWilson/*
// #version 1
// #grant none
// #run-at document-start
// ==/UserScript==
// scope encapsulation in newer GM versions not necessary, nevertheless...
(function()
{
// save constructor
let XMLHttpRequest = window.XMLHttpRequest;
// replace constructor function
window.XMLHttpRequest = function()
{
// new instance
let obj = new XMLHttpRequest();
// register a listener
obj.addEventListener('load', function (event)
{
console.log('EVENT LISTENER: ajax load: responseText =', event.target.responseText);
});
//return the created instance instead of `this`
return obj;
};
})();
If you also want to manipulate results, you will need a proxy or you have rebuild the entire XHR Object by hand as a wrapper. This is a proxy example:
// ==UserScript==
// #name ajaxmanipulator
// #namespace http://example.com
// #description observes & manipulate ajax responses
// #include http://project-gc.com/Statistics/TopFavWilson
// #include http://project-gc.com/Statistics/TopFavWilson/*
// #version 1
// #grant none
// #run-at document-start
// ==/UserScript==
(function()
{
let
// a function call handler for onevent functions
applyEventHandler =
{
apply: function(targetFunc, thisArg, [event])
{
if
(
'readystatechange' === event.type && 4 === event.target.readyState && 200 === event.target.status
||
'load' === event.type
)
console.log('EVENT', event.type + ':', event.target.responseText);
return targetFunc.call(thisArg, event);
}
},
// a function call handler for onevent functions
applyOpenHandler =
{ // destructuring arguments array into individual named arguments
apply: function(targetFunc, thisArg, [method, url, async, user, password])
{
console.log('open handler:', 'target =', targetFunc, ', thisArg =', thisArg );
console.log
( 'XMLHttpRequest.open\n',
'method:' , method,
'url:' , url,
'async:' , async,
'user:' , user,
'password:', password
);
// let's manipulate some argument
url += '?get-parameter-added-by-proxy';
// finally call the trapped function in context of thisArg passing our manipulated arguments
return targetFunc.call(thisArg, method, url, async, user, password);
}
},
// a property handler for XMLHttpRequest instances
xmlHttpReq_PropertiesHandler =
{
// target : the proxied object (native XMLHttpRequest instance)
// property: name of the property
// value : the new value to assign to the property (only in setter trap)
// receiver: the Proxy instance
get:
function (target, property /*, receiver*/)
{
console.log('%cget handler: ', 'color:green', property);
switch (property)
{
case 'responseText':
// let's return a manipulated string when the property `responseText` is read
return '<div style="color:red;border:1px solid red">response manipulated by proxy'
+ target.responseText + '</div>';
case 'open':
// All we can detect here is a get access to the *property*, which
// usually returns a function object. The apply trap does not work at this
// point. Only a *proxied function* can be trapped by the apply trap.
// Thus we return a proxy of the open function using the apply trap.
// (A simple wrapper function would do the trick as well, but could be easier
// detected by the site's script.)
// We use bind to set the this-context to the native `XMLHttpRequest` instance.
// It will be passed as `thisArg` to the trap handler.
return new Proxy(target.open, applyOpenHandler).bind(target);
default:
return 'function' === typeof target[property]
// function returned by proxy must be executed in slave context (target)
? target[property].bind(target)
// non-function properties are just returned
: target[property]
;
}
},
set:
function (target, property, value, receiver)
{
try
{
console.log('%cset handler: ', 'color:orange', property, '=', value);
switch (property)
{
// Event handlers assigned to the proxy must be stored into the proxied object (slave),
// so that its prototype can access them (in slave context). Such an access is not trapped by
// the proxy's get trap. We need to store proxied functions to get ready to observe invokations.
// Old ajax style was checking the readystate,
// newer scripts use the onload event handler. Both can still be found.
case 'onreadystatechange':
case 'onload':
// only create proxy if `value` is a function
target[property] = 'function' === typeof value
? new Proxy(value, applyEventHandler).bind(receiver)
: value
;
break;
default:
target[property] = value;
}
return true; // success
}
catch (e)
{
console.error(e);
return false; // indicate setter error
}
}
},
oldXMLHttpRequest = window.XMLHttpRequest
; // end of local variable declaration
window.XMLHttpRequest = function(...argv)
{
return new Proxy(new oldXMLHttpRequest(...argv), xmlHttpReq_PropertiesHandler);
}
})();
Be aware that this direct acces only works as long as you do not grant any privileged GM_functions. When you get enclosed into sandbox, you will have to inject the function as sting into the site's scope, e.g. via setTimeout.

Related

How to create repeating form section in ServiceNow

I'm new to ServiceNow and going to start create a new form. One of the requirment is to create a repeating form section (below image is the design of the component).
May I know any default component in ServiceNow or we need to create a custom widget for this?
There is no OOB way to do this.
The way that I've solved the problem in the past, was as follows:
Create a single variable or set of variables representing the data you want to capture
Create a UI Macro/button "Add"
When clicked, that button should trigger a Script which will add the data from the fields into a JSON object which is then used to populate an HTML element with some friendly-looking representation of the data.
Here are some swatches of code I saved to do that, but keep in mind that I had to do this like a year ago so it's not super fresh.
"Add" button:
<?xml version="1.0" encoding="utf-8" ?>
<!--
UI Page
Name: http_addServerButton_loadBalancer
Category: General
Direct: false
Note that this will change slightly in name, and in the argument being passed, depending on whether the button is for the http, https, or TCP
sections.
-->
<j:jelly trim="false"
xmlns:j="jelly:core"
xmlns:g="glide"
xmlns:j2="null"
xmlns:g2="null" >
<script language="javascript"
src="addServerButtonClicked.jsdbx" />
<button name="add"
onclick="addServerButtonClicked('http')" >Add
</button >
</j:jelly >
UI Script that handled the add actions:
/*
UI Script
Name: addServerButtonClicked
Category: General
Direct: false
*/
/***********BEGIN***********/
// todo: Create onSubmit validation. Make sure users don't submit a form with existing server name/port data filled out but not added to the JSON.
// todo: Change add new button name to "add server".
/**
* This function is called when the "Add Server" button is clicked
*/
function addServerButtonClicked(protocol) {
g_form.hideFieldMsg('server_name_' + protocol, true);
g_form.hideFieldMsg('server_port_' + protocol, true);
//todo: validate the server AND port are filled out for the specific protocol selected, using "'server_name_' + protocol" and "'server_port_' + protocol"
//todo: If not BOTH populated, then add field mesage and tell the user that they are a bad user and should feel bad about their life choices.
var isFormValid = validateForm(protocol);
if (isFormValid) {
var fieldName = 'server_name_' + protocol;
g_form.getReference(fieldName, function(gr) {
//cheap way to combine relevant objects from differing scope without writing a gigantic anonymous inline function.
buildDataObject(gr, protocol);
});
} else {
alert('form invalid'); // todo: remove
// todo: throw some error or something
}
}
/**
* This function is called whenever a new server is added to the request. It parses the existing JSON data into an object, and then goes about adding on to it.
* #param serverGR {GlideRecord} - The GlideRecord object returned from the asynchronous nonHireATSCandidatesEncodedQuery that's run when add button is clicked. This param is auto-populated.
*/
function buildDataObject(serverGR, protocol) {
if (!serverGR) {
console.error('No valid server was found.');
}
// Grab the value of the JSON Data field
var existingJsonData = g_form.getValue('json_data');
// If the JSON Data field already contains some existing JSON data, use that as the starting
// point for our object. Otherwise (if this is the first entry), declare a new object.
var dataObject = existingJsonData ? JSON.parse(existingJsonData) : {
http: {},
https: {},
tcp: {}
};
//todo: write and call a function to get the protocol header info crap and append it to the object.
// Set the "serverName" property to either the server's name, or (if no name exists for this server),
// its' IP address of (if no name OR IP address exists for this server), its' sys_id.
var requestedFor = g_form.getValue('requested_for');
var serverSysId = serverGR.getValue('sys_id');
var serverIP = serverGR.getValue('ip_address');
var serverName = serverGR.getValue('name') ? serverGR.getValue('name') : serverGR.getValue('ip_address') ? serverGR.getValue('ip_address') : serverSysId = serverGR.getValue('sys_id');
var serverPort;
var tcpIPPort;
var lbMethod;
var persistence;
var monitorRequest;
var monitorResponse;
//Okay, yeah, so I could've used one line to set each of these vars, and used syntax like "'server_port_' + protocol.toLowerCase()".
//But instead of that, I did it this way. Why? Because I thought for a minute at 2AM that this would help future-me, in the event that
//we ever had stupid variable names to compete with. Sooo... behold, the pointless switch-case block.
switch (protocol.toLowerCase()) {
case 'http':
serverPort = g_form.getValue('server_port_http');
tcpIPPort = g_form.getValue('tcp_ip_http');
lbMethod = g_form.getValue('lb_method_http');
persistence = g_form.getValue('persistence_http');
monitorRequest = g_form.getValue('monitor_request_http');
monitorResponse = g_form.getValue('monitor_response_http');
// Clear the data from these two fields so it's clear that they need to be re-populated.
g_form.setValue('server_port_http', '');
g_form.setValue('server_name_http', '');
break;
case 'https':
serverPort = g_form.getValue('server_port_https');
tcpIPPort = g_form.getValue('tcp_ip_https');
lbMethod = g_form.getValue('lb_method_https');
persistence = g_form.getValue('persistence_https');
monitorRequest = g_form.getValue('monitor_request_https');
monitorResponse = g_form.getValue('monitor_response_https');
// Clear the data from these two fields so it's clear that they need to be re-populated.
g_form.setValue('server_port_https', '');
g_form.setValue('server_name_https', '');
break;
case 'tcp':
serverPort = g_form.getValue('server_port_tcp');
tcpIPPort = g_form.getValue('tcp_ip_tcp');
lbMethod = g_form.getValue('lb_method_tcp');
persistence = g_form.getValue('persistence_tcp');
monitorRequest = g_form.getValue('monitor_request_tcp');
monitorResponse = g_form.getValue('monitor_response_tcp');
// Clear the data from these two fields so it's clear that they need to be re-populated.
g_form.setValue('server_port_tcp', '');
g_form.setValue('server_name_tcp', '');
break;
}
if (!serverIP || !serverSysId || !serverPort || !serverName) {
// return; //Halt execution, since we don't have some of the data we need.
// todo: re-enable the above line after testing. Need to figure out how to handle errors, and what constitutes an error.
console.error('Not able to get one of these important values: [IP, Sys ID, Port, Server]')
}
// Populate the data object.
// Using bracket-notation here, in order to use a variable name as the object property name.
dataObject[protocol][serverSysId] = {};
dataObject[protocol][serverSysId].name = serverName;
dataObject[protocol][serverSysId].port = serverPort;
dataObject[protocol][serverSysId].sysid = serverSysId;
dataObject[protocol][serverSysId].ip = serverIP;
console.log(dataObject);
var dataSummary = '';
/*
This bit's pretty complex.
For each "prot" (protocol) in the outermost object,
check if the object corresponding to that protocol is truthy (not empty).
If it isn't empty, insert a header (H3) for that protocol/section.
Then, for each "prop" (server element) in that protocol object, print out some details about it.
*/
for (var prot in dataObject) {
if (!isObjEmpty(dataObject[prot]) && dataObject.hasOwnProperty(prot) && prot !== 'requestor' && prot !== 'generalInfo') {
dataSummary += '<h3>' + prot.toUpperCase() + '</h3>';
for (var prop in dataObject[prot]) {
if (dataObject[prot].hasOwnProperty(prop) && prop !== 'protocolDetails') {
dataSummary += '<b>Server name</b>: ' + dataObject[prot][prop].name + '<br />Server Sys ID: ' + dataObject[prot][prop].sysid + '<br />IP: ' + dataObject[prot][prop].ip + '<br />Port: ' + dataObject[prot][prop].port + '<br /><br />';
}
}
}
}
dataObject = populateObjectMeta(dataObject);
g_form.setValue('request_summary', dataSummary);
g_form.setValue('json_data', JSON.stringify(dataObject));
g_form.setVisible('request_summary', true);
}
function populateObjectMeta(dataObject) {
var i;
//Get boolean values for the three check-boxes representing whether the user wants HTTP, HTTPS, or TCP.
var httpSelected = isThisTrueOrWhat(g_form.getValue('http_select'));
var httpsSelected = isThisTrueOrWhat(g_form.getValue('https_select'));
var tcpSelected = isThisTrueOrWhat(g_form.getValue('tcp_select')); //todo: use these to populate more metadata in the relevant object
//Populate requestor details
dataObject.requestor = {};
dataObject.requestor.name = g_form.getReference('requested_by').getValue('name');
dataObject.requestor.sysID = g_form.getValue('requested_by');
dataObject.requestor.email = g_form.getValue('email');
//Populate general load balancing details
dataObject.generalInfo = {};
dataObject.generalInfo.dnsHost = g_form.getValue('dns_host');
dataObject.generalInfo.domainName = g_form.getValue('domain_name');
dataObject.generalInfo.application = isThisTrueOrWhat(g_form.getValue('application_not_found')) ? g_form.getValue('application_name_text') : g_form.getReference('application_name_ref').getValue('name');
dataObject.generalInfo.lifeCycle = g_form.getValue('lifecycle');
dataObject.generalInfo.site = g_form.getReference('site').getValue('name');
//Populate protocol details for HTTP, HTTPS, and TCP.
var selectedProtocols = getSelectedProtocols();
console.log(selectedProtocols);
for (i = 0; i < selectedProtocols.length; i++) {
console.log('Adding data to dataObj with selected protocol ' + [selectedProtocols[i]]);
dataObject[selectedProtocols[i]].protocolDetails = {};
dataObject[selectedProtocols[i]].protocolDetails.tcpIPPort = g_form.getValue('tcp_ip_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.lbMethod = g_form.getValue('lb_method_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.persistence = g_form.getValue('persistence_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.monitorRequest = g_form.getValue('monitor_request_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.monitorResponse = g_form.getValue('monitor_response_' + selectedProtocols[i]);
}
return dataObject;
}
function getSelectedProtocols() {
var selectedProtocols = [];
var httpSelected = isThisTrueOrWhat(g_form.getValue('http_select'));
var httpsSelected = isThisTrueOrWhat(g_form.getValue('https_select'));
var tcpSelected = isThisTrueOrWhat(g_form.getValue('tcp_select'));
if (httpSelected) {
selectedProtocols.push('http');
}
if (httpsSelected) {
selectedProtocols.push('https');
}
if (tcpSelected) {
selectedProtocols.push('tcp');
}
return selectedProtocols;
}
function validateForm(protocol) {
var port;
switch (protocol.toLowerCase()) {
case 'http':
port = g_form.getValue('server_port_http');
break;
case 'https':
port = g_form.getValue('server_port_https');
break;
case 'tcp':
port = g_form.getValue('server_port_tcp');
break;
}
//todo: validate port, and a bunch of other stuff.
return true;
}
/**
* Adds one object to another, nesting the child object into the parent.
* this is to get around javascript's immutable handling of objects.
* #param name {String} - The name of the property of the parent object, in which to nest the child object. <br />For example, if the name parameter is set to "pickles" then "parent.pickles" will return the child object.
* #param child {Object} - The object that should be nested within the parent object.
* #param [parent={}] {Object} - The parent object in which to nest the child object. If the parent object is not specified, then the child object is simple nested into an otherwise empty object, which is then returned.
* #returns {Object} - A new object consisting of the parent (or an otherwise empty object) with the child object nested within it.
* #example
* //sets myNewObject to a copy of originalObject, which now also contains the original (yet un-linked) version of itself as a child, under the property name "original"
* var myNewObject = addObjToObj("original", originalObj, originalObj);
*/
function addObjToObj(name, child, parent) {
if (!parent) {
parent = {};
}
parent[name] = child;
return parent;
}
function isObjEmpty(o) {
for (var p in o) {
if (o.hasOwnProperty(p)) {
return false;
}
}
return true;
}
function isThisTrueOrWhat(b) {
return ((typeof b == 'string') ? (b.toLowerCase() == 'true') : (b == true)); //all this just to properly return a bool in JS. THERE'S GOT TO BE A BETTER WAY!
}
Just create a UI page with the functionality that you want to have.
Ex- clicking on new button opens up the repeated form view.
Get the inputs from the filled form as list of objects, send them over to the client-callable script include(GlideAjax) that handles creation.

socket.io v1.1 beforeSend?

Is there a way to globally and automatically modify a message before emitting it? Something along the lines of jQuery ajax's beforeSend.
Right now, I'm manually adding a timestamp to the payload for each emit and it would be much less error-prone to have that done automatically.
Thanks!
You can either override the .emit() method (saving the original so you can call it) or if you control all the code that calls .emit(), then just make your own method that adds the timestamp to the payload and then calls .emit().
To patch the original .emit(), you could do this:
(function() {
var origEmit = Socket.prototype.emit;
Socket.prototype.emit = function(msg, data) {
if (typeof data === "object") {
data.timeStamp = Date.now();
}
return origEmit.apply(this, arguments);
}
})();
To create your own emit method that all your code could use, you could do this:
Socket.prototype.emitT = function(msg, data) {
if (typeof data === "object") {
data.timeStamp = Date.now();
}
return this.emit.apply(this, arguments);
}

Restrictions in file types in FineUploader 3.7.0

I am using the option "allowedExtensions" without any problem but there is a situation where I have to permit any type of extension but two.
Is there a simple way to do that? I didn't find an option like 'restrictedExtensions' to do that in the code.
Thanks
From the docs:
The validate and validateBatch events are thrown/called before the default Fine Uploader validators (defined in the options) execute.
Also, if your validation event handler returns false, then Fine Uploader will register that file as invalid and not submit it.
Here's some code you could try in your validate event handler. It has not been tested yet so YMMV.
var notAllowedExts = ['pptx', 'xlsx', 'docx'];
/* ... */
onValidate: function (fileOrBlobData) {
var valid = true;
var fileName = fileOrBlobData.name || '';
qq.each(notAllowedExts, function(idx, notAllowedExt) {
var extRegex = new RegExp('\\.' + notAllowedExt + "$", 'i');
if (fileName.match(extRegex) != null) {
valid = false;
return false;
}
});
return valid;
}
/* ... */

KnockoutJS not calling update function in custom binding handler

I have written a multiselect jQuery plugin that can be applied to a normal HTML select element.
However, this plugin will parse the select element and its options and then remove the select element from the DOM and insert a combination of divs and checkboxes instead.
I have created a custom binding handler in Knockout as follows:
ko.bindingHandlers.dropdownlist = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
// This will be called when the binding is first applied to an element
// Set up any initial state, event handlers, etc. here
// Retrieve the value accessor
var value = valueAccessor();
// Get the true value of the property
var unwrappedValue = ko.utils.unwrapObservable(value);
// Check if we have specified the value type of the DropDownList items. Defaults to "int"
var ddlValueType = allBindingsAccessor().dropDownListValueType ? allBindingsAccessor().dropDownListValueType : 'int';
// Check if we have specified the INIMultiSelect options otherwise we will use our defaults.
var elementOptions = allBindingsAccessor().iniMultiSelectOptions ? allBindingsAccessor().iniMultiSelectOptions :
{
multiple: false,
onItemSelectedChanged: function (control, item) {
var val = item.value;
if (ddlValueType === "int") {
value(parseInt(val));
}
else if (ddlValueType == "float") {
value(parseFloat(val));
} else {
value(val);
}
}
};
// Retrieve the attr: {} binding
var attribs = allBindingsAccessor().attr;
// Check if we specified the attr binding
if (attribs != null && attribs != undefined) {
// Check if we specified the attr ID binding
if (attribs.hasOwnProperty('id')) {
var id = attribs.id;
$(element).attr('id', id);
}
if (bindingContext.hasOwnProperty('$index')) {
var idx = bindingContext.$index();
$(element).attr('name', 'ddl' + idx);
}
}
if ($(element).attr('id') == undefined || $(element).attr('id') == '') {
var id = "ko_ddl_id_" + (ko.bindingHandlers['dropdownlist'].currentIndex);
$(element).attr('id', id);
}
if ($(element).attr('name') == undefined || $(element).attr('name') == '') {
var name = "ko_ddl_name_" + (ko.bindingHandlers['dropdownlist'].currentIndex);
$(element).attr('name', name);
}
var options = $('option', element);
$.each(options, function (index) {
if ($(this).val() == unwrappedValue) {
$(this).attr('selected', 'selected');
}
});
if (!$(element).hasClass('INIMultiSelect')) {
$(element).addClass('INIMultiSelect');
}
$(element).iniMultiSelect(elementOptions);
ko.bindingHandlers['dropdownlist'].currentIndex++;
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var unwrappedValue = ko.utils.unwrapObservable(valueAccessor());
var id = $(element).attr('id').replace(/\[/gm, '\\[').replace(/\]/gm, '\\]');
var iniMultiSelect = $('#' + id);
if (iniMultiSelect != null) {
iniMultiSelect.SetValue(unwrappedValue, true);
}
}};
ko.bindingHandlers.dropdownlist.currentIndex = 0;
This will transform the original HTML select element into my custom multiselect.
However, when the update function is called the first time, after the init, the "element" variable will still be the original select element, and not my wrapper div that holds my custom html together.
And after the page has been completely loaded and I change the value of the observable that I am binding to, the update function is not triggered at all!
Somehow I have a feeling that knockout no longer "knows" what to do because the original DOM element that I'm binding to is gone...
Any ideas what might be the issue here?
There is clean up code in Knockout that will dispose of the computed observables that are used to trigger bindings when it determines that the element is no longer part of the document.
You could potentially find a way to just hide the original element, or place the binding on a container of the original select (probably would be a good option), or reapply a binding to one of the new elements.
I ran into a similar problem today, and here's how I solved it. In my update handler, I added the following line:
$(element).attr("dummy-attribute", ko.unwrap(valueAccessor()));
This suffices to prevent the handler from being disposed-of by Knockout's garbage collector.
JSFiddle (broken): http://jsfiddle.net/padfv0u9/
JSFiddle (fixed): http://jsfiddle.net/padfv0u9/2/

backbone.js - working with the event dispatcher var dispatcher = _.clone(Backbone.Events)

In backbone.js documentation it says:
To make a handy event dispatcher that can coordinate events among different areas of your application: var dispatcher = _.clone(Backbone.Events)
Can anyone explain how to implement the dispatcher to communicate from one view to another? Where do I have to place the code in my app?
Here is a good article about using an event aggregator.
Can anyone explain how to implement the dispatcher to communicate from one view to another? Where do I have to place the code in my app?
You will probably have some kind of App Controller object, which will control the flow of the app, creating views, models, etc. This is also a good place for the event aggregator.
From my point of view, I think that article explains it pretty well.
Recently I needed an EventDispatcher to handle a large amount of events without loosing track of their names and their behave.
Perhaps it helps you too.
Here a simple example View:
define(['backbone', 'underscore', 'eventDispatcher'],
function(Backbone, _, dispatcher){
new (Backbone.View.extend(_.extend({
el: $('#anyViewOrWhatever'),
initialize: function () {
window.addEventListener('resize', function () {
// trigger event
dispatcher.global.windowResize.trigger();
});
// create listener
dispatcher.server.connect(this, this.doSomething);
// listen only once
dispatcher.server.connect.once(this, this.doSomething);
// remove listener:
dispatcher.server.connect.off(this, this.doSomething);
// remove all listener dispatcher.server.connect from this:
dispatcher.server.connect.off(null, this);
// remove all listener dispatcher.server.connect with this method:
dispatcher.server.connect.off(this.doSomething);
// remove all listener dispatcher.server.connect no matter what and where:
dispatcher.server.connect.off();
// do the same with a whole category
dispatcher.server.off(/* ... */);
// listen to all server events
dispatcher.server.all(this, this.eventWatcher);
},
doSomething: function(){
},
eventWatcher: function(eventName){
}
})
))();
});
Here the EventDispatcher with some example events. The events itself are predefined in the template Object. Your IDE should recognize them and lead you through the list.
As you can see, the Dispatcher run on its own. Only your View or whatever needs underlying Event methods from Backbone.
// module eventDispatcher
define(['backbone', 'underscore'], function (Backbone, _) {
var instance;
function getInstance () {
if ( !instance ) {
instance = createInstance();
}
return instance;
}
return getInstance();
function createInstance () {
// dummy function for your ide, will be overwritten
function e (eventContext, callback) {}
var eventHandler = {},
// feel free to put the template in another module
// or even more split them in one for each area
template = {
server: {
connect: e,
disconnect: e,
login: e,
logout: e
},
global: {
windowResize: e,
gameStart: e
},
someOtherArea: {
hideAll: e
}
};
// Create Events
_.each(template, function (events, category) {
var handler = eventHandler[category] = _.extend({}, Backbone.Events);
var categoryEvents = {
// turn off listener from <category>.<**all events**> with given _this or callback or both:
// off() complete purge of category and all its events.
// off(callback) turn off all with given callback, no matter what this is
// off(null, this) turn off all with given this, no matter what callback is
// off(callback, this) turn off all with given callback and this
off: function (callback, _this) {
if(!callback && _this){
handler.off();
}else{
_.each(template[category], function(v, k){
k != 'off' && template[category][k].off(callback, _this);
});
}
}
};
events.all = e;
_.each(events, function (value, event) {
// create new Listener <event> in <category>
// e.g.: template.global.onSomething(this, fn);
categoryEvents[event] = function (_this, callback) {
_this.listenTo(handler, event, callback);
};
// create new Listener <event> in <category> for only one trigger
// e.g.: template.global.onSomething(this, fn);
categoryEvents[event].once = function (_this, callback) {
_this.listenToOnce(handler, event, callback);
};
// trigger listener
// e.g.: template.global.onSomething.trigger();
categoryEvents[event].trigger = function (debugData) {
console.log('**Event** ' + category + '.' + event, debugData ? debugData : '');
handler.trigger(event);
};
// turn off listener from <category>.<event> with given _this or callback or both:
// off() complete purge of category.event
// off(callback) turn off all with given callback, no matter what this is
// off(null, this) turn off all with given this, no matter what callback is
// off(callback, this) turn off all with given callback and this
// e.g.: template.global.onSomething.off(fn, this);
categoryEvents[event].off = function (callback, _this) {
handler.off(event, callback, _this);
}
});
template[category] = categoryEvents;
});
return template;
}
});
The behavior of Backbones Event-system is not affected in any way and can be used as normal.

Resources