Load multiple JSON objects from multiple files - javascript

I am trying to create a quiz website. The quiz data (questions, answers, and correct answer) are stored in JSON files. Everything works as is, but I would like to include a unique image in each individual JSON file. I figured that the best way would be to create another object; meaning I'd have the structure shown below:
[
{"adImage" : "images/NoOvertake.jpg"}
],
[
{
"question" : "Before making a U - turn in the road you should always:",
"answers":[
{"id" : 0, "text" : "Select a higher gear than normal"},
{"id" : 1, "text" : "Signal so that other drivers can slow down"},
{"id" : 2, "text" : "Look over your shoulder for final confirmation"},
{"id" : 3, "text" : "Give another signal as well as using your indicators"}
],
"correct" : [2],
"allAns":[]
},
{
"question" : "As a driver what do you understand by the term 'Blind Spot'?",
"answers" : [
{"id" : 0, "text" : "An area covered by your left hand mirror" },
{"id" : 1, "text" : "An area not covered by your headlights" },
{"id" : 2, "text" : "An area covered by your right hand mirror" },
{"id" : 3, "text" : "An area not covered by your mirrors" }
],
"correct" : [3],
"allAns":[]
}
]
This is the JavaScript which used to work before I added the new image object above all the questions:
var app = angular.module('myQuiz',[]);
app.controller('QuizController'
['$scope','$http','$q','$sce',function($scope,$http,$q,$sce){
var jsonData = ['alertness','attitude', 'safety and your vehicle',
'safety margins','hazard awareness',
'vulnerable road users','other type of vehicles',
'vehicle handling','dual carriageway rules',
'rules of the road','road and traffic signs',
'documents','accidents','vehicle loading'];
var promise = [];
$scope.allQuestions = [];
for(var i=0;i<jsonData.length;i++) {
promise.push($http.get(jsonData[i]+'.json'))
}
$q.all(promise).then(function(quizData){
for(var i=0;i<quizData.length;i++) {
$scope.allQuestions[i] = {};
$scope.allQuestions[i].quizName = jsonData[i];
$scope.allQuestions[i].data = quizData[i].data;
$scope.allQuestions[i].score = 0;
$scope.allQuestions[i].activeQuestion = -1;
$scope.allQuestions[i].activeQuestionAnswered = 0;
$scope.allQuestions[i].percentage = 0;
var questionNumber = quizData.length;
}
});
]);
Now, not even the questions will show up. I appreciate any sort of help, or even alternative solutions. All I need to do is add an image which would remain there for every question. What HTML code would I need to show the image?
Thanks in advance!

A valid JSON object only has one root element. You can use JSON linters to see if your JSON is valid http://jsonlint.com. I'd suggest to use something like this as a structure.
{
"adImage": "images/NoOvertake.jpg",
"questions": [
{
"question": "Before making a U - turn in the road you should always:",
"answers": [
{
"id": 0,
"text": "Select a higher gear than normal"
},
{
"id": 1,
"text": "Signal so that other drivers can slow down"
},
{
"id": 2,
"text": "Look over your shoulder for final confirmation"
},
{
"id": 3,
"text": "Give another signal as well as using your indicators"
}
],
"correct": [
2
],
"allAns": []
},
{
"question": "As a driver what do you understand by the term 'Blind Spot'?",
"answers": [
{
"id": 0,
"text": "An area covered by your left hand mirror"
},
{
"id": 1,
"text": "An area not covered by your headlights"
},
{
"id": 2,
"text": "An area covered by your right hand mirror"
},
{
"id": 3,
"text": "An area not covered by your mirrors"
}
],
"correct": [
3
],
"allAns": []
}
]
}

Related

How to bind a conditional panelCount to a question type of 'paneldynamic' in SurveyJs

In my survey in SurveyJs, I'm attempting to implement a question with the type 'paneldynamic', which has the following structure:
{
type: "paneldynamic",
name: "9.2",
visible: false,
visibleIf: "{9.a.n} > 0",
title: "When did you give birth?",
enableIf: "{9.a.n} > 0",
requiredIf: "{9.a.n} > 0",
templateTitle: "Date of birth:",
templateElements: [
{
type: "text",
name: "9.2.1",
inputType: "date",
maxValueExpression: "today()",
titleLocation: 'hidden'
},
],
panelCount: "{9.a.n}"
}
where Question 9a is a number entry. I want the number of panels on this question to vary depending on the answer given to Q9a, but with this question structure the survey does not appear to bind the value of 9a to the actual panel count.
I've been unable to find anything in the documentation, and have tried variants such as "bindings: { "panelCount": "9.a.n" } which also don't seem to work.
How can I correctly set the variable panelCount property?
You may wish to create a custom function and calculate the number of panels based on another question answer. For example: https://plnkr.co/edit/iYNMt7JRPOh2dTOy.
Survey.Serializer.addProperty("paneldynamic", {
name: "panelCountExpression:expression",
onExecuteExpression: (obj, res) => {
if(res !== undefined) {
obj.panelCount = res;
}
}
});
var json = {
"pages": [
{
"name": "page1",
"elements": [
{
"type": "text",
"name": "question1",
"inputType":"number",
"min": 0,
"max": 5,
"defaultValue":"2"
},
{
"type": "paneldynamic",
"name": "question2",
"panelCountExpression":"{question1}",
"templateElements": [
{
"type": "text",
"name": "question3"
}]
}
]
}
],
};
I also recommend that you review the following:
Blogpost (the example was taken from it): SurveyJS Library — Calculate Properties and Hide Elements With Expressions and Functions.
Documentation: Conditional Logic and Dynamic Texts.
Thanks

How to implement hyperlinks in JSON & D3 plugin?

My goal here is to have the childless nodes contain hyperlinks. This is the D3 plugin I'm basing things off of: https://github.com/deltoss/d3-mitch-tree
Image Example
I'm newer to JS and JSON so I'm having difficulties on figuring out how to proceed, especially since there's little to refer to in regard to hyperlinks & JSON. If there's a better way to go about this, I'm certainly open to new ideas.
Thank you in advance
<script src="https://cdn.jsdelivr.net/gh/deltoss/d3-mitch-tree#1.0.2/dist/js/d3-mitch-tree.min.js"></script>
<script>
var data = {
"id": 1,
"name": "Animals",
"type": "Root",
"description": "A living that feeds on organic matter",
"children": [
{
"id": 6,
"name": "Herbivores",
"type": "Type",
"description": "Diet consists solely of plant matter",
"children": [
{
"id": 7,
"name": "Angus Cattle",
"type": "Organism",
"description": "Scottish breed of black cattle",
"children": []
},
{
"id": 8,
"name": "Barb Horse",
"type": "Organism",
"description": "A breed of Northern African horses with high stamina and hardiness. Their generally hot temperament makes it harder to tame.",
"children": []
}
]
}
]
};
var treePlugin = new d3.mitchTree.boxedTree()
.setData(data)
.setElement(document.getElementById("visualisation"))
.setMinScale(0.5)
.setAllowZoom(false)
.setIdAccessor(function(data) {
return data.id;
})
.setChildrenAccessor(function(data) {
return data.children;
})
.setBodyDisplayTextAccessor(function(data) {
return data.description;
})
.setTitleDisplayTextAccessor(function(data) {
return data.name;
})
.initialize();
</script>
Chain this method before .initialize():
.on("nodeClick", function(event, index, arr) {
if (!event.data.children.length) {
console.log('you clicked a child-less item', event.data);
}
})
Inside the condition, event.data is the clicked childless item. Feel free to place URLs inside those objects and use those URLs to navigate away.
Taken from the repo's /examples folder, where I found one named Advanced example with Node Click Event.
According to the comment on the same page, you can also achieve this using the options syntax:
/* const tree = [
place your data here...
]; // */
new d3.mitchTree.boxedTree({
events: {
nodeClick({ data }) {
if (!data.children.length) {
console.log('You clicked a childless item', data);
}
}
}
})
.setData(tree)
// ...rest of chain
.initialize();

Is there a way to de-dupe a javascript array and combine values of the data?

I'm using an ajax request to grab some XML data which I then need to push into a chart in fusioncharts.
The XML data is formatted as [time taken], [work done], [which team done for], [who did it] (see below).
I'm iterating over the XML and then building the array using the code below:
//Time Recorded
if (columnidchecker == 7781) {
timearray.push($j(this).find('displayData').text());
temp1 = $j(this).find('displayData').text();
}
//Type of Activity
if (columnidchecker == 7782) {
activityarray.push($j(this).find('displayData').text());
temp2 = $j(this).find('displayData').text();
}
//Team Done For
if (columnidchecker == 7783) {
subjectarray.push($j(this).find('displayData').text());
temp3 = $j(this).find('displayData').text();
}
//Name
if (columnidchecker == 7777) {
internalclientarray.push($j(this).find('displayData').text());
temp4 = $j(this).find('userDisplayName').text();
}
});
//PUSH INTO A NEW ARRAY WHICH CAN THEN BE SORTED AND DE-DUPED WITH TIME COMBINED AGAINST ACTIVITY / TEAM.
objectarray.push([temp1, temp2, temp3, temp4]);
This builds an array of entries from the XML which basically outputs to something which looks like this:
0: (4) ["1.50", "Ad-hoc queries or calls", "Team 1", "James"]
1: (4) ["2.50", "Ad-hoc queries or calls", "Team 1", "James"]
2: (4) ["1.00", "Advice", "Team 2", "James"]
3: (4) ["3.50", "Meeting (External 3rd Party)", "Team 1", "James"]
4: (4) ["1.20", "Administration", Team 2", "James"]
5: (4) ["5.50", "Advice", "Team 1", "John"]
I'm trying to build a chart in fusioncharts which needs the format as shown below (ignore foot stuffs - it's taken straight from the fusioncharts help pages!).
{
"chart": {
"theme": "fusion",
"caption": "Revenue split by product category",
"subCaption": "For current year",
"xAxisname": "Quarter",
"yAxisName": "Revenues (In USD)",
"showSum": "1",
"numberPrefix": "$"
},
"categories": [
{
"category": [
{
"label": "Q1"
},
{
"label": "Q2"
},
{
"label": "Q3"
},
{
"label": "Q4"
}
]
}
],
"dataset": [
{
"seriesname": "Food Products",
"data": [
{
"value": "11000"
},
{
"value": "15000"
},
{
"value": "13500"
},
{
"value": "15000"
}
]
},
{
"seriesname": "Non-Food Products",
"data": [
{
"value": "11400"
},
{
"value": "14800"
},
{
"value": "8300"
},
{
"value": "11800"
}
]
}
]
}
The problem i'm having is that I cannot work out how to take the array of data with times, activity, team, name and push them into categories.
I think the first step is to create a new array of names which can be pushed into the "Category" data field in fusioncharts.
I then need a way in which to take the times being recorded against each activity and for each team and make sure it's assigned to the right person within the stacked bar chart and combine the amount of time spent. (i.e. "James" spent a total of 4 hours doing "Ad Hoc Queries and Calls" for Team 1 but this is split across two time entries so I need a way in which to combine them into one.)
Any help on this would be massively appreciated.
I can de-dupe the names to create a new array by using the following code:
namesarray.push(temp4);
uniq = [...new Set(namesarray)];
but after that it starts getting pretty complicated.
Maybe this can help you along the way. It's probably not exactly in the form you want it, but it demonstrates how you could break the problem down into smaller parts.
Pseudo-code:
get the unique names.
get the unique "task" names (for lack of a
better word)
for each unique person name:
3.1. get the data rows for that person
3.2 for each of all unique tasks names:
find the person data rows matching the task name
sum the duration of those data rows
const testData = [
[
"1.50",
"Ad-hoc queries or calls",
"Team 1",
"James"
],
[
"2.50",
"Ad-hoc queries or calls",
"Team 1",
"James"
],
[
"1.00",
"Advice",
"Team 2",
"James"
],
[
"3.50",
"Meeting (External 3rd Party)",
"Team 1",
"James"
],
[
"1.20",
"Administration",
"Team 2",
"James"
],
[
"5.50",
"Advice",
"Team 1",
"John"
]
];
const columnIndexByName = {
TASK_DURATION: 0,
TASK_NAME: 1,
FOR_WHICH_TEAM: 2,
PERSON_DOING_TASK: 3
};
const sum = (acc, next) => acc + next;
const uniqueNames = [...new Set(testData.map(row => row[columnIndexByName.PERSON_DOING_TASK])) ];
const uniqueTaskNames = [...new Set(testData.map(row => row[columnIndexByName.TASK_NAME])) ];
let result = {};
uniqueNames.forEach(personName => {
const personDataRows = testData.filter(row => row[columnIndexByName.PERSON_DOING_TASK] === personName);
let taskDurationsByTaskName = {};
uniqueTaskNames.forEach(taskName => {
const taskRows = personDataRows.filter(row => row[columnIndexByName.TASK_NAME] === taskName);
const taskDurations = taskRows.map(row => Number.parseFloat( row[columnIndexByName.TASK_DURATION] ));
const taskTotalDuration = taskDurations.reduce(sum, 0);
taskDurationsByTaskName[taskName] = taskTotalDuration;
})
result[personName] = taskDurationsByTaskName;
})
const renderData = data => document.querySelector("#output").innerHTML = JSON.stringify(data, null, 2);
renderData(result);
<pre id="output"></pre>

