In my angular app, i use modal windows for filters/selectors and there are many components which are binded with window resize event like SVG, wordcloud etc.. Whenever i open or close the modal window, it triggers window resize event which results in reloading of all those components (Charts)..
Is there a way to stop the triggering of window resize event while opening or closing the modal window..?.. I googled and got a thread about this issue which is still open..
https://github.com/driftyco/ionic/issues/2309
The window.resize is triggered intentionally by the framework itself -- from the source: ionic.trigger('resize');.
solution
option 1
You can just comment out the above mentioned line and create your own bundle of the ionic framework.
option 2
You can use decorator to replace $ionicModal service altogether by copying and modifying the source code. Let me know if anything unclear. I personally prefer this option as it should be easier to maintain in the long run and let you customise the behaviour of the modal even further.
HERE is an example of what I mean:
.config(function($provide){
$provide.decorator('$ionicModal',function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $q, $log) {
var PLATFORM_BACK_BUTTON_PRIORITY_MODAL = 200;
var delegate = (function(){
/**
* #ngdoc controller
* #name ionicModal
* #module ionic
* #description
* Instantiated by the {#link ionic.service:$ionicModal} service.
*
* Be sure to call [remove()](#remove) when you are done with each modal
* to clean it up and avoid memory leaks.
*
* Note: a modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
* scope, passing in itself as an event argument. Note: both modal.removed and modal.hidden are
* called when the modal is removed.
*/
var ModalView = ionic.views.Modal.inherit({
/**
* #ngdoc method
* #name ionicModal#initialize
* #description Creates a new modal controller instance.
* #param {object} options An options object with the following properties:
* - `{object=}` `scope` The scope to be a child of.
* Default: creates a child of $rootScope.
* - `{string=}` `animation` The animation to show & hide with.
* Default: 'slide-in-up'
* - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
* the modal when shown. Default: false.
* - `{boolean=}` `backdropClickToClose` Whether to close the modal on clicking the backdrop.
* Default: true.
* - `{boolean=}` `hardwareBackButtonClose` Whether the modal can be closed using the hardware
* back button on Android and similar devices. Default: true.
*/
initialize: function(opts) {
ionic.views.Modal.prototype.initialize.call(this, opts);
this.animation = opts.animation || 'slide-in-up';
},
/**
* #ngdoc method
* #name ionicModal#show
* #description Show this modal instance.
* #returns {promise} A promise which is resolved when the modal is finished animating in.
*/
show: function(target) {
var self = this;
if (self.scope.$$destroyed) {
$log.error('Cannot call ' + self.viewType + '.show() after remove(). Please create a new ' + self.viewType + ' instance.');
return;
}
var modalEl = angular.element(self.modalEl);
self.el.classList.remove('hide');
$timeout(function() {
$ionicBody.addClass(self.viewType + '-open');
}, 400);
if (!self.el.parentElement) {
modalEl.addClass(self.animation);
$ionicBody.append(self.el);
}
if (target && self.positionView) {
self.positionView(target, modalEl);
// set up a listener for in case the window size changes
ionic.on('resize',function() {
ionic.off('resize',null,window);
self.positionView(target,modalEl);
},window);
}
modalEl.addClass('ng-enter active')
.removeClass('ng-leave ng-leave-active');
self._isShown = true;
self._deregisterBackButton = $ionicPlatform.registerBackButtonAction(
self.hardwareBackButtonClose ? angular.bind(self, self.hide) : angular.noop,
PLATFORM_BACK_BUTTON_PRIORITY_MODAL
);
self._isOpenPromise = $q.defer();
ionic.views.Modal.prototype.show.call(self);
$timeout(function() {
modalEl.addClass('ng-enter-active');
// ionic.trigger('resize');
self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.shown', self);
self.el.classList.add('active');
self.scope.$broadcast('$ionicHeader.align');
}, 20);
return $timeout(function() {
//After animating in, allow hide on backdrop click
self.$el.on('click', function(e) {
if (self.backdropClickToClose && e.target === self.el) {
self.hide();
}
});
}, 400);
},
/**
* #ngdoc method
* #name ionicModal#hide
* #description Hide this modal instance.
* #returns {promise} A promise which is resolved when the modal is finished animating out.
*/
hide: function() {
var self = this;
var modalEl = angular.element(self.modalEl);
self.el.classList.remove('active');
modalEl.addClass('ng-leave');
$timeout(function() {
modalEl.addClass('ng-leave-active')
.removeClass('ng-enter ng-enter-active active');
}, 20);
self.$el.off('click');
self._isShown = false;
self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.hidden', self);
self._deregisterBackButton && self._deregisterBackButton();
ionic.views.Modal.prototype.hide.call(self);
// clean up event listeners
if (self.positionView) {
ionic.off('resize',null,window);
}
return $timeout(function() {
$ionicBody.removeClass(self.viewType + '-open');
self.el.classList.add('hide');
}, self.hideDelay || 320);
},
/**
* #ngdoc method
* #name ionicModal#remove
* #description Remove this modal instance from the DOM and clean up.
* #returns {promise} A promise which is resolved when the modal is finished animating out.
*/
remove: function() {
var self = this;
self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.removed', self);
return self.hide().then(function() {
self.scope.$destroy();
self.$el.remove();
});
},
/**
* #ngdoc method
* #name ionicModal#isShown
* #returns boolean Whether this modal is currently shown.
*/
isShown: function() {
return !!this._isShown;
}
});
var createModal = function(templateString, options) {
// Create a new scope for the modal
var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
options.viewType = options.viewType || 'modal';
angular.extend(scope, {
$hasHeader: false,
$hasSubheader: false,
$hasFooter: false,
$hasSubfooter: false,
$hasTabs: false,
$hasTabsTop: false
});
// Compile the template
var element = $compile('<ion-' + options.viewType + '>' + templateString + '</ion-' + options.viewType + '>')(scope);
options.$el = element;
options.el = element[0];
options.modalEl = options.el.querySelector('.' + options.viewType);
var modal = new ModalView(options);
modal.scope = scope;
// If this wasn't a defined scope, we can assign the viewType to the isolated scope
// we created
if (!options.scope) {
scope[ options.viewType ] = modal;
}
return modal;
};
return {
/**
* #ngdoc method
* #name $ionicModal#fromTemplate
* #param {string} templateString The template string to use as the modal's
* content.
* #param {object} options Options to be passed {#link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
* #returns {object} An instance of an {#link ionic.controller:ionicModal}
* controller.
*/
fromTemplate: function(templateString, options) {
var modal = createModal(templateString, options || {});
return modal;
},
/**
* #ngdoc method
* #name $ionicModal#fromTemplateUrl
* #param {string} templateUrl The url to load the template from.
* #param {object} options Options to be passed {#link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
* options object.
* #returns {promise} A promise that will be resolved with an instance of
* an {#link ionic.controller:ionicModal} controller.
*/
fromTemplateUrl: function(url, options, _) {
var cb;
//Deprecated: allow a callback as second parameter. Now we return a promise.
if (angular.isFunction(options)) {
cb = options;
options = _;
}
return $ionicTemplateLoader.load(url).then(function(templateString) {
var modal = createModal(templateString, options || {});
cb && cb(modal);
return modal;
});
}
}
})();
console.log(delegate);
return delegate;
});
})
caution
Changing the behaviour may have some unwanted side effects, but I do not know ionic framework that much. Moreover, if there are any negative side effects then surely that is a bad design of the framework itself, as general events should not be used for framework specific purposes. If I had a bit more time I will try to make a PR to have triggering of the event optional.
Related
This question already has answers here:
Assigning prototype methods *inside* the constructor function - why not?
(6 answers)
Closed 2 years ago.
And thank you in advance for your help. I've written a pretty standard Modal class in Vanilla JavaScript, and I am wondering why when I call the myModal.showModal method, we always call it on the last instance of the Modal.
Here is the code for my Modal:
(function() {
/**
* Creates a Modal dialog. The user should call `removeModal` when they are
* finished using it.
* #param {boolean} defaultShow whether or not the modal is shown by default
* #param {HTMLElement} bodyContent the body of the modal
* #param {HTMLButtonElement | undefined} footerContent footer of the modal
*/
function Modal(defaultShow, initialBodyContent, initialFooterContent) {
let isActive = false;
// Create the modal and append it to the dom
let containerElement = document.createElement("div");
containerElement.className = "modal-container";
containerElement.id = Math.random().toString();
let modalBody = document.createElement("div");
modalBody.className = "modal-body";
if (initialBodyContent) {
modalBody.appendChild(initialBodyContent);
}
containerElement.appendChild(modalBody);
let modalFooter = document.createElement("div");
modalFooter.className = "modal-footer";
if (initialFooterContent) {
modalFooter.appendChild(initialFooterContent);
}
containerElement.appendChild(modalFooter);
document.body.appendChild(containerElement);
/**
* Shows the modal with new content, optionally
* #param {HTMLElement | undefined} newBodyContent replacement body element
* #param {HTMLElement | undefined} newFooterContent replacement footer element
*/
function showModal(newBodyContent, newFooterContent) {
if (isActive) {
return;
}
if (newBodyContent) {
modalBody.innerHTML = "";
modalBody.appendChild(newBodyContent);
}
if (newFooterContent) {
modalFooter.innerHTML = "";
modalFooter.appendChild(newFooterContent);
}
isActive = true;
containerElement.classList.add("show");
containerElement.classList.remove("hide");
}
/**
* Hides the modal, but does not delete it from the DOM
*/
function hideModal() {
isActive = false;
containerElement.classList.add("hide");
containerElement.classList.remove("show");
}
/**
* Completely removes the modal from the DOM
*/
function removeModal() {
$(containerElement).remove();
}
Modal.prototype.showModal = showModal;
Modal.prototype.hideModal = hideModal;
Modal.prototype.removeModal = removeModal;
Modal.prototype.isShowing = function() { return isActive; };
if (defaultShow) {
showModal();
} else {
hideModal();
}
}
module.exports = { Modal: Modal };
}())
And I am using it like so:
const myModal1 = new Modal(false);
const myModal2 = new Modal(false);
At this point, I see both Modals on the DOM. However, when i call myModal1.show(content, footer), we're calling the showModal prototype of myModal2, instead of myModal1. However, if I change the prototype methods to:
function Modal() {
....
this.showModal = showModal;
this.hideModal = hideModal;
etc...
...
}
the code behaves as I expect it to. What is going on here exactly? I would expect the prototype function to capture my variables, but perhaps I'm mistaken.
Modal.prototype holds the same value for all your Modal instances, so the showModal method of all your Modal instances refer to the showModal method of the last instance.
For example :
function A () {
const randomNumber = Math.floor(Math.random() * 100);
A.prototype.id = randomNumber;
console.log("creating instance with id: " + randomNumber);
}
const a1 = new A()
const a2 = new A()
console.log("Current ID of a1: " + a1.id);
console.log("Current ID of a2: " + a2.id);
console.log(a1.__proto__ === a2.__proto__); // Will return "true" because it's the same object
You can read more on the documentation.
I'm using Aurelia and TypeScript for my web application.
I created following 'user_checking' function to check users and status as follows.
user_checking(registerdata, tested_users) {
return registerdata.userid && typeof tested_users[registerdata.userid] == 'undefined';
}
This function has two parameters and also a return type. I don't see any kind of error there. But when I run the application, I'm getting following error.
error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. [07:46:58] gulp-notify: [Error running Gulp] Error:
Can anyone please tell me what's the error in this function.
Actually, as was also mentioned in the comments, your code snippet works fine when compiled with TypeScript v2.3.x.
You first might want to check your TypeScript version through tsc --v and update it if required.
I've taken the snippet from GitLab you mentioned and tested that it works as expected. The two basic examples below arrange and return both the 'true' and 'false':
/**
* Example that arranges the function to return 'true'
*/
public exampleTrue(): boolean {
let registerdata = { userid: 1001 };
let tested_users = [];
return this.register.user_checking(registerdata, tested_users);
}
/**
* Example that arranges the function to return 'false'
*/
public exampleFalse(): boolean {
let registerdata = { userid: 1001 };
let tested_users = [];
this.tested_users[registerdata.userid] = 'foo';
return this.register.user_checking(registerdata, this.tested_users);
}
For the record, here is your class without any modifications:
import {autoinject} from 'aurelia-framework';
import {HttpClient, json} from 'aurelia-fetch-client';
#autoinject
export class Register {
heading:string = 'sTeam register';
registerdata = {};
passwordmatch = true;
tested_users = {}; //in coffee this is 'tested_users = {}'
/**
* CoffeeScript(AngularJS based) code as follows.
* ------------------------------
* S.tested_users = () ->
* tested_users
* ------------------------------
* #param tested_users
* #returns {any}
*/
tested_users_check(tested_users) {
return tested_users;
}
/**
* CoffeeScript(AngularJS based) code as follows.
* ------------------------------
* S.user_checking = ->
* S.registerdata.userid and typeof tested_users[S.registerdata.userid] == 'undefined'
* ------------------------------
* #param registerdata
* #param tested_users
* #returns {number}
*/
user_checking(registerdata, tested_users) {
return registerdata.userid && typeof tested_users[registerdata.userid] == 'undefined';
}
/**
* CoffeeScript(AngularJS based) code as follows.
* ------------------------------
* S.user_available = ->
* typeof tested_users[S.registerdata.userid] != 'undefined' and !tested_users[S.registerdata.userid]
* ------------------------------
* #param registerdata
* #param tested_users
*/
user_available(registerdata, tested_users) {
typeof tested_users[registerdata.userid] != 'undefined' && !tested_users[registerdata.userid];
}
/**
* CoffeeScript(AngularJS based) code as follows.
* ------------------------------
* S.user_taken = ->
* typeof tested_users[S.registerdata.userid] != 'undefined' and tested_users[S.registerdata.userid]
* ------------------------------
* #param registerdata
* #param tested_users
*/
user_taken(registerdata, tested_users) {
typeof tested_users[registerdata.userid] != 'undefined' && tested_users[registerdata.userid];
}
/**
* CoffeeScript(AngularJS based) code as follows.
* ------------------------------
* S.register = ->
* S.registerdata.group = 'techgrind'
* steam.post('register', S.registerdata).then(handle_request)
* ------------------------------
* #param registerdata
*/
register(registerdata) {
registerdata.group = 'kaishr';
// Have to implement the steam post method call.
}
}
As a sidenote I want to point out that it might be seriously helpful to take advantage of the strong-typing of TypeScript. Your class hardly uses any of the constrains nor type declarations. But perhaps, as a starting point, you might appreciate some minor tweaking like:
// define a strong-typed array, constrained to numbers
// (assuming userid is a number)
tested_users: Array<number> = [];
// define the class 'RegisterData'
// forcing it to have a userid of type number
export class RegisterData {
public userid: number;
}
With this in place you can refactor your operation a bit more to take full advantage of the compile-time safety and checks, before you even run your application. Like so:
public user_checking(registerdata: RegisterData, tested_users: Array<number>): boolean {
return registerdata.userid && typeof tested_users[registerdata.userid] == 'undefined';
}
Don't be overwhelmed with what, at first sight, might look like a lot of overhead in ceremony. With only adding some types, you've done the following:
Forced your function to return boolean
Strong-typed the RegisterData class
Eliminated any possible type mismatches for userid
Constrained the input parameters to strong types
In the end, this will allow your compiler to help you feed much better info before you even run your program.
Hope this helped...
The code exists here https://review.openstack.org/#/c/418828/ but I will go into more detail below:
I am writing tests for this particular piece of code: https://review.openstack.org/#/c/418828/26/openstack_dashboard/static/app/core/network_qos/qos.service.js
(function() {
"use strict";
angular.module('horizon.app.core.network_qos')
.factory('horizon.app.core.network_qos.service', qosService);
qosService.$inject = [
'$filter',
'horizon.app.core.openstack-service-api.neutron',
'horizon.app.core.openstack-service-api.userSession'
];
/*
* #ngdoc factory
* #name horizon.app.core.network_qos.service
*
* #description
* This service provides functions that are used through the QoS
* features. These are primarily used in the module registrations
* but do not need to be restricted to such use. Each exposed function
* is documented below.
*/
function qosService($filter, neutron, userSession) {
var version;
return {
getPolicyPromise: getPolicyPromise,
getPoliciesPromise: getPoliciesPromise
};
/*
* #ngdoc function
* #name getPoliciesPromise
* #description
* Given filter/query parameters, returns a promise for the matching
* policies. This is used in displaying lists of policies. In this case,
* we need to modify the API's response by adding a composite value called
* 'trackBy' to assist the display mechanism when updating rows.
*/
function getPoliciesPromise(params) {
return userSession.get().then(getQoSPolicies);
function getQoSPolicies() {
return neutron.getQoSPolicies(params).then(modifyResponse);
}
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyQos)}};
function modifyQos(policy) {
policy.trackBy = policy.id;
policy.apiVersion = version;
policy.name = policy.name || policy.id;
return policy;
}
}
}
/*
* #ngdoc function
* #name getPolicyPromise
* #description
* Given an id, returns a promise for the policy data.
*/
function getPolicyPromise(identifier) {
neutron.getVersion().then(setVersion);
return neutron.getQosPolicy(identifier).then(modifyResponse);
function modifyResponse(response) {
response.data.apiVersion = version;
return {data: response.data};
}
}
}
})();
This is my current test file:
(function() {
"use strict";
describe('qosService', function() {
var service;
beforeEach(module('horizon.app.core'));
beforeEach(inject(function($injector) {
service = $injector.get('horizon.app.core.network_qos.service');
}));
describe('getPoliciesPromise', function() {
it("provides a promise that gets translated", inject(function($q, $injector, $timeout) {
var neutron = $injector.get('horizon.app.core.openstack-service-api.neutron');
var session = $injector.get('horizon.app.core.openstack-service-api.userSession');
var deferred = $q.defer();
var deferredSession = $q.defer();
spyOn(neutron, 'getQoSPolicies').and.returnValue(deferred.promise);
spyOn(session, 'get').and.returnValue(deferredSession.promise);
var result = service.getPoliciesPromise({});
deferred.resolve({
data: {
items: [{id: 123, name: 'policy1'}]
}
});
$timeout.flush();
expect(neutron.getQoSPolicies).toHaveBeenCalled();
expect(result.$$state.value.data.items[0].name).toBe('policy1');
}));
});
});
})();
When I run the tests I currently get errors saying:
Expected spy getQoSPolicies to have been called.
As you can see, getQoSPolicies is definitely called. If anyone can see what is wrong with the tests to give me that error it would be much appreciated!! Many thanks in advance!
You should be resolving following promise (deferredSession) along with neutron one, or it won't go inside .then of userSession.get().then(getQoSPolicies):
var deferredSession = $q.defer();
spyOn(session, 'get').and.returnValue(deferredSession.promise);
...
...
deferredSession.resolve({});
deferred.resolve(...);
$timeout.flush();
Resolve that along with the existing and it should work as you expect!
I'm attempting to program a text adventure game using ECMAScript 6, transpiling into ECMAScript 5 using Babel. I've run into a strange case that's not appearing elsewhere in my code.
I have a generic action class that parses commands, determines if a given input matches a command that triggers the action, and then calls function to actually execute the action.
/**
* An action represents a mapping between a string and some kind of action in
* the system.
*/
export class Action {
/**
* Construct a new action instance.
*/
constuctor () {
this.commands = [];
}
/**
* Add a command as a trigger for this action.
*
* #param {String} command The command string to add as a trigger.
*
* #returns {Boolean} True if the command was added, false otherwise.
*/
addCommand (command) {
var paramRegex = /\{([a-zA-Z0-9_]+)\}/g;
var i = 0;
var regexResult;
var commandRegex;
var parameters;
if (typeof command !== 'string' || command.length < 1) {
return false;
}
parameters = {};
// Make spaces generic
commandRegex = command.replace(/\w+/g, '\\w+');
regexResult = paramRegex.exec(commandRegex);
while (regexResult) {
parameters[i] = regexResult[1];
commandRegex = commandRegex.replace(regexResult[0], '([^\\w]+)');
i++;
regexResult = paramRegex.exec(commandRegex);
}
this.commands.push({
regex: new RegExp(commandRegex, 'gi'),
parameters: parameters
});
return true;
}
/**
* Determine if the action has a trigger matching the given command string.
*
* #param {String} command The command to parse.
*
* #returns {Boolean} True if the command triggers the action, false
* otherwise.
*/
isTrigger (command) {
var i;
for (i = 0; i < this.commands.length; i++) {
if (this.commands[i].regex.test(command)) {
return true;
}
}
return false;
}
/**
* Trigger the action using the given command. This method checks if the
* command is a trigger for the action, extracts any arguments from the
* command string, and passes them to the {#link Action#execute} function
* using {#link Function#apply}.
*
* #param {String} command The command to use to trigger the action.
*
* #returns {Boolean} True if the trigger was successful, false otherwise.
*/
trigger (command) {
var args;
var result;
var i;
for (i = 0; i < this.commands.length; i++) {
result = this.commands[i].regex.exec(command);
if (result != null) {
break;
}
}
if (result == null) {
return false;
}
args = [];
for (i = 1; i < result.length; i++) {
args.push(result[i]);
}
this.execute.apply(this, args);
}
/**
* Execute the action with the given arguments.
*
* #returns {Boolean} True if the execution was successful, false otherwise.
*
* #virtual
*/
execute () {
throw new Error('Not implemented');
}
}
I have a sub-class which is supposed to implement a movement action and move the player from one location to another.
/**
* An action that moves the player.
*
* #extends {Action}
*/
export class MoveAction extends Action {
constructor () {
super();
this.addCommand('go to {place}');
this.addCommand('move to {place}');
this.addCommand('go {place}');
}
execute (place) {
var loc = GameObject.getGameObject(place);
if (!loc) {
return false;
}
return player.setLocation(loc);
}
}
I've run Babel on my code to produce compatible ECMAScript 5 code and attempted to run them manually via Node.JS to validate they work before I push them to my Git repository (I plan to write the test cases later).
I've run into an issue, though, where Node.JS complains that this.commands is undefined.
> var MoveAction = require('./compiled/actions/moveAction').MoveAction;
undefined
> var act = new MoveAction();
TypeError: Cannot read property 'push' of undefined
at MoveAction.addCommand (compiled\action.js:64:26)
at new MoveAction (compiled\actions\moveAction.js:41:39)
at repl:1:11
at REPLServer.defaultEval (repl.js:248:27)
at bound (domain.js:280:14)
at REPLServer.runBound [as eval] (domain.js:293:12)
at REPLServer.<anonymous> (repl.js:412:12)
at emitOne (events.js:82:20)
at REPLServer.emit (events.js:169:7)
at REPLServer.Interface._onLine (readline.js:210:10)
I can't spot anything immediately wrong with what I've written and I've done something similar in other places in the current code without any issues. I'm going to keep poking at it to see if I can solve the issue myself, but if anyone has suggestions that could save me time, it'd be much appreciated.
Solved it, I misspelled constructor as constuctor.
I'm trying to implement a little MVC framework and now I'm implementing the view-model binder, I mean, when the model changes, trigger a refresh/render/whatever on the model. So, I need an event listener on a object:
model.on("customEvent",appendItem);
$("#button").on("click",function(){
model.add(item);
model.trigger("customEvent");
});
function appendItem(item) {
$("#content").append(item.toHTML());
}
So how can I create my event listener on objects?
If you are already using jQuery, you can use its built-in event handling facilities.
It's a little known fact that you can also put any kind of Javascript objects into a jQuery collection, not just DOM elements. Then you can use a limited set of jQuery methods (.data(), .prop(), .on(), .off(), .trigger() and .triggerHandler()) on these objects.
//create the model - it can be any kind of object
var model = {};
//pass it to the jQuery function and you have a jQuery collection
//you can put an event handler on the object
$(model).on('customEvent', function () { console.log('hehe'); });
//and trigger it
$(model).trigger('customEvent');
Read more in the Manual
Try the Demo
For those who are not using jQuery and are interested in wiring their own stuf, here's how you may achieve similar aim:
/**
* EventfulObject constructor/base.
* #type EventfulObject_L7.EventfulObjectConstructor|Function
*/
var EventfulObject = function() {
/**
* Map from event name to a list of subscribers.
* #type Object
*/
var event = {};
/**
* List of all instances of the EventfulObject type.
* #type Array
*/
var instances = [];
/**
* #returns {EventfulObject_L1.EventfulObjectConstructor} An `EventfulObject`.
*/
var EventfulObjectConstructor = function() {
instances.push(this);
};
EventfulObjectConstructor.prototype = {
/**
* Broadcasts an event of the given name.
* All instances that wish to receive a broadcast must implement the `receiveBroadcast` method, the event that is being broadcast will be passed to the implementation.
* #param {String} name Event name.
* #returns {undefined}
*/
broadcast: function(name) {
instances.forEach(function(instance) {
(instance.hasOwnProperty("receiveBroadcast") && typeof instance["receiveBroadcast"] === "function") &&
instance["receiveBroadcast"](name);
});
},
/**
* Emits an event of the given name only to instances that are subscribed to it.
* #param {String} name Event name.
* #returns {undefined}
*/
emit: function(name) {
event.hasOwnProperty(name) && event[name].forEach(function(subscription) {
subscription.process.call(subscription.context);
});
},
/**
* Registers the given action as a listener to the named event.
* This method will first create an event identified by the given name if one does not exist already.
* #param {String} name Event name.
* #param {Function} action Listener.
* #returns {Function} A deregistration function for this listener.
*/
on: function(name, action) {
event.hasOwnProperty(name) || (event[name] = []);
event[name].push({
context: this,
process: action
});
var subscriptionIndex = event[name].length - 1;
return function() {
event[name].splice(subscriptionIndex, 1);
};
}
};
return EventfulObjectConstructor;
}();
var Model = function(id) {
EventfulObject.call(this);
this.id = id;
this.receiveBroadcast = function(name) {
console.log("I smell another " + name + "; and I'm model " + this.id);
};
};
Model.prototype = Object.create(EventfulObject.prototype);
Model.prototype.constructor = Model;
// ---------- TEST AND USAGE (hopefully it's clear enough...)
// ---------- note: I'm not testing event deregistration.
var ob1 = new EventfulObject();
ob1.on("crap", function() {
console.log("Speaking about craps on a broadcast? - Count me out!");
});
var model1 = new Model(1);
var model2 = new Model(2);
model2.on("bust", function() {
console.log("I'm model2 and I'm busting!");
});
var ob2 = new EventfulObject();
ob2.on("bust", function() {
console.log("I'm ob2 - busted!!!");
});
ob2.receiveBroadcast = function() {
console.log("If it zips, I'll catch it. - That's me ob2.");
};
console.log("start:BROADCAST\n---------------");
model1.broadcast("crap");
console.log("end :BROADCAST\n---------------\n-\n-\n");
console.log("start:EMIT\n---------------");
ob1.emit("bust");
console.log("end:EMIT\n---------------");
<h1>THE CODE IS IN THE JavaScript SECTION!</h1>
<h3>AND... THE SHOW IS ON YOUR CONSOLE!</h3>