Jquery convert dynamic form data to json - javascript

I am working with my project that will create quizzes that form. I want it to be submitted into json format, which will be look like this:
[
{
"questions": [
{
"question": "Who is Mark Zuckerberg?",
"options": [
{
"answer": "Facebook CEO",
"correct": 1
},
{
"answer": "Google Programmer",
"correct": 0
}
]
},
{
"question": "Who is the founder of Apple?",
"options": [
{
"answer": "Mark Zuckerberg",
"correct": 0
},
{
"answer": "Bill Gates",
"correct": 0
},
{
"answer": "Steve Jobs",
"correct": 1
}
]
}
]
}
]
I have my form that allows the user to add & delete questions and options. User can also select the correct answer in the list of options.
Here is the JSFiddle link.
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="row">
<button id="btn-add-tab" type="button" class="btn btn-primary pull-right">Add Question</button>
</div>
<div class="row">
<form id="form">
<!-- Nav tabs -->
<ul id="tab-list" class="nav nav-tabs" role="tablist">
<li class="active">Question 1</li>
</ul>
<!-- Tab panes -->
<div id="tab-content" class="tab-content">
<br>
<div class="tab-pane fade in active" id="tab1">
<div class="input-group">
<input type="text" class="form-control" id="question" placeholder="Your question" required>
<span class="input-group-btn">
<button class="btn btn-success" id="add-option" type="button">Add Option</button>
</span>
</div>
<br>
<div id="options">
<!--- OPTIONS -->
<div class="well">
<textarea id="answer" class="form-control" placeholder="Your answer" required></textarea>
<div class="radio"><label><input type="radio" id="correct-answer" name="correct-ans-1" required>Correct Answer</label></div>
</div>
<!--- END OPTIONS -->
</div>
</div>
</div>
</div>
<div class="row">
<button id="btn-get-json" type="submit" class="btn btn-success pull-right btn-block">Get JSON</button>
</div>
</form>
</div>
</div>
</div>
With myjavascript code, I am experiencing an error which only shows json from the first question. Also, it doesn't display the list of options. In my code I used each to get all input fields in the form. Then I used JSON.stringify(); to convert array to JSON.
$("#form").submit(function(e) {
var jsonArr = [];
var obj = {};
var questionsArr = [];
var questionsCont = {};
var tabs = $("#form :input:not(input[type='submit'],button[type='button'])");
$(tabs).each(function(k,v){
var id = $(this).attr("id");
var value = $(this).val();
questionsCont[id] = value;
});
questionsArr.push(questionsCont);
obj["questions"] = questionsArr;
jsonArr.push(obj);
var json = JSON.stringify(jsonArr, null, "\t");
alert(json);
e.preventDefault();
});
I would like to have a json result that will looked-like from the post above. For testing my code, please see this JSFiddle link.
Any help is appreciated. Thank you!

First, IDs are meant to be unique -- so you cannot have two or more elements with the same ID. When you open a new tab or create a new option, you violate that rule.
Therefore, you should change your IDs to classes and/or names (using []). So, what I did is change input elements to use names and other problem elements to use classes.
How I see of going about is to start by looping through each tab pane. On each pane, find the question and its options, and add them to a data structure that will hold all your data. I am using the $.map to translate each tab into a question.
$("#form").submit(function(e) {
e.preventDefault();
var json = {};
// loop through each tab pane
json.questions = $('.tab-pane').map(function() {
return {
question: $('[name^=question]', this).val(),
// loop through each answer
options: $('[name^=answer]', this).map(function() {
return {
answer: $(this).val(),
correct: $(this).siblings('.radio').find('[name^=correct-ans]').prop('checked')
};
}).get()
};
}).get();
alert(JSON.stringify(json, null, "\t"));
});
Demo

Related

JSON to unordered list, sorted

