Event based editor like in StarCraft 2 Editor (algorithm) - javascript

I'm trying to create an algorithm for an event based editor like in StarCraft 2 Editor that can support:
Create UI
Play sounds
Handle keyboard/mouse inputs
Display messages
Button(or some referenced UI object) is pressed etc.
Pretty much the same thing as in StarCraft 2 Editor (of course not the 3D stuff too)
So far I'm thinking to use JSON , add every event in an object and then loop through them and create an event using the addEventListener() method.
The JSON Events Object(of course it will be created by the user in the editor with no programming):
var Events={
//your event's names here
onReady:{ //on page ready to manipulate
displayMessage:{//just a simple popup
text:"Hello user!",
title:"Welcome!",
type:"normal",
},
createButton:{ //creates a buton on the screen
text:"Click me!",
id:"myButton"
}
},
onClick:{
id:"myButton" ,//the id of the button we just created
actions:{ //the actions applied after we click the button
displayMessage:{//just a simple popup
text:"You pressed me!",
title:"Button",
type:"error",//show the message as an error
}
}
}
}
I found some softwares (GameMaker,Construct 2,GameDevelop) that have an event based editor if you would like to get an idea about what I'm talking about (if you don't already know about StarCraft 2 Editor)
My question is:
What is the best algorithm that I can use to achieve this?

