Knockout JS -> Update the view with value from new GET Call - api

I am consuming web services. I am making GET call to get all the questions -> user will answer that question and post it using the post call. This part is working as expected right now. Now, I would like make another GET call as soon as POST call is successful. I am able to make a GET call after POST call is complete but view is still showing data from my old GET call. How can I update the view with information from new GET call.
GET -> POST -> New GET (Data is not updated in this call.)
JSON
{
"Respondent_ID":"hello111",
"Group_Name":"",
"Product_ID":80,
"Language_ID":1,
"First_Name":"hello",
"Last_Name":"111",
"Respondent_EMail":"",
"Gender":"M",
"AllQuestions":[
{
"Question_Number":76,
"Question_Text":"I think ",
"Definition":"",
"Answer":0
},
{
"Question_Number":77,
"Question_Text":"I am ",
"Definition":"",
"Answer":0
},
{
"Question_Number":78,
"Question_Text":"I am mild mannered",
"Definition":"",
"Answer":0
},
{
"Question_Number":79,
"Question_Text":"I am strong",
"Definition":"",
"Answer":0
},
{
"Question_Number":80,
"Question_Text":"I am a risk taker",
"Definition":"",
"Answer":0
}
],
"AnswerChoice":[
{
"Answer_Choice":"Strongly disagree",
"Answer_Choice_Value":1
},
{
"Answer_Choice":"Disagree",
"Answer_Choice_Value":2
},
{
"Answer_Choice":"Neutral",
"Answer_Choice_Value":3
},
{
"Answer_Choice":"Agree",
"Answer_Choice_Value":4
},
{
"Answer_Choice":"Strongly agree",
"Answer_Choice_Value":5
}
]
}
--
#{
ViewBag.Title = "Questions";
}
<html>
<body>
<script src="~/Scripts/knockout.mapping-latest.js"></script>
<script>
function GetAllEmployees() {
$.ajax({
url: '/api/Questions?respondent_id=hello111',
type: 'GET',
dataType: 'json',
success: function (data) {
var data2 = data.AllQuestions;
var viewModel = {
data: ko.mapping.fromJS(data2),
Question_Number: ko.observable(data.AllQuestions[0].Question_Number),
Question_Text: ko.observable(data.AllQuestions[0].Question_Text),
save: function () {
$.ajax({
url: '/api/lms',
type: 'POST',
data: data,
dataType: 'json',
success: function (data) {
$.ajax({
url: '/api/Questions?respondent_id=hello111',
type: 'GET',
dataType: 'json',
success: function (data) {
//How can update the view with the new data I got from the get call.
}
ko.applyBindings(viewModel);
}
});
},
error: function (x, y, z) {
alert(x + '\n' + y + '\n' + z);
}
});
}
}
ko.applyBindings(viewModel);
},
error: function (x, y, z) {
alert(x + '\n' + y + '\n' + z);
}
});
}
</script>
</body>
</html>
Get Questions
<form data-bind="submit: save">
<table>
<thead>
<tr><th>#</th>Question<th>Strongly disagree</th><th>Strongly disagree</th><th>Disagree</th><th>Neutral</th><th>Agree</th><th>Strongly agree</th></tr>
</thead>
<tbody data-bind="foreach: $data">
<tr>
<td>
<span data-bind="text: Question_Number"></span>
</td>
<td>
<span data-bind="text: Question_Text"></span>
</td>
<td><input type="radio" class="radio" value="1" data-bind="attr: { name: Question_Number}"></td>
<td><input type="radio" class="radio" value="2" data-bind="attr: { name: Question_Number }"></td>
<td><input type="radio" class="radio" value="3" data-bind="attr: { name: Question_Number }"></td>
<td><input type="radio" class="radio" value="4" data-bind="attr: { name: Question_Number }"></td>
<td><input type="radio" class="radio" value="5" data-bind="attr: { name: Question_Number }"></td>
</tr>
</tbody>
</table>
<button type="submit">Go</button>
</form>