I have an xml file that I made a JSON array with objects that are senators with a party, name, and a status of whether they have been voted or not. Only the name needs to be displayed on my HTML list. I don't know how to get it there, though, and I want to sort democrats and republicans dynamically as I go. here is a sample of the array:
[{"name":"Chuck Schumer","party":"Democrat","voted":false},
{"name":"Dick Durbin","party":"Democrat","voted":false}, ...]
I'm not sure how one does this. I have ID elements set up in my html because I know I need that.
Do I need to JSON.parse first? how do you connect them to the ID values?
Here is my HTML body.
<div id="dropLists" style="display: table">
<div style="display: table-row">
<div class="dropList">
<fieldset>
<legend>Democrats:</legend>
<ul id="democrats">
</ul>
</fieldset>
</div>
<div class="dropList">
<fieldset>
<legend>Republicans:</legend>
<ul id="republicans">
</ul>
</fieldset>
</div>
</div>
</div>
You're almost there. Here's an overview:
document.querySelector to select the 2 ul elements
document.createElement to create li elements
element.appendChild to insert the li elements into the `uls element.
let data = [{
"name": "Chuck Schumer",
"party": "Democrat",
"voted": false
},
{
"name": "Dick Durbin",
"party": "Democrat",
"voted": false
},
{
"name": "X Y Z",
"party": "Republican",
"voted": false
},
];
data.forEach(({name, party}) => {
let itemEl = document.createElement('li');
itemEl.textContent = name;
let listId = party === 'Democrat' ? '#democrats' : '#republicans';
let listEl = document.querySelector(listId);
listEl.appendChild(itemEl);
});
<div id="dropLists" style="display: table">
<div style="display: table-row">
<div class="dropList">
<fieldset>
<legend>Democrats:</legend>
<ul id="democrats">
</ul>
</fieldset>
</div>
<div class="dropList">
<fieldset>
<legend>Republicans:</legend>
<ul id="republicans">
</ul>
</fieldset>
</div>
</div>
</div>
Filter the list to get either "democrats" or "republicans", map the results to have only the name wrapped in a li and set the innerHTML of the coresponding ul ( html elements with id will be global variables, so you do democrats.innerHTML = ... )
const data = [{"name":"Chuck Schumer","party":"Democrat","voted":false},{"name":"Dick Durbin","party":"Democrat","voted":false},{"name":"Dick Durbin 2","party":"Republican","voted":false}]
democrats.innerHTML = data.filter(o => o.party === "Democrat").map(o => '<li>' + o.name + '</li>').join('');
republicans.innerHTML = data.filter(o => o.party === "Republican").map(o => '<li>' + o.name + '</li>').join('');
<div id="dropLists" style="display: table">
<div style="display: table-row">
<div class="dropList">
<fieldset>
<legend>Democrats:</legend>
<ul id="democrats">
</ul>
</fieldset>
</div>
<div class="dropList">
<fieldset>
<legend>Republicans:</legend>
<ul id="republicans">
</ul>
</fieldset>
</div>
</div>
</div>
Where is the code getting the data from? Is it reading an XML file? Calling a URL and downloading it? Or are you copying/pasting your array into the code? If you're copying/pasting it, then it's already an array and doesn't need to be JSON.parsed. If it's coming as the entire contents of a file obtained via fetch, then you can just use response.json() on the result of the fetch to convert it into an array. Any other source would likely give it to you as a string, in which case you just need to call JSON.parse() on the string to get the array.
Once you have an array, it's extremely easy to split it into two lists, one for each party:
const dems = senators.filter(senator => senator.party === 'Democrat');
const reps = senators.filter(senator => senator.party === 'Republican');
And then to convert them to an HTML list inside your existing ul elements:
const demList = document.getElementById('democrats');
dems.forEach(dem => {
const listItem = document.createElement('li');
listItem.textContent = dem.name;
demList.appendChild(listItem);
});
(The code is similar for the Republican list.)

jQuery on click function or random quote machine and JSON API

For the first time using JSON API. Please tell me where and what I'm doing wrong. This is a challenge from freeCodeCamp. We need to do build a random quote machine.
Once I click on New Quote button is should give us a random quote. In jQuery I'm looping through json and on click function has to change me current h2 class = text with the new random quote from JSON.
Project is here http://codepen.io/ekilja01/full/Lbdbpd/.
Please help.
Here is my HTML:
<div class="container-fluid">
<div class = "well">
<div class="row">
<h2 class="text text-center"><i class="fa fa-quote-left"> </i> Hey, what when and why there is no and yes?</h2>
<p class="author">-Alina Khachatrian</p>
<div class="buttons">
<div class="row">
<div class="col-xs-6">
<a id="tweet-quote" title="Tweet current quote" target="_blank" href="#">
<i class="fa fa-twitter fa-2x"></i>
</a>
</div>
<div class="col-xs-6">
<button type="button" class="btn btn-default btn-transparent" id ="getNewQuote" title="Get a new quote">New Quote</button>
</div>
</div>
<footer class="text-center">
<hr>
<p>Written and coded by Edgar Kiljak.</p>
</footer>
</div>
</div>
and JS:
$(document).ready(function(){
$(".btn").on("click", function(){
$.getJSON("http://quotes.rest/qod.json", function (json) {
var html = "";
json.forEach(function(val){
var keys = Object.keys(val);
html += "<div class = 'newQuote>'";
"<h2 = '" + val.quote + "'>";
html += "</h2>";
html += "</div>";
})
$(".text").html(html);
});
});
});
and JSON:
{
"success": {
"total": 1
},
"contents": {
"quotes": [
{
"quote": "Great things are done by a series of small things brought together.",
"length": "67",
"author": "Vincent Van Gogh",
"tags": [
"inspire",
"small-things",
"tso-art"
],
"category": "inspire",
"date": "2016-12-10",
"title": "Inspiring Quote of the day",
"background": "https://theysaidso.com/img/bgs/man_on_the_mountain.jpg",
"id": "DLThmumKP4CCe1833rRvNQeF"
}
]
}
}
your problem is here: json.forEach(function(val)...
since the JSON is not an array, it should be: json.contents.quotes.forEach(function(val)
json.contents.quotes is an array ([brackets] instead of {}) and forEach is only for arrays
Please post the error you are getting.
Also in your .js file, you are doing :
"<h2 = '" + val.quote + "'>";
which should be
"<h2>'" + val.quote + "'</h2>";
Another advice would be to put the code where you are handling the response in .done(). This method as far as I know, is available with the $.get() method.
From the jQuery Docs,
$.get( "test.cgi", { name: "John", time: "2pm" } )
.done(function( data ) {
alert( "Data Loaded: " + data );
});

AngularJS adding a new item and removing an existing item from a list

(1) I am trying to add a new user to a list of items (userList). The functionality works, but there is an issue. I made the list 'selectable' aka.. when a user clicks on an item on the list the textboxes in my html5 code gets populated w/ values from the selected item in the list. This allows the user to edit the individual properties from the item. Right underneath the group of textboxes is my 'add new user' button..... When the app first runs, the textboxes are empty and I fill them w/ appropriate text and click the add button and the new user is appended to the list. However the issues is, when I have already selected an item, edited it... then the textboxes are still populated w/ the item values... now if I click add new user... a new user is added... but now I have duplicate users in my list.. which is fine because I can always edit one of them... However.... it looks like both the new and the old user are now somehow linked... if I edit one of them, the values in the other also change... (I hope this makes sense). I feel that because the new user was created via the selected record of the old user, somehow their indexes are related....can't seem to figure out how to create a new user without having the old user connected to it.
(2) Deleting a user works fine, but except, the user deleted is always from the bottom of the list. I want to be able to select any item in the list and delete that specific item. I tried using something like:-
$scope.userList.splice($scope.userList.indexOf(currentUser), 1);
but to no avail.
My Javascript:-
<script type="text/javascript">
function UserController($scope) {
$scope.userList = [
{ Name: "John Doe1", Title: "xxxx", Company: "yyyy", Place: "zzzz" },
{ Name: "John Doe2", Title: "xxxx", Company: "yyyy", Place: "zzzz" },
{ Name: "John Doe3", Title: "xxxx", Company: "yyyy", Place: "zzzz" },
{ Name: "John Doe4", Title: "xxxx", Company: "yyyy", Place: "zzzz" }
];
$scope.selectUser = function (user) {
$scope.currentUser = user;
}
$scope.addNew = function (currentUser) {
$scope.userList.push(currentUser);
$scope.currentUser = {}; //clear out Employee object
}
$scope.removeItem = function (currentUser) {
// $scope.userList.pop(currentUser);
$scope.userList.splice($scope.userList.indexOf(currentUser), 1);
$scope.currentUser = {}; //clear out Employee object
}
}
</script>
My HTML:-
<div class="row">
<div style="margin-top: 40px"></div>
<div data-ng-app="" data-ng-controller="UserController">
<b>Employee List</b><br />
<br />
<ul>
<li data-ng-repeat="user in userList">
<a data-ng-click="selectUser(user)">{{user.Name}} | {{user.Title}} | {{user.Company}} | {{user.Place}}. </a>
</li>
</ul>
<hr>
<div style="margin-top: 40px"></div>
<b>Selected Employee</b><br />
<br />
<div style="border:dotted 1px grey; padding:20px 0 20px 0; width:40%;">
<div class="row" style="margin-left: 30px">
<div style="display: inline-block;">
Name:
</div>
<div style="display: inline-block; margin-left: 35px;">
<input type="text" data-ng-model="currentUser.Name">
</div>
</div>
<div style="margin-top: 20px"></div>
<div class="row" style="margin-left: 30px">
<div style="display: inline-block;">
Title:
</div>
<div style="display: inline-block; margin-left: 45px;">
<input type="text" data-ng-model="currentUser.Title">
</div>
</div>
<div style="margin-top: 20px"></div>
<div class="row" style="margin-left: 30px">
<div style="display: inline-block;">
Company:
</div>
<div style="display: inline-block; margin-left: 10px;">
<input type="text" data-ng-model="currentUser.Company">
</div>
</div>
<div style="margin-top: 20px"></div>
<div class="row" style="margin-left: 30px">
<div style="display: inline-block;">
Place:
</div>
<div style="display: inline-block; margin-left: 35px;">
<input type="text" data-ng-model="currentUser.Place">
</div>
</div>
</div>
<div>
<div style="margin: 2% 0 0 8%; display:inline-block">
<button data-ng-click="addNew(currentUser)" class="btn btn-primary" type="button">Add New Employee</button>
</div>
<div style="margin: 2% 0 0 1%; display:inline-block">
<button data-ng-click="removeItem(currentUser)" class="btn btn-primary" type="button">Delete Employee</button>
</div>
</div>
<hr>
<div style="margin-top: 40px"></div>
<b>Employee Details:</b><br />
<br />
{{currentUser.Name}} is a {{currentUser.Title}} at {{currentUser.Company}}. He currently lives in {{currentUser.Place}}.
</div>
</div>
* EDIT * I solved the delete user issue as:-
$scope.removeItem = function (currentUser) {
if ($scope.userList.indexOf(currentUser) >= 0) {
$scope.userList.splice($scope.userList.indexOf(currentUser), 1);
$scope.currentUser = {}; //clear out Employee object
}
}
and thanks to the suggestion, the add new user issue has also been resolved.
You have two issues. In $scope.addNew():
$scope.userList.push(currentUser);
This line pushes a reference to the same object you are currently editing. This is why the users appear linked, because you have the same object in the list twice. You instead need to copy the properties of the object onto a new object, which you can do in this case with angular.extend():
$scope.userList.push(angular.extend({}, currentUser));
You might instead consider having the "add new" button just add a blank user to the list and select it for editing:
$scope.addNew = function () {
$scope.userList.push($scope.currentUser = {});
};
In $scope.removeItem(), you use the pop() method of the array to try to remove a specific item, but pop() removes the last item and doesn't actually accept any arguments:
$scope.userList.pop(currentUser);
You could iterate the list to remove a specific object:
var i;
for (i = 0; i < $scope.userList.length; ++i) {
if ($scope.userList[i] === currentUser) {
$scope.userList.splice(i, 1);
break;
}
}
Or you could use indexOf() but test that the return value is not equal to -1, or the splice() call will remove the last element from the list.

How to update a textfield based on other form elements

I'm writing a little database query app.
What i'm trying to do: Each time a checkbox is clicked, i'd like for a query that includes the selected fields to be generated and inserted into the textarea.
The problem: For some reason, with every click, its showing the query from the previous click event, not the current one.
Here's the markup:
<div class="application container" ng-controller="OQB_Controller">
<!-- top headr -->
<nav class="navbar navbar-default navbar-fixed-top navbar-inverse shadow" role="navigation">
<a class="navbar-brand">
Algebraix Database Client
</a>
<ul class="nav navbar-nav navbar-right">
<!--<li>Clear Queries</li>-->
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-import"></span> Load Data <b class="caret"></b></a>
<ul class="dropdown-menu">
<li>Default Data</li>
<li>Custom Import</li>
<!-- <li class="divider"></li> -->
</ul>
</li>
<li>
<a href="" class="queries-clear">
<span class="glyphicon glyphicon-remove"></span> Clear Queries
</a>
</li>
</ul>
</nav>
<!-- left column -->
<div class="col-md-4">
<div class="well form-group">
<ul>
<li ng-repeat="option in options">
<input type="checkbox" class="included-{{option.included}}" value="{{option.value}}" ng-click="buildQuery()" ng-model="option.included"> {{option.text}}
</li>
</ul>
</div>
</div>
<!-- right column -->
<div class="col-md-8">
<form role="form" id="sparqlForm" method="POST" action="" class="form howblock">
<div class="form-group">
<!--<label>Query</label>-->
<textarea type="text" name="query" class="form-control" rows="10" placeholder="Write your SPARQL query here">{{query}}</textarea>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Submit Query" data-loading-text="Running Query..." />
</div>
</form>
</div>
</div>
And in my controller, i am doing the following:
var OQB_Controller = function($scope) {
console.log('OQB_CONTROLLER');
$scope.query = 0;
$scope.options = [
{ text: "checkbox1", value: "xyz123", included: false }
,{ text: "checkbox2", value: "abcRRR", included: false }
,{ text: "checkbox2", value: "abcRRR", included: false }
];
$scope.buildQuery = function() {
console.log('click');
var lines = [];
lines.push("SELECT *");
lines.push("WHERE {");
lines.push(" ?s ?p ?o .");
for(var i = 0; i<$scope.options.length; i++) {
var line = $scope.options[i];
console.log( line.value, line.included, i );
if( line.included ) {
lines.push(" OPTIONAL { ?s "+line.value+" ?o } .");
}
}
lines.push("}");
lines.push("LIMIT 10");
var _query = lines.join("\n");
$scope.query = _query;
};
};
To reiterate, every time the build query method is called, the state of the included booleans is from one click event prior. this has the symptoms of the classic javascript problem of the keyup vs keydown and the state of the event... however, i'm not sure if that is what is happening here.
is there a better way to do build the query (than what i'm currently doing) and populate the textarea based on the checked boxes?
use ng-change instead of ng-click because it is more appropriate for this particular desired behavior. See the ng-change documentation below:
The ngChange expression is only evaluated when a change in the input
value causes a new value to be committed to the model.
It will not be evaluated:
if the value returned from the $parsers transformation pipeline has
not changed if the input has continued to be invalid since the model
will stay null if the model is changed programmatically and not by a
change to the input value

emberjs arraycontroller issue

I am new to Ember and am having an issue. I would like the user to be able to select a number of workstations, and when they hit the next button, I would like the controller to create a number of objects equal to the number the user selected. Once they are taken to the next screen I want to view to append a number of divs with the questions equal to the number the user selected.
I have this for the app.js:
//Initialize the application
App = Ember.Application.create({
rootElement: '#main'
});
//Initialize the data model
App.CustomerController = Ember.Object.extend({
first: null,
last: null,
email: null,
phone: null,
clinic: null,
number: null
});
App.Workstation = Ember.Object.extend({
id: null,
title: null,
newOrExisting: null,
cabling: null
});
App.workstationController = Ember.ArrayController.create({
content: [],
num: null,
init: function() {
this.set('content',[]);
var num = this.get('num');
var tempId = Date.now();
var ws = App.Workstation.create({
id: tempId
});
this.pushObject(ws);
}
});
App.selectNoComputers = ["1", "2", "3", "4", "5"];
App.workstationSelect = ["Counter 1", "Counter 2", "Counter 3", "Counter 4", "Office 1", "Office 2", "Office 3"];
App.yesNo = ["New", "Existing"];
App.Router.map(function(match) {
match("/").to("captcha");
match("/customer").to("customer");
match("/wsnum").to("computers");
match("/overview").to("overview");
});
App.CaptchaRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('captcha');
}
});
App.CustomerRoute = Ember.Route.extend();
App.ComputersRoute = Ember.Route.extend();
App.OverviewRoute = Ember.Route.extend({
});
App.initialize();
And this for my html:
<script type="text/x-handlebars" data-template-name="overview">
<div class="navbar">
<div class="navbar-inner">
<div class="progress-bar-label-div">
Progress:
</div>
<div class="progress-bar-div">
<div class="progress progress-striped">
<div class="bar" style="width:60%;"></div>
</div>
</div>
<div class="btn-group pull-right">
{{#linkTo "computers" class="btn"}}
Prev
{{/linkTo}}
</div>
</div>
</div>
<div class="row-a top">
<div class="pull-left" >
<h3>Workstation Overview</h3>
</div>
<div class="pull-right">
</div>
</div>
{{#each App.workstationController}}
<div class="workstation-b">
<div class="row-b">
<div class="pull-left workstation-title" >
<h4>{{id}}</h4>
</div>
<div class="pull-right form-inputs input-text">
<a class="btn btn-primary" >
Start
</a>
</div>
</div>
<div class="row-b">
<div class="pull-left questions">
What station will this be?
</div>
<div class="pull-right form-inputs input-text">
{{view Ember.Select prompt="Please Select" contentBinding="App.workstationSelect"}}
</div>
</div>
<div class="row-b">
<div class="pull-left questions">
Is this computer a new purchase or replacing and existing workstation?
</div>
<div class="pull-right form-inputs input-text">
{{view Ember.Select prompt="Please Select" contentBinding="App.yesNo"}}
</div>
</div>
</div>
{{/each}}
</script>
I'm sure I'm missing something pretty easy, but any help is appreciated.
I put together a fiddle to illustrate my problem.
Working fiddle: http://jsfiddle.net/mgrassotti/xtNXw/3/
Relevant changes: Added an observer to listen for changes to the num property. When it changes, the content array is reset and then an appropriate number of blank workstation objects are created.
App.workstationController = Ember.ArrayController.create({
content: [],
num: null,
init: function() {
this.set('content',[]);
},
addBlankWorkstation: function() {
var tempId = Date.now();
var ws = App.Workstation.create({
id: tempId
});
this.pushObject(ws);
}
});
App.workstationController.addObserver( 'num', function() {
var num = this.get('num');
this.set('content',[]);
for (var i=0;i<num;i++) {
this.addBlankWorkstation();
}
});
I hesitated to say working fiddle above since there are many things that might be worth refactoring. You'll find most of the complexity could be reduced by following ember naming conventions. Suggest looking at latest guides for more detail.

Categories

Resources