Saving nested data via angular - javascript

I'm trying to save the contents of a form into a hierarchical data structure:
$scope.milestones = [
{milestoneName: "milestone 1",
id:"milestoneOne",
headingID:"headingOne",
panelClass:"in",
tasks:[
{
taskSubject:"Get cost agreement confirmation",
category:"#7FFF00",
dueDate:"July 5, 2015",
repeat: true,
assignee:"Jiman Ilitad",
estHours:"3"},
{
taskSubject:"Get cost agreement confirmation",
category:"#7FFF00",
dueDate:"July 5, 2015",
repeat: true,
assignee:"Jiman Ilitad",
estHours:"3"}
]}
];
I'm using ng-repeat to display milestones and then within each of the milestones I have tasks. Each milestone can have one or more tasks added to it via a basic html form where each input corresponds with a value in the push.
Here is the script which defines the push:
$scope.addTask = function(index){
$scope.milestones.tasks.push({
taskSubject: $scope.index.formTaskSubject,
category: $scope.index.formCategory,
dueDate: $scope.index.formDate,
repeat: $scope.index.formRepeat,
assignee: $scope.index.formAssignee,
estHours: $scope.index.formEstTime
})
};
I'm currently getting an error: TypeError: Cannot read property 'push' of undefined

This error means that you're trying to call the method push on something that doesn't exist. Since $scope.milestones is an array, you need to specify which element in that array you're trying to add a task to.
Based on your plunker, you just need to call addTask() with an additional parameter specifying the index of the milestone you wish to modify.
In your ng-click, pass in an index to the milestone. For example, change:
<a class="btn btn-primary" ng-click="addTask()">Save</a>
To:
<a class="btn btn-primary" ng-click="addTask($index)">Save</a>
The above assumes that $index is the index to your $scope.milestones array, which is assigned by ng-repeat="milestone in milestones". It can easily change if you nest ng-repeats, breaking your code.
To avoid this, just pass the milestone object itself directly into addTask.
In your HTML:
<div ... ng-repeat="milestone in milestones" ...>
...
<a ... ng-click="addTaskTo(milestone)" ...>Save</a>
...
In your controller:
$scope.addTaskTo = function(milestone) {
milestone.tasks.push(...);

Related

How to fix value update delay in vuejs after axios request?

I am retrieving a list of data from an api and need to fill the specific <select></select> tags, which is associated to a few radio button, with some of the data as <options></options>. The radio buttons waiting for an event (#change/#click) and executing and axios get request. Everthing works fine. I click on a radio button and retrieving the data as response (vue tools also showing the right data) but the <option></option> tags are not updating. Now when I click on another radio button, I am getting again the right data from the api BUT now the <option></option> tags are refreshing with the data from the previous response.
Template
<!-- CREATING 7 RADIO BUTTONS FOR THE CURRENT WEEK FROM MON-SUN -->
<div class="wrapper" v-for="item in inputDetails">
<input :id="'datetime[0]['+item.labelText+']'" type="radio" name="datetime[0][date]" v-model="formData.datetime[0].date" :value="item.inputValue" #change="getTimes" />
</div>
<!-- CREATING THE TIME PICKER -->
<select id="datetime[0][time]" name="datetime[0][time]" v-model="formData.datetime[0].time">
<option selected="selected"></option>
<option v-for="item in selectOptionTimes[0]" :value="item.value">{{ item.label }}</option>
</select>
<!--
2 MORE RADIO BUTTON SECTION AND TIME PICKER SECTIONS WITH DIFFERENT INDEXES
<input id="datetime[1][time]"...
-->
Script
data() {
return {
formData: {
datetime: [
{date: '', time: ''},
{date: '', time: ''},
{date: '', time: ''},
]
}
selectOptionTimes: [],
}
},
methods: {
getTimes: function (current) {
let instanceIndex = current.currentTarget.id.match(/(?<=\[)([0-9])(?=])/g)[0]; // getting the index of the current datetime section
axios.get('/api-url', {
params: {
location_id: this.formData.location_id,
date: current.currentTarget.value
}
}).then(response => {
this.selectOptionTimes[instanceIndex] = response.data;
});
}
}
Does someone know what the problem is here?
You cannot assign a value to an arbitrary index within an empty Array in this way. You must either completely replace the Array with values that hydrate that index, or you must use $set.
So, to recap:
BAD
this.selectOptionTimes[instanceIndex] = response.data
GOOD
this.$set(this.selectOptionTimes, instanceIndex, response.data)
Note though, that this has an unintended consequence. If you have an empty array, and call this.$set on an index greater than 0, the array will be filled with empty values up to your index.
What might make more sense is using an {} instead along with this.$set and looping over the Object.keys instead of the array directly.
Fiddle showing $set on index with an empty array
Fiddle showing Object usage instead

Angular HttpClient - accessing value buried in response data

I am accessing an online API and want to use the text value to populate a ngb-typeahead dropdown. There is a working example on the Angular Bootstrap website using Wikipedia, but the returned data from the Wikipedia API is different to the data I am getting from a geocoding API. The data I get is returned in this format:
{
"suggestions": [
{
"text": "23 Queen Charlotte Drive, Aotea, Porirua, Wellington, 5024, NZL",
"magicKey": "dHA9MCNsb2M9NDMwNzcyNzQjbG5nPTMzI2huPTIzI2xicz0xMDk6NDg1NDQwMzU=",
"isCollection": false
},
{
"text": "23 Queen Mary Avenue, Epsom, Auckland, 1023, NZL",
"magicKey": "dHA9MCNsb2M9NDMwNDY4MjUjbG5nPTMzI2ZhPTE0NDE3OTIjaG49MjMjbGJzPTEwOTo0ODU0NDMyNA==",
"isCollection": false
},
I have been trying to access text in response data with the following:
return this.http
.get<any>(GIS_URL, {params: GIS_PARAMS.set('text', term)}).pipe(
map(response => response.suggestions)
);
I have also read the Angular tutorial here on dealing with response data, but the difference in the example is that they are getting an array of Hero's whereas I am getting an object containing an array of suggestions.
The typeahead looks like:
HTML
<fieldset class="form-inline">
<div class="form-group">
<label for="typeahead-http">Search for a wiki page:</label>
<input id="typeahead-http" type="text" class="form-control mx-sm-3" [class.is-invalid]="searchFailed" [(ngModel)]="model" [ngbTypeahead]="search" placeholder="Wikipedia search" />
<small *ngIf="searching" class="form-text text-muted">searching...</small>
<div class="invalid-feedback" *ngIf="searchFailed">Sorry, suggestions could not be loaded.</div>
</div>
</fieldset>
<hr>
<pre>Model: {{ model | json }}</pre>
Full code on StackBlitz is here.
I am new to Angular, so a verbose answer would be great.
You need to specify resultFormatter and inputFormatter on the typeahead input (refer to Typeahead).
Explanation
Your search method in the service returns a list of suggestion Objects which each look like:
{
isCollection: ...
magicKey: ...
text: ...
}
However by default the typeahead control expects a list of strings, hence it displays your objects as [Object object].
You need to tell the typeahead control how to determine a string value from your object, you do this via resultFormatter and inputFormatter.
These inputs take a function, which has the object as an input and the string display value as its output.
formatter below is that function, it will be called for each item displayed in the list. If you expand it to a normal function you can put a breakpoint in it and see it being called in this manner.
Solution
<input id="typeahead-http" ... [inputFormatter]="formatter" [resultFormatter]="formatter"/>
TypeScript file:
formatter = (item:any) => item.text as string;
Updated StackBlitz
https://stackblitz.com/edit/so-typeahead?file=src%2Fapp%2Ftypeahead-http.ts
Follow-up questions
item in the formatter:
Consider:
formatter = (item:any) => item.text as string;
is shorthand for:
function format(item: any){
return item.text as string;
}
They typeahead control/directive iterates the items returned by search(..) and calls this method which each one. The results are displayed in the select list.
map(response => response.suggestions)
The response from the service is an object like:
{ // object
suggestions:
[
{ ..., text: 'Place 1' },
{ ..., text: 'Place 2' }
]
}
That is an object containing a list named suggestions. The typeahead expects a list only, so the map transforms the object containing list => list only.
Does the formatter that you have defined do both input and result?
Yes, as it is assigned to both [inputFormatter] and [resultFormatter] in the template.
Alternative answer
The mapping is done entirely in the service:
return this.http
.get<any>(GIS_URL, {params: GIS_PARAMS.set('text', term)}).pipe(
map(response => response.suggestions.map(suggestion => suggestion.text)),
);
Each response object is mapped to the list of suggestions. Each suggestion is mapped (using JavaScript map) to its text value.
You can use this solution provided you don't need access to any of the other suggestion properties outside of the service.

createRecord is creating a record with null

I have hasMany property.
Here is my model
status: DS.attr(),
OnInsert: DS.attr('boolean',{defaultValue: true}),
rOnUpdate: DS.attr('boolean',{defaultValue: false}),
filterQuery:DS.attr({defaultValue: {"condition": "AND", "subRules": [], "subGroups" : []}}),
ruleCase:DS.hasMany('rule',{async:true}),
In my hbs i have created button with some action on it:
Here is my hbs code:
<button type="button" class="btn btn-warning btn-sm" {{action 'createrecord'}}>Case</button>
Then in the controllers i have defined this action:
Here is my controller code:
actions:{
createrecord:function(){
return this.store.createRecord('rulecase');
}
}
But every time when i click on the add button it creates an empty record with id =null because on which i am not able to iterate over model.ruleCase.
How to overcome this problem????
If what you're wanting is to be able to associate the new rulecase to the model, you don't need an ID for it.
Modify the createRecord method and push it to model.ruleCase
createrecord:function(){
const newRulecase = this.store.createRecord('rulecase');
model.get('ruleCase').pushObject(newRulecase)
return newRulecase;
}
If there's some other reason that's causing you to need an ID, you can generate one on the client side using generateIdForRecord. You'll need to generate an adapter file for the ruleCase model and apply the logic for the ID.

Checkbox form array data Vue 2

I have a checkbox list which is generated using a for loop that consists of an id and a name:
Data:
yards[{id:1,name:'test'}] etc
HTML:
<ul class="checkbox-list">
<template v-for="(yard, index) in yards">
<li>
<input type="checkbox"
v-bind:id="'yardlist_'+yard.name"
v-bind:value="yard.id"
v-model="newSchedule.yards.id">
<label v-bind:for="'yardlist_'+yard.name">{{ yard.name }}</label>
</li>
<li>
<input type="text"
class="form-control"
placeholder="Yard notes..."
v-model="newSchedule.yards.notes">
</li>
</template>
</ul>
I want to save the selected checkbox with the id and notes field in an array:
newSchedule: {
due_at: '',
notes: '',
users: [],
yards: [{id:'',notes:'']
}
I have tried using the index from the yards array: newSchedule.yards[index].notes but am getting the following error "TypeError: undefined is not an object (evaluating 'newSchedule.yards[index].id')"
Any ideas how I can achieve this?
** Update **
Here is a basic fiddle of what I am wanting to achieve:
https://jsfiddle.net/j7mxe5p2/13/
I think you are trying to mix the old jQuery or javascript way of doing things with Vue framework. You should not have to set id on <input> elements to capture or set its value.
The correct way to do this is as follows:
new Vue({
el: '#app',
data: function() {
return {
yards: [
{id: 1, name: 'test', selected: true},
{id: 2,name: 'test 2', selected: false},
{id: 3,name: 'test 3', selected: false},
{id: 4,name: 'test 4', selected: true}
]
};
},
template: `
<div class="list-of-yards"> <!-- You cannot use v-for on the top-level element -->
<label for="jack" v-for="yard in yards">
<input type="checkbox" v-model="yard.selected"> {{yard.name}}
</label>
</div>
`,
});
Here is a jsFiddle of the above code: https://jsfiddle.net/z48thf9a/
Things to note:
You cannot use v-for on the template tag itself
You cannot use v-for on the top-level element just inside template. The template should contain one and only enclosing element, typically a <div>
There is no need to set id on input elements. You need to use v-model for your model-view bindings
If you still have issues, please provide a jsFiddle to debug further.
Edited after comment #1 and #2:
My above response is focused more on constructing the Vue component and rendering the list with proper binding to the checkboxes.
To get the checked items into a separate array, you can use a computed property in the same component to run through the original array this.yards and create a new array of selected items only.
Here is the jsFiddle for capturing the checked values: https://jsfiddle.net/z48thf9a/1/
You may modify the above to capture only the id part, and rename selectedYards to newSchedule or whatever your app requires.
I am not sure if I understood your question correctly, and if this solves your issue. Can you please provide more code samples?

Angular: Keep model data in sync with database

i'm making my first weapons with Angular. I cannot understand the best way to handle data modifications in model and store it in the database, e.g:
In controller:
$scope.items = [
{ id: 1, status: 0, data: 'Foo Item' }
{ id: 2, status: 0, data: 'Foooo' }
{ id: 3, status: 1, data: 'OooItem' }
];
In view:
<tr ng-repeat="item in items | orderBy:'-id'">
<td>{{item.id}}</td>
<td>{{item.data}}</td>
<td>
<div class="btn-group" role="group">
<button type="button" class="btn btn-default" ng-click="status(item.id, 1)">Acept</button>
<button type="button" class="btn btn-default" ng-click="status(item.id, 2)">Reject</button>
</div>
</td>
</tr>
What should I do to update the item status to 1 or 2? Make a call to the server, and then retrieve the new model? Update the model data with JS, and make the call? Is any way to do it automatic? Angular provide some method to access to the current "clicked" item to update the prop (status in this case)?
Hope I was clear.
Thanks.
EDIT: So, based on Dr Jones comment, i write this using underscore.
function status(id, status) {
$http.put().then()....
//after success response, update the model
item = _.find($scope.items, function (rw) {
return rw.id == id
});
item.status = status;
}
This is a valid and correct way to this?
The answer is that it's really up to you. As with all async programming you have the option to 'auto-sync' or sync as a result of a user event (e.g. hitting a save button). It really depends on how your app is designed.
Frameworks like firebase (angular-fire lib) have handy built in auto-sync functionality, alternatively the REST Post/Put request is a more traditional design pattern.

Categories

Resources