You must un-nest your code.
And you should start to use jQuery's native .get() and .post() calls and deferred callbacks (.then(), .done(), .fail(), .always()) for Ajax handling.
I rewrote your JS code:
function showAjaxError(x, y, z) {
alert(x + '\n' + y + '\n' + z);
}
function QuestionViewModel(respondentId) {
var self = this;
self.data = ko.observableArray();
self.currentQuestionId = ko.observable();
self.currentQuestion = ko.computed(function () {
return self.data()[self.currentQuestionId()];
});
self.mapQuestions = function (rawData) {
return ko.mapping.fromJS(rawData.AllQuestions);
};
self.rewind = function () {
self.currentQuestionId(0);
};
self.updateFromServer = function () {
$.get('/api/Questions', { respondent_id: respondentId })
.then(self.mapQuestions)
.done(self.data)
.done(self.rewind)
.fail(showAjaxError);
};
self.save = function () {
$.post('/api/lms', { data: ko.mapping.toJS(self.data) })
.done(self.updateFromServer)
.fail(showAjaxError);
};
self.updateFromServer();
}
ko.applyBindings(new QuestionViewModel('hello111'));
Notes
'/api/Questions?respondent_id=hello111' should not be hard-coded. Make it a variable.
View models work best when they are built with a constructor, because otherwise it's very hard to make them refer to themselves internally.
Don't repeat yourself. Make small, re-usable functions (like showAjaxError()) and re-use them. If your code nests more than 3 levels deep you are doing it wrong.
By commonly accepted coding style conventions, everything that is not a constructor starts with a lowercase letter and names don't have underscores (i.g. questionText instead of Question_Text).

Related

How can I get the :value from input with Vuejs?

