Model and Computed Property interaction in Vue.js - javascript

Using vue.js I am trying to build a simple task manager.
When a user clicks the "complete" checkbox I want two things to happen:
If the "Show all tasks" is unchecked, hide the task.
Send an ajax request to the server to mark the task as complete/open.
The impotent parts are shown below:
<div id="tasks-app">
<input type="checkbox" id="checkbox" v-model="show_all">
<label for="checkbox">Show all tasks</label><br>
<table class="table">
<tr><th v-for="column in table_columns" v-text="column"></th><tr>
<tr v-for="row in visibleTasks" :class="{danger: !row.daily_task.complete && row.daily_task.delayed, success: row.daily_task.complete}">
<td v-text="row.task.name"></td>
<td v-text="row.task.deadline"></td>
<td v-text="row.daily_task.status"></td>
<td v-text="row.daily_task.task_user"></td>
<td>
<input type="checkbox" v-on:change="updateStatus(row)" v-model="row.daily_task.complete" >Complete</input>
</td>
<td><input v-model="row.daily_task.delay_reason"></input></td>
</table>
</div>
And the VUE.js code:
app = new Vue({
el: '#tasks-app',
data: {
table_columns: ['Task','Deadline','Status','User','Actions','Reason'],
tasks: [],
filter_string: '',
show_all: false
},
computed: {
visibleTasks() {
show_all = this.show_all
if(show_all){
search_filter = this.tasks
}else{
search_filter = _.filter(this.tasks,function(task){
return !task.daily_task.complete;
})
}
return search_filter
}
},
methods: {
updateStatus(row){
var id = row.daily_task.id
var complete = row.daily_task.complete
if(complete){
axios.get('set_task_complete/' + id)
}else{
axios.get('set_task_open/' + id)
}
}
}
})
If the show all checkbox is checked, this works as expected. The data changes and then the updateStatus function is called.
If however the show all checkbox is unchecked, the visibleTasks will trigger and the logic for the updateStatus will fail, as the row will be hidden and the ID that is send to the server will be off by one. If the row hides before updateStatusis called the wrong row is passed to the updateStatus function.
I could solve this by adding a filter update at the end of updateStatus function but that does not seems to utilize the Vue.js library. Could someone help me what components of Vue you would use to solve this problem?

You can simplify a lot if you separate the logic:
Loop over every tasks (completed or not) to display them (without considering show_all)
Update tasks with v-on:click="updateStatus(row)"
On each tr, add v-show="show_all || !row.status.complete"

Your problem is use both change event handle and model. Both actually, trigger at same time when you click on the checkbox.
<input type="checkbox" v-on:change="updateStatus(row)" v-model="row.daily_task.complete" >Complete</input>
You should edit your code use only v-on:change="updateStatus(row)". After updateStatus complete ajax calls toggle row.daily_task.complete to trigger visibleTasks to update your view.
updateStatus(row){
var id = row.daily_task.id
var complete = !row.daily_task.complete
var p;
if(complete){
p = axios.get('set_task_complete/' + id)
}else{
p = axios.get('set_task_open/' + id)
}
p.then(() => row.daily_task.complete = complete)
}

