updating an item in a knockout bound list - javascript

I have a list of questions and each question has a list of answers and I am using knockout to display each question 1 at a time. What I am doing is setting up my model with the full list and then making a currentQuestion property observable and after each question is answered, I increment this to the next question. Problem is that I have to change some data on the question when the user hovers it but cant figure out how to make the answers observable.
I've put together a jsfiddle and what I want to do is change the answer text to 'modified' when the user clicks the answer.
How do I make the AnswerText observable so that when the click handler changes its value this is reflected in the UI.
Any ideas where I am going wrong would be appreciated.
jsfiddle code if below:
<div class="top">
<div data-bind="foreach: currentQuestion().Answers">
<div data-bind="click: $root.answerClicked">
<div data-bind="text: AnswerText"></div>
</div>
</div>
</div>
function MyVM() {
var self = this;
this.session = {
Questions: [
{
QuestionText: "Q1",
Answers: [
{
AnswerText: "Q1A1"
},
{
AnswerText: "Q1A2"
}
]
},
{
QuestionText: "Q2",
Answers: [
{
AnswerText: "Q2A1"
},
{
AnswerText: "Q2A2"
}
]
}
]
};
this.currentQuestion = ko.observable();
this.currentQuestion(self.session.Questions[1]);
this.answerClicked = function (selectedAnswer, event) {
alert('hello');
selectedAnswer.AnswerText = 'modified1';
selectedAnswer.AnswerText('modified');
};
}
var model = new MyVM();
ko.applyBindings(model);

Currently you're binding the UI to a static string. In order to make the UI reflect changes the string must be wrapped in an observable as you stated so it sounds like you were on the right track. All you need to do is use an observable in each answer object.
Answers: [
{
AnswerText: ko.observable("Q2A1")
},
{
AnswerText: ko.observable("Q2A2")
}
]
Then in the click function you'll want to get rid of the first assignment operator where it would be replacing the observable and use only the second line where a value is assigned to the observable instead.
this.answerClicked = function (selectedAnswer, event) {
alert('hello');
//selectedAnswer.AnswerText = 'modified1';
selectedAnswer.AnswerText('modified');
};

Related

How to display dynamic variable in HTML

I'm wondering how to display a number in HTML that was created dynamically later on in the code. For instance I initialize the value reportDataLength in data(), and I tested that it was returning the right value by doing a console.log(), but now I want to display this value in HTML?
name: 'notification-tray',
data() {
return {
headers: [
{
text: 'Notifications',
value: 'Body',
width: '380px',
},
],
reportData: [],
reportDataLength: 0,
};
},
async created() {
await this.getReportDataLength();
},
methods: {
async getReportDataLength() {
... this.reportDataLength = this.reportData.length;
console.log(this.reportDataLength);
},
},
};
But obviously when I do something like this,
<span class="d-none">Notification Button</span>
<span class="badge">this.reportDataLength</span>
it doesn't work correctly. The other solutions I saw for similar problems used JQuery, and I feel like there's a simple way to reference this, but I can't seem to find it out.
okay so if you are using pure Javascript , u can use this in your javascript function :
Document.getElementsByClassName('badge').innerHtml=this.reportDataLength ;
otherwise if you are using vue js you can try something like this in your html file :
<span class="badge">{{reportDataLength}}</span>
You can do document.getElementById("ID-HERE").innerHTML = "new stuff", but be warned that setting innerHTML is dangerous.

How do I use vuelidate with an array of objects?