Hi I have list like this:
<tr v-for="(post, index) in posts" v-bind:index="index">
<td>{{ post.rut }}</td>
<td>{{ post.names }} {{ post.father_lastname }} {{ post.mother_lastname }}</td>
<td>
<input type="number" class="form-control" id="exampleInputEmail1" v-bind:value="post.employee_id" #input="form.amount[post.employee_id]" placeholder="Ingresa el monto">
</td>
</tr>
I defined in v-bind:value="" a initial value for every input of the list, then I need to send that data with axios but when I do that it does not send anything I mean I can not catch the vale for every input why? because it displays the value.. my axios is:
onSubmit(e) {
this.loading = true; //the loading begin
e.preventDefault();
let currentObj = this;
const config = {
headers: { 'content-type': 'multipart/form-data' }
}
let formData = new FormData();
formData.append('amounts', JSON.stringify(this.form.amount));
axios.post('/api/payroll_management/store?api_token='+App.apiToken, formData, config)
.then(function (response) {
currentObj.success = response.data.success;
})
.catch(function (error) {
console.log(error);
});
}
so I wonder how can I get the data from the inputs? if it returns empty this.form.amount
Thanks
Since each post has an amount value that gets changed with the <input>, it is easier to have that value be part of the post item itself.
This is done using v-model="post.amount" (see documentation) on the <input> of each post.
This way, there is a single place where the amount value is and where it gets updated.
Then when you submit the form, you can get the an array of these amount values by using a computed property (see documentation).
For better understanding what is happening, I highly recommend going through VueJS's documentation, since it's very readable and explains everything quite well.
Now, bringing it all together, have a look at this example:
new Vue({
el: "#app",
data: {
posts: [
{
rut: "A",
names: "Name Name",
father_lastname: "Lastname",
mother_lastname: "Lastname2",
employee_id: 5,
amount: 5, // Default value here
},
{
rut: "B",
names: "Name Name",
father_lastname: "Lastname",
mother_lastname: "Lastname2",
employee_id: 2,
amount: 2, // Default value here
},
],
},
computed: {
// Make form data a computed object.
form: function() {
// Get only the amount values from the posts.
// The inputs will update those automatically,
// so these will change as well.
let amount = this.posts.map((post, idx) => {
return parseInt(post.amount); // parseInt() is optional here
});
return {
amount: amount,
// ... other stuff
};
},
},
methods: {
getFormData: function() {
console.log( JSON.stringify(this.form.amount) );
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<h2>Posts:</h2>
<table>
<tr v-for="(post, index) in posts" v-bind:index="index">
<td>{{ post.rut }}</td>
<td>{{ post.names }} {{ post.father_lastname }} {{ post.mother_lastname }}</td>
<td>
<input type="number" class="form-control" v-model="post.amount">
</td>
</tr>
</table>
<br/>
<button v-on:click="getFormData()">Console log form data</button>
</div>

Performing action when clicking off an element

The below code allows me to have a click-to-edit header tag within my application.
I'm looking for the best way to handle exiting editing mode when any other action is performed on the page... either a click or a drag-n-drop.
<validator name="teamSetValidation">
<input id='teamSetName' v-if="isEditingName" type="text" v-model="teamSet.name" class="semi-bold p-t-10 p-b-10 m-l-15 edit-header" v-on:keyup.enter="saveTeamSetName()" v-on:keyup.esc="doneEditing()" v-validate:name.required.maxlength="teamSetRules" :isEditingName="true"/>
<h3 v-else class="semi-bold p-t-10 p-b-10 m-l-15" v-on:click="editing()" :isEditingName="false">{{ teamSet.name }} <span class="fa fa-edit"></span></h3>
<div class="text-small">
<span class="text-danger" v-if="$teamSetValidation.teamSet.name.required">A name is required.</span>
<span class="text-danger" v-if="$teamSetValidation.teamSet.name.maxlength">The name you provided is too long.</span>
</div>
<div class="b-grey b-b m-t-10"></div>
</validator>
Javascript:
var vm = new Vue({
el: '#page',
data: {
// When true, user can edit the teamSet name
isEditingName: false,
teamSet: teamSet,
teamSetRules: {
required: false,
maxlength: 64
}
},
methods: {
editTeamSetName: function () {
alert('editing');
},
saveTeamSetName: function () {
if(this.$teamSetValidation.valid) {
this.doneEditing();
var teamSet = this.teamSet,
self = this;
$.ajax({
url: '/team/'+teamSet.id,
type: 'PATCH',
data: {
'name': teamSet.name
},
error: function(res) {
Messenger().post({
message: 'Unable to save changes',
type: 'error',
hideAfter: 3
});
self.editing();
}
});
}
},
editing: function () {
this.isEditingName = true;
Vue.nextTick(function () {
$('#teamSetName').focus();
});
},
doneEditing: function () {
this.isEditingName = false;
}
}
});
Attaching a blur event to the input should do the trick:
<input id='teamSetName' v-if="isEditingName"
type="text" v-model="teamSet.name"
class="semi-bold p-t-10 p-b-10 m-l-15 edit-header"
v-on:keyup.enter="saveTeamSetName()"
v-on:keyup.esc="doneEditing()"
v-validate:name.required.maxlength="teamSetRules"
:isEditingName="true" v-on:blur="doneEditing()"
/>

Loading Remote Data in Select2

I am using Select2's Loading Remote Data Functionality.The problem is that data is not getting loaded on the dropdownlist.On keypress remote function is getting called and data is returning properly,but its not showing in dropdownlist.
HTML
<div class=" form-group col-md-4" data-url="#Url.Action("GetStudentWalkInnName")" id="WalkinnName">
<div>
<label for="txtEmployee" class=" control-label">
Name
</label>
</div>
<div>
<select class="form-control " id="ddlName"></select>
</div>
</div>
Jquery
//REGISTRATION=>INDEX.JS
$(function () {
var ddlNameUrl=$("#WalkinnName").data("url");
$("#ddlName").select2({
placeholder: "Search for Name",
minimumInputLength: 1,
ajax: { // instead of writing the function to execute the request we use Select2's convenient helper
url: ddlNameUrl,
type: "POST",
dataType: 'json',
data: function (params) {
return {
term: params.term, // search term
page: params.page
};
},
processResults: function (data, params) {
params.page = params.page || 1;
return {
results: data,
};
}
}
});
});
Controller is
public JsonResult GetStudentWalkInnName(string term)
{
try
{
var walkInnNameList = _db.StudentWalkInns
.Where(s => s.CandidateName.StartsWith(term))
.Select(x => new
{
Id=x.Id,
Text=x.CandidateName
}).ToList();
return Json(walkInnNameList, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json("", JsonRequestBehavior.AllowGet);
}
}
Any help will be highly appreciated.
According to the documentation, the format if the data should be an array of objects with names id and name i.e. lowercase (not Id and Name).
Change you query to
var walkInnNameList = _db.StudentWalkInns
.Where(s => s.CandidateName.StartsWith(term))
.Select(x => new
{
id = x.Id,
text = x.CandidateName
}); // .ToList() should not be necessary

knockout.js binding issue when trying to refresh data

I am using knockout.js data binding. At the page load the binding works fine and data is shown on the page correctly. Then I try to push data back to the database and the push is successful. The database receives the data OK.
The problem comes when I try to reload the data upon push success. At this time the binding already happen once (at the page load). If I don't bind it again the data on the page does not refresh. If I do the binding again knockout.js issues an error "cannot bind multiple times". If I do a cleanup before rebinding I receive an error "nodeType undefined".
Can anyone tell me what I have missed here? I am using ASP.NET MVC 4.0 with knockout.js 3.0.0.
Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplJSON.Controllers
{
public class KnockoutController : Controller
{
//
// GET: /Knockout/
public ActionResult Index()
{
return View();
}
[HttpGet]
public JsonResult GetProductList()
{
var model = new List<Product>();
try
{
using (var db = new KOEntities())
{
var product = from p in db.Products orderby p.Name select p;
model = product.ToList();
}
}
catch (Exception ex)
{ throw ex; }
return Json(model, JsonRequestBehavior.AllowGet);
}
// your controller action should return a JsonResult. There's no such thing as a controller action that returns void. You have specified dataType: 'json' so return JSON. That's it. As far as what parameter this controller action should take, well, from the JSON you are sending ({"Name":"AA"}) it should be a class that has a Name property of type string.
// modify your action signature to look like this: [HttpPost] public ActionResult SaveProduct (Product product) { ... return Json(new { success = true }); }. Or get rid of this dataType: 'json' attribute from your AJAX request if you don't want to return JSON. In this case you could return simply status code 201 (Created with empty response body): return new HttpStatusCodeResult(201);.
[HttpPost]
public ActionResult SaveProduct(Product product)
{
using (var db = new KOEntities())
{
db.Products.Add(new Product { Name = product.Name, DateCreated = DateTime.Now });
db.SaveChanges();
}
return Json(new { success = true });
}
}
}
View:
#{
ViewBag.Title = "Knockout";
}
<h2>Knockout</h2>
<h3>Load JSON Data</h3>
<div id="loadJson-custom" class="left message-info">
<h4>Products</h4>
<div id="accordion" data-bind='template: { name: "product-template", foreach: product }'>
</div>
</div>
<h3>Post JSON Data</h3>
<div id="postJjson-custom" class="left message-info">
<h4>Add Product</h4>
<input id="productName" /><br />
<button id="addProduct">Add</button>
</div>
#section Scripts {
#Scripts.Render("~/bundles/knockout")
#Scripts.Render("~/bundles/livequery")
<script src="/Scripts/jquery.livequery.min.js"></script>
<style>
#accordion { width: 400px; }
</style>
<script id="product-template" type="text/html">
<h3><a data-bind="attr: {href:'#', title: Name, class: 'product'}"><span data-bind="text: Name"></span></a></h3>
<div>
<p>Here's some into about <span data-bind="text: Name" style="font-weight: bold;"></span> </p>
</div>
</script>
<script>
var isBound;
function loadJsonData() {
$.ajax({
url: "/knockout/GetProductList",
type: "GET",
contentType: "application/json",
dataType: "json",
data: {},
async: true,
success: function (data) {
var loadJsonViewModel = {
product: ko.observableArray(),
init: function () {
loadAccordion();
}
};
var a = $('loadJson-custom');
loadJsonViewModel.product = ko.mapping.fromJS(data);
if (isBound) { }
else
{
ko.applyBindings(loadJsonViewModel, $('loadJson-custom')[0]);
isBound = true;
}
loadJsonViewModel.init();
}
});
}
// push data back to the database
function pushJsonData(productName) {
var jData = '{"Name": "' + productName + '"}';
$.ajax({
url: "/knockout/SaveProduct",
type: "POST",
contentType: "application/json",
dataType: "json",
data: jData,
async: true,
success: function (data, textStatus, jqXHR) {
console.log(textStatus + " in pushJsonData: " + data + " " + jqXHR);
//ko.cleanNode($('loadJson-custom')[0]);
loadJsonData();
},
error: function (jqXHR, textStatus, errorThrown) {
alert(textStatus + " in pushJsonData: " + errorThrown + " " + jqXHR);
}
});
}
function loadAccordion() {
$("#accordion > div").livequery(function () {
if (typeof $("#accordion").data("ui-accordion") == "undefined")
{
$("#accordion").accordion({ event: "mouseover" });
}
else
{
$("#accordion").accordion("destroy").accordion({ event: "mouseover" });
}
});
}
$(function () {
isBound = false;
loadJsonData();
$("#addProduct").click(function () {
pushJsonData($("#productName").val());
});
});
</script>
}
Here is a complete solution for your question.
I have just implemented and checked.
Please have a look.
I have created a class for getting some records ie: Records.cs.
public static class Records
{
public static IList<Student> Stud(int size)
{
IList<Student> stud = new List<Student>();
for (int i = 0; i < size; i++)
{
Student stu = new Student()
{
Name = "Name " + i,
Age = 20 + i
};
stud.Add(stu);
}
return stud;
}
}
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
Here is a controller for the respective view.
//
// GET: /HelpStack/
private static IList<Student> stud = Records.Stud(10);
public ActionResult HelpStactIndex()
{
return View();
}
[HttpGet]
public JsonResult GetRecord()
{
return Json(stud, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public void PostData(Student model)
{
//do the required code here as All data is in "model"
}
Here is a view as HTML, I have taken two section one for list and other to Add records
<div id="loadJson-custom">
<h4>Student</h4>
<table width="100%">
<tr>
<td style="width: 50%">
<div id="accordion" data-bind='template: { name: "product-template", foreach: Student }'>
</div>
</td>
<td valign="top">
<div id="NewStudent">
<input type="text" data-bind="value: Name" /><br />
<input type="number" data-bind="value: Age" /><br />
<input type="submit" data-bind="click: Save" value="AddNew" />
</div>
</td>
</tr>
</table>
Here is your scripts for Knockoutjs.
<script id="product-template" type="text/html">
<h3><a data-bind="attr: { href: '#', title: Name, class: 'product' }"><span data-bind=" text: Name"></span></a>
<br />
Age: <span data-bind="text: Age"></span>
</h3>
<div>
<p>Here's some into about <span data-bind="text: Name" style="font-weight: bold;"></span></p>
</div>
</script>
<script type="text/javascript">
//Model for insert new record
var Model = {
Name: ko.observable(''),
Age: ko.observable(''),
};
var Student = ko.observableArray([]); // List of Students
function loadData() { //Get records
$.getJSON("/HelpStack/GetRecord", function (data) {
$.each(data, function (index, item) {
Student.push(item);
});
}, null);
}
function Save() { //Save records
$.post("/HelpStack/PostData", Model, function () { //Oncomplete i have just pushed the new record.
// Here you can also recall the LoadData() to reload all the records
//Student.push(Model);
Student.unshift(Model); // unshift , add new item at top
});
}
$(function () {
loadData();
ko.applyBindings(Model, $('#NewStudent')[0]);
});
</script>
You are declaring your model inside loadJsonData function success callback, & creating new object on every success callback, move the model outside that function, create an object & use it inside loadJsonData function, it will fix the issue.

paging using knockout js

At first I have displlay data using knockout js successful,here is my code:
Js
var viewMode = {
lookupCollection: ko.observableArray()
};
$(document).ready(function () {
$.ajax({
type: "GET",
url: "/Home/GetIndex",
}).done(function (data) {
$(data).each(function (index, element) {
viewModel.lookupCollection.push(element);
});
ko.applyBindings(viewMode);
}).error(function (ex) {
alert("Error");
});
});
View:
<table class="paginated">
<tr>
<th>
Name
</th>
<th>
Price
</th>
<th>
Category
</th>
<th></th>
</tr>
<tbody data-bind="foreach: lookupCollection">
<tr>
<td data-bind="text: Name"></td>
<td data-bind="text: price"></td>
<td data-bind="text: Category"></td>
<td>
<button class="btn btn-success">Edit</button>
<button class="btn btn-danger">Delete</button>
</td>
</tr>
</tbody>
</table>
However, I need more code to paging the list items, I follow this site http://knockoutjs.com/examples/grid.html and replay my code but It has not display my list items:
JS:
var initialData = {
lookupCollection: ko.observableArray()
};
var PagedGridModel = function (items) {
this.items = ko.observableArray(items);
this.sortByName = function () {
this.items.sort(function (a, b) {
return a.name < b.name ? -1 : 1;
});
};
this.jumpToFirstPage = function () {
this.gridViewModel.currentPageIndex(0);
};
this.gridViewModel = new ko.simpleGrid.viewModel({
data: this.items,
columns: [
{ headerText: "Name", rowText: "Name" },
{ headerText: "Category", rowText: "Category" },
{ headerText: "Price", rowText: function (item) { return "$" + item.price.toFixed(2) } }
],
pageSize: 4
});
};
$(document).ready(function () {
$.ajax({
type: "GET",
url: "/Home/GetIndex",
}).done(function (data) {
$(data).each(function (index, element) {
viewModel.lookupCollection.push(element);
});
ko.applyBindings(new PagedGridModel(initialData));
}).error(function (ex) {
alert("Error");
});
});
View:
<div data-bind='simpleGrid: gridViewModel'> </div>
<button data-bind='click: sortByName'>
Sort by name
</button>
<button data-bind='click: jumpToFirstPage, enable: gridViewModel.currentPageIndex'>
Jump to first page
</button>
thankyou very much your answer:
The "simpleGrid" binding u try to use is a custom one, not available by default in knockout.
Here's a simple example for pagination using a computed observable :
Fiddle : http://jsfiddle.net/RapTorS/qLHwx/
var viewModel = function () {
var self = this;
self.pageSize = 4;
self.currentPage = ko.observable(1);
self.lookupCollection = ko.observableArray([]);
self.currentCollection = ko.computed(function () {
var startIndex = self.pageSize * (self.currentPage() - 1);
var endIndex = startIndex + self.pageSize;
return self.lookupCollection().slice(startIndex, endIndex);
});
};