I've refactored your code a bit and it seems to work fine:
you shouldn't use v-on:change, instead, use <input type="checkbox" v-on:click="updateStatus(row)" v-bind:checked="row.daily_task.complete">
Don't update row.daily_task.complete straight away, update it only when the asynchronous axios is complete.
const fakeUpdateComplete = (id) => {
return new Promise((resolve, reject) => { resolve(true); });
};
const fakeUpdateIncomplete = (id) => {
return new Promise((resolve, reject) => { resolve(true); });
};
const app = new Vue({
el: '#tasks-app',
data: {
history: [],
table_columns: ['Task','Deadline','Status','User','Actions','Reason'],
tasks: [
{
task: {
name: 'A',
deadline: '2017-01-01',
},
daily_task: {
id: 1,
status: '',
task_user: '',
complete: true,
delayed: false,
delay_reason: ''
}
},
{
task: {
name: 'B',
deadline: '2017-01-02',
},
daily_task: {
id: 2,
status: '',
task_user: '',
complete: false,
delayed: false,
delay_reason: ''
}
},
{
task: {
name: 'C',
deadline: '2017-01-03',
},
daily_task: {
id: 3,
status: '',
task_user: '',
complete: false,
delayed: false,
delay_reason: ''
}
},
{
task: {
name: 'D',
deadline: '2017-01-03',
},
daily_task: {
id: 4,
status: '',
task_user: '',
complete: true,
delayed: false,
delay_reason: ''
}
},
{
task: {
name: 'E',
deadline: '2017-01-03',
},
daily_task: {
id: 5,
status: '',
task_user: '',
complete: false,
delayed: false,
delay_reason: ''
}
}
],
filter_string: '',
show_all: true
},
computed: {
visibleTasks() {
const show_all = this.show_all
let search_filter;
if(show_all){
search_filter = this.tasks
}else{
console.log(this.tasks);
search_filter = this.tasks.filter(task => {
return !task.daily_task.complete
});
}
return search_filter
}
},
methods: {
updateStatus(row){
const id = row.daily_task.id;
const complete = !row.daily_task.complete;
if (complete) {
fakeUpdateComplete(id).then(() => {
this.history.push(`Task ${row.task.name} with id ${row.daily_task.id} is marked as complete`);
row.daily_task.complete = !row.daily_task.complete;
});
} else {
fakeUpdateIncomplete(id).then(() => {
this.history.push(`Task ${row.task.name} with id ${row.daily_task.id} is marked as incomplete`);
row.daily_task.complete = !row.daily_task.complete;
});
}
/*
if(complete){
axios.get('set_task_complete/' + id)
}else{
axios.get('set_task_open/' + id)
}
*/
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="tasks-app">
<input type="checkbox" id="checkbox" v-model="show_all">
<label for="checkbox">Show all tasks</label><br>
<table class="table">
<tr><th v-for="column in table_columns" v-text="column"></th><tr>
<tr
v-for="row in visibleTasks"
:class="{danger: !row.daily_task.complete && row.daily_task.delayed, success: row.daily_task.complete}"
>
<td>{{row.task.name}}</td>
<td>{{row.task.deadline}}</td>
<td>{{row.daily_task.status}}</td>
<td>{{row.daily_task.task_user}}</td>
<td>
<input type="checkbox" v-on:click="updateStatus(row)" v-bind:checked="row.daily_task.complete">
<label for="complete">Complete</label>
</td>
<td><input v-model="row.daily_task.delay_reason" /></td>
</tr>
</table>
<div>
<div><b>Data:</b></div>
<div>{{this.tasks}}</div>
</div>
<div>
<div><b>History:</b></div>
<div v-for="item in history">
{{item}}
</div>
</div>
</div>

Related

$refs resetFields not a function AntDesign

I have a problem with reseting fields of my form.
I have a form. The user can adding another forms and another ...
If everything is OK, I would like to recording data in my DB and in my store AND reset all of inputs of my form. This is this last point I cannot do.
I have tried different solutions but It does not work.
This is my code :
<div v-for="(question, index) in questionsSignaletiques" :key="index" class="blockQuestion" >
<!--form to add questions : one form per question, each form have a different name in the ref -->
<a-form-model
layout="inline"
:ref="'p' + index"
>
<p>New question</p>
<a-alert v-if="question.error.here" type="error" :message="question.error.message" show-icon />
<a-form-model-item>
<a-input v-model="question.question.type" placeholder="Title">
</a-input>
</a-form-model-item>
<a-form-model-item>
<a-input v-model="question.question.item" placeholder="Item">
</a-input>
</a-form-model-item>
<br><br>
<a-form-model-item label="Question multiple" prop="delivery">
<a-switch v-model="question.question.multiple" />
</a-form-model-item>
<a-form-model-item label="Obligatoire" prop="delivery">
<a-switch v-model="question.question.obligatoire" />
</a-form-model-item>
<br><br>
<div class="blockChoices">
<div v-for="subrow in question.question.choices">
<a-form-model-item>
<a-input v-model="subrow.name" placeholder="Choix">
</a-input>
</a-form-model-item>
</div>
</div>
<a-button #click="addChoice(question)" type="secondary">Add a choice</a-button>
</a-form-model>
</div>
<div>
<a-button #click="addItem" type="secondary">Add a question</a-button>
</div>
<br>
<div>
<a-button #click="submit" type="primary">Send</a-button>
</div>
Javascript code :
data() {
return {
idStudy: this.$route.params.id,
aucuneQuestion:false,
questionsSignaletiques: [
{
"study":"/api/studies/" + this.$route.params.id,
"question":
{
type: "",
item: "",
multiple: false,
obligatoire: false,
inverse: false,
barometre: false,
originale: false,
signaletik: true,
choices: [{name:''}]
},
"error": {
message:"",
here:false
}
}
],
}
},
mounted() {
//retreive all the questions still recorded
axios
.get('http://127.0.0.1:8000/api/studies/' + this.idStudy + '/question_studies?question.signaletik=true')
.then((result)=>{
console.log(result.data["hydra:member"])
this.aucuneQuestion = result.data["hydra:member"].length === 0;
//on met les données dans le store
this.$store.commit("setListeQuestionsSignaletiques", result.data["hydra:member"])
})
.catch(err=>console.log(err))
},
methods: {
//Adding a question
addItem () {
this.questionsSignaletiques.push(
{
"study":"/api/studies/" + this.idStudy,
"question":
{
type: "",
item: "",
multiple: false,
obligatoire: false,
inverse: false,
barometre: false,
originale: false,
signaletik: true,
choices: [{name:''}]
},
"error": {
message:"",
here:false
}
}
)
},
//adding a choice
addChoice: function(choice) {
choice.question.choices.push({
name: ''
})
},
// Sending the forms
submit () {
//loop table to retrieve all questions and indexes if the user adding several questions
this.questionsSignaletiques.forEach((element,index) =>
{
const inputType = element.question.type
const inputItem = element.question.item
const inputChoice = element.question.choices
//loop on choices to see if empty one or not
for (const oneChoice of inputChoice)
{
if ((inputChoice.length == 1) ||(inputChoice.length == 2 && oneChoice.name == ""))
{
element.error.here=true
element.error.message = "You must have two choices at least"
return false; // stop here if error
}
else
{}
}// end loop of choices
// verify other fields
if (inputType == "" || inputItem =="")
{
element.error.here=true
element.error.message = "Title and item must not be empty"
}
else
{
// everything is ok we can record in db and store
//reset fields == does not work
this.$refs['p' + index][0].fields.resetField()
//this.$refs['p' + index][0].resetFields();
// adding questions in db one by one
/*
axios
.post('http://127.0.0.1:8000/api/question_studies', element)
.then((result)=>{
console.log(result)
//add in the state
this.$store.commit("addQuestionSignaletique", element)
})
.catch(error => {
console.log("ERRRR:: ",error);
});
*/
}
}) //end loop foreach
}
}
};
Thanks a lot for help
EDIT AFTER THE FIRST ANSWER
Ok I didn't know. So I changed my mind : I added a "show" in my "data" which is true at the beginning. If everything is ok, I save the question and set the show to false.
The problem now is that when I have a question that is OK and the other one that is not. When I correct the question that was not ok and save it, BOTH questions go into the STATE. So there is a duplicate in my state AND my DB... What can I do? This is the code :
I just added this in the HTML :
<div v-for="(question, index) in questionsSignaletiques" :key="index" >
<a-form-model
layout="inline"
v-if="question.show"
class="blockQuestion"
>
...
data() {
return {
idStudy: this.$route.params.id,
aucuneQuestion:false,
questionsSignaletiques: [
{
"study":"/api/studies/" + this.$route.params.id,
"question":
{
type: "",
item: "",
multiple: false,
obligatoire: false,
inverse: false,
barometre: false,
originale: false,
signaletik: true,
choices: [{name:''}]
},
"error": {
message:"",
here:false
},
"show":true,
}
],
}
},
mounted() {
//retrieve question still recorded
axios
.get('http://127.0.0.1:8000/api/studies/' + this.idStudy + '/question_studies?question.signaletik=true')
.then((result)=>{
console.log(result.data["hydra:member"])
this.aucuneQuestion = result.data["hydra:member"].length === 0;
this.$store.commit("setListeQuestionsSignaletiques", result.data["hydra:member"])
})
.catch(err=>console.log(err))
},
methods: {
//adding a question
addItem () {
this.questionsSignaletiques.push(
{
"study":"/api/studies/" + this.idStudy,
"question":
{
type: "",
item: "",
multiple: false,
obligatoire: false,
inverse: false,
barometre: false,
originale: false,
signaletik: true,
choices: [{name:''}]
},
"error": {
message:"",
here:false
},
"show":true
}
)
},
//adding a choice
addChoice: function(choice) {
choice.question.choices.push({
name: ''
})
},
// submit the form
submit () {
this.questionsSignaletiques.forEach((element,index) =>
{
const inputType = element.question.type
const inputItem = element.question.item
const inputChoice = element.question.choices
for (const oneChoice of inputChoice)
{
if ((inputChoice.length == 1) ||(inputChoice.length == 2 && oneChoice.name == ""))
{
element.error.here=true
element.error.message = "You must have two choices at least"
return false; //on s'arrête là si il y a une erreur
}
else
{
console.log("no problem")
}
}
if (inputType == "" || inputItem =="")
{
element.error.here=true
element.error.message = "You must fill type and item"
}
else
{
// hide the question form
element.show =false
//adding in db
axios
.post('http://127.0.0.1:8000/api/question_studies', element)
.then((result)=>{
//add in the state
this.$store.commit("addQuestionSignaletique", element)
})
.catch(error => {
console.log("ERRRR:: ",error);
});
}
}) //end loop foreach
}
}
};
Thanks for help !
form.reset() does not work when using v-model.
Reset the reactive data instead.
reset() {
this.question.question.type = ""
...
...
}

Using an if condition inside a V-For Loop in Vue JS

I am having a problem passing an if condition inside a v-for loop in VueJS. I want to see if a value in the text field is greater than 30 to give an alert but I can't figure out how i can call my function inside the loop. Here is my code: I have tried using v-if but still not lucky
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js">
</script>
<tbody>
<tr> v-for="item in marksData">
<td>{{item.studentName}}</td>
<td>{{item.studentRegNo}}</td>
<td><input v-model.number="item.cat1Marks" required="required" /> *...V-if
to check the condition here.... or call any function to check*</td>
<td><button v-on:click.prevent="saveMarks(item)"
type="submit">Save</button></td>
</tr>
</tbody>
var subjectStudentsVM = new Vue({
el: "#subjectStudentsSection",
data: function() {
return {
id: '',
studentRegNo: '',
studentName: '',
marksData: Array(),
}
},
created: function() {
this.getAllSubjects();
},
methods: {
getAllSubjectStudents: function(subject) {
var self = this;
console.log(this.subject.subjectCode)
axios.get("/Marks/students/" +
this.subject.subjectCode).then(function(response) {
this.marksData = response.data;
}.bind(this)).catch(function(error) {
console.log('Error while fetching student data: ' + error)
})
},
computed:{
CheckData(item) {
if (item.cat1Marks > 30 && item.cat1Marks > 0) {
alert("Marks should be less than 30");
}
console.log(mark);
return mark;
}
}
If I understand your question. I think you should check the input first. Then return the alert() if it's necessary.
inside methods
giveAlert(){
alert("Marks should be less than 30");
}
inside computed:
checkInput(val){
if (val > 30 && val > 0) {
this.giveAlert()
} else {//do nothing}
}
Also mentioned in the comments. You should correct the typo of v-for.
the html will not cause a alert it's self so you have to run the checkdata in the created function.
created:function(){
this.getAllSubjects();
for(var i=0;i<this.marksData.length;i++){
var item=this.marksData[i];
if(item.cat1marks>30){
alert("marks should be less than 30");
}
}
}
Computed variable are defined as functions cause they react on changes in the reactive varables of the vue component. But they are not designed to expect any variable passing them directly.
Use subcomponent created hook like this:
<div id="app">
<p v-for="student in marksData">
<mystudent :student="student" />
</p>
</div>
<script>
var app = new Vue({
el: "#app",
data: function() {
return {
marksData: [{
studentName: 'Alan',
studentRegNo: 1,
cat1Marks: 21
},{
studentName: 'Jessica',
studentRegNo:2,
cat1Marks: 31
},{
studentName: 'Joe',
studentRegNo: 3,
cat1Marks: 4
}],
}
},
components: {
Mystudent: {
props: ['student'],
created() {
if (this.student.cat1Marks > 30) alert(`Student ${ this.student.studentName } reached ${ this.student.cat1Marks } mark`)
},
render (h) {
return h('span', `Name: ${ this.student.studentName } RegNo: ${ this.student.studentRegNo } Marcs: ${ this.student.cat1Marks }`)
}
}
}
});
</script>
UPDATED
After the comment i realized that i didn't understood the question
So here is updated code example, where the score check is in method section not in computed and the trigger is the #change event. Notice the #change triggers first when the focus is taken out of the input field.
<div id="app">
<p v-for="(student, i) in marksData">
Student: {{ student.studentName }} Mark: <input v-model="student.cat1Marks" type="text" #change="checkStudent(student)">
</p>
</div>
<script>
var app = new Vue({
el: "#app",
data: function() {
return {
test: 1,
marksData: [{
studentName: 'Alan',
studentRegNo: 1,
cat1Marks: 21
},{
studentName: 'Jessica',
studentRegNo:2,
cat1Marks: 31
},{
studentName: 'Joe',
studentRegNo: 3,
cat1Marks: 4
}],
}
},
methods: {
checkStudent (student) {
if (student.cat1Marks > 30) alert(`Student ${student.studentName } reached ${ student.cat1Marks } mark`)
}
}
});
</script>
For anyone having a similar problem I added this function
up(item){
if (item.cat1Marks > 30) {
item.cat1Marks=0;
alert("Marks cannot be greater than 30")
}else{}
},
//and called this function in my input
<td> <input type="text" name="cat1Marks"
v-model.number="item.cat1Marks" v on:input="up(item)"
required="required" /></td>

Vue.js : How to iterate with v-for into a dynamic array

I want to display multiples html tables of tools (1 table = 1 tool's categorie / 1 tr = 1 tool).
data() {
return {
cats: '',
tools: [],
};
},
methods: {
getToolCats() {
var rez = getToolCats();
rez.then(data => this.receiveCats(data) )
},
receiveCats(_cats){
this.cats = _cats;
_cats.forEach(cat => {
getToolsByCat(cat.href).then(data => this.tools[cat.href] = data);
});
console.log(this.tools);
},
},
mounted() {
this.getToolCats();
},
cats (ie categories) is an array populated with an API call. Then for each cat, an API Call give me a tool list of that cat, that I place into the tools array (this.tools[cat.href] = data).
Here is the display code :
<div v-for="cat in cats" :key="cat.href" class="tbox col-xs-12 col-sm-6">
....
<table class="table table-hover">
<tr v-for="tool in tools[cat.href]" :key="tool.href">
<td>...</td>
</tr>
</table>
....
</div>
If i'm using a single var to store lthe tool list, all is OK. But while I don't know how many cats I'm going to have, I can't create a car for each category.
I think the problem could be there :
Using an array in v-for with a key not defined at mounted state :
v-for="tool in tools[cat.href]
I'll appreciate any help !
Vue can't detect dynamic property addition in this.tools[cat.href] = data, but it would detect the change with this.$set or Vue.set in this.$set(this.tools, cat.href, data):
new Vue({
el: '#app',
data() {
return {
cats: [],
tools: {}, // <-- make this an object (not an array)
};
},
mounted() {
this.getToolCats();
},
methods: {
getToolCats() {
// setTimeout to simulate delayed API calls...
setTimeout(() => {
this.cats = [
{ href: 'https://google.com' },
{ href: 'https://microsoft.com' },
{ href: 'https://apple.com' },
{ href: 'https://amazon.com' },
];
this.cats.forEach((cat, i) => {
setTimeout(() => {
const data = { href: cat.href };
this.$set(this.tools, cat.href, data); // <-- use this.$set for dynamic property addition
}, i * 1000);
})
}, 1000);
}
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<div v-if="!cats || cats.length == 0">Loading...</div>
<div v-for="cat in cats" :key="cat.href" v-else>
<table>
<tr v-for="tool in tools[cat.href]" :key="tool.href">
<td>cat.href: {{cat.href}}</td>
</tr>
</table>
</div>
<pre>tools: {{tools}}</pre>
</div>

Gijgo Grid doesn't bind data if I call action from ajax

I am using gijgo grid to bind data. I did it two ways using gijgo grid.
1)First Binding data with html helpers to html table and doing CRUD with Gijgo,it binds data,do CRUD but does not reload grid on add,edit and delete.
<table id="grid">
<thead>
<tr>
<th width="56">ID</th>
<th data-sortable="true">Brand</th>
<th data-sortable="true">Model</th>
<th width="64" data-tmpl="<span class='material-icons gj-cursor-pointer'>edit</span>" align="center" data-events="click: Edit"></th>
<th width="64" data-tmpl="<span class='material-icons gj-cursor-pointer'>delete</span>" align="center" data-events="click: Delete"></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Id)
</td>
<td>
#Html.DisplayFor(modelItem => item.Brand)
</td>
<td>
#Html.DisplayFor(modelItem => item.Model)
</td>
</tr>
}
</tbody>
</table>
in delete function,grid doesn't reload after deleting
function Delete(e) {
if (confirm('Are you sure?')) {
$.ajax({ url: '/Home/Delete', data: { id: e.data.id }, method: 'POST' })
.done(function () {
//grid.reload({ page: 1});
grid.reload();
})
.fail(function () {
alert('Failed to delete.');
});
}
}
2) Then I did a different implementation of gijgo grid binding data via ajax call of gijgo using this example
Gijgo Grid example
The Get function returns JSON
public JsonResult Get(int? page, int? limit, string sortBy, string direction, string brand, string model)
{
List<CarsViewModel> records;
int total;
var query = _context.Cars.Select(p => new CarsViewModel
{
Id = p.Id,
Brand = p.Brand,
Model = p.Model
});
if (!string.IsNullOrWhiteSpace(model))
{
query = query.Where(q => q.Model != null && q.Model.Contains(model));
}
if (!string.IsNullOrWhiteSpace(brand))
{
query = query.Where(q => q.Brand != null && q.Brand.Contains(brand));
}
if (!string.IsNullOrEmpty(sortBy) && !string.IsNullOrEmpty(direction))
{
if (direction.Trim().ToLower() == "asc")
{
switch (sortBy.Trim().ToLower())
{
case "brand":
query = query.OrderBy(q => q.Brand);
break;
case "model":
query = query.OrderBy(q => q.Model);
break;
}
}
else
{
switch (sortBy.Trim().ToLower())
{
case "brand":
query = query.OrderByDescending(q => q.Brand);
break;
case "model":
query = query.OrderByDescending(q => q.Model);
break;
}
}
}
//else
//{
// query = query.OrderBy(q => q.o);
//}
total = query.Count();
if (page.HasValue && limit.HasValue)
{
int start = (page.Value - 1) * limit.Value;
records = query.Skip(start).Take(limit.Value).ToList();
}
else
{
records = query.ToList();
}
return this.Json(new { records, total }, JsonRequestBehavior.AllowGet);
}
jQuery(document).ready(function ($) {
grid = $('#grid').grid({
primaryKey: 'Id',
dataSource: '/Home/Get',
columns: [
{ field: 'Id', width: 56 },
{ field: 'Brand', sortable: true },
{ field: 'Model', sortable: true },
{ width: 64, tmpl: '<span class="material-icons gj-cursor-pointer">edit</span>', align: 'center', events: { 'click': Edit } },
{ width: 64, tmpl: '<span class="material-icons gj-cursor-pointer">delete</span>', align: 'center', events: { 'click': Delete } }
],
pager: { limit: 5 }
});
dialog = $('#dialog').dialog({
autoOpen: false,
resizable: false,
modal: true,
width: 360
});
$('#btnAdd').on('click', function () {
$('#Id').val('');
$('#Brand').val('');
$('#Model').val('');
dialog.open('Add Car');
});
$('#btnSave').on('click', Save);
$('#btnCancel').on('click', function () {
dialog.close();
});
$('#btnSearch').on('click', function () {
grid.reload({ page: 1, name: $('#txtBrand').val(), nationality: $('#txtModel').val() });
});
$('#btnClear').on('click', function () {
$('#txtBrand').val('');
$('#txtModel').val('');
grid.reload({ brand: '', model: '' });
});});
which results in JSON returned in this format
{"records":[{"Id":7,"Brand":"toyota","Model":"matrix"},{"Id":8,"Brand":"Mazda","Model":"M3"}],"total":2} and gives error unable to bind data like
SyntaxError: Unexpected token o in JSON at position 1
If I remove the record and total section and put raw json as datasource like this
[{"Id":7,"Brand":"toyota","Model":"matrix"},{"Id":8,"Brand":"Mazda","Model":"M3"}]
then data is bound but again grid.reload() doesnt work. I am really frustrated why this issues are there. First the gijgo grid server side controller code returns JSON dataas record with total and then I am not able to bind it with the code that gijgo has provided in jquery. Then grid.reload() isn't working
Could you review our ASP.NET examples where everything is setup correctly. You can find them at https://github.com/atatanasov/gijgo-asp-net-examples

UI does not update when Updating ObservableArray

When a user clicks on a row from a list in the UI (html table tblAllCert), I have a click event that fires to populate another observable array that should populate a second html table (tblCertDetails). My click event fires, I can see data come back, and i can see data in the observable array, but my view does not update.
Here is an overview of the sequence in the code-
1. user clicks on row from html table tblAllCert, which fires selectThing method.
2. In viewmodel code, selectThing passes the row data from the row selected to the GetCertificateDetails(row) method.
3. GetCertificateDetails(row) method calls getCertDetails(CertificateDetails, source) function on my data service (the data service gets data from a web api). getCertDetails(CertificateDetails, source) function returns an observable array.
4. once the data is returned to selectThing, CertificateDetails observable array is populated. It does get data to this point.
Step 5 should be updating the UI (specifically tblCertDetails html table), however the UI does not get updated.
Here is my view-
<table id="tblAllCert" border="0" class="table table-hover" width="100%">
<tbody data-bind="foreach: allCertificates">
<tr id="AllCertRow" style="cursor: pointer" data-bind="click: $parent.selectThing, css: { highlight: $parent.isSelected() == $data.lwCertID }">
<td>
<ul style="width: 100%">
<b><span data-bind=" text: clientName"></span> (<span data-bind=" text: clientNumber"></span>) <span data-bind=" text: borrowBaseCount"></span> Loan(s) </b>
<br />
Collateral Analyst: <span data-bind=" text: userName"></span>
<br />
Certificate: <span data-bind="text: lwCertID"></span> Request Date: <span data-bind=" text: moment(requestDate).format('DD/MMM/YYYY')"></span>
</td>
</tr>
</tbody>
</table>
<table id="tblCertDetails" border="0" class="table table-hover" width="100%">
<tbody data-bind="foreach: CertificateDetails">
<tr id="Tr1" style="cursor: pointer">
<td>
<ul style="width: 100%">
<b>Loan: <span data-bind=" text: loanNum"></span>
</td>
</tr>
</tbody>
</table>
My viewmodel-
define(['services/logger', 'durandal/system', 'durandal/plugins/router', 'services/CertificateDataService'],
function (logger, system, router, CertificateDataService) {
var allCertificates = ko.observableArray([]);
var myCertificates = ko.observableArray([]);
var isSelected = ko.observable();
var serverSelectedOptionID = ko.observable();
var CertificateDetails = ko.observableArray([]);
var serverOptions = [
{ id: 1, name: 'Certificate', OptionText: 'lwCertID' },
{ id: 2, name: 'Client Name', OptionText: 'clientName' },
{ id: 3, name: 'Client Number', OptionText: 'clientNumber' },
{ id: 4, name: 'Request Date', OptionText: 'requestDate' },
{ id: 5, name: 'Collateral Analyst', OptionText: 'userName' }
];
var activate = function () {
// go get local data, if we have it
return SelectAllCerts(), SelectMyCerts();
};
var vm = {
activate: activate,
allCertificates: allCertificates,
myCertificates: myCertificates,
CertificateDetails: CertificateDetails,
title: 'Certificate Approvals',
SelectMyCerts: SelectMyCerts,
SelectAllCerts: SelectAllCerts,
theOptionId: ko.observable(1),
serverOptions: serverOptions,
serverSelectedOptionID: serverSelectedOptionID,
SortUpDownAllCerts: SortUpDownAllCerts,
isSelected: isSelected,
selectThing: function (row, event) {
CertificateDetails = GetCertificateDetails(row);
isSelected(row.lwCertID);
}
};
serverSelectedOptionID.subscribe(function () {
var sortCriteriaID = serverSelectedOptionID();
allCertificates.sort(function (a, b) {
var fieldname = serverOptions[sortCriteriaID - 1].OptionText;
if (a[fieldname] == b[fieldname]) {
return a[fieldname] > b[fieldname] ? 1 : a[fieldname] < b[fieldname] ? -1 : 0;
}
return a[fieldname] > b[fieldname] ? 1 : -1;
});
});
return vm;
function GetCertificateDetails(row) {
var source = {
'lwCertID': row.lwCertID,
'certType': row.certType,
}
return CertificateDataService.getCertDetails(CertificateDetails, source);
}
function SortUpDownAllCerts() {
allCertificates.sort();
}
function SelectAllCerts() {
return CertificateDataService.getallCertificates(allCertificates);
}
function SelectMyCerts() {
return CertificateDataService.getMyCertificates(myCertificates);
}
});
And releveant excerpt from my data service-
var getCertDetails = function (certificateDetailsObservable, source) {
var dataObservableArray = ko.observableArray([]);
$.ajax({
type: "POST",
dataType: "json",
url: "/api/caapproval/CertDtlsByID/",
data: source,
async: false,
success: function (dataIn) {
ko.mapping.fromJSON(dataIn, {}, dataObservableArray);
},
error: function (error) {
jsonValue = jQuery.parseJSON(error.responseText);
//jError('An error has occurred while saving the new part source: ' + jsonValue, { TimeShown: 3000 });
}
});
return dataObservableArray;
}
Ideas of why the UI is not updating?
When you're using Knockout arrays, you should be changing the contents of the array rather than the reference to the array itself. In selectThing, when you set
CertificateDetails = GetCertificateDetails(row);
the new observableArray that's been assigned to CertificateDetails isn't the same observableArray that KO bound to your table.
You're very close to a solution, though. In your AJAX success callback, instead of creating a new dataObservableArray and mapping into that, you can perform the mapping directly into parameter certificateDetailsObservable. ko.mapping.fromJSON should replace the contents of the table-bound array (you would also get rid of the assignment in selectThing).

Categories