My API populates some nested models. Using a shopping example, when you query orders, the items property is populated with the description and quantity instead of returning just the item IDs.
orders: [
{_id: 1,
items: [
{_id: 100,
description: "apple", // from lookup in the Items table
quantity: 4 // from lookup in the Items table
}, ...
],
...
}, ...
]
My view looks like:
<div ng-repeat="order in vm.orders">
<ul ng-repeat="item in order.items">
<li ng-model="vm.orders[$parent.$index].items[$index].description" ng-blur="item.$update()"></li>
<li ng-model="vm.orders[$parent.$index].items[$index].quantity" ng-blur="item.$update()"></li>
</ul>
</div>
The goal is to let the user update the item description and quantity from this view. (The lis are contenteditable directives.) The update call should be made on the Item $resource, not the Order $resource.
Is there a way to get Angular to recognize that the embedded documents are Items, upon which I can call methods like $update?
The workaround that I have is to change the ng-blur to call a controller method equivalent to this:
ng-blur="Item.update({_id: item._id})"
(If I do that, there's also no point in using the $parent.$index and $index syntax -- just order.item and item.description.)
What you want to do is transform the nested resources into actual resources. To do so, add a response transformer to your Order resource. For example
.factory('Order', function($resource, $http, Item) {
var responseTransformer = function(order) {
order.items = order.items.map(function(item) {
return new Item(item);
});
return order;
};
// somewhat lifted from https://docs.angularjs.org/api/ng/service/$http#overriding-the-default-transformations-per-request
var appendResponseTransformer = function(transformer) {
var defaults = $http.defaults.transformResponse;
defaults = angular.isArray(defaults) ? defaults : [defaults];
return defaults.concat(transform);
};
return $resource('/orders', whatever, {
get: {
method: 'GET',
url: '/orders/:id',
params: { id: '#id' },
transformResponse: appendResponseTransform(responseTransformer)
},
query: {
method: 'GET',
isArray: true,
transformResponse: appendResponseTransform(function(orders) {
return orders.map(responseTransformer);
})
}
});
});
An alternative to this would have been to just handle the item update in your controller. For example
ng-blur="updateItem(item)"
with
$scope.updateItem = function(item) {
Item.update(item);
};
Related
I have a Drop down in my Anjularjs Application implemented using 'ui.select2'
I had initialized it as below
<input type="text" ng-model="objCas.iProjectId" id=" iprojectid" ui-select2="iProjectIdOption" />
And My Js Implementation is getting data from remote server with pagination and filter
var app = angular.module('CASApp', ['ui.select2', 'checklist-model']);
app.controller('CASController', function ($scope, $http) {
$scope.iProjectIdOption = {
placeholder: "Click to choose the Project...",
allowClear: true,
initSelection: function (element, callback) {
},
ajax: {
url: "/Prj/dummy/Ajaxlist",
quietMillis: 0,
type: "POST",
data: function (term, page) {
return {
q: term,
page: page,
listType: "ProjectDetails"
}; // query params go here
},
results: function (data, page) { // parse the results into the format expected by Select2.
// since we are using custom formatting functions we do not need to alter remote JSON data
var more = (page * 30) < data.total_count; // whether or not there are more results available
return {
results: $.map(data.items, function (item) {
return {
text: item.text,
id: item.id
}
}),
more: more
}
},
cache: true
}
}
}
}
Everything works fine .I am able to use all the features and post the values also. But problem is with setting the already selected values at time of edit
Tried
$Scope.objCas.iProjectId= {"text":"2010 / 256 - / dummytext","id":240}
$Scope.objCas.iProjectId=2;
$scope.iProjectId.selected = {"text":"2010 / 256 - / dummytext","id":240}
Get the select2 element object and apply the following code:
angular.element("#select2_id").select2('data', { "text": "text", "id": [id]});
HTML code: no need of id
<input type="text" ng-model="objCas.iProjectId" ui-select2="iProjectIdOption" />
js code:
use only this for default biniding
$Scope.objCas.iProjectId= {"text":"2010 / 256 - / dummytext","id":240}
example code:
link: http://embed.plnkr.co/K66Pf0/
replace script.js file:
// Code goes here
var myAppModule = angular.module('MyApp', ['ui.select2']);
myAppModule.controller('MyController', function($scope) {
$scope.select2Options = {
initSelection: function (element, callback) {
},
ajax: {
url: "./data.json",
data: function (term, page) {
return {}; // query params go here
},
results: function (data, page) { // parse the results into the format expected by Select2.
// since we are using custom formatting functions we do not need to alter remote JSON data
return {results: data}
}
}
}
console.log("$scope.select2Options-",$scope.select2Options.initSelection);
$scope.testModel={ "id": 4, "text": "Fourth", "color": "red" }
;
});
you can view default selected value in dropdown.
My project outputs results to a DataTable from an AngularJS controller function, but I'm running into some strangeness when I try to modify my search params. The first rendering of the table works as expected. But when I select different options and run the search again, extra rows appear in the table, but the info section shows the previous search's row count, and changing the number of rows shown via the length menu causes the new rows to disappear. Here's my table declaration, using attributes to wire up DataTables:
<table ui-jq="dataTable" ui-options="dataTableOptions" id="search-results" class="display nowrap datatable cell-borders" style="width: 100%;">
And this is my AngularJS controller code:
$scope.dataTableOptions = {
dom: "lfBrtip",
lengthMenu: [[25, 50, -1], [25, 50, "All"]],
language: {
emptyTable: 'No items matched your search criteria'
},
buttons: [
{
text: 'Export',
className: 'button button:hover',
extend: 'csv'
}
]
};
$scope.getItemInfo = function (model) {
$http({
method: 'POST',
url: $scope.getUrl('/My/ServerSide/Url'),
data: { model: $scope.model }
}).then(function successCallBack(response) {
$scope.model.SearchResults = response.data;
}, function errorCallback(response) {
alert("There was an error gathering the entity information. Please try again");
});
};
I'm not sure why submitting new queries with different params doesn't simply update the data in the DataTables table. Any suggestions?
I ended up using a bit of an ugly hack to get this to work. Even DataTables author wasn't sure how to get around the issue of using AngularJS with DataTables, so I had to force a reinitialization every time the form posted. I persisted the search params to localStorage, and called location.reload(). Then when the page loads and the AngularJS init() function runs, I pick up the search params and call the search function from inside an Angular document ready function, like this:
$scope.init = function () {
$scope.ValidationErrors = [];
$scope.model = {};
$scope.model.SearchResults = [];
$scope.model.ItemNumber = localStorage.getItem("itemNumber");
$scope.model.StartDate = localStorage.getItem("startDate");
$scope.model.EndDate = localStorage.getItem("endDate");
angular.element(document).ready(function () {
if ($scope.model.ItemNumber) {
$scope.getItemRecords();
}
});
localStorage.clear();
};
And then of course I clear the localStorage after the query. Not terribly elegant, but it'll have to do for now.
I have this firebase data:
{
"userData": {
"user01": {
"name": "User One"
"provider": "provider0001"
},
"user02": {
"name": "User Two"
"provider": "provider0001"
},
"user03": {
"name": "User Three"
"provider": "provider0002"
}
},
"provider": {
"provider0001": {
"users": {
"user01": true,
"user02": true
}
},
"provider0002": {
"users": {
"user03": true
}
}
}
}
controller
vm.provider = $firebaseObject(ref.child('provider').child('provider0001'));
simple html
<ul>
<li ng-repeat="user in $ctrl.provider.users">
{{user}}
</li>
</ul>
Why I cannot list {{user}} as object? The above will display list of true's, but how can I access the user object it self?
You need to map the keys (your userId's) in the users-object, to the values in your userData-object. You can get the keys in an ng-repeat-directive like this:
ng-repeat="(key, value) in $ctrl.provider.users"
Then in your controller you need to add the userData object to your controller scope:
vm.userData = $firebaseObject(ref.child('userdata'));
So you can implement your ng-repeat like this:
<ul>
<li ng-repeat="(key, value) in $ctrl.provider.users"
ng-init="user = $ctrl.userData[key]">
{{user}}
</li>
</ul>
With that said, you probably ought to do this mapping when you retrieve the data in the controller initially. Something like this:
function loadData(){
var providerUsers = $firebaseObject(ref.child('provider').child('provider0001').child('users'));
var userData = $firebaseObject(ref.child('userData'));
vm.users = [];
angular.forEach(providerUsers, function(value, key) {
vm.users.push(userData[key]);
});
}
Then you can just iterate using ng-repeat over this new vm.users-array.
Update
Here's a solution where you only retrieve the actual mapped users. It makes a query for each user, since I couldn't find a decent way to join two collections by key using just a single Firebase query.
function loadData(){
var userRef = ref.child('provider').child('provider0001').child('users');
var userDataRef = ref.child('userData');
vm.users = [];
userRef.once('value', function(snapshot){
snapshot.forEach(function(userId){
userDataRef.child(userId.key()).once("value", function(user){
$scope.$apply(function () {
vm.users.push(user.val());
});
});
})
});
}
My familiarity with Firebase is very limited so there might be better solutions out there
<ul>
<li ng-repeat="(key,user) in $ctrl.provider.users">
{{key}}:{{user}}
</li>
key will give the key of the user and user will give value
I am currently trying to learn react by building a simple app that works with a JSON array by calling a certain API. I would then like to show the results of the array in a list item and when click one of the list-item and then it returns a parameter and call an api and display data in other part of the page.
I have successfully called the API and am showing the correct data in the list-item but I am struggling to figure out how to show the data after the click in another part of the page.
So I currently have this in my page:
<div class="col-lg-3 col-md-3">
<div class="block-job-list" id="JobCardBlock"></div>
</div>
<div class="col-lg-9 col-md-9">
<div class="block-job-list" id="JobDetailBlock"></div>
</div>
<script type="text/jsx">
var JobCard = React.createClass({
// get game info
loadGameData: function() {
document.getElementById("overlay").style.display = "block";
var a_token = window.localStorage.getItem('access_token');
$.ajax({
type: 'GET',
url: this.props.source,
data: {
page: 1,
},
dataType: "json",
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + a_token);
},
success: function(data) {
$('#overlay').hide();
this.setState({
data: data.order_list.data
});
}.bind(this),
error: function(xhr, status, err) {
$('#overlay').hide();
console.error('#GET Error', status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {
data: []
}
},
componentDidMount: function() {
this.loadGameData();
},
render: function() {
return ( < div className = "CurrentGame" >
< JobList data = {
this.state.data
}
/> < /div>
);
}
});
var JobList = React.createClass({
displayData: function(e) {
var a_token = window.localStorage.getItem('access_token');
$.ajax({
type: 'POST',
url: 'http://zipship-beta.herokuapp.com/job_detail',
data: {
order_id: e,
},
dataType: "json",
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + a_token);
},
success: function(data) {
$('#overlay').hide();
this.setState({
data: data.order_list
});
console.log(this.state.data);
}.bind(this),
error: function(xhr, status, err) {
$('#overlay').hide();
console.error('#GET Error', status, err.toString());
}.bind(this)
});
},
render: function() {
if (!this.props.data) {
return null;
}
return (
<ul className="list-group">
{
this.props.data.map(function(jobDetail, i) {
return <li className="list-group-item" key=jobDetail.id} onClick={()=>{this.displayData(jobDetail.id)}}>
</div>
</li>
},this)
}
</ul>
);
}
});
</script>
If you are looking to make actions of job list change the state of job card here are two options you might consider.
The easiest of which is to create a parent react class such as JobInfo which simply acts as a container for both these elements.
render: function() {
<div>
<JobCard/>
<JobList/>
</div>
}
Then JobInfo would maintain state for both of these two objects. In React when you find ways to reduce state its always a good sign. Then the JobInfo object would have some function such as onListItemClick or updateJobCard. Since you are using your ajax request to update the state, it would be move up to this parent class since the state lives here. Then you simply pass this function down (as a prop) to the JobList object and use this for your onClick event on your list items. Now since the state of the parent is changed from the onClick event the state change will propagate down to both the JobList and JobCard info.
The other option you can look into is having an external event handler. These kind of methods are what you find in Flux structures. The idea use you JQuery or some event library to have your JobList object Trigger an event globally, and have your JobCard object subscribed to this event.
The second option I would recommend against. For most simple cases its completely unnecessary and to implement this type of system.
TLDR; Centralize your state and pass state altering functions as props.
Given the following json:
{
"admin": false,
"data": [
{
value: key,
value :key
},
{
value: key,
value :key
}
]
}
I defined my collection like this:
var myCollection = Backbone.Collections.extend({
url: myurl.com,
parse : function (response) {
return response.data;
}
});
It works like charm, it fill my collection with the data array, however, into the tamplate, I need to render some content when admin is equal true. But I cannot find a way to pass that value to the template.
Any chance any of u kind guys can point it into the right direction to solve this?
You could save the admin flag as a property of the collection in the parse method:
var myCollection = Backbone.Collection.extend({
model: myModel,
isAdmin: false,
...
parse : function (response) {
this.isAdmin = response.admin; //save admin flag from response
return response.data;
}
});
Then you could retrieve it and pass it to your template or use it in any other way in the view render method:
var myView = Backbone.View.extend({
collection: new myCollection(),
...
render: function(){
//retrieve admin flag from collection:
var isAdmin = this.collection.isAdmin;
//you could add it into the json you pass to the template
//or do anything else with the flag
}
});
You can try this fiddle with a very basic render function.