Immutable JS map or list

Say we have a plain javascript array of objects
[
{id : 1, name : "Hartford Whalers"},
{id : 2, name : "Toronto Maple Leafs"},
{id : 3, name : "New York Rangers"}
]
and we wanted to bring it in to immutablejs. Would it be most natural to make it into a map or a list, and how would we update a property in one of the objects? Say change the name of "Hartford Whalers" to "Carolina Hurricanes".
You should create a List of Maps for this. Immutable.js has a function Immutable.fromJS that will recursively convert JS Arrays to Immutable.List and JS Objects to Immutable.Map.
var input = [
{id : 1, name : "Hartford Whalers"},
{id : 2, name : "Toronto Maple Leafs"},
{id : 3, name : "New York Rangers"}
];
var list = Immutable.fromJS(input);
list.toString(); // => "List [ Map { "id": 1, "name": "Hartford Whalers" }, Map { "id": 2, "name": "Toronto Maple Leafs" }, Map { "id": 3, "name": "New York Rangers" } ]"
You can set the name of the first item using .setIn:
var list2 = list.setIn([0, "name"], "Carolina Hurricanes");
list2.toString(); // => "List [ Map { "id": 1, "name": "Carolina Hurricanes" }, Map { "id": 2, "name": "Toronto Maple Leafs" }, Map { "id": 3, "name": "New York Rangers" } ]"
You can set the name of any item with name = "Hartford Whalers" to "Carolina Hurricanes" :
var list3 = list.map(function(item) {
if(item.get("name") == "Hartford Whalers") {
return item.set("name", "Carolina Hurricanes");
} else {
return item;
}
});
list3.toString(); // => "List [ Map { "id": 1, "name": "Carolina Hurricanes" }, Map { "id": 2, "name": "Toronto Maple Leafs" }, Map { "id": 3, "name": "New York Rangers" } ]"
I disagree.
The example you give each element of the array is an object that has an { id } property. One would assume you will want to access the elements of this array via their id. thats what ids are for.
Yes the native structure is a List of Maps. Although what you want is a Map of Maps.
Why?
Think about what you will need to do to retrieve { id: 1928, "Some Team" }. You would have to iterate one-by-one through the list till you found your matching Id.
A far more efficient way to do this is create a map so you can directly "pluck" your object e.g.
const input = Immutable.fromJS({
1: {id : 1, name : "Hartford Whalers"},
2: {id : 2, name : "Toronto Maple Leafs"},
3: {id : 3, name : "New York Rangers"},
// ...
1928: {id : 1928, name : "Some Team"},
});
Then to access it all you have to do is:
const team = input.get('1928');
you need to do the initial conversion, do that server-side if its coming from an API, its called normalisation. It has the benefit of saving data as well.
This is actually where the term "map" comes from

