Javascript - Array of prototype functions - javascript

I'm a javascript newbie so I'm writing ugly code so far sometimes due to my lack of experience and how different it is to the languages I'm used to, so the code I'll post below works, but I'm wondering if I'm doing it the right way or perhaps it works but it's a horrible practice or there is a better way.
Basically, I have a little dude that moves within a grid, he receives from the server an action, he can move in 8 directions (int): 0:up, 1: up-right, 2: right... 7: up-left.
the server will send him this 0 <= action <= 7 value, and he has to take the correct action... now, instead of using a switch-case structure. I created a function goUp(), goLeft(), etc, and loaded them in an array, so I have a method like this:
var getActionFunction = actions[action];
actionFunction();
However, what to set all this up is this:
1) create a constructor function:
function LittleDude(container) {
this.element = container; //I will move a div around, i just save it in field here.
}
LittleDude.prototype.goUp() {
//do go up
this.element.animate(etc...);
}
LittleDude.prototype.actions = [LittleDude.prototype.goUp, LittleDude.prototype.goUpLeft, ...];
//In this array I can't use "this.goUp", because this points to the window object, as expected
LittleDude.prototype.doAction = function(action) {
var actionFunction = this.actions[action];
actionFunction(); //LOOK AT THIS LINE
}
Now if you pay attention, the last line won't work.. because: when i use the index to access the array, it returns a LittleDude.prototype.goUp for instance... so the "this" keyword is undefined..
goUp has a statement "this.element"... but "this" is not defined, so I have to write it like this:
actionFunction.call(this);
so my doAction will look like this:
LittleDude.prototype.doAction = function(action) {
var actionFunction = this.actions[action];
actionFunction.call(this); //NOW IT WORKS
}
I need to know if this is hackish or if I'm violating some sort of "DO NOT DO THIS" rule. or perhaps it can be written in a better way. Since it seems to me kind of weird to add it to the prototype but then treating it like a function that stands on its own.

What you are trying to do is one of the possible ways, but it is possible to make it more simple. Since object property names are not necessary strings, you can use action index directly on prototype. You even don't need doAction function.
LittleDude = function LittleDude(container) {
this.container = container;
}
LittleDude.prototype[0] = LittleDude.prototype.goUp = function goUp() {
console.log('goUp', this.container);
}
LittleDude.prototype[1] = LittleDude.prototype.goUpRight = function goUpRight() {
console.log('goUpRight', this.container);
}
var littleDude = new LittleDude(123),
action = 1;
littleDude[action](); // --> goUpRight 123
littleDude.goUp(); // --> goUp 123

actionFunction.call(this); //NOW IT WORKS
I need to know if this is hackish or if I'm violating some sort of "DO NOT DO THIS" rule. or perhaps it can be written in a better way.
No, using .call() is perfectly fine for binding the this keyword - that's what it's made for.
Since it seems to me kind of weird to add it to the prototype but then treating it like a function that stands on its own.
You don't have to define them on the prototype if you don't use them directly :-) Yet, if you do you might not store the functions themselves in the array, but the method names and then call them with bracket notation:
// or make that a local variable somewhere?
LittleDude.prototype.actions = ["goUp", "goUpLeft", …];
LittleDude.prototype.doAction = function(action) {
var methodName = this.actions[action];
this[methodName](); // calls the function in expected context as well
}

Related

ES6 calling method in another class, using modules

I know there is many questions like this asked, but I have been searching for hours and can't find any answers. I have this method, which takes in a parameter, which should be ID of two selects. Using this parameter, I want to determine which select is used and execute the if statement, but to no avail. When I run it, it shows no errors in console in Chrome and it does nothing. Can anyone shed some light on it, this is the method in one export class:
static styleCircle(select) {
if(this.select === ELEMENTS.ELEMENT_COLOR_SELECT) {
var getColor = ELEMENTS.ELEMENT_COLOR_SELECT;
var colorValue = getColor.options[getColor.selectedIndex].value;
ELEMENTS.ELEMENT_STYLE_CIRCLE.style.backgroundColor = colorValue;
} else if(select == ELEMENTS.ELEMENT_BORDER_SELECT) {
var getRadius = ELEMENTS.ELEMENT_BORDER_SELECT;
var radiusValue = getRadius.options[getRadius.selectedIndex].value;
ELEMENTS.ELEMENT_STYLE_CIRCLE.style.borderRadius = radiusValue;
}
}
This is it being called in another class, on two select elements, and the class is imported at the top of the file:
ELEMENTS.ELEMENT_COLOR_SELECT.onchange = Script.styleCircle(this);
ELEMENTS.ELEMENT_BORDER_SELECT.onchange = Script.styleCircle(this);
ELEMENTS is a file with constants, which are just being used to get ID's from the HTML file. I used other methods like this, with onclick events, but none had parameters, and now I'm stuck here. Thanks in advance.
You don't want to call the functions right now but instead you probably want to pass functions. Through that you can access the proper this and pass it to styleCircle:
ELEMENTS.ELEMENT_COLOR_SELECT.onchange = function() {
Script.styleCircle(this);
};
ELEMENTS.ELEMENT_BORDER_SELECT.onchange = function() {
Script.styleCircle(this);
};
Additionally this.select is probably causing you troubles as window.select is undefined.
First step would be to try debugging and ensure select is equivalent to either of those constants. Make sure you have full branching coverage in your debugging. That would mean start by adding an else statement to that if/else if statement - it's possible that your select is not equal to either constant and so neither branch is run.

