I'm writing a 'personality type' quiz and want to assign values to choices that a user selects as they progress through the quiz. The idea is each response to a question will have a different numeric weight, which I'll tally as someone progresses through and ultimately completes the quiz. At the end, I'll use the tally to present one of a few different results.
At the moment, users are able to progress through the quiz but I can't get the numeric weight to work.
This is my approach in the code below:
- I am using countValue as my tally. It's set at 1 at the top of my JS.
- In each question within the quiz, there's a value titled addition that is assigned a number, like initial view of the quiz has addition: 0
- As the quiz progresses, there's a field for addition.
- At the bottom of the quiz is a function titled display_scenario in which I attempt to add the value of addition to countValue, but it's not working. I can see that it's not working because the console log gives me values of NaN for the values within the function.
So it seems like I'm not turning the addition value into an integer/number. I'm not sure how to do this. I've tried using parseInt() and Number() and had no luck.
The full code is on Codepen - http://codepen.io/msummers40/pen/EKJQmN - but the key JavaScript/jQuery is below. Thanks in advance for any help.
//establishing counter for weighted answer
var countValue = 1;
console.log(countValue);
// var additionInt = 1;
// console.log(additionInt);
// JSON for personality quiz
// Contains the story, paths and variable to weight answers
var story = {
intro: {
prompt: 'Welcome message. Let\'s get started.',
quizImage: '<img id="quizImage" src="https://media.giphy.com/media/vc6gDdl4rHusE/giphy.gif"><br />',
options: [{
name: 'get started',
path: 'get_started',
addition: 0,
}]
},
get_started: {
prompt: 'You need pancakes. What kind will you make: thin or fluffy?',
quizImage: '<img id="quizImage" src="http://www.allgifs.com/wp-content/uploads/2013/09/pancake.gif">',
options: [{
name: 'thin',
path: 'thin',
addition: '2'
}, {
name: 'fluffy (leading to thin temporarily)',
path: 'thin',
addition: '3'
}]
},
//THIN PANCAKE SECTION
thin: {
prompt: 'Yum. What do you do next?',
quizImage: '<img id="quizImage" src="//media.giphy.com/media/2Mp7y2FkcW80M/giphy.gif">',
options: [{
name: 'pop out to get store-bought pancakes',
path: 'result',
addition: '2'
}, {
name: 'use a recipe',
path: 'result',
addition: '1'
}]
},
result: {
prompt: 'That is all I have for this example!',
quizImage: '<img id="quizImage" src="http://www.technologytell.com/entertainment/files/2013/11/Leonardo-DiCaprio-Toast-Fireworks-Gif.gif">',
options: [{
name: 'Reset',
path: 'intro' //RESET
}]
}
};
/* Chosen option is an object with properties {name, path} */
function display_scenario(chosen_option) {
var option_name = chosen_option.name;
var option_path = chosen_option.path;
var additionInt = chosen_option.addition;
additionInt = Number(additionInt);
console.log(additionInt);
countValue = (additionInt + countValue);
console.log(countValue);
var scenario = story[option_path];
You should double check that your additionInt is not undefined before incrementing the countValue (which is what's causing the problem at the intro).
function display_scenario(chosen_option) {
var option_name = chosen_option.name;
var option_path = chosen_option.path;
var additionInt = chosen_option.addition;
// Make sure all is well
if(additionInt){
countValue += additionInt;
}
var scenario = story[option_path];
...
...
}
As a side note, your addition property is already a number so you can remove the line additionInt = Number(additionInt);
1) As the other answer states, your chosen option's addition property starts out undefined, so you can check for this and give it a default value in one line:
// with default value using ||
var additionInt = +chosen_option.addition || 0;
// or with ternary operator
var additionInt = !chosen_option.addition ? 0 : +chosen_option.addition;
Then augment countValue:
countValue += additionInt;
2) The + operator in the first couple of lines above will coerce the option.addition value to a number, but you should be consistent with your addition values one way or the other. You currently have the first as a number and the rest strings.
3) Below the section you duplicated here from the codepen, you have:
jQuery('<p>').html(countValue).appendTo('#countValueHere');
But you do not have an element with that id in your html. I assume you meant #runningTotalHere.
Related
sorry if this is a easy question, I am just having a hard time trying to figure out how I would tackle this problem.
For example, I have 2 Objects as below:
cont oldCar = {
model: 'Honda',
notes: {
id: 1,
timestamp: 2000,
text: 'is old'
}
}
cont oldCar = {
model: 'Toyota',
notes: {
id: 1,
timestamp: 4000,
text: 'is new'
}
}
I want to try and combine the above two objects. I know they have same key's so I wanted to merge the values of each key if they are the same. Such as:
mode: 'Honda / Toyota'
I tried the following:
let merged = {...obj1, ...obj2};
But this will merge both objects but it only retains the values from the right object. I was trying to do a for loop and add check if the key is same in both objects then combine the values together but I keep getting lost and it is hard to visualise. If someone could help me understand how i can create an for loop to start the comparison that would help me in completing the rest.
To do this merge, perhaps you could do some array-reduce, it will also work with a list of unspecific size:
let array = Array(oldCar1,oldCar2)
let result = array.reduce((a,b)=> {
let r = Object.assign({},a)
r.notes = Object.assign({},r.notes)
if (a.model != b.model) {
r["model"] = a.model + " / " + b.model;
}
if (a.notes.text != b.notes.text) {
r.notes.text = a.notes.text + " / " + b.notes.text;
}
// ...
return r;
})
What exactly do you want to achieve? Is it only the merging of model prop or something else?
Do you have more than two objects, is the amount of objects dynamic? If there are only two objects you can do that without any loops.
const merged = {
model: `${firstCar.model} / ${secondCar.model}`,
// etc...
};
But as I said before - if the amount of objects is not constant then you'd need a map that would:
go through each car
try and find a match by ID from other cars
if there's a match return a merged result, if there's no match return the object as it is
Let me know what exactly are your needs here.
I am trying to make a generator of simple sentences by combining data from two arrays. One array has the nouns/subjects and the other the verbs/actions.
Obviously, some nouns are singular and some plural. To make this work, I make the nouns objects (and a class), eg: { txt: "Jason", snglr: true }, { txt: "The kids" }.
Then, I create a function that forms the verb according to whether the noun is singular or plural (snglr: true or false/undefined) and apply it as a method of the noun class.
I call the method from within the string of the verb/action, as in: { txt: `${curr_noun.verb("go", "goes")} to music class.`}
My problem is that all the verbs get their values from the first noun used. When I change the noun, the verbs still refer to the plural or singular form called by the first noun.
Is there a way to refresh the actions objects, so that each time their values refer to the current noun?
(Or if I'm totally missing an easier way, can you please let me know?)
Thanks. Here is the whole code:
class noun {
constructor(text, singular) {
this.txt = text; // The subject of the sentence
this.snglr = singular; // Whether the subject is singular (true) or plural (undefined/false)
this.verb = function (plur, sing) { //this function is called from within the string containing the verb (see "actions" array, below)
if (sing == undefined) { // for regular verbs, we call it with one argument, eg. .verb("walk"), it creates "walks" by adding "s."
sing = plur + "s";
}
if (this.snglr) { // for irregular verbs, we call it with two arguments, eg. .verb("go", "goes")
return sing;
} else {
return plur;
}
}
}
}
var curr_noun = {};
var nouns = [new noun("Jason", true), new noun("The kids")];
curr_noun = nouns[0]; // We pick "Jason" as the sentence's subject.
var actions = [
{ txt: `${curr_noun.verb("go", "goes")} to music class.`},
{ txt: `${curr_noun.verb("visit")} London.`}
];
curr_action = actions[1];
console.log(`${curr_noun.txt} ${curr_action.txt}`);
// All good, so far.
// My problem is that the above values of actions[0].txt and actions[1].txt are given based on Jason.
// When I later change the curr_noun to "The kids," I still get singular verb forms.
curr_noun = nouns[1];
curr_action = actions[0];
curr_noun.verb();
console.log(`${curr_noun.txt} ${curr_action.txt}`);
I think we could simplify this code a bit. You could create a function to create the sentences. This function would take a noun object and verb object and the grammatical object which is a string.
This way we can remove the class and reduce the number of lines that we write. With this approach, we can also ensure the nouns and verbs match in terms of plurality. Here is an example:
const nouns = [
{
text: 'Jason',
isSingular: true,
},
{
text: 'The kids',
isSingular: false,
},
];
// in grammar, the noun at the end of the sentence is called object
const verbs = [
{
// "to" is actually part of the verb here(prepositional verb)
singular: 'goes to',
plural: 'go to',
},
{
singular: 'visits',
plural: 'visit',
},
];
// in grammar, the noun at the end of the sentence is called object
const objects = ['music class.', 'London.'];
const createSentence = (noun, verb, object) => {
const myVerb = noun.isSingular === true ? verb.singular : verb.plural;
return `${noun.text} ${myVerb} ${object}`;
};
console.log(createSentence(nouns[0], verbs[1], objects[1]));
console.log(createSentence(nouns[1], verbs[0], objects[0]));
Note: I am not saying that this is how it should be done, but just pointing out where the problem is.
You define your var actions based on curr_noun = nouns[1];, i.e. "Jason", here:
var actions = [
{ txt: `${curr_noun.verb("go", "goes")} to music class.` },
{ txt: `${curr_noun.verb("visit")} London.` }
];
This never changes. So when referring to actions[0] later, they are still based on "Jason".Try to console.log(this.txt) inside the .verb-method to see where it goes wrong.
When reassigning them again to "The kids" before logging, it works fine:
curr_noun = nouns[1];
var actions = [
{ txt: `${curr_noun.verb("go", "goes")} to music class.` },
{ txt: `${curr_noun.verb("visit")} London.` }
];
curr_action = actions[0];
console.log(`${curr_noun.txt} ${curr_action.txt}`);
// -> The kids go to music class.
I have a list of tasks which can have 4 possible states taken from a json object.
"Not Done", "Done", "Doing", "Later"
This states are stored in an object which is basically loaded via json.
var states = { status: ['doing','done', 'later' ] };
Tasks are loaded from another json object.
var tasks = [
{id: 1, text: 'Do something.', status: 'doing'},
{id: 2, text: 'Undo that thing.', status: 'done'},
{id: 3, text: 'Redo it again.', status: 'started'},
{id: 4, text: 'Responsive', status:'later'}
];
The html would be something like this.
<ul>
<li>Do something - <span class="status">Doing</span> </li>
<li>Undo that thing - <span class="status">Done</span> </li>
<li>Redo it again. - <span class="status">Started</span> </li>
</ul>
Each time user clicks on the link the status should toggle the value from the states object
and loop through them. For example, when use click on a task with status Done, its should become later and when they click again it should become Not Done. This should keep looping each time the user clicks.
What is the best way to do this. I felt if else won't be the right way to do as if the values of states increase or reduce that code will have to be revisited again.
Edit:
I didn't understand 100% of your question. But i think i do now. You want the code to work even on all cases at all time. I've made code a little safer now:
if a state contain a faulty entered text (like 'dOne') it will still compare.
if a task has a faulty (=non-existent) state, it will just reset itself to the first state available.
Here's an explosion of code, i'm a little tired so you might need to refactor some parts. Also thought it would be easier for you with the documentation included.
If you have more things to say, leave it in the comments and i will try again....!
Working jsfiddle: http://jsfiddle.net/kychan/62sUX/2/
//
// Retrieve the data with $.get()/AJAX or with an inline
// echo through PHP/ASP.
//
// Get states. Make the code extra fuzzy.
var states = { status: ['doing','dOne', 'Later' ] };
// Get the tasks.
// In this example we do it more quickly.
var tasks = [
{id: 1, text: 'Do something.', status: 'doing'},
{id: 2, text: 'Undo that thing.', status: 'done'},
{id: 3, text: 'Redo it again.', status: 'started'},
{id: 4, text: 'Responsive', status: 'later'}
];
// prepare the retrieved JSON data for fuzzy input.
states.status.forEach(function(e,i,a) {
states.status[i]=e.toUpperCase();
});
tasks.forEach(function(e,i,a) {
tasks[i].status=e.status.toUpperCase();
});
// create the li's.
for (var i in tasks) {
var item = '<li>'
+ '<a href="#" id="{id}">{text} - '
+ '<span class="status">{status}</span>'
+ '</a>'
+ '</li>'
;
item = item.replace(/{(\w+)}/g, function (m, n) {
return (typeof (tasks[i][n]) !== 'undefined') ? tasks[i][n] : '';
});
$('ul').append(item);
}
// Override the states with the button; will make it harder
// for the code and test it for future proofness.
$('.replace').click(function() {
// we will keep 'done' for testing.
states = {
status:['CONCEPT', 'ELABORATION', 'CONSTRUCTION', 'TESTING', 'PRODUCTION', 'DONE']
};
// remove the replace link, because after press it's useless.
$('.replace').detach();
});
//
// actual code.
//
// create listeners on the a tags of tag ul.
$('ul a').click(function (e) {
// fetch status DOM object and its text before.
var status = $(this).children('.status');
var text = status.html();
// iterate through states array.
for (var i in states.status) {
// if the task matches your text, then update it to the next.
if (text==states.status[i]) {
// get next status.
if ((i++)>=states.status.length-1) i=0;
// update. Don't forget to push the update to the database.
status.html(states.status[i]);
return;
}
}
// state not found. reset it to first. Don't forget to push the update to the DB.
status.html(states.status[0]);
});
How about something like this?
$('a').click(function(){
var id = parseInt($(this).attr('href').substring(1));
var task = tasks.filter(function(t){t.id == id});
if (task.length) {
var status = task[0].status;
var indexStatus = states.status.indexOf(status);
var nextStatus = null;
if (indexStatus === states.status.length-1) {
nextStatus = states.status[0];
} else if (indexStatus !== -1) {
nextStatus = states.status[indexStatus+1];
}
if (nextStatus !== null) {
task[0].status = nextStatus;
$(this).children('.status').text(nextStatus);
}
}
});
The idea is to look for the current status in the list, and just reset the index to 0 if it is the last one, otherwise increment it.
I have updated both JSON and HTML, as you did not precise if you want them in sync. The code can be made simpler if you don't need to sync data and view. Also I made all necessary bound checks, I don't know how robust you want the code to be and how much you trust the json data.
I have a list of JS objects defined by an integer ID.
objects = [{
id: 0,
type: 'null'
}, {
id: 1,
type: 'foo'
}, {
id: 2,
type: 'bar'
}];
I implemented a function to remove an element from my list :
removeObject = function(o){
objects.splice(objects.indexOf(o), 1);
}
My problem is that I need to create a function to add a new item in my list with a id not already used (for example the lower positive integer not present in the list).
I tried to do something like that but it did not work when I remove the object 0 (for example).
addObject = function(type){
objects.push({
id: objects.length,
type: type
});
};
How can I do this ?
EDIT 1
According to your answers, I assume that the best solution in term of performance is to just use a topId which is always incremented when I add a new object in my list.
But that do not answer to my requierement. Actually I think that #X-Pippes response could be good.
Should I do someting like that :
objects = [{
id: 0,
type: 'null'
}, {
id: 1,
type: 'foo'
}, {
id: 2,
type: 'bar'
}];
// Init available ids list with the default value
availableIds = [objects.length];
removeObject = function(o){
// Remove the object from the list
objects.splice(objects.indexOf(o), 1);
// Add its id to the available ids list
availableIds.push(o.id);
}
addObject = function(type){
// Get lower id available
var newId = Math.min.apply(Math,availableIds);
// Push the new object with the id retrieved
objects.push({
id: newId,
type: type
});
// Remove used id from the available ids list
availableIds.splice(availableIds.indexOf(newId), 1);
// Add a default id if available list is empty
if(availableIds.length < 1) availableIds.push(objects.length);
};
if you remove for instance 0 and the next addObject is 0 you have to do something like:
keep a list [initial empty] with every ID removed. When you need to add a new one, pick the shorter, add and delete from list.
Also keep a var with the biggest ID added. If the previous list is empty, add +1 to the var and addObject with that id
Use the correct structures. A JavaScript object will do the job. It guarantees that you only get one item for key, you can look up and remove by key in probably O(1)ish. No point trying to re-implement it in a less efficient manner, which will be O(n) lookup.
var structure = {
objects : {},
topId : 0
}
structure.add = function(item) {
var id = this.topId ++;
structure.objects[id] = item;
}
structure.add("thing")
structure.add("other thing")
structure.add("another thing")
structure.objects
>>> Object {0: "thing", 1: "other thing", 2: "another thing"}
structure.objects[1]
>> "other thing"
Then the normal index operations to get/set/delete.
If you use that function then you have an invariant (guarantee) on your data structure that you won't use the same ID twice.
You need a function to find the first free number:
addObject = function(type){
objects.push({
id: firstOpenIndex(),
type: type
});
};
firstOpenIndex = function() {
for(var idx = 0; true; i++) {
var found = false;
for(var o in objects) {
if (objects[o].id == idx) {
found = true;
break;
}
}
if (!found) return idx;
}
}
In Javascript MaxInt is 9007199254740992. Why not just keep incrementing?
You can and probably should just use an array(s) like:
objects.type=['null','foo','bar'];
to add an object see:
How to append something to an array?
to find a value: var index = objects.type.indexOf('foo');
to find 1st empty field var index = objects.type.indexOf(''); which you can use to find the element for adding (if index is -1 use objects.type.length) if you "delete" an element by setting it to "" or... unless you have specific reason for keeping the "ID" static (in this case the array index), remove the element and only append new ones to the end
to remove an element see:
How do I remove a particular element from an array in JavaScript?
which will allow you to just push/append the next data.
if you need a new object array with empty fields to fill because you get new data to track:
object.newField=new Array(objects.type.length);
If you get to this point where your object contains multiple arrays, you will probably want to create functions for insert/add and delete/remove, so you don't do an operation on 1 and not the other.
Everything is already built in (read likely already pretty fast) and you don't need to reinvent constructors for your really cool object type.
I`m not sure if the title of question is correct. Look at the code:
var trails = new Array(trail1, trail2, trail3, trail4, trail5, trail6, trail7, trail8, trail9, trail10, trail11, trail12, trail13);
var circles = new Array(circle1, circle2, circle3, circle4, circle5, circle6, circle7, circle8, circle9, circle10, circle11, circle12, circle13);
var texts = new Array(text1, text2, text3, text4, text5, text6, text7, text8, text9, text10, text11, text12, text13);
for(var i=0;i<=13;i++) {
$([trails[i].node,circles[i].node,texts[i].node]).qtip({
content: {
text: 'test qtip',
title: {text: 'test', button: 'close'}
},
position: {
target: 'mouse',
adjust: {mouse: false}
},
show: {
event: 'click'
},
style: 'qtip-rounded qtip-shadow qtip-blue',
hide: {
event: 'click '
}
});
}
In this example I`m calling an array elements inside another array, so i'm not sure it's correct, but otherwise .qtip will not show when click on circle[i] or text[i], but only when onclick the trails[i]. There is also a .node property which make this issue much more complicated for beginner. Have any ideas how to improve the code to make it work?
First: Your loop has '<=' where your arrays contain 13 items, the '<=' will iterate 14 times probably causing whatever error your experiencing...
Just to clean up the code a bit...(this part is arbitrary)
var trails = [],circles = [],texts = [], i = 13;
while (i--){
trails[i] = eval('trail'+i);//parses the text to return your js variable
circles[i] = eval('circle'+i);
texts[i] = eval('text'+i);
. . .
/** Continue with whatever else you wish to do inside the loop,
* I just included this bit to show how you can instantiate your
* arrays without having to hard code each of your variables...
* Also, it is possible to use the variables name as a reference
* inside the array like so: trails['trail'+i] = . . .
* that way you can still call each variable by name.
*/
}
And just as a tip, js gets cranky when using the keywork 'new' for arrays, use '[]' instead, you can read why here: W3Schools.com - JS - Best Practices