MongoDB : Map Reduce : Create one sub-document from another one

I have a mongodb collection which has documents like this :
{
"_id" : ObjectId("safdsd435tdg54trgds"),
"startDate" : ISODate("2013-07-02T17:35:01.000Z"),
"endDate" : ISODate("2013-08-02T17:35:01.000Z"),
"active" : true,
"channels" : [
1, 2, 3, 4
],
}
I want to convert this to something like this :
{
"_id" : ObjectId("safdsd435tdg54trgds"),
"startDate" : ISODate("2013-07-02T17:35:01.000Z"),
"endDate" : ISODate("2013-08-02T17:35:01.000Z"),
"active" : true,
"channels" : [
1, 2, 3, 4
],
"tags" :[
{
"name": one
"type": channel
},
{
"name": two
"type": channel
},
{
"name": three
"type": channel
},
{
"name": four
"type": channel
}
]
}
I already have a mapping of what 1,2,3,4 mean. Just for the sake of simplicity I put them as their alphabetical format. the values could be different, but they're static mappings.
You seem to be trying to do this update without a big iteration of your collection, So you "could" do this with mapReduce, albeit in a very "mapReduce way" as it has it's own way of doing things.
So first you want to define a mapper that encapsulates your current document :
var mapFunction = function (){
var key = this._id;
var value = {
startDate: this.startDate,
endDate: this.endDate,
active: this.active,
channels: this.channels
};
emit( key, value );
};
Now here the reducer is actually not going to be called as all the keys from the mapper will be unique, being of course the _id values from the original document. But to make the call happy:
var reduceFunction = function(){};
As this is a one to one thing this will go to finalize. It could be in the mapper, but for cleanliness sake
var finalizeFunction = function (key, reducedValue) {
var tags = [
{ name: "one", type: "channel" },
{ name: "two", type: "channel" },
{ name: "three", type: "channel" },
{ name: "four", type: "channel" }
];
reducedValue.tags = [];
reducedValue.channels.forEach(function(channel) {
reducedValue.tags.push( tags[ channel -1 ] );
});
return reducedValue;
};
Then call the mapReduce:
db.docs.mapReduce(
mapFunction,
reduceFunction,
{
out: { replace: "newdocs" },
finalize: finalizeFunction
}
)
So that will output to a new collection, but in the way that mapReduce does it so you have this:
{
"_id" : ObjectId("53112b2d0ceb66905ae41259"),
"value" : {
"startDate" : ISODate("2013-07-02T17:35:01Z"),
"endDate" : ISODate("2013-08-02T17:35:01Z"),
"active" : true,
"channels" : [ 1, 2, 3, 4 ],
"tags" : [
{
"name" : "one",
"type" : "channel"
},
{
"name" : "two",
"type" : "channel"
},
{
"name" : "three",
"type" : "channel"
},
{
"name" : "four",
"type" : "channel"
}
]
}
}
So all your document fields other than _id are stuck under that value field, so that's not the document that you want. But that is how mapReduce works.
If you really need to get out of jail from this and are willing to wait a bit, the upcoming 2.6 release has added an $out pipeline stage. So you "could" transform the documents in your new collection with $project like this:
db.newdocs.aggregate([
// Transform the document
{"$project": {
"startDate": "$value.startDate",
"endDate": "$value.endDate",
"active": "$value.active",
"channels": "$value.channels",
"tags": "$value.tags"
}},
// Output to new collection
{"$out": "fixeddocs" }
])
So that will be right. But of course this is not your original collection. So to back to that state you are going to have to .drop() collections and use .renameCollection() :
db.newdocs.drop();
db.docs.drop();
db.fixeddocs.renameCollection("docs");
Now please READ the documentation carefully on this, there are several limitations, and of course you would have to re-create indexes as well.
All of this, and in particular the last stage is going to result in a lot of disk thrashing and also keep in mind that you are dropping collections here. It almost certainly is a case for taking access to your database off-line while this is performed.
And even as such the dangers here are real enough that perhaps you can just live with running an iterative loop to update the documents, using arbitrary JavaScript. And if you really must have to do so, you could always do that using db.eval() to have that all execute on the server. But if you do, then please read the documentation for that very carefully as well.
But for completeness even if I'm not advocating this:
db.eval(function(){
db.docs.find().forEach(function(document) {
var tags = [
{ name: "one", type: "channel" },
{ name: "two", type: "channel" },
{ name: "three", type: "channel" },
{ name: "four", type: "channel" }
];
document.tags = [];
document.channels.forEach(function(channel) {
document.tags.push( tags[ channel -1 ] );
});
var id = document._id;
delete document._id;
db.docs.update({ "_id": id },document);
});
})

Categories

Resources