Using a parameter with a Javascript object?

Currently I can do:
function addStat() {
player.str = player.str + 1;
}
But I want to be able to use things other than just "str" with my player object. So I decided with doing something like this:
function addStat(stat) {
player.stat = player.stat + 1;
}
But that doesn't seem to work, iv'e tried looking up the syntax for using parameters but could not find anything similar to the way I need.
I learned about "this" but I can't get it to work with my function.
I thought this:
function addStat(thing, stat) {
thing.stat = thing.stat + 1;
statReset();
}
would work but I can see why it won't. I made sure the rest of my javascript and html work and when I add those functions nothing breaks, it just doesn't work.
When assigning properties with a variable, you need to use bracket notation, as opposed to dot notation. This, then, looks like:
function addStat(stat) {
(stat in player) ? ++player[stat] : player[stat] = 1;
}
Due to comments (that I disagree with), I figured I should mention that since you are attempting to modify a property that may not exist, you should also add a safety check to see if you can modify it.
Otherwise you will be modifying undefined, and that will cause undesired output..
You can access properties with []:
function addStat(prop) {
player[prop] = player[prop] + 1;
}
so calling addStat("stat") will actually set player.stat.
In javascript, the syntax
object.key
is equivalent to
object["key"]
So your thing.stat is equivalent to thing["stat"], i.e. the key is the literal string "stat" when what you really want is to use the value referenced by the parameter stat as the key:
thing[stat] = thing[stat] + 1;

Variable Dependency with knockoutJS

I'm building an application with KnockoutJS with a component that essentially acts as a sequential spreadsheet. On different lines users may define variables or use them to represent a value.
So for example
x =2
x //2
x = 4
x //4
I have this working in the straightforward case of continuing adding new lines. The output function for each line checks and iterates backwards to see if the variable was ever defined previously. If it was it uses the first example it finds and sets that as the value. This works when initially defining the lines, and also works when you edit a line after a previous line has changed.
However, I would like variables to update if a previous definition of that variable has changed, been removed, or been added. That behavior does not exist right now. I have tried adding my own custom dependency handling code using a map to track the variables, but it badly impacted performance. I would like to tap into Knockouts dependency management to solve this, but I'm not sure of the best way to do so. Here is a brief summary of my code structure, I would be happy to add more detail if needed.
calcFramework is the view-model object I bind to the map. It consists of an observable list of Lines, a varMap, and other unrelated properties and functions
Line is a custom object. The relevant code is below
var Line = function (linenum,currline) {
var self = this;
self.varMap = {};
self.input = ko.observable("");
self.linenum = ko.observable(linenum);
self.lnOutput = ko.computed({
read:function(){
return outputFunction(self,self.input());
},
write:function(){},
owner:self
});
};
function outputFunction(self,input) {
try{
var out = EQParser.parse(input,10,self);
return out.toString();
}
catch(ex){
//error handling
}
}
Line.prototype.getVar = function (varName, notCurrentLine) {
if(typeof varName === "undefined"){
return null;
}
//Actually don't want ones set in the current varMap, only past lines
if(varName in this.varMap && notCurrentLine){
return this.varMap[varName];
}
if (this.linenum() > 0) {
var nextLine = calcFramework.lines()[this.linenum() - 1];
return nextLine.getVar(varName,true);
} else {
//eventually go to global
return calcFramework.varMap[varName];
}
};
Line.prototype.setVar = function(varName,value){
this.varMap[varName] = value;
};
SetVar and getVar are passed to eqParser, which gets the value of the expression, calling those functions as needed if a variable is referenced. So the variable value is not explicitly passed to the function and thus knockout does not view it as a dependency. But I'm not sure how I would pass the variable as a parameter without traversing the list every time.
So my question is, given this setup, what is the best way to track changes to a variable assignment (and/or new assignments) and update the lines that reference that variable, while maintaining good performance.
I recognize my question is lengthy and I have attempted to trim out all unnecessary detail. Thanks for your patience in reading.
I would be tempted to use a publish/subscribe model, using something like Peter Higgins' PubSub jquery plugin
Your overall app would subscribe/listen out for lines publishing an event that they have a variable definition. This would store any variable names in a standard javascript hashtable, along with the value. When a variable found event is published by a line, the app would check through all the known variables, and if it finds that it is a change to an existing variable value, it would publish a variable changed event. All the lines would subscribe to that event. They can then check whether they have a variable matching that name, and update the value accordingly.
Here's some untested code to give you an idea of what I mean:
var app = function()
{
var self = this;
self.variables = {};
$.subscribe('/variableAssigned', function (key, value)
{
// I think that this is the best way of checking that there is a variable
// in the object
if(self.variables.hasOwnProperty(key))
{
if(self.variables[key] !== value)
{
$.publish('/variableChanged', [ key, value ]);
}
}
});
}
In your Line object:
$.subscribe('/variableChanged', function (key, value)
{
// loop through varMap and see if any of them need updating.
});

