Angular: Keep model data in sync with database - javascript

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.

Related

How to get the current record_id to access specific model records in javascript odoo

On a record's XML button click I'm returning a client action qweb wizard with javascript functions. After running some javascript functions I need to save the results in a specific record(The record from which this wizard was opened with a button click). All my functions work but I do not know how to fetch the ID of the record so I'm not able to save the data to that specific record. How to fetch the ID of the current record in Javascript?
The wizard is a QWEB Javascript Client Action type.
XML Button:
<button name="open_bio_window" string="Bio" type="object" class="oe_highlight"/>
Python method I use to call the wizard:
#api.multi
def open_bio_window(self):
return {
'res_model': 'bank.agent',
'type': 'ir.actions.client',
'tag': 'action_biometric_page',
'target': 'new'
}
Javascript Sample:
events: {
'click .discoverService': 'discoverService',
},
discoverService: function(ev){
// do something
var endPoint = "ENDPOINT";
// save endPoint to the record that called this method
},
How to save endPoint to field end_point in parent record?
Qweb Template Sample:
<t t-name="bank_model.BioCapture">
<h3>Bio Capture</h3>
<div>Please verify Device is connected! Press 'Start' after
verification.
</div>
<br/>
<img src="/bank_model/static/description/device_ready.png"/>
<br/>
<br/>
<div>
<button class="btn btn-info discoverService btnheight">Start</button>
</div>
</t>
I solved it by passing the ID as a parameter in python method call.
#api.multi
def open_bio_window(self):
for record in self:
return {
'res_model': 'bank.agent',
'type': 'ir.actions.client',
'tag': 'action_biometric_page',
'target': 'new',
'params': {'rec_id': record.id}
}
Then access that parameter in javascript.
init: function (parent, context) {
this._super(parent, context);
this.rec_id = context.params.rec_id;
this.dashboards_templates = ['bank_model.BioCapture'];
},

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.

Issues Building Datatable Within ng-bootstrap Modal

I have an Angular 7.2.15 project that I have installed ng-bootstrap (https://ng-bootstrap.github.io/#/components/modal/examples) on with great success until now when I realize I need to change the contents of the modal dialog.
The dialog will simply display the result of an API call which I know is coming back fine from Chrome Network views, essentially for each record that comes back, we need to display the "name" attribute in the datatable as a link (eventually, that link will load a saved query by its name) and an icon to delete it.
Here are the two important snippets from the search-bar.component.html in question:
<ng-template #contentOpen let-modal>
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Open Query</h4>
<button type="button" class="close" aria-label="Close" (click)="modal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
Select from the following list of saved queries:
<table #dataTable class="table">
<thead>
<tr class="table-header">
<td class="max-width-80">Search Name</td>
<td class="max-width-20">Delete</td>
</thead>
<tbody class="table-body">
</tbody>
</table>
</div>
</ng-template>
...
<div class="col-md-1">
<button class="SearchGrayButton" (click)="openModal(contentOpen)">Open Search <span class="fa fa-folder-open-o"></span></button>
</div>
Then, in our search-bar.component.ts - we successfully get the SearchHistory from our webservice from the filteringService, but rendering the results poses the issue. If I take the datatable out from the ng-template #contentOpen section above, it will render the datatable fine (albeit not in the modal as we would like it to be). So within the modal, it does not render at all, but outside of it, the table will render without issue.
openModal(content) {
this.GetSearchHistory();
this.modalService.open(content, {ariaLabelledBy: 'modal-basic-title'}).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed`;
});
}
/* Obtains all of the search history for a user and renders table in
the modal to display them */
GetSearchHistory() {
this.filteringService.getSearchHistory().subscribe(
resp => {
console.log(resp, 'res');
let r: WebServiceResponse;
r = resp as WebServiceResponse;
this.searchHistory = r.data;
this.buildTableForSearchHistory();
},
error => { console.log(error, 'error'); }
);
}
buildTableForSearchHistory() {
const options = {
sDom: 't',
renderer: 'bootstrap',
destroy: true,
data: this.searchHistory,
columns: [
{ data: 'name',
className: 'dt-center max-width-10'
}
],
order: [0, 'desc'],
createdRow( row, data, dataIndex ) {
},
drawCallback: () => {
}
};
this.dataTable = $(this.table.nativeElement);
this.dataTable.DataTable(options);
}
As a test, I also set up a mock "refresh" button of sorts within the modal that would trigger the getSearchHistory() we see above and build the table after we know the modal is in focus, and this also does not resolve the issue. The console complains about the following line, which makes sense as I think it's having trouble finding the table in question to render to:
this.dataTable = $(this.table.nativeElement);
I don't know if it's needed beyond context especially for how simple it is, but a sample of the web service's JSON response in question:
{"status":null,"code":null,"messages":[],"data":[{"id":1,"userId":null,"name":"manual test name","queryText":null,"filtersJson":null},{"id":2,"userId":null,"name":"testname","queryText":null,"filtersJson":null},{"id":3,"userId":null,"name":"testname","queryText":null,"filtersJson":null}]}
Also noteworthy is we don't necessarily have to use DataTables here, as requirements really only are to display all the names as links and perhaps have the queryText and filtersJson as metadata since we'll need them for later. I just thought it might be a "nice to have" if we allow them to sort the results.
Does anyone have any thoughts or ways to help resolve this?
Realized binding in this manner resolves it since there was no need for the DataTables usage:
<div class="modal-body">
Select from the following list of saved queries:
<ul>
<li *ngFor="let s of searchHistory">{{s.name}}</li>
</ul>
</div>

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.

Saving nested data via angular

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(...);

Categories

Resources