I'm trying to learn JavaScript, using an OO approach. This is my code:
/*global document, MouseEvent */
MouseEvent.prototype.mouseCoordinates = function () {
return {
'x' : this.pageX - this.target.offsetLeft,
'y' : this.pageY - this.target.offsetTop
};
};
(function () {
var Pencil = function () {},
Canvas = function () {
this.element = document.getElementById('canvas');
this.tool = new Pencil();
this.element.addEventListener('click', this.tool.draw, false);
},
c;
Pencil.prototype.draw = function (event) {
var context = event.target.getContext('2d'),
coordinates = event.mouseCoordinates();
context.fillRect(coordinates.x, coordinates.y, 5, 5);
};
c = new Canvas();
}());
I'm trying to do something like MS Paint. So, I've created a Canvas object and a Pencil one. I am able to do it using a procedural approach but I don't want to. I don't want to use any library now, I'm just studying.
I've these questions:
Are there any good practice to register events? Should I register events using the object constructor?
My canvas object has a tool (pencil in this case) object. Conceptually it's not good. My canvas should not have a tool object. It must provides a surface to draw and that's it! But, it's there as a callback (click event). How could I solve this?
Every tool will have the same interface but different behaviours. Could I create interfaces using Javascript?
What else can I improve?
UPDATE:
(function () {
var pencil = {
draw : function (event) {
var context = event.target.getContext('2d'),
coordinates = event.mouseCoordinates();
context.fillRect(coordinates.x, coordinates.y, 5, 5);
}
},
currentTool = pencil,
canvas = (function () {
var object = {};
object.element = document.getElementById('canvas');
object.element.addEventListener('click', function (event) {
currentTool.draw(event);
}, false);
return object;
}());
}());
I've followed all the tips (thank you, I appreciate your help!). What do you think? Now I have a "global" currentTool variable. What do you think about it? Thanks.
Thank you.
I know you said you don't want to use library but I gotta recommend you look into source code in a good open source library such as jquery. If you want to seriously learn more, you should look at the code real good developers wrote and see how they did for what you just asked. As far as I can tell, that, except for keeping reading, is one of the best way of learning a programming language with good practice.
It's kinda tricky to register events without any framework (capturing
or bubbling phase is only the beginning of your problems), so here
are answers on other questions
Your pencil tool can listen to the canvas events and, eventually,
when someone dispatch a click on it, the pencil tool looks in the
global object (singleton) if it's an active tool. If it is, you
change the color of some appropiate pixels on the canvas.
There's no interface (as in php) in javascript, only prototypical
behaviour. You can, howewer write an abstract class, which methods
(in prototype namespace) will throw an exception "not implemented",
forcing you to override them.
As for improvements, you will surely find yourself fighting with
different browser's behaviour. That's why (well, that's not all)
frameworks exist. As I can see, you like developpent in OO style, I
can give you an advice to try MooTools or, a harder one, Google
Closure framework. Feel free to ask questions about them here.
Related
I'm writing an rgl widget within the htmlwidgets framework so that rgl scenes can be used for output in Shiny apps. Things are basically working (though it's still rough around the edges; see the rglwidget package on http://R-forge.r-project.org ), but it's not nearly as responsive as the native Javascript controls that are already in rgl.
I suspect the problem is the round trip to the server.
Sometimes this will be unavoidable: if you want to make big changes to a scene, you may want to do a lot of calculations in R. But in other cases (the ones covered by the native controls), there's no need for R to be involved, everything can be done in Javascript.
I don't want to duplicate all the work that has gone into writing the Shiny input controls, but I'd like to use them. So my question is:
Is there a way to tell a Shiny input to call a Javascript function when it is changed, not to send its value to the server to be used in a Shiny output?
The answer to my question is "Yes"! It's actually fairly straightforward, at least if the control uses the htmlwidgets framework.
Warning: I'm not very experienced in Javascript, so this may not be very good Javascript style. Please let me know if so, and I'll improve it.
Here's the idea: If I have a Shiny sliderInput() control with inputId = "foo", then my own Javascript code can get it using window["foo"], and can set an "onchange" event handler on it. When that event handler is triggered, I can read the "value" property, and send it to my controls.
If I don't use the reactive inputs from the slider, I don't get the delay from going to the server.
Here's my current renderValue function for the widget:
renderValue: function(el, x, instance) {
var applyVals = function() {
/* We might be running before the scene exists. If so, it
will have to apply our initial value. */
var scene = window[x.sceneId].rglinstance;
if (typeof scene !== "undefined") {
scene.applyControls(x.controls);
instance.initialized = true;
} else {
instance.controls = x.controls;
instance.initialized = false;
}
};
el.rglcontroller = instance;
if (x.respondTo !== null) {
var control = window[x.respondTo];
if (typeof control !== "undefined") {
var self = this, i, oldhandler = control.onchange;
control.onchange = function() {
for (i=0; i<x.controls.length; i++) {
x.controls[i].value = control.value;
}
if (oldhandler !== null)
oldhandler.call(this);
applyVals();
};
control.onchange();
}
}
applyVals();
},
I have a context menuitem which is activated if an image is right-clicked, the exact same way that 'context-copyimage' is activated.
Is it possible to tie/pair that menuitem to the 'context-copyimage' therefore eliminating the need to add extra (duplicate) event-listeners and show/hide handlers??!!
(Adding an observer to 'context-copyimage' defeats the purpose)
If not, is it possible to use the event-listener that 'context-copyimage' uses?
Update:
I am trying to reduce listeners. At the moment, script has a popupshowing listeners. On popupshowing, it checks for gContextMenu.onImag and if true, it shows the menuitem. Firefox's context-copyimage does the exact same thing. I was wondering if it was possible to tie these 2 in order to remove/reduce the in-script event listeners.
I was also chatting with Dagger and he said that:
... the state of built-in items isn't set from an event handler, it's
set from the constructor for nsContextMenu, and there are no
mechanisms to hook into it
So it seems, that is not possible
No, there is no sane way of avoiding the event listener that would perform better than another event listener and is compatible with unloading the add-on in session.
Hooking nsContextMenu
As you have been already told, the state is initialized via gContextMenu = new nsContextMenu(...). So you'd need to hook the stuff, which is actually quite easy.
var newProto = Object.create(nsContextMenu.prototype);
newProto.initMenuOriginal = nsContextMenu.prototype.initMenu;
newProto.initMenu = function() {
let rv = this.initMenuOriginal.apply(this, arguments);
console.log("ctx", this.onImage, this); // Or whatever code you'd like to run.
return rv;
};
nsContextMenu.prototype = newProto;
Now, the first question is: Does it actually perform better? After all this just introduced another link in the prototype-chain. Of course, one could avoid Object.create and just override nsContextMenu.prototype.initMenu directly.
But the real question is: How would one remove the hook again? Answer: you really cannot, as other add-ons might have hooked the same thing after you and unhooking would also unhook the other add-ons. But you need to get rid of the reference, or else the add-on will leak memory when disabled/uninstalled. Well, you could fight with Components.utils.makeObjectPropsNormal, but that doesn't really help with closed-over variables. So lets avoid closures... Hmm... You'd need some kind of messaging, e.g. event listeners or observers... and we're back to square one.
Also I wouldn't call this sane compared to
document.getElementById("contentAreaContextMenu").addEventListener(...)
I'd call it "overkill for no measurable benefit".
Overriding onpopupshowing=
One could override the <menupopup onpopupshowing=. Yeah, that might fly... Except that other add-ons might have the same idea, so welcome to compatibility hell. Also this again involves pushing stuff into the window, which causes cross-compartment wrappers, which makes things error-prone again.
Is this a solution? Maybe, but not a sane one.
What else?
Not much, really.
Yes this is absolutely possible.
Morat from mozillazine gave a great solution here: http://forums.mozillazine.org/viewtopic.php?p=13307339&sid=0700480c573017c00f6e99b74854b0b2#p13307339
function handleClick(event) {
window.removeEventListener("click", handleClick, true);
event.preventDefault();
event.stopPropagation();
var node = document.popupNode;
document.popupNode = event.originalTarget;
var menuPopup = document.getElementById("contentAreaContextMenu");
var shiftKey = false;
gContextMenu = new nsContextMenu(menuPopup, shiftKey);
if (gContextMenu.onImage) {
var imgurl = gContextMenu.mediaURL || gContextMenu.imageURL;
}
else if (gContextMenu.hasBGImage && !gContextMenu.isTextSelected) {
var imgurl = gContextMenu.bgImageURL;
}
console.log('imgurl = ', imgurl)
document.popupNode = node;
gContextMenu = null;
}
window.addEventListener("click", handleClick, true);
this gives you access to gContextMenu which has all kinds of properties like if you are over a link, or if you right click on an image, and if you did than gContextMenu.imageURL holds its value. cool stuff
This code here console logs imgurl, if you are not over an image it will log undefined
I'm using JS and jQuery for the first time after a lot of experience with Java and C++. I'm loving jQuery's idea of $(document).on('click', 'btn-selector', react), but for more complex widgets I'm finding myself in the same rut over and over: in each react handler, I have to look up the widget as a whole and reconstruct all my knowledge about it.
For example, I'm making a simple widget out of <input>s with which the user can make a grading scale: 90 maps to an A, 80 maps to a B, etc. When one of the inputs changes, I want to check to make sure that the inputs are still in order (your scale can't go 90, 70, 80, for example).
So, I have something like
Actual
$(document).on('click', '.scale-input', function() {
var widget = $(this).closest('.scale-widget-container');
ensureLevelsAreInOrder(widget);
});
Almost every single handler has to have this first line to find its context. I'd much rather have code that looks like this:
Preferred
$(document).on('click', '.scale-input', ensureLevelsAreInOrder);
The problem is that in this form, ensureLevelsAreInOrder only has a reference to the input that changed, not the larger context.
In Java or C++, I would have called a constructor on the widget, and each input would have a handler with the context baked in via member variables. I could do something similar with
$(function() {
$('.scale-widget-container').scaleWidget();
});
with scaleWidget() setting up the contextualized handlers, but the page I'm working with loads a lot of its html with ajax and I don't have a reliable time to run that initialization.
Is this a common problem that we just have to deal with if we don't want JS in our HTML, or is there a solution I haven't come across yet?
Not sure what it is you're after exactly, but you don't seem to touch on two quite important concepts when it comes to JS: the event object, and closures. Both of these are open to you to get what you need:
event object:
The callback function is passed an argument, that describes the event itself, and references the elements affected by that event, This isn't exclusive to jQ (just google addEventListener), but it's quite handy:
$(document).on('click', '.scale-input', function(e)//<-- e is our event
{
console.log(e);//check console
});
Which, in vanilla JS would look like this:
document.addEventListener('click', function(e)
{
if (!e.className.test(/\bscale\-input\b/))
{
return e;
}
console.log(e);
}, false);
Another thing you might want to consider is enclosing references to whatever it is you need in an IIFE's scope:
(function()
{
var containers = $('.scale-widget-container'),
localBool = false,
asMany = 'varsAs you need',
previousScales = [],
inputs = $('.scale-input');//references to all DOM nodes you mention
$(document).on('click','.scale-input',function(e)
{
console.log($(this));
console.log(containers);
previousScales.push(this.value);//or something
console.log(previousScales);
//and so on.
});
}());
Hope this helped
Update:
If IE isn't a browser you don't care about that much, you could use one of the DOM-modified events, specifically DOMTreeModified:
(function()
{
var nodes = [];//<-- store current nodes here, if applicable
nodes.containsNode = function(node)
{
var i;
for (i=0;i<this.length;i++)
{
if (this[i] && this[i] === node)
{//node is set, return its index
return i;
}
}
//node not found, return -1
return -1;
};
document.body.addEventListener('DOMSubtreeModified',function(e)
{
var all = document.getElementsByClassName('scale-input'),
i;
for (i=0;i<all.length;i++)
{
if (nodes.containsNode(all[i]) === -1)
{
nodes.push(all[i]);//add new
}
}
},false);
}());
More on the mutation events, and their issues, on the DOM events wiki
I am making a custom events object so that I can inject custom events into another object, so far I have (simplified),
function Game() {
new Events(["pause", "resume"], this);
};
function Events(events, obj) {
// Event object for object
obj.events = {};
// For each event
events.forEach(function(event) {
// Attach event array to store callbacks
this.events[event] = [];
}, obj);
// Fire event
obj.fire = function(event) {
////////////
};
// Add event
obj.on = function(event, callback) {
////////////
};
};
My question is, is this the right way to do this? Is it considered ok to call Event from Game and add to Game from Events? It for some reason seems wrong to me, and I do not know why?
Is there any way that I should be structuring this code that I am not aware of?
(I do not want to add Events to Game's prototype for the sole reason that Game has events and is not an extension of Events)
Thank you for taking the time to read my question.
My question is, is this the right way to do this? Is it considered ok to call Event from Game and add to Game from Events?
Yes, it's perfectly fine. This is called the decorator pattern. I don't see a reason for your feeling that it was wrong.
Is there any way that I should be structuring this code that I am not aware of?
Don't use new. Events is not a constructor. A better and more descriptive signature might be
function makeEventEmitter(obj, events) {
That how I usually implement my plugins:
(function($){
$.fn.plugingName = function(inSettings){
//code that extands settings with default
return this.each(function(){
new PluginClass($(this), settings);
});
}
PluginClass = function(jElem, settings){
//here I usually put variable and function in such a way to compose better
var events = {
onMouseUp : function(){
//some actions
},
onMouseMove : function(event){
//some actions
},
//etc.
},
draw = {
drawGrid : function(){//some actions},
//etc.
},
//etc.
/*****************************
* This is a place of question
****************************/
}
})(jQuery);
I just want to know if there is a pattern to separate the algorithm from declaration part that I described above. Some thing like to put all your algorithm part inside
function main(){
}();
If there is a better approach to distinguish the main part of your algorithm. I hope I described everything clear.
All others improvment that might be done with represented code are also appreciate.
You mean putting the function main() outside of the big wrapping function($){ ...? But this is not desired, the wrapping function is there for the very reason not to overcrap the global namespace. This is the very clean design and it is desired. So don't be worry to use this standard pattern.