Building Classes in javascript for beginner

I'm well versed with javascript and jQuery. I've just never built a class before. Maybe a class is not even what I'm looking for.
I have a function I call to launch an overlay and it's used a lot and contains some parameters.
function launchOverlay(method, content, width, closeBtn) {
$("body").append('<div id="overlay-backdrop" style="display:none"></div>');
$("#overlay-backdrop").css({
width: $(document).width(),
height:$(document).height()
}).fadeIn();
$("#overlay-backdrop").append('<div id="overlay-canvas-area"><div class="inner-canvas-area"></div><div>');
if(typeof closeBtn == 'undefined'){
$("#overlay-canvas-area").append('<div class="close-btn"><a class="close" onClick="closeOverlay()">Close</a></div>');
}
if (method == "load"){
$("#overlay-canvas-area .inner-canvas-area").load(content);
}if(method == "append"){
$("#overlay-canvas-area .inner-canvas-area").append(content);
}
var canvasAreaWidth = width+($("#overlay-canvas-area").width());
var canvasAreaHeight = $("#overlay-canvas-area").height();
$("#overlay-canvas-area").animate({
top:((($(document).height())-(canvasAreaHeight))/2),
left: ((($(document).width())-(canvasAreaWidth))/2)
},700);
}
I find myself modifying this constantly to fit my needs and then going back to old instances and modifying the function call. I would also like to pass json as settings with the function.
first question is, are classes what I'm looking for?
if so, where is a good place to learn?
if not, what should I do to improve functionality?
A class (or in javascript a "prototype" is actually the more correct term) is appropriate when there is a lasting object that contains some data and then you want to operate on that data with multiple different methods over time.
The class allows you to neatly specify how the data is stored and what methods operate on the data.
If you just have one operation that produces an output and can take in a variety of different input data, then you won't really benefit from a class. You just need a function that takes a variety of parameters and chooses its operation based on what was passed to it.
In javascript when there are lots of options for a function and they may be variable, then it is sometimes common to pass in an options object that contains properties that direct the operation of the function. The function can then examine which properties are present and what values they have to select how it should behave. The use of the options object can allow much simpler maintenance any time you want to add or modify a parameter rather than continuing to add more and more function arguments. An options object like this can also be passed around more easily rather than passing every single argument individually. You can also create a default state for the options object that contains all the default values for the arguments (what their value should be if they aren't passed). While all of this can be done with multiple traditional function arguments, it can be a lot cleaner to code with an options object.
The use of an options object would look like this:
function doWhatever(mainData, options) {
if (options.foo) {
// do it one way
} else {
// do it the other way
}
}
doWhatever(myData, {foo: true, output: "commas", fee: "whatever"};
For a straight class like implementation I like this: http://ejohn.org/blog/simple-class-instantiation/
But it sounds like you want to create something that in jQuery parlance would be called a 'widget', which gives you a lot of what you are asking for free, plus more:
http://ajpiano.com/widgetfactory/#slide1
http://wiki.jqueryui.com/w/page/12138135/Widget%20factory
http://bililite.com/blog/understanding-jquery-ui-widgets-a-tutorial/
I agree with the others because this function is entirely behavior with no state. You might consider putting the function into a namespace along with related UI helper functions, but that's strictly for better organization and ease of use on multiple pages.
Also, you could improve the readability and performance of this function by storing the jQuery objects returned by append and reusing them for further calls.
function launchOverlay(method, content, width, closeBtn) {
var $overlay = $("body").append('<div id="overlay-backdrop" style="display:none"></div>');
$overlay.css({
width: $(document).width(),
height:$(document).height()
}).fadeIn();
var $canvas = $overlay.append('<div id="overlay-canvas-area"><div class="inner-canvas-area"></div><div>');
if (typeof closeBtn == 'undefined') {
$canvas.append('<div class="close-btn"><a class="close" onClick="closeOverlay()">Close</a></div>');
}
if (method == "load") {
$("#overlay-canvas-area .inner-canvas-area").load(content);
}
else if (method == "append") {
$("#overlay-canvas-area .inner-canvas-area").append(content);
}
var canvasAreaWidth = width + $canvas.width();
var canvasAreaHeight = $canvas.height();
$canvas.animate({
top: (($(document).height() - canvasAreaHeight) / 2),
left: (($(document).width() - canvasAreaWidth) / 2)
}, 700);
}
Note that I've left the var declarations inline to match your style, but you should be aware that their declarations are hoisted to the top of the function. Here it doesn't matter, but it could bite you in more complicated functions later.

Extremely annoying JavaScript array/object error

Basically, I am rewriting part of one of my web applications. I had a script that would collapse some or all panels of the interface at once, and another to code them.
However, my old functions looked really ugly, and were annoying to type and not powerful enough:
function collapse_all()
{
document.getElementById("panel_1").style.display="none"
document.getElementById("panel_2").style.display="none"
document.getElementById("panel_3").style.display="none"
}
function expand_all()
{
document.getElementById("panel_1").style.display=""
document.getElementById("panel_2").style.display=""
document.getElementById("panel_3").style.display=""
}
Now I have this:
function panel() //first variable in argument is collapse or expand, all others are panels to act on
{
var panels = panel.arguments
alert(typeof panel.arguments)
var mode = panels.shift() //here's my problem
if(mode=="collapse") {mode="none"}
if(mode=="expand") {mode=""}
var items = panels.length
for (i = 0;i < items;i++) {document.getElementById(panels[i]).style.display=mode}
}
panel("collapse","panel_1","panel_2","panel_3")
I have a problem though. Firebug tells me panels.shift() is not a function. With some Googling I managed to find out that panel.arguments isn't an array but an object, so I can't use array methods on it. I'm just really confused as to how I could either convert the object into an array or find another workaround, as I know next to nothing about JavaScript objects. Some example code would be highly appreciated.
You can convert the arguments object into an array like this:
var argsArray = Array.prototype.slice.call(arguments);
What this does is use the slice method common to all arrays via Array.prototype to create a genuine Array object from the array-like arguments. call() (a method of all functions) is used to call this slice method with a this value of arguments and no parameters, which has the effect of copying all of the elements of this into a new array. This may seem devious or hacky but it is actually designed into the language: see the note at the bottom of section 15.4.4.10 of the ECMAScript 3rd Edition spec.
Also, within a function you are provided the arguments object as a variable, so you don't need to access it as a property of the function object as you are doing. In your case, just use arguments rather than panel.arguments.
You could keep it much simpler (cleaned up your formatting, semi-colons, etc.):
function panel()
{
var panels = Array.prototype.slice.call(arguments);
var displayMode = (panels[0] == "collapse" ? "none" : "");
for (var i = 1; i < panels.length - 1; i++)
{
document.getElementById(panels[i]).style.display = displayMode;
}
}
Also, if you're rewriting your application, it might be a good time to consider using things like jQuery. You could assign each one of your panels a certain class name, and reduce your code to something like this:
function panel(hide)
{
$('.className').css({ display: (hide ? 'none' : '') });
}
which you could use like so:
panel(true); // or
panel(false);
Or, because now it's so syntactically simple, you might as well just create two separate functions so that your code is straightforward and you know exactly what it's going to do from the function names alone:
function showPanels() {
$('.className').css({ display: '' });
}
function hidePanels() {
$('.className').css({ display: 'none' });
}
And finally, if you don't worry about doing it via CSS, you could really shorten your script to this, which can't be any clearer:
function showPanels() {
$('.className').show();
}
function hidePanels() {
$('.className').hide();
}
Cheers!

Categories

Resources