Reputation: 10989
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!
Upvotes: 2
Views: 697
Reputation: 7141
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)
Upvotes: 2
Reputation: 1767
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:
Upvotes: 2