mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-1204: Add generic, listener-driven event system.
This commit is contained in:
@@ -135,7 +135,29 @@
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Unit test using Jasmin and PhantomJS -->
|
||||
<plugin>
|
||||
<groupId>com.github.searls</groupId>
|
||||
<artifactId>jasmine-maven-plugin</artifactId>
|
||||
<version>2.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<phantomjs>
|
||||
<version>2.1.1</version>
|
||||
</phantomjs>
|
||||
<sourceIncludes>
|
||||
<sourceInclude>**/*.min.js</sourceInclude>
|
||||
</sourceIncludes>
|
||||
<jsSrcDir>${project.build.directory}/${project.build.finalName}</jsSrcDir>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
243
guacamole-common-js/src/main/webapp/modules/Event.js
Normal file
243
guacamole-common-js/src/main/webapp/modules/Event.js
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* An arbitrary event, emitted by a {@link Guacamole.Event.Target}. This object
|
||||
* should normally serve as the base class for a different object that is more
|
||||
* specific to the event type.
|
||||
*
|
||||
* @constructor
|
||||
* @param {String} type
|
||||
* The unique name of this event type.
|
||||
*/
|
||||
Guacamole.Event = function Event(type) {
|
||||
|
||||
/**
|
||||
* The unique name of this event type.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
this.type = type;
|
||||
|
||||
/**
|
||||
* An arbitrary timestamp in milliseconds, indicating this event's
|
||||
* position in time relative to other events.
|
||||
*
|
||||
* @type {Number}
|
||||
*/
|
||||
this.timestamp = new Date().getTime();
|
||||
|
||||
/**
|
||||
* Returns the number of milliseconds elapsed since this event was created.
|
||||
*
|
||||
* @return {Number}
|
||||
* The number of milliseconds elapsed since this event was created.
|
||||
*/
|
||||
this.getAge = function getAge() {
|
||||
return new Date().getTime() - this.timestamp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests that the legacy event handler associated with this event be
|
||||
* invoked on the given event target. This function will be invoked
|
||||
* automatically by implementations of {@link Guacamole.Event.Target}
|
||||
* whenever {@link Guacamole.Event.Target#emit emit()} is invoked.
|
||||
* <p>
|
||||
* Older versions of Guacamole relied on single event handlers with the
|
||||
* prefix "on", such as "onmousedown" or "onkeyup". If a Guacamole.Event
|
||||
* implementation is replacing the event previously represented by one of
|
||||
* these handlers, this function gives the implementation the opportunity
|
||||
* to provide backward compatibility with the old handler.
|
||||
* <p>
|
||||
* Unless overridden, this function does nothing.
|
||||
*
|
||||
* @param {Guacamole.Event.Target} eventTarget
|
||||
* The {@link Guacamole.Event.Target} that emitted this event.
|
||||
*/
|
||||
this.invokeLegacyHandler = function invokeLegacyHandler(eventTarget) {
|
||||
// Do nothing
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A {@link Guacamole.Event} that relates to one or more DOM events. Continued
|
||||
* propagation and default behavior of the related DOM events may be prevented
|
||||
* with {@link Guacamole.Event.DOMEvent#stopPropagation stopPropagation()} and
|
||||
* {@link Guacamole.Event.DOMEvent#preventDefault preventDefault()}
|
||||
* respectively.
|
||||
*
|
||||
* @constructor
|
||||
* @augments Guacamole.Event
|
||||
*
|
||||
* @param {String} type
|
||||
* The unique name of this event type.
|
||||
*
|
||||
* @param {Event[]} events
|
||||
* The DOM events that are related to this event. Future calls to
|
||||
* {@link Guacamole.Event.DOMEvent#preventDefault preventDefault()} and
|
||||
* {@link Guacamole.Event.DOMEvent#stopPropagation stopPropagation()} will
|
||||
* affect these events.
|
||||
*/
|
||||
Guacamole.Event.DOMEvent = function DOMEvent(type, events) {
|
||||
|
||||
Guacamole.Event.call(this, type);
|
||||
|
||||
/**
|
||||
* Requests that the default behavior of related DOM events be prevented.
|
||||
* Whether this request will be honored by the browser depends on the
|
||||
* nature of those events and the timing of the request.
|
||||
*/
|
||||
this.preventDefault = function preventDefault() {
|
||||
events.forEach(function applyPreventDefault(event) {
|
||||
if (event.preventDefault) event.preventDefault();
|
||||
event.returnValue = false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops further propagation of related events through the DOM. Only events
|
||||
* that are directly related to this event will be stopped.
|
||||
*/
|
||||
this.stopPropagation = function stopPropagation() {
|
||||
events.forEach(function applyStopPropagation(event) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* An object which can dispatch {@link Guacamole.Event} objects. Listeners
|
||||
* registered with {@link Guacamole.Event.Target#on on()} will automatically
|
||||
* be invoked based on the type of {@link Guacamole.Event} passed to
|
||||
* {@link Guacamole.Event.Target#dispatch dispatch()}. It is normally
|
||||
* subclasses of Guacamole.Event.Target that will dispatch events, and usages
|
||||
* of those subclasses that will catch dispatched events with on().
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
Guacamole.Event.Target = function Target() {
|
||||
|
||||
/**
|
||||
* A callback function which handles an event dispatched by an event
|
||||
* target.
|
||||
*
|
||||
* @callback Guacamole.Event.Target~listener
|
||||
* @param {Guacamole.Event} event
|
||||
* The event that was dispatched.
|
||||
*
|
||||
* @param {Guacamole.Event.Target} target
|
||||
* The object that dispatched the event.
|
||||
*/
|
||||
|
||||
/**
|
||||
* All listeners (callback functions) registered for each event type passed
|
||||
* to {@link Guacamole.Event.Targer#on on()}.
|
||||
*
|
||||
* @private
|
||||
* @type {Object.<String, Guacamole.Event.Target~listener[]>}
|
||||
*/
|
||||
var listeners = {};
|
||||
|
||||
/**
|
||||
* Registers a listener for events having the given type, as dictated by
|
||||
* the {@link Guacamole.Event#type type} property of {@link Guacamole.Event}
|
||||
* provided to {@link Guacamole.Event.Target#dispatch dispatch()}.
|
||||
*
|
||||
* @param {String} type
|
||||
* The unique name of this event type.
|
||||
*
|
||||
* @param {Guacamole.Event.Target~listener} listener
|
||||
* The function to invoke when an event having the given type is
|
||||
* dispatched. The {@link Guacamole.Event} object provided to
|
||||
* {@link Guacamole.Event.Target#dispatch dispatch()} will be passed to
|
||||
* this function, along with the dispatching Guacamole.Event.Target.
|
||||
*/
|
||||
this.on = function on(type, listener) {
|
||||
|
||||
var relevantListeners = listeners[type];
|
||||
if (!relevantListeners)
|
||||
listeners[type] = relevantListeners = [];
|
||||
|
||||
relevantListeners.push(listener);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatches the given event, invoking all event handlers registered with
|
||||
* this Guacamole.Event.Target for that event's
|
||||
* {@link Guacamole.Event#type type}.
|
||||
*
|
||||
* @param {Guacamole.Event} event
|
||||
* The event to dispatch.
|
||||
*/
|
||||
this.dispatch = function dispatch(event) {
|
||||
|
||||
// Invoke any relevant legacy handler for the event
|
||||
event.invokeLegacyHandler(this);
|
||||
|
||||
// Invoke all registered listeners
|
||||
var relevantListeners = listeners[event.type];
|
||||
if (relevantListeners) {
|
||||
for (var i = 0; i < relevantListeners.length; i++) {
|
||||
relevantListeners[i](event, this);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Unregisters a listener that was previously registered with
|
||||
* {@link Guacamole.Event.Target#on on()}. If no such listener was
|
||||
* registered, this function has no effect. If multiple copies of the same
|
||||
* listener were registered, the first listener still registered will be
|
||||
* removerd.
|
||||
*
|
||||
* @param {String} type
|
||||
* The unique name of the event type handled by the listener being
|
||||
* removed.
|
||||
*
|
||||
* @param {Guacamole.Event.Target~listener} listener
|
||||
* The listener function previously provided to
|
||||
* {@link Guacamole.Event.Target#on on()}.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the specified listener was removed, false otherwise.
|
||||
*/
|
||||
this.off = function off(type, listener) {
|
||||
|
||||
var relevantListeners = listeners[type];
|
||||
if (!relevantListeners)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < relevantListeners.length; i++) {
|
||||
if (relevantListeners[i] === listener) {
|
||||
relevantListeners.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
};
|
139
guacamole-common-js/src/test/javascript/EventSpec.js
Normal file
139
guacamole-common-js/src/test/javascript/EventSpec.js
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/* global Guacamole, jasmine, expect */
|
||||
|
||||
describe("Guacamole.Event", function EventSpec() {
|
||||
|
||||
/**
|
||||
* Test subclass of {@link Guacamole.Event} which provides a single
|
||||
* "value" property supports an "ontest" legacy event handler.
|
||||
*
|
||||
* @constructor
|
||||
* @augments Guacamole.Event
|
||||
*/
|
||||
var TestEvent = function TestEvent(value) {
|
||||
|
||||
Guacamole.Event.apply(this, [ 'test' ]);
|
||||
|
||||
/**
|
||||
* An arbitrary value to expose to the handler of this event.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
this.value = value;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
this.invokeLegacyHandler = function invokeLegacyHandler(target) {
|
||||
if (target.ontest)
|
||||
target.ontest(value);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Event target instance which will receive each fired {@link TestEvent}.
|
||||
*
|
||||
* @type {Guacamole.Event.Target}
|
||||
*/
|
||||
var eventTarget;
|
||||
|
||||
beforeEach(function() {
|
||||
eventTarget = new Guacamole.Event.Target();
|
||||
});
|
||||
|
||||
describe("when an event is dispatched", function(){
|
||||
|
||||
it("should invoke the legacy handler for matching events", function() {
|
||||
|
||||
eventTarget.ontest = jasmine.createSpy('ontest');
|
||||
eventTarget.dispatch(new TestEvent('event1'));
|
||||
expect(eventTarget.ontest).toHaveBeenCalledWith('event1');
|
||||
|
||||
});
|
||||
|
||||
it("should invoke all listeners for matching events", function() {
|
||||
|
||||
var listener1 = jasmine.createSpy('listener1');
|
||||
var listener2 = jasmine.createSpy('listener2');
|
||||
|
||||
eventTarget.on('test', listener1);
|
||||
eventTarget.on('test', listener2);
|
||||
|
||||
eventTarget.dispatch(new TestEvent('event2'));
|
||||
|
||||
expect(listener1).toHaveBeenCalledWith(jasmine.objectContaining({ type : 'test', value : 'event2' }), eventTarget);
|
||||
expect(listener2).toHaveBeenCalledWith(jasmine.objectContaining({ type : 'test', value : 'event2' }), eventTarget);
|
||||
|
||||
});
|
||||
|
||||
it("should not invoke any listeners for non-matching events", function() {
|
||||
|
||||
var listener1 = jasmine.createSpy('listener1');
|
||||
var listener2 = jasmine.createSpy('listener2');
|
||||
|
||||
eventTarget.on('test2', listener1);
|
||||
eventTarget.on('test2', listener2);
|
||||
|
||||
eventTarget.dispatch(new TestEvent('event3'));
|
||||
|
||||
expect(listener1).not.toHaveBeenCalled();
|
||||
expect(listener2).not.toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
it("should not invoke any listeners that have been removed", function() {
|
||||
|
||||
var listener1 = jasmine.createSpy('listener1');
|
||||
var listener2 = jasmine.createSpy('listener2');
|
||||
|
||||
eventTarget.on('test', listener1);
|
||||
eventTarget.on('test', listener2);
|
||||
eventTarget.off('test', listener1);
|
||||
|
||||
eventTarget.dispatch(new TestEvent('event4'));
|
||||
|
||||
expect(listener1).not.toHaveBeenCalled();
|
||||
expect(listener2).toHaveBeenCalledWith(jasmine.objectContaining({ type : 'test', value : 'event4' }), eventTarget);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("when listeners are removed", function(){
|
||||
|
||||
it("should return whether a listener is successfully removed", function() {
|
||||
|
||||
var listener1 = jasmine.createSpy('listener1');
|
||||
var listener2 = jasmine.createSpy('listener2');
|
||||
|
||||
eventTarget.on('test', listener1);
|
||||
eventTarget.on('test', listener2);
|
||||
|
||||
expect(eventTarget.off('test', listener1)).toBe(true);
|
||||
expect(eventTarget.off('test', listener1)).toBe(false);
|
||||
expect(eventTarget.off('test', listener2)).toBe(true);
|
||||
expect(eventTarget.off('test', listener2)).toBe(false);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user