I have a javascript app that I'm developing where I'm building an action tree on the fly and I've found myself in the curious situation of wanting to purposefully introduce a circular dependency. After an initial very hacky attempt, I discovered that JavaScript variable scoping actually introduces a a pretty reasonable way to solve this. I'm still no expert at JavaScript, so I wanted to get some input on best practices. Here is a snippet of working code:
var Step = function(title, instructions, action) {
this.title = ko.observable(title);
this.instructions = instructions;
this.action = action;
};
var Action = function(cancelText, cancelDescription, cancelStep, nextDescription, addText, addStep, continueText, continueStep) {
this.cancelText = cancelText;
this.cancelDescription = cancelDescription;
this.cancelStep = cancelStep;
this.nextDescription = nextDescription;
this.addText = addText;
this.addStep = addStep;
this.continueText = continueText;
this.continueStep = continueStep;
};
var PersonalStep = new Step(
"Contact Information",
"How can we contact you about your awesome assortment of vehicles? Fill out the form below",
new Action(null, null, null, null, null, null, null, null)
);
var AddVehicleStep = new Step(
"Additional Vehicle",
"You have another car? Great, tell us about it too!",
new Action("Cancel",
"No, nevermind about this vehicle.",
PersonalStep,
"Add another vehicle?",
"+ Add",
AddVehicleStep, // This is the weird bit to me
"No, continue on",
PersonalStep)
);
var VehicleStep = new Step(
"Vehicle Details",
"Tell us about your primary vehicle by filling out the form below.",
new Action(null, null, null,
"Add another vehicle?",
"+ Add",
AddVehicleStep,
"No, continue on",
PersonalStep)
);
So in effect, the AddVehicleStep can continuously add additional vehicles when the user chooses the 'Add' action on the form. In most languages (that I'm familiar with, anyways), the AddVehicleStep variable would fail to parse from within its own constructor. This is very strange to me and I would like to learn more about this idiom of JS. Is there a better way to do object trees like this on the fly?
It also got me to thinking, I had been purposefully declaring my step variables in reverse order so they would be parseable. My discovery about referencing the variable in its own constructor led me to believe that this wasn't necessary. But I just tested it and if I move the AddVehicleStep var after the VehicleStep, VehicleStep gets null for its action.addStep. Is there a way to get around this limitation and let my variables be declared in any order? Would using blank declarations and later setting them work? e.g.
var a;
var b;
var a = new Step(b);
var b = new Step(b);
// a should now have the full instantiated object of b within it, right?
// (provided the step constructor assigned it, of course)
This has probably been answered elsewhere, I just haven't found the keyword to bring it up...
Also, I'm using these steps as part of a Knockout.js app which is essentially implementing a dialog/form wizard - I hope the example code stands on its own for posing the conundrum, but in case you were curious.
Update
I had this working in a JS fiddle last night. Turns out that there is something about how the js memory is handled between subsequent runs on jsfiddle that caused it work in the particular window I had been working in (Chrome, latest version). However, opening it in a new window or new browser and it stops working.
The really weird part is that I can't replicate the behavior in any browser. Maybe one of my edits had it declared differently and got it lodged in memory somehow. I really wish I could replicate it, just to prove I'm not crazy...
Thanks for the help!
Since steps can have actions that refer to the same step, I think it'd be simplest to just allow yourself the ability too add the action after the step has been constructed. So, something like this.
var Step = function(title, instructions, action) {
this.title = ko.observable(title);
this.instructions = instructions;
if (action === undefined)
this.action = null; //action was not passed
else
this.action = action;
};
//set the action after constructor invocation
Step.prototype.SetAction = function(action) {
this.action = action;
};
var AddVehicleStep = new Step(
"Additional Vehicle",
"You have another car? Great, tell us about it too!"
);
//AddVehicleStep is now instantiated with a new Step,
// so we can now set its action refering to that step
AddVehicleStep.SetAction(new Action("Cancel",
"No, nevermind about this vehicle.",
PersonalStep,
"Add another vehicle?",
"+ Add",
AddVehicleStep, // This is the weird bit to me
"No, continue on",
PersonalStep));
or hell, forget the method and do it directly
AddVehicleStep.action = new Action(...);
but then if you start doing that you loose the ability to always determine what happens when you set your action without rewriting your code.
Why do this? You have to understand order of operations and how that effects things here.
in
a = b(c(a))
the order of operations is
c(a) -> result1
b(result1) -> result2
a gets the value of result2
assuming a (in the local scope) was not assigned to before, then c(a) is equivalent to c(undefined)
Have a look at the below code:
function b(data, ref) {
alert("instantiated B with : " + data + " and " + ref);
}
function c(ref){
alert("instantiated C with : " + ref + " ... this doesnt work");
}
var a = new b('blah', new c(a));
'b' is initialized properly. But alert you get in initializing 'c' is as follows:
"instantiated C with : undefined ... this doesnt work"
This is because by the time 'c' is initiated, 'a' is not initiated and referenced properly.
Try on jsfiddle:
http://jsfiddle.net/sNbm5/
Related
So I followed a udemy course on JS and during the making of an app he writes the code that is written bellow. When I come to run the code an error is raised saying "TypeError: this.validate is not a function". I tried different ways of exporting User and sometimes it told me that it cannot read User as a constructor which is what I want it to be. I have been on this for the past 4 hours and I am still unable to figure out how it works. The whole file is required by other files. When on these other files I create an instance of the object like below. It works although the .push method of an array cannot be accessed(error message pops up)when I call the pushError function
const User = require('../models/User.js')
let user = new User(req.body);
//I can then run the .validate function
user.validate();
//But in that function another error raises that says that the
//"push cannot be accessed in undefined"
//And it leads me to think that during the construction the
//empty list becomes undefined????
let User = function(data) {{
this.username = data.username;
this.mail = data.email;
this.password = data.password;
this.errors = [];
}
}
User.prototype.validate = function(){
if(this.username.replace(" ","") == ""){pushError("Username")}
if(this.password == ""){pushError("Password")}
if(this.mail.replace(" ","") == ""){pushError("Email")}
}
User.prototype.register = ()=>{
//Step #1: Validate user Data
this.validate();
//Step #2:If validated store data to DB
}
function pushError(str){
this.errors.push(`You must provide a valid ${str}.`);
};
module.exports = User;
If you read through all this thank you!
The problem is that your pushError function is in no way related to the User instance you are creating.
Inside pushError, this is not the new User object you're attempting to create, hence this.errors is undefined, and you cannot call push on undefined.
Also, writing register as an arrow function instead of a regular function makes it lose the value of this (this becomes that of the enclosing context, window in a browser or global in Node.js).
There three steps involved to solve this.
First you should rewrite pushError as part of User's prototype chain, like so:
User.prototype.pushError = function(str) {
this.errors.push(`You must provide a valid ${str}.`);
};
Second, you should use this.pushError instead of pushError in validate:
User.prototype.validate = function() {
if (this.username.replace(" ", "") == "") {
this.pushError("Username");
}
if (this.password == "") {
this.pushError("Password");
}
if (this.mail.replace(" ","") == "") {
this.pushError("Email");
}
}
Third, write register as a regular function:
User.prototype.register = function() {
//Step #1: Validate user Data
this.validate();
//Step #2:If validated store data to DB
}
That should do it. Now, a few additional comments and resources. It might help you to:
Dig into JavaScript Objects on MDN, especially the Object prototypes section.
Write your code as an ES6 class, which is a more "modern" way to do the same thing: this article gives examples of how to write things the "prototype way" or with classes.
Learn more about the differences between regular and "fat arrow" functions in this article.
Edit: the code below was made up on the spot to show how I was going about what I was doing. It definietely won't run, it is missing a lot of things.
Here is a working example in codepen: https://codepen.io/goducks/pen/XvgpYW
much shorter example: https://codepen.io/goducks/pen/ymXMyB
When creating a function that is using call or apply, the this value stays null when using getPerson. however, when I use apply or call with getPerson it returns the correct person.
Please critique, I am really starting to learn more and more. I am in the middle of a project section so it might be hard to change all the code, but my next project could implement this better.
call and apply are setting to the window and not the object.
I will provide code that is much simpler with the same concept of what I am talking about.
function createPerson(){
this.manager = null;
this.teamManager = null;
this.setTeamManager = function(val){
this.teamManager = val;
}
this.setManager = function(val){
console.log('setting manager to',val);
this.teamManager = val;
}
this.getTeamManager = function(){
console.log('setting team manager to',val);
return this.teamManager ;
}
this.getManager = function(){
return this.manager;
}
this.appendSelect = function(elem){
var that = this;
createOtherSelects(that,elem);
}
//some functions that create selects with managers etc
//now assume there are other selects that will filter down the teams,
//so we might have a function that creates on change events
function createOtherSelects(that){
//code that creates locations, depending on location chosen will
//filter the managers
$('#location').on('change',function(){
//do some stuff
//... then call create management
createManagement(that,elem);
});
}
function createManagement(that,elem){
var currentLocation = that.location; //works
var area = that.area;//works ... assume these are set above
//code that returns a filter and unique set of managers back
that.teamManager = [...new Set(
data.map(person=>{
if(person.area==area &&
person.currentLocation==currentLocation
){
return person;
}
})
)].filter(d=>{if(d){return d}});
if(elem.length>0){
var selectNames = ['selectManager','selectTeamManager'];
var fcns = [that.setManager,that.setTeamManager];
for(var i = 0; i < selectNames.length;i++){
//do stuff
if(certainCriteriaMet){
// filter items
if(filteredManager == 1){
fcns[i].call(null,currentManager);//
}
}
}
}
}
}
var xx = new createPerson()
In console I see setting manager and setting team manager to with the correct values.
however when I call xx in console, I see everything else set except for
xx.teamManager and xx.manager
instead it is applying to the window, so if I type teamManager in the console, it will return with the correct person.
If I straight up say
that.setManager('Steve')
or even it works just fine.
xx.setManager('steve')
the this value in setManager is somehow changing from the current instance of the object to this window. I don't know why, and I would like to learn how to use apply and call using that for future reference.
I think the issue is with your following code
fcns[i].call(null,currentManager)
If you are not supplying "this" to call, it will be replaced with global object in non-strict mode.
fcns[i].call(that,currentManager)
See mdn article here
From your codepen example, you need to change that line
fcnset[0].apply(that,[randomName]);
The first argument of the apply method is the context, if you are not giving it the context of your method it's using the global context be default. That's why you end up mutating the window object, and not the one you want !
I'm a new dev coming from a Ruby background. Recently I've been working very hard in JS and I'm having some issues with class inheritance in the new ES 6. I feel like it may be an issue with my understanding of JS or conflating it to much to Ruby. I've been trying to translate a Ruby Project into JS to practice, but I'm now failing a feature test.
Failing Feature test when trying to initialize two instances of a class
const STATS = { str:1, dex:1, int:1 }
class Example {
constructor(race, clas) {
this.race = race,
this.clas = clas,
this.stats = this.add(STATS)
}
add(stats) {
if(this.race != 'empty'){
stats.str += this.race.str
stats.dex += this.race.dex
stats.int += this.race.int
}
if(this.clas != 'empty') {
stats.str += this.clas.str
stats.dex += this.clas.dex
stats.int += this.clas.int
}
return stats
}
}
var a = new Example({str: 1, dex:0, int:0}, 'empty');
var b = new Example('empty', {str: 0, dex:0, int:1});
console.log('Should be str:2 dex:1 int:1');
console.log(a.stats);
console.log('Should be str:1 dex:1 int:2');
console.log(b.stats);
My class has functions that change the state when constructed, but the issue is any time a new Class is called it retains the changes from the previous variable. It is only an issue on my feature test as it is the only time that class is called twice.
This is the link to my feature test https://github.com/RyanWolfen7/jsRPG/blob/master/cypress/integration/featureTest/characterFeature_test.js
And this is the class thats failing the tests
https://github.com/RyanWolfen7/jsRPG/blob/master/models/characters/character.js
Honestly I'm probably going to scrap my project and start fresh anyways, but I would like to understand what my issue is. I was taking an OOD approach to JS and translating my ruby project https://github.com/RyanWolfen7/ruby_rpg to JS. I'm not sure if its because I wrote the test wrong or a deep misunderstanding of how es-6 works.
Things I have tried:
Creating a new object
Assigning a newly created object to new class
It's not an inheritance issue. In fact, it has nothing to do with OO at all. What you are seeing is the result of the fact that most things in javascript are references (pointers) but you are writing code as if STATS is a value.
In your function add you do this:
add(stats) {
if(this.race != 'empty'){
// modifying passed in object, not creating new object
stats.str += this.race.str
stats.dex += this.race.dex
stats.int += this.race.int
}
if(this.clas != 'empty') {
// modifying passed in object, not creating new object
stats.str += this.clas.str
stats.dex += this.clas.dex
stats.int += this.clas.int
}
return stats
}
So no matter how many times you call add() and from whichever instance of Example you call it from you are only accessing and overwriting the single shared STATS object.
To create new copies of STATS on each function call you need to copy it to a new object. The fastest old-school way to do this is to serialise the object to a string then convert the string back to an object:
add (input) {
var stats = JSON.parse(JSON.stringify(input));
// ...
}
This feels ugly but multiple benchmarks really did show it to be the fastest method.
Modern javascript can do this using Object.assign:
add (input) {
var stats = Object.assign({},input);
// ...
}
However, I don't know if it is faster. You will have to benchmark it yourself. You can google the phrase "js clone object" for more info.
this would be my first question ever on stackoverflow, hope this goes well.
I've been working on a game (using corona SDK) and I used Node.js to write a small little server to handle some chat messages between my clients, no problems there.
Now I'm working on expanding this little server to do some more, and what I was thinking to do is create an external file (module) that will hold an object that has all the functions and variables I would need to represent a Room in my games "Lobby", where 2 people can go into to play one against the other, and each time I have 2 players ready to play, I would create a copy of this empty room for them, and then initialize the game in that room.
So I have an array in my main project file, where each cell is a room, and my plan was to import my module into that array, and then I can init the game in that specific "room", the players would play, the game will go on, and all would be well... but... my code in main.js:
var new_game_obj = require('./room.js');
games[room_id] = new_game_obj();
games[room_id].users = [user1_name,user2_name];
Now, in my room.js, I have something of the sort:
var game_logistics = {};
game_logistics.users = new Array();
game_logistics.return_users_count = function(){
return game_logistics.users.length;
}
module.exports = function() {
return game_logistics;
}
So far so good, and this work just fine, I can simply go:
games[room_id].return_users_count()
And I will get 0, or 1, or 2, depending of course how many users have joined this room.
The problems starts once I open a new room, since Node.js will instance the module I've created and not make a copy of it, if I now create a new room, even if I eliminated and/or deleted the old room, it will have all information from the old room which I've already updated, and not a new clean room. Example:
var new_game_obj = require('./room.js');
games["room_1"] = new_game_obj();
games["room_2"] = new_game_obj();
games["room_1"].users = ["yuval","lahav"];
_log(games["room_1"].return_user_count()); //outputs 2...
_log(games["room_2"].return_user_count()); //outputs 2...
Even doing this:
var new_game_obj = require('./room.js');
games["room_1"] = new_game_obj();
var new_game_obj2 = require('./room.js');
games["room_2"] = new_game_obj2();
games["room_1"].users = ["yuval","lahav"];
_log(games["room_1"].return_user_count()); //outputs 2...
_log(games["room_2"].return_user_count()); //outputs 2...
Gives the same result, it is all the same instance of the same module in all the "copies" I make of it.
So my question as simple as that, how do I create a "clean" copy of my original module instead of just instancing it over and over again and actually have just one messy room in the end?
What you're doing is this (replacing your require() call with what gets returned);
var new_game_obj = function() {
return game_logistics;
}
So, every time you call new_game_obj, you return the same instance of game_logistics.
Instead, you need to make new_game_obj return a new instance of game_logistics;
// room.js
function Game_Logistics() {
this.users = [];
this.return_users_count = function(){
return this.users.length;
};
}
module.exports = function() {
return new Game_Logistics();
}
This is quite a shift in mentality. You'll see that we're using new on Game_Logistics in module.exports to return a new instance of Game_Logistics each time it's called.
You'll also see that inside Game_Logistics, this is being used everywhere rather than Game_Logistics; this is to make sure we're referencing the correct instance of Game_Logistics rather than the constructor function.
I've also capitalized your game_logistics function to adhere to the widely-followed naming convention that constructor functions should be capitalized (more info).
Taking advantage of the prototype chain in JavaScript is recommended when you're working with multiple instances of functions. You can peruse various articles on "javascript prototypical inheritance* (e.g. this one), but for now, the above will accomplish what you need.
I'm aware that there is a Cross site forgery attack that can be performed on a request that returns an array by overloading the Array constructor. For example, suppose I have a site with a URL:
foo.com/getJson
that returns:
['Puff the Dragon', 'Credit Card #']
This would normally be Javascript eval'd by my own site after an XHR request, but another site can sniff this data by including something like:
<script>
function Array() {
var arr = this;
var i = 0;
var next = function(val) {
arr[i++] setter = next;
document.write(val);
};
this[i++] setter = next;
}
</script>
<script src="http://foo.com/getJson"></script>
My question is, can the same thing be done when the request returns a Javascript object? i.e.
{ name: 'Puff the Dragon', cc: 'Credit Card #' }
I couldn't figure out a way to do this, but maybe I'm missing something. I know there are better solutions to protect my site, like using the while(1) hack or requiring an auth token in the URL, but I'm trying to figure out if this sort of security hole exists.
The sources I've seen, such as Haacked and Hackademix, specifically indicate that root objects are safe (presumably in all major browsers). This is because a script can not start with an object literal. By default, ASP.NET wraps both objects and arrays with a d prefix, but I think this is just to simplify the client library.
It looks like from the Ecmascript spec, the JSON object shouldn't be treated as a valid Javascript program:
"Note that an ExpressionStatement
cannot start with an opening curly
brace because that might make it
ambiguous with a Block.
So assuming that all browser implement this correctly, a response like { name: 'Puff the Dragon', cc: 'Credit Card #' } won't be executed as valid Javascript. However expressions like ({name: 'Puff the Dragon', cc: 'Credit Card #' }) and {['Puff the Dragon', 'Credit Card #']} will.
You could use the same technique for Object. It wouldn't affect the prototype chain, so it wouldn't be inherited by all objects. But you could, for example, log all new objects getting created with this:
function Object() {
var obj = this;
if (window.objectarray === undefined) {
window.objectarray = [];
}
window.objectarray.push(this);
return this;
}
Any time code on your page uses new Object(), it would get written to window.objectarray -- even if it were created in a private scope. So, for example, look at this code:
var Account = function() {
var createToken = function() {
var objToken = new Object();
objToken.timestamp = new Date().getTime();
objToken.securestring = "abc123";
return objToken.timestamp + objToken.securestring;
}
var objPrivate = new Object();
objPrivate.bankaccount="123-456789";
objPrivate.token = createToken();
};
var myAccount = new Account();
In this case, if you create a new account with new Account(), a token will be created using private properties (and maybe methods) and nothing about myAccount is left hanging outside in public. But both 'objectToken' and objPrivate will be logged to window.objectarray.