Why is observableArray not observable in Knockout JS? - javascript

I am new to programming (especially in JS and even more with KO) and I'm trying to come up with an interactive quiz destined to be used in class by high-school students. Thanks to this article (which provided the how-to and the code, which I used as a base, trying to readapt it to my needs) and some good people's help here, I have now come up with something that looks like this:
http://jsfiddle.net/sNJm3/2/
All is well because this is functional. But... :p I would now like to add an observableArray where I would push() all the selectedAnswer each time the user clicks one so that I could, at the end, compare selectedAnswers().length to questions().length and, if they are the same, I'd make a (not included in the code yet) visible.
I declared my array in the QuizViewModel constructor like so (as it concerns the whole quiz, so I think that's where it should go):
var selectedAnswers = ko.observableArray();
And then I need, each time, to push the selectedAnswer property from the Question constructor into it. And that's where the rub is... Here's the part of my script:
//Construction
$.each(quizName.controls, function(index, question) {
quiz.questions.push(new Question(index + 1, question));
quiz.selectedAnswers().push(question.selectedAnswer);
});
This does populate an array called selectedAnswers() but it is only populated with Undefineds, which 1) do not vary even when I click an answer (undefined is not replaced with clicked selectedAnswer...) and selectedAnswers().length is already equal to the total number of questions, which means the comparison I wanted to make will not work...
There must be some fundamental KO logic I'm not getting here (or is it JS logic, which definitely seems to be eluding me!) Any thoughts on the matter would be greatly appreciated!

Use a computed for your "selected answers" list.
function Question(config) {
this.text = text;
this.questionText = config.questionText;
this.answers = config.answers;
this.selectedAnswer = ko.observable();
}
function QuizViewModel(quizName) {
this.questions = ko.observableArray(
ko.utils.arrayMap(quizName.controls, function (control) {
return new Question(control);
})
);
this.selectedAnswers = ko.computed(function () {
return ko.utils.arrayMap(this.questions(), function (q) {
return q.selectedAnswer();
}
});
}
There is no need to maintain a separate stack (i.e. observable array) of answers when the answer already is a property of the question itself.

Related

javascript: setting default value with IF statement instead of OR

I'm only sharing a small bit of code because there is so much going on, and I hope this is enough to answer my question.
I have some existing JS where a value is determined with an OR statement and I think I need to convert that to an IF statement. The final output is currently giving me both values if they both exist, and I only want "question" where both "question" and "name" values exist.
var question = new fq.Question(questionData.answerId, topicId,
questionData['question'] || questionData['name'],
questionData['text']);
Instead of using the OR operator (answerData['question'] || answerData['name']), I'd like to do something similar to the following:
if (questionData['question'] is undefined) {
use questionData['question'];
} else {
use instead questionData['name']
}
But, I don't know how I might accomplish such a statement within the () in the existing code pasted above. The name variable/value is always present, so there's no risk in defaulting to that. Question is only defined some of the time. And I don't ever want both appearing.
This is probably outside of the scope of my query here, but to fill in a little more detail, this code eventually outputs JSON files for topics and questions. Topics only have names values, and questions have both names and questions, but I only want the questions json to include questions values, not names. I'm pretty sure this is the key part in all of the JS to determin
Create a function and get value from there.
Need to remember scope of function:
Example Snippet:
var that = this;
var question = new fq.Question(questionData.answerId, topicId,
that.getValue(),
questionData['text']);
function getValue() {
if (questionData['question']) { //null and undefined both are false
return questionData['question']
} else {
return questionData['name']
}
}

How do I for-each over a Knockout.JS observableArray?

All attempts to iterate over a KO observableArray have failed. The flow just jumps over the block like the array is empty.
It's not, because its bound to some HTML and the debugger shows 7 items.
I've tried a normal for with an indexer, an ECMA-5+ forEach and now KO's own arrayForEach utility.
var EditorViewModel = function (firstDayOfWeek) {
this.firstDayOfWeek = firstDayOfWeek;
this.days = ko.observableArray([]); // Added in server-side generated view below.
// Reads the activity data from each day and constructs and uploads models.
this.save = function () {
var basket = [];
// Construct the upload activity models.
ko.utils.arrayForEach(this.days(), function(d) {
... // never falls into this block.
There's nothing much out on the web about this, so I guess its a no-brainer. I'm obviously messing it up somehow, but its eluding me this afternoon.
this.days array looks good to me.
Thanks, Luke
here days is a observableArray which is nothing but a function , to iterate you need to read the value like days() , this is will give you a javascript array.
var days = ko.observableArray([1,3,4,5,6]);
days().forEach(function(v,i){
alert(v);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
I don't know why its not working with the forEach methods, but I did get it going with a properly written traditional for index iterator.
for (dayIndex = 0; dayIndex < this.days().length; dayIndex++) { ... }

Filtering an underscore.js max method?

I am trying to get the max value of only two teams, there are several teams, but In my app I am trying to return the max value for the teams that are playing. They are set in a data attribute that I can access via jQuery. Below is my max variable.
var max = this.collection.max(function(player){
return player.get('points');
});
I tried using the where function after this.collection and after player just to test it, and obviously it isn't valid, I feel like the answer is right in front of me, I've been through the docs, but I am not good enough with backbone.js yet to realize what I need to do here.
So what I am asking is how can I filter this max function to be limited to only two teams.
Lakers
Heat
Kings
The teams that are playing are Lakers and Heat set in the data-attribute, in the example below I am using a static string for the teams, I will substitute them for variables later, but I am more focused on the syntax. I am not sure how to do this, just some bad examples below.
//Bad example #1
var max = this.collection.where({teams: 'Lakers', teams: 'Heat'}).max(function(player) {
return player.get('points');
})
//Bad example #2
if(this.collection.where({teams: 'Lakers'}) || this.collection.where({teams: 'Heat'}))
{
var max = this.collection.max(function(player){
return player.get('points');
});
}
I don't think the examples above are valid or technically make sense to the browser, but hopefully they help you understand what I am going for?
Thanks, ask questions if you need to, this should be pretty easy.
Assuming teams is the property of player then try
var max = this.collection.max(function (player) {
return player.get('teams') == 'Lakers' || player.get('teams') == 'Heat' ? player.get('points') : 0;
});

Javascript - Array of prototype functions

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
}

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.
});

Categories

Resources