Sounds like a job for jQuery UI.
When the user creates a custom area in your editor all it's attributes are stored inside an object (that you can save as JSON) that would then be applied to a div as param when loading the map (using html-attributes.
function create_areas(areas){
var map = $('#map_area');
for(var i=0;i<areas.length;i++){
map.append($('<div>', area[i].params));
}
}
whereas params would look something like this:
params = {
width: 100,
height: 200,
....
mousedown: function(){ play_music('hello'); },
keydown: function(e){ alert('you pressed ' + e.keyCode; }
}
also the jQuery UI tools like draggable and resizeable should ease up building your editor.

I'd model this more after backbone's event system:
events: {
'click selector': handler,
'mouseover selector': handler2,
...
}
Handlers can be any javascript function, this would allow you to create a bunch of pre-defined functions like displayMessage.
Then you could curry your own handlers, which would allow your users to specify configuration if they need it.
Example:
var events = {
'click element': displayMessage({
text:"Hello user!",
title:"Welcome!",
type:"normal",
}),
'mouseover pizza': createButton({...})
}
function displayMessage(options) {
var options = options;
return function() {
//display message logic
}
}
Then you can supply a compose function among other helpers (look up promises perhaps?) to combine your functions together:
var events = {
'click element': compose(
displayMessage({
text:"Hello user!",
title:"Welcome!",
type:"normal",
}),
createButton({})
),
'mouseover pizza': createButton({...})
}
This could work out?
Caveat: it might be better if events was an array that contained objects. That way you can have multiple click handlers on some selector without collisions.

The way I see this there are really severall choices you need to make. I would, although I prefer JSON as a data construct not limit myself to this subset of an actuall programming language. And engener this the other way around.
You have events, handlers and options. Where a option, or better a option list is the user inputed data, the handlers are the actual action, and the events are triggers to set some action off.
If you read this carefully you will notice this is the exact description of the basic structure of most jQuery-Scripts or Event-Driven Software in generall. Only the users options in jQuery are (since it is a DOM Framework) most often the context of a single DOM-Element. So, here we are and I would suggest to simply borrow the theorie behind this and make use of promisses wich make a very clear and great way to generate code!
So my call to any event chain would look like this.
...when(chainObject['event'])
.then(function(event) {
//call handler
handlers[chainObject[selectedHandler]].call(event.context, chainObject['options']);
//apply next element(s) in chain, this is the current promise
appendNextElement(chainObject['followingHandlers'], this);
})...
Notice how apply makes it easy for you to change the environement and in turn behaviour of any hanlder based on what the user and event did. And promisses make error handling very easy!
This of course applies to only one node in your chain. So what should a data structure look like to let you generate this kind of code?
One node in your structure would look like this:
{
event: 'click',
selectedHandler: 'sohwText',
options: {
'text': 'helloWorld'
},
followingChain: {...OTHER HANDLERS....}
}
The important thing to notice is that like a good structured functional programm you are looking at a tree and not at a simple list of events. So every actual DOM Element holds many of these
var eventTree = {
'.someButton': [..Handlers of this button...],'
'.someOtherButton': [..Handlers of the other button...],
}
And there we go. You have a context (the button), a event, user input and a handler.
The resulting app should not only work, but will be styled for any experienced JavaScript-Programmer to expand or mod.

Related

How to Get Event Listener of an Element

Is there any way to get the list of all event listeners of an element on the HTML page using JavaScript on that page.
Note: I know we can see them with Chrome dev tools event listeners but I want to log/access see list using the JavaScript of the page.
Also, I know we can get them through jQuery but for that, we also have to apply the events using jQuery, but I want something that would be generic so I could also access the event listeners applied to other elements such as web components or react components.
If you really had to, a general way to do this would be to patch EventTarget.prototype.addEventListener:
const listeners = [];
const orig = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(...args) {
if (this instanceof HTMLElement) {
listeners.push({
type: args[0],
fn: args[1],
target: this,
});
}
return orig.apply(this, args);
};
document.body.addEventListener('click', () => console.log('body clicked'));
console.log(listeners[0].fn);
click this body
To find listeners attached to an element, iterate through the listeners array and look for targets which match the element you're looking for.
To be complete, also patch removeEventListener so that items can be removed from the array when removed.
If you need to watch for listeners attached via on, then you'll have to do something similar to the above to patch the HTMLElement.prototype.onclick getter/setter, and for each listener you want to be able to detect.
That said, although you said you want a generic solution, rather than patching built-in prototypes, it'd be better to add the listeners through jQuery or through your own function.
What I did when I had a similar problem is add a data attribute when the listener was set, so I could identify it later.
At the end of the function that adds the listener:
elm.setAttribute('data-has_mask', true);
At the beginning of that same function:
if("true" == elm.getAttribute('data-has_mask')) {
return;
}
Maybe not exactly what the OP is looking for, but I was having a lot of trouble with this, and this is an obvious solution for a particular use case, and I guess it might help someone out.

How to implement OnEnter and OnExit event on ContentControl using Javascript API for Word 2016

There are many contentcontrols in a document and I need to find out a way that the cursor is in which content control so that I will select that control and do the operation accordingly. I think by implementing onEnter and onExit events for contentcontrols , I can achieve it. But I don't know how to declare and invoke those eventhandlers in JavaScript API. Any help is really appreciated.
You would need to use a combination of APIs to implement that functionality with the current API set:
First add an event handler for the Document.selectionChanged event.
Every time the event fires, get the Range object corresponding to the selection in the document, using the Document.getSelection() API.
Check the range to see if there's a content control in it, using the Range.contentControls relationship.
-Michael (PM for add-ins)
Good question! We do have an onEnter event for content controls (we call it binding.selectionChanged. We also have a binding.dataChanged event who gets triggered if the user changes the content and exits the content control
so an alternative solution to what Michael proposed is to create bindings for each content control in the document and then register for such events.
you can achieve this by:
1. traversing the content control collection.(use body.contentControls collection)
2. for each content control, grab or set the title and use it to create a binding by named item. check the bindings.addFromNamedItem method.
3. on the callBack make sure to subscribe to the selectionChanged (or DataChanged) for the binding.
the create binding code and register to the events will look like this:
function CreateCCSelectionChangedEvent() {
Office.context.document.bindings.addFromNamedItemAsync("TitleOfTheContentControl", { id: 'Binding01' }, function (result) {
if (result.status == 'succeeded') {
result.value.addHandlerAsync(Office.EventType.BindingSelectionChanged, handler);
}
});
}
function handler() {
console.log("Event Triggered!");
}
Hope this helps!
Michael,
My company tried this approach a few years ago in a VSTO addin. It ended badly. The problem is the number of events you have to handle is horrific. The performance penalty is drastic and grows with the document size.

Detach event handlers selectively

I have a html richText editor. My code structure is like this:
function richTextEditor(div)
{
var self=this;
self.instanceIdentifier=Math.floor(Date.now());
//Richtext editor creation logic
$(document).on('click.'+self.instanceIdentifier,function()
{
//some logic
})
self.destroy=function()
{
//delete all properties of self
// detach all listeners
$(document).off('click.'+self.instanceIdentifier) ;
}
}
Our app is single page application, and there are multiple richtexteditor instances opened in different panes. I need to destroy the instance when the node corresponding to this has been removed. Destroy should remove all the event handlers attached by that instance.
So far Date.now() for uniquely identifying the handler is working but I think there must be some elegant way to do that.
var div1=$('#notes')[0];
var editorInstance1=new richTextEditor(div1);
//remove is not a valid jquery event, its just for illustration
// I am getting remove event from another library
$(div1).on('remove',function(){
editorInstance1.destroy();
})
Please suggest if this is the correct way to go.
What you want is a GUID or UUID. There is a great answer to this question here.

Keyboard shortcuts and Durandal dialogs

Using Durandal,
app.showMessage(message, title);
shows a modal dialog with the supplied message and title, and a single primary button OK. All this is styled by Bootstrap.
It doesn't seem to respect the keyboard. Clicking the OK button with the mouse works as expected, but neither the ESC nor ENTER keys clicks the OK button.
I have yet to find anything useful in the rather Spartan Durandal documentation, but sometimes that means it's covered by the documentation of a subsystem such as Knockout or Bootstrap.
Can anyone suggest a strategy or some pertinent reading on the customary browser independent way to bind buttons to keyboard shortcuts?
I find it so hard to believe that Durandal dialogs ignore the keyboard that I wonder whether I've screwed something up.
For a few moments I thought I had an answer in CSS specified behaviour. It was even specified in a W3C document. But it's obsolete and not supported.
No doubt I can present a view and view model modally. This will give me a chance to bind additional behaviour, and I can pass parameters, pretty much plug-in replacing app.showMessage() but it all seems rather heavy handed.
I noticed today that spacebar closes message dialogs. Presumably with only one focusable control, that's where the focus is. Given the Durandal habit of minimalism this might even be by design, it's hard to say.
You can use jwerty, which can be found here. We use jwerty in our Durandal project (as does Ayende Rahien in RavenDB's new HTML5 Raven Studio, which is also written with Durandal).
To see the rich keyboard support with jwerty in action, take a look at this video on my DropBox account of a Dropdown Datepicker that I wrote entirely with Durandal (and jwerty).
If you're interested, I can show you how to integrate jwerty into Durandal (pretty trivial).
[EDIT]
There are two strategies for using jwerty with Durandal: inline and custom Knockout binding.
INLINE
Below is a snippet directly from our CalendarNavigationEngine:
//Bind the directional keys
CalendarNavigationEngine.prototype.bindDirectionalKeys = function () {
var that = this;
var kContainerClass = this.kContainerClass;
//Bind directional keys
jwerty.key('left', function () { that.prevItem(); }, kContainerClass);
jwerty.key('up', function () { that.prevWeek(); }, kContainerClass);
jwerty.key('pgup / ctrl+left', function () { that.prevPage(); }, kContainerClass);
jwerty.key('right', function () { that.nextItem(); }, kContainerClass);
jwerty.key('down', function () { that.nextWeek(); }, kContainerClass);
jwerty.key('pgdown / ctrl+right', function () { that.nextPage(); }, kContainerClass);
jwerty.key('home', function () { that.firstItem(); }, kContainerClass);
jwerty.key('end', function () { that.lastItem(); }, kContainerClass);
jwerty.key('enter / tab', that.select.bind(that), kContainerClass);
};
We usually call bindDirectionalKeys Durandal's compositionComplete handler on the viewModel. In this case, the call chain is much deeper as CalendarNavigationEngine is not a viewModel.
By inline I mean to say that you are making the call directly from your viewModel (or module), as shown above. kContainerClass is the DOM class on the element, or elements, that has focus and should receive and process keyboard events.
With this approach, you need a corresponding unbind method:
//Unbind key bindings
CalendarNavigationEngine.prototype.clearKeyBindings = function () {
$(this.kContainerClass).unbind('keydown.jwerty');
};
This would [eventually] be called from Durandal's detached handler, and cleans up all key bindings in the context of kContainerClass to avoid memory leaks. THIS IS NOT OPTIONAL.
CUSTOM KNOCKOUT BINDING
There are plenty of resources on StackOverflow on how to write custom Knockout bindings. But basically, the strategy involves binding the DOM element to a jwerty key binding. This is pretty trivial, although we did not take this approach (we are considering it).
There is one caveat: Knockout does not permit multiple identical bindings. So you cannot call a jwerty binding multiple times, which is what you would need to do in order to associate multiple key bindings with a single element (as in the example I gave above). However, what you can do is bind an array of key bindings to a single element, and then just have your custom Knockout binding, in its init handler, iterate over that array, binding with jwerty for each item. Again, still trivial.

Sending data from classes?

I'm looking into deferred and custom events. I'm not sure what method would suit my application.
I have a class that calls another class. Inside this class a user can drag files on to the window.
Once the file has been dragged on, I wish to send details about the files to my main class.
I've thought about using deferred, but the user needs to drag files over and over again, and as of my understanding this can only be used once.
So in my main class I:
this.dropBox = new DropBox();
Then in the DropBox class I have:
$(window).on('drop', this.drop);
But what should I put in my 'drop' method. Every time something is dropped I wish to 'alert' my main class about it and act upon it. How can I 'listen' for the event. Should I use deferred, custom event or something else?
There are typically two options for this:
Delegate
A delegate should implement a certain "interface", a set of functions that handle certain events.
function DropBox(delegate)
{
this.delegate = delegate;
$(window).on('drop', $.proxy(this, 'drop'));
}
DropBox.prototype.drop = function(e) {
// do stuff with event
this.delegate.drop(e);
}
// inside main instance
this.dropBox = new DropBox(this);
// delegate interface
this.drop = function(e) {
// handle file drop
};
Callback
If the delegate only needs one function, you can use a callback as well:
function DropBox(dropEventHandler)
{
this.dropEventHandler = dropEventHandler;
$(window).on('drop', this.drop);
}
DropBox.prototype.drop = function(e) {
this.dropEventHandler(e);
};
// inside main instance
var self = this;
this.dropBox = new DropBox(function(e) {
// handle file drop
// use 'self' to reference this instance
});
Why not just give a callback over to the DropBox?
Well, like this code in the main class:
this.dropBox = new DropBox(function(fileInfo) {
// this code can be executed by the DropBox Object multiple times!
});
And the DropBox:
window.DropBox = function(callback) {
this.userHasDroppedFiles = function(fileinfo) {
// do stuff
callback(fileinfo); // give the fileinfo back with the callback!
}
}
Also, there are no classes in JavaScript! You have only Objects, and you can use constructor-functions combined with prototypes to generate a class like behaviour, but you will never actually have classes like in Java, C# or similar languages. Keep this always in mind.
Some JS frameworks build their own class layer on top of the main JS possibilities, then you may have Framework classes, but also never native JavaScript classes, because native JavaScript classes dont exist!

Categories