I have an array of objects that I loop through in my form questionnaire. There are five properties in each object, but only one property requires validation. I set up the validations part of the component as below:
specificGifts: {
$each: {
passThrough: {
required: requiredIf(function(value) {
return this.docs.includes('Will')
})
}
}
},
I saw on vuelidate documents that in my form html, instead of doing the following code below:
<div
v-for="(gift, i) in specificGifts"
:key="i"
>
<v-select
label="How does this specific gift pass if the recipient does not survive?"
v-model="gift.passThrough"
:items="specificGiftPassThrough"
></v-select>
</div>
I should use:
<div
v-for="(gift, i) in $v.specificGifts.$each.$iter"
:key="i"
>
<v-select
label="How does this specific gift pass if the recipient does not survive?"
v-model="gift.passThrough.$model"
:items="specificGiftPassThrough"
></v-select>
</div>
The data part of my vuejs component is as follows:
data(){
return{
specificGifts: []
}
}
However, I then get the following console error "Cannot read property $model of undefined". When I console.log $v.specificGifts.$each.$iter, I also get console errors. Does anyone know what I am doing wrong? Is there a better way to use validation? It seems vuelidate may not be up to speed in that it requires me to hardcode loop through a $v property just so I can use vuelidate, anyhow.
I've been facing the validation of a survey and want to share my solution.
My specific case is I have many questions and any of them has many answers (a group of radio buttons). My objective is to validate every question so that for each, one radio must be checked.
First I get the questions array of objects from API (my "value" properties are different):
[
{
"value":"a",
"name":"first question",
"answers":[
{
"label":"first answer",
"value":"a1"
},
{
"label":"second answer",
"value":"a2"
},
...
]
},
{
"value":"b",
"name":"second question",
"answers":[
{
"label":"first answer",
"value":"b1"
},
{
"label":"second answer",
"value":"b2"
},
...
]
}
...
]
Then, after getting the API response, I prepared another array of objects for the answers:
this.questions.map(function (item) {
return {
questionId: item.value,
answerId: null
};
})
the result is this structure:
[
{
questionId: 'a',
answerId: null,
},
{
questionId: 'b',
answerId: null,
}
...
]
I used quasar framework for the template but I think you can reproduce this logic with basic html:
<q-field v-for="(question, key) in questions" :key="question.value"
filled
:label="question.name"
>
<template v-slot:control>
<q-option-group
v-model="answers[key].answerId"
:options="question.answers"
type="radio"
/>
</template>
</q-field>
Finally my validation rule is:
validations: {
answers: {
$each: {
answerId: {
required
}
}
}
}
i've also had a lot of trouble with Vuelidate.
The best solution i can give you is to change from Vuelidate to VeeValidate.
It's not complicated to change and it's going to save you a lot of time in the long run.
VeeValidate offers a lot of easy ways to use validation including custom Messages for each rule (no more computed's to give error messages) and allow's you to use rules depending on certain conditions.
You can check the documentation here

How to dynamically add a new object to array of vue (object) data- Vue.js?

I am new to vue.js. I am working on form. I have a add button in my form, as user click on this button same form field will be added to this form. And user can add as many times he/she want. For this my data is .
data () {
return {
form: [{
fieldOne: '',
fieldTwo: '',
}]
}
}
As user click on add buton in html my addForm fucntion is called.
addForm() {
let newObject = {
fieldOne: '',
fieldTwo: '',
}
this.form.push(newObject); // Gives error.
}
I read about Vue.set. I can easliy add single field or object. But I don't know how to add object to my form array.
Please help me out.
That works. What problem are you having?
markup
<div id="vueRoot">
<button #click="addForm">
Click Me !
</button>
{{form}}
</div>
code
var vm = new Vue({
el : "#vueRoot",
data : {
form: [{
fieldOne: '',
fieldTwo: '',
}]
},
methods : {
addForm() {
let newObject = {
fieldOne: '',
fieldTwo: ''
}
this.form.push(newObject); // Gives error.
}
}
});
Even if you're new and just looking around and trying things out, you'll have more fun if you give real names to things. "form" and "fieldOne" will quickly lead into headwreck !

AngularJS custom filter being called twice

I've created a custom filter using AngularJS that prints out the fruits that start with a p. As far as I can tell, I've implemented the custom filter correctly.
I'm printing out a message every time the filter is called but I'm curious to why my filter is being called twice.
Looking at similar problems on stackoverflow I found one person who had a similar issue however the problem wasn't answered and was a little different.
JSFiddle Solution
http://jsfiddle.net/ddemott/U3pVM/22606/
HTML Code
<body>
<div ng-controller="ExampleCtrl" ng-app="sampleApp">
<div class="showDiffTags" ng-repeat="val in values | myFilter:'p'">{{val.name}}</div>
</div>
</body>
AngularJS Code
angular.module('sampleApp', []).filter('myFilter', function() {
return function(items, firstLetter) {
var groups = [];
console.log("called function");
console.log(items.length);
for (var i = 0; i < items.length; i++) {
if (items[i].name.substring(0, 1) == firstLetter) {
groups.push(items[i]);
}
}
return groups;
}
}).controller('ExampleCtrl', function($scope) {
$scope.values = [{
name: 'apple'
}, {
name: 'banana'
}, {
name: 'orange'
}, {
name: 'avocado'
}, {
name: 'pineapple'
}, {
name: 'peach'
}, {
name: 'plum'
}, {
name: 'grapes'
}, {
name: 'mango'
}, {
name: 'papaya'
}, ];
});
That is correct behaviour and it's strongly coupled with how $digest cycle works
Every time model changes the $digest is run at least twice:
After model changes it runs the watchers and updates the models
To check if the first $digest made changes to model, if so another digest is called up to max ten iterations then angular throw errors.
There is nothing to worry unless you have a lot of functions in templates and unstable models (changing often)
I've updated your fiddle with simple button that updates model on scope
http://jsfiddle.net/U3pVM/22610/
<button ng-click="numModel = numModel + 1">
update model {{numModel}}
</button>
You will see that every time you click the button filter runs twice

Swapping data in Angular UI-Grid, new columns not visible when changing dataset externally

I've got a query tool I've been working on, which has an angular form that is filled out, and then when it's submitted it uses AJAX which returns JSON, which is then rendered into ui-grid, that JSON response looks like
{
"success": true,
"message": "",
"columns": ["first_name", "last_name", "company", "employed"]
"results": [
{first_name: "John", last_name: "Smith", company: "Abc Inc", employed: true},
{first_name: "Johnny", last_name: "Rocket", company: "Abc Inc", employed: true}]
}
I'm working on both the PHP and angular so I have full control over this JSON response if need be. I'm running into an issue when my JSON response from a first AJAX call is rendered, and then I run another, seperate AJAX call on the same page and get a new data set: this new data set does not render any of the columns that were not in the original data set. This is hugely problematic as the table is essentially cleared when none of the columns are the same, and I often need to load completely different data into ui-grid in this single page app.
When the JSON is recieved I simply bind the jsonResult.results to the old $scope.myData variable that ui-grid is bound to.
I've made a plunker isolating this issue. A dataset with a "punk" column is loaded, and then clicking "swap data" will try to load a dataset with "employee" column instead of "punk". I've so far looked into directives that will refresh or reload when the $scope.myData variable changes using $watch, and looked at finding something like $scope.columnDefs to let ui-grid know. Relatively new to angular and javascript so directives are still a bit over my head.
I have updated your plunker slightly:
$scope.swapData = function() {
if ($scope.gridOpts.data === data1) {
$scope.gridOpts.columnDefs = [
{ name:'firstName' },
{ name:'lastName' },
{ name:'company' },
{ name:'employee' }
];
$scope.gridOpts.data = data2;
//punk column changes to employee
}
else {
$scope.gridOpts.columnDefs = [
{ name:'firstName' },
{ name:'lastName' },
{ name:'company' },
{ name:'punk' }
];
$scope.gridOpts.data = data1;
//employee column changes to punk
}
};
http://plnkr.co/edit/OFt86knctJxcbtf2MwYI?p=preview
Since you have the columns in your json, it should be fairly easy to do.
One additional piece that I figured out with the help of Kevin Sage's answer and the plunker example... If you are using the backward-compatible "field" attribute the swapping does not work properly when there are field name overlaps between the two sets of column definitions. The column headers and the column widths are not rendered properly in this case. Using the "name" attribute of the column definition corrects this.
$scope.swapData = function() {
if ($scope.gridOpts.data === data1) {
$scope.gridOpts.columnDefs = [
{ field:'firstName' },
{ field:'lastName' },
{ field:'company' },
{ field:'employee' }
];
$scope.gridOpts.data = data2;
//punk column changes to employee
}
else {
$scope.gridOpts.columnDefs = [
{ field:'firstName' },
{ field:'lastName' },
{ field:'company' },
{ field:'punk' }
];
$scope.gridOpts.data = data1;
//employee column changes to punk
}
};
Example here: Plunker
My solution:
$http.get('url').success(function(res) {
// clear data
gridOptions.data.length = 0;
// update data in next digest
$timeout(function() {
gridOptions.data = res;
});
});

Categories

Resources