Ng-repeat a function with json Angular - javascript

I'm new in Angular. I'm trying to do an application. Before I did
this (watch the second Fork, in the middle, not the last one)
that there are two selection: the second one is empty and auto complete with a subarray of the option chosen in the first select. This works.
Now, this is for a table that I'm doing. In fact the rows contain the two selection above. I will add also a button for ADD new line, so with new selection. I've tried to iterate, but of course it doesn't work.... In this update THIS you can see that in the script.js i've added new code (i made comments). I've created one array to include all the info, one function to add new line and one function to delete line. But of course it doesn't work....... what I am doing wrong?
In particular this is the new lines of js' code:
$scope.allInfo = [
{
"Product ID" : $scope.selectedProduct.id,
"Product Nome" : $scope.selectedProduct.nome,
"Product Codice": $scope.selectedProduct.codice,
"Lot ID" : $scope.selectedLot.id,
"Lot Value" : $scope.selectedLot.value
}
];
$scope.foods = [
{
"select1": "",
"select2": ""
}
]
$scope.newLine = function () {
var itemToAdd = { "select1": "", "select2": ""};
$scope.allInfo.push(itemToAdd);
}
$scope.removeLine = function (itemIndex) {
$scope.foods.splice(itemIndex, 1);
}

First of all, In the newLine method, you need to read the $scope.selectedProduct and $scope.selectedLot and use that to create a new item which you will be pushing to $scope.allInfo array.
$scope.newLine = function () {
var itemToAdd = { "select1": $scope.selectedProduct.descrizione,
"select2": $scope.selectedLot.value};
$scope.allInfo.push(itemToAdd);
}
And now in your UI, you can use ng-repeat to display the items in $scope.allInfo
<h1>INFO ABOUT SELECTION</h1>
<div ng-repeat="item in allInfo">
{{item.select1}} - {{item.select2}}
</div>
Here is a working sample.

Related

Angular: How to re-bind to previously selected object when list changes

By default, when you modify the source list that a select is bound to using ng-options, it clears the selected list item even if that item exists in its entirety in the new list. I figure there may be a way to tell angular that if they have the same WhateverKey property, then consider them the same item and don't de-select. Is there a way to do this?
In the example below, selecting a new customer re-binds (think: re-downloads) the list of possible locations, but the selected location ($scope.Location) still exists in the new list, and thus user experience intuition says it should be selected.
I understand the problem is that it's literally a different reference object with a different angular object key. I'm asking how to auto-rebind based on a property of the object (in this case, LocationKey) without resorting to looping through JavaScript to find the corresponding object in the new list manually.
function Ctrl($scope) {
$scope.Customers = [{CustomerKey: 1, CustomerCode: 'Customer1'},
{CustomerKey: 2, CustomerCode: 'Customer2'},
{CustomerKey: 3, CustomerCode: 'Customer3'}];
$scope.Locations = [{LocationKey: 1, LocationCode: 'Location1'},
{LocationKey: 2, LocationCode: 'Location2'},
{LocationKey: 3, LocationCode: 'Location3'}];
$scope.Customer = $scope.Customers[0];
$scope.Location = $scope.Locations[0];
$scope.SelectCustomer = function() {
$scope.Locations = angular.copy($scope.Locations);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller="Ctrl">
<select ng-model="Customer"
ng-options="c as c.CustomerCode for c in Customers"
ng-change="SelectCustomer()"></select>
<select ng-model="Location"
ng-options="l as l.LocationCode for l in Locations"></select>
<div ng-bind="'Customer: ' + Customer.CustomerCode"></div>
<div ng-bind="'Location: ' + Location.LocationCode"></div>
</div>
$scope.SelectCustomer = function() {
$scope.Locations = angular.copy($scope.Locations);
$scope.Location = $scope.Locations.find(function(loc){
return loc.LocationKey === $scope.LocationKey;
}
}
Sorry for the lazy answer. Would have been a comment if you could format code properly in a comment.

prevent reflecting ng-model value across all select tags

I am pretty new to AngularJS. I am working on a project wherein I need to append certain html select tags based on a button click. Each select tag is bound to a ng-model attribute (which is hardcoded). Now the problem I am facing is, once I append more than 2 such html templates and make changes in a select tag then value selected is reflected across all the tags bound to the corresponding ng-model attribute (which is pretty obvious). I would like to know if there is a way around it without naming each ng-model differently.
JS code:
EsConnector.directive("placeholderid", function($compile, $rootScope, queryService, chartOptions){
return {
restrict : 'A',
scope : true,
link : function($scope, element, attrs){
$scope.current_mount1 = "iscsi";
$scope.current_dedupe1 = "on";
$scope.y_axis_param1 = "Total iops";
var totalIops =[];
var totalBandwidth =[];
element.bind("click", function(){
$scope.count++;
$scope.placeholdervalue = "placeholder12"+$scope.count;
var compiledHTML = $compile('<span class="static" id='+$scope.placeholdervalue+'>choose mount type<select ng-bind="current_mount1" ng-options="o as o for o in mount_type"></select>choose dedupe<select ng-model="current_dedupe1" ng-options="o as o for o in dedupe"></select>choose y axis param<select ng-model="y_axis_param1" ng-options="o as o for o in y_axis_param_options"></select></span><div id='+$scope.count+' style=width:1400px;height:300px></div>')($scope);
$("#space-for-buttons").append(compiledHTML);
$scope.$apply();
$(".static").children().each(function() {
$(this).on("change", function(){
var id = $(this).closest("span").attr("id");
var chartId = id.slice(-1);
queryService.testing($scope.current_mount1, $scope.current_dedupe1, function(response){
var watever = response.hits.hits;
dataToBePlot = chartOptions.calcParams(watever, totalIops, totalBandwidth, $scope.y_axis_param1);
chartOptions.creatingGraph(dataToBePlot, $scope.y_axis_param1, chartId);
});
});
});
});
}
}
});
Code explanation:
This is just the directive which I am posting.I am appending my compiledHTML and doing $scope.apply to set the select tags to their default values. Whenever any of the select tags are changed I am doing a set of operations (function calls to services) on the values selected.
As you can see the ng-model attribute being attached is the same. So when one select tag is changed the value is reflected on all the appended HTML even though the data displayed does not match to it.
Hope this PLunker is useful for you. You need to have one way binding over such attributes
<p>Hello {{name}}!</p>
<input ng-model="name"/>
<br>Single way binding: {{::name}}
Let me know if I misunderstood your question
It is a bit hard to understand your whole requirement from your description and your code, correct me if I'm wrong: you are trying to dynamically add a dropdown on a button click and then trying to keep track on each of them.
If you are giving the same ng-model for each generated items, then they are bound to the same object, and their behavior is synchronized, that is how angular works.
What you can do is, change your structure to an array, and then assigning ng-model to the elements, so you can conveniently keep track on each of them. I understand you came from jquery base on your code, so let me show you the angular way of doing things.
angular.module('test', []).controller('Test', Test);
function Test($scope) {
$scope.itemArray = [
{ id: 1, selected: "op1" },
{ id: 2, selected: "op2" }
];
$scope.optionList = [
{ name: "Option 1", value: "op1" },
{ name: "Option 2", value: "op2" },
{ name: "Option 3", value: "op3" }
]
$scope.addItem = function() {
var newItem = { id: $scope.itemArray.length + 1, selected: "" };
$scope.itemArray.push(newItem);
}
$scope.changeItem = function(item) {
alert("changed item " + item.id + " to " + item.selected);
}
}
select {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<div ng-app='test' ng-controller='Test'>
<button type='button' ng-click='addItem()'>Add</button>
<select ng-repeat='item in itemArray'
ng-options='option.value as option.name for option in optionList'
ng-model='item.selected'
ng-change='changeItem(item)'></select>
</div>

delete row(s) from ng-grid table from button

I have a table with ng-grid, and the problem is that i'm not sure how to collect the selected row(s) id or variable to pass into my delete function.
here is a quick mockup of what i'm trying to do
http://plnkr.co/edit/zy653RrqHmBiRJ7xDHlV?p=preview
the following code is from my html, a clickable delete button that takes in 2 parameters, the array of checkbox ids and the index at the corresponding table. This delete method was obtained from this tutorial : http://alexpotrykus.com/blog/2013/12/07/angularjs-with-rails-4-part-1/
<div class="btn-group">
<button class="my-btn btn-default button-row-provider-medical-services" ng-click="deleteProviderMedicalService([], $index)">Delete</button>
</button>
</div>
<div class="gridStyle ngGridTable" ng-grid="gridOptions">
</div>
The following code grabs the json data from a url, queries it and returns it. It also contains the delete function that gets called from the controller in the html page.
app.factory('ProviderMedicalService', ['$resource', function($resource) {
function ProviderMedicalService() {
this.service = $resource('/api/provider_medical_services/:providerMedicalServiceId', {providerMedicalServiceId: '#id'});
};
ProviderMedicalService.prototype.all = function() {
return this.service.query();
};
ProviderMedicalService.prototype.delete = function(providerId) {
this.service.remove({providerMedicalServiceId: providerId});
};
return new ProviderMedicalService;
}]);
The following is my controller(not everything, just the most important bits). $scope.provider_medical_services gets the json data and puts it into the ng-grid gridOptions.
After reading the ng-grid docs, i must somehow pass the checkbox ids from the selectedItems array and pass it into html doc to the delete function. Or, i'm just doing this completely wrong, as i hacked this together. Solutions and suggestions are greatly appreciated
(function() {
app.controller('ModalDemoCtrl', ['$scope', 'ProviderMedicalService', '$resource', '$modal', function($scope, ProviderMedicalService, $resource, $modal) {
var checkBoxCellTemplate = '<div class="ngSelectionCell"><input tabindex="-1" class="ngSelectionCheckbox" type="checkbox" ng-checked="row.selected" /></div>';
$scope.provider_medical_services = ProviderMedicalService.all();
$scope.deleteProviderMedicalService = function(ids,idx) {
$scope.provider_medical_services.splice(idx, 1);
return ProviderMedicalService.delete(ids);
};
$scope.gridOptions = {
columnDefs: [
{
cellTemplate: checkBoxCellTemplate,
showSelectionCheckbox: true
},{
field: 'name',
displayName: 'CPT Code/Description'
},{
field: 'cash_price',
displayName: 'Cash Price'
},{
field: 'average_price',
displayName: 'Average Price'
},
],
data: 'provider_medical_services',
selectedItems: []
};
i think the easiest option is pass an (array index) as data-id to your dom, which u can pick it from there.
{{$index}} is a variable you can use in ng-repeat
======= ignore what i said above, since i normaly writes my own custom stuff ======
I just had a look at ng-grid, i took their example. i've added a delete all selected function, as well as someone elses delete current row function ( these is pure angular way ) to see the code, hover over the top right corner < edit in jsbin >
http://jsbin.com/huyodove/1/
Honestsly i don't like it this way, you would be better off use something like lodash to manage your arrays and do your own custom grid. Using foreach to find the row index isn't good performance.
In their doc, it says you can change the row template, and which you should, so you can add the {{index}} to that row, and filter your data through that index rather which is a little bit better. anyway beware of deleting cells after you have filter your table.
I don't quite get much your question, but you can access to selectedItems of ng-grid as following: $scope.gridOptions.$gridScope.selectedItems (see ng-grid API for more information, but technically this array holds the list of selected items in multiple mode - or only one item in single mode)
For your case, the deleteAll() function could be someething like this:
$scope.deleteAll = function() {
$scope.myData = [];
}
The delete() function which delete selected items can be:
$scope.delete = function() {
$.each($scope.gridOptions.$gridScope.selectedItems, function(index, selectedItem) {
//remove the selected item from 'myData' - it is 2-ways binding to any modification to myData will be reflected in ng-grid table
//you could check by either name or unique id of item
});
}

How do I keep the original value selected when transitioning to edit mode?

The select lists are not rendering with the correct option selected. I've tried this a number of different ways including a computed selected observable (this.selected = ko.computed(return parseInt(selected(), 10) == this.id; )) and find in array functions.
In production, the dataArea elements would be populated with server side data. Using the divs with "data-" attributes keeps server side and client side scripting separate (I find this helps the designers).
A record would be displayed in non edit mode first with the option to edit by clicking the edit button. In edit mode, the initial values for the record appear in input controls. You would have the option to say, choose another customer and the having the form load new associated projects. Loading a new customer would reset the project list as expected.
So while loading a new customer would work well, its the transition to editing the current values that is causing an issue. The selected project needs to appear in the drop down list. If a new customer is chosen, the list populates with new options and no defaults are required.
http://jsfiddle.net/mathewvance/ZQLRx/
* original sample (please ignore) http://jsfiddle.net/mathewvance/wAGzh/ *
Thanks.
<p>
issue: When the select options are read, the inital value gets reset to the first object in the options. How do I keep the original value selected when transitioning to edit mode?
</p>
<div>
<h2>Edit Quote '1001'</h2>
<div class="editor-row" data-bind="with: selectedCustomer">
<label>Customer</label>
<div data-bind="visible: !$root.isEditMode()">
<span data-bind="text: CompanyName"></span>
</div>
<div data-bind="visible: $root.isEditMode()">
<input type="radio" name="customerGroup" value="1" data-bind="value: id"> Company Name 1
<input type="radio" name="customerGroup" value="2" data-bind="value: id"> Company Name 2
</div>
</div>
<div class="editor-row">
<label>Project</label>
<div data-bind="visible: !isEditMode()">
<span data-bind="text: selectedProject.Name"></span>
</div>
<div data-bind="visible: isEditMode()">
<select data-bind="options: selectedCustomer().projects, optionsText: 'Name', value: selectedProject"></select>
</div>
</div>
<div>
<button data-bind="click: function() { turnOnEditMode() }">Edit</button>
<button data-bind="click: function() { turnOffEditMode() }">Cancel</button>
</div>
</div>
<hr/>
<div data-bind="text: ko.toJSON($root)"></div>
function ajaxCallGetProjectsByCustomer(customerId) {
var database = '[{"CustomerId": 1, "Name":"Company Name 1", "Projects": [ { "ProjectId": "11", "Name": "project 11" }, { "ProjectId": "12", "Name": "project 12" }, { "ProjectId": "13", "Name": "project 13" }] }, {"CustomerId": 2, "Name": "Company Name 2", "Projects": [ { "ProjectId": "21", "Name": "project 21" }, { "ProjectId": "22", "Name": "project 22" }, { "ProjectId": "23", "Name": "project 23" }] }]';
var json = ko.utils.parseJson(database);
//console.log('parseJson(database) - ' + json);
//ko.utils.arrayForEach(json, function(item) {
// console.log('CustomerId: ' + item.CustomerId);
//});
return ko.utils.arrayFirst(json, function(item){
return item.CustomerId == customerId;
});
}
var Customer = function(id, name, projects) {
var self = this;
this.id = ko.observable(id);
this.CompanyName = ko.observable(name);
this.projects = ko.observableArray(ko.utils.arrayMap(projects, function(item) {
return new Project(item.ProjectId, item.Name);
}));
};
Customer.load = function(id) {
var data = ajaxCallGetProjectsByCustomer(id);
var customer = new Customer(
data.CustomerId,
data.Name,
data.Projects);
};
var Project= function(id, name) {
this.id = id;
this.Name = ko.observable(name);
};
var QuoteViewModel = function () {
var self = this;
$customerData = $('#customerData'); // data from html elements
$projectData = $('#projectData');
// intial values to display from html data
var customer = new Customer (
$customerData .attr('data-id'),
$customerData .attr('data-companyName'),
[{"ProjectId": $projectData .attr('data-id'), "Name": $projectData .attr('data-name')}]
)
this.selectedCustomer = ko.observable(customer);
this.selectedProject = ko.observable($projectData.attr('data-id'));
this.isEditMode = ko.observable(false);
this.selectedCustomer.subscribe(function(){
// todo: load customer projects from database api when editing
});
this.turnOnEditMode = function() {
var customerId = self.selectedCustomer().id();
console.log('customerId: ' + customerId);
Customer.load(customerId);
self.isEditMode(true);
};
this.turnOffEditMode = function() {
self.isEditMode(false);
};
};
var viewModel = new QuoteViewModel();
ko.applyBindings(viewModel);
One the initial value you load
this.dongle = ko.observable($dongleData.attr('data-id'));
This would be the string value "3". Where as the dongle html select element is actually saving/expecting to retrieve the object { "Id": "3", "Name": "dongle 3" }.
Here is a working version that gets the correct initial values and allows editing.
http://jsfiddle.net/madcapnmckay/28FVr/5/
If you need to save the a specific value and not the whole dongle/widget object, you can use the optionsValue attribute to store just the id. Here is it working in the same way.
http://jsfiddle.net/madcapnmckay/VnjyT/4/
EDIT
Ok I have a working version for you. I'll try to summarize everything I changed and why.
http://jsfiddle.net/madcapnmckay/jXr8W/
To get the customer info to work
The Customer name was not stored in the ajaxCallGetProjectsByCustomer json so when you loaded a customer there was no way to determine the new name from the data received. I added a Name property to each customer in the json with name "Company Name 1" etc.
To get the projects collection to work
The problem here was as stated originally with the dongles. You initialize the selectedProject observable with $projectData.attr('data-id') which equates to string value of 13. This is incorrect as the select list is configured in such a way that it actually saves/expects to receive the project object itself. Changing this id assignment to an object assignment made the initial value of project work correctly.
var project = ko.utils.arrayFirst(customer.projects(), function(project){
return project.id == Number($projectData.attr('data-id'));
});
this.selectedProject = ko.observable(project);
FYI there was a minor error in the html, the selectedProject.Name needed to be selectedProject().Name. No big deal.
I'm sure you could have figured out those pretty easily. The next bit is where the real issue is. You reload the Customer every time the edit button is clicked. This seems strange and you may want to reconsider that approach.
However what happens is you load a customer object from the server by id. Assign it to the selectedCustomer observable, this actually works fine. But then because the drop down is bound to selectedCustomer().projects and viewModel.selectedProject it expects that selectedProject is a member of selectedCustomer().projects. In the case of objects the equality operator is assessing whether the references match and in your case they do not because the original selectedProject was destroyed with its associated customer when you overwrote the selectedCustomer value. The fact that the ids are the same is irrelevant.
I have put in place a hack to solve this.
var oldProjectId = viewModel.selectedProject().id;
viewModel.selectedCustomer(customer);
var sameProjectDifferentInstance = ko.utils.arrayFirst(customer.projects(), function(project){
return project.id == oldProjectId;
});
viewModel.selectedProject(sameProjectDifferentInstance || customer.projects()[0]);
This saves the old projectId before assigning the new customer, looks up a project object in the new customer object and assigns it or defaults to the first if not found.
I would recommend rethinking when you load objects and how you handle their lifecycle. If you hold the current objects it memory with a full list of projects included you don't need to reload them to edit, simply edit and then send the update back to the server.
You may find it easier to hold json from the server in js variables instead of html dom elements. e.g.
<script>var projectInitialData = '#Model.ProjectInitialData.toJSON()';</script>
Hope this helps.

jquery autocomplete in variable length list

Trying to figure out how to do this, using Sanderson begincollectionitems method, and would like to use autocomplete with a field in each row.
I think I see how to add a row with an autocomplete, just not sure the approach for existing rows rendered with guid.
Each row has an of field that the user can optionally point to a record in another table. Each autocomplete would need to work on the html element idfield_guid.
I'm imagining using jquery to enumerate the elements and add the autocomplete to each one with the target being the unique of field for that row. Another thought is a regex that maybe let you enumerate the fields and add autocomplete for each in a loop where the unique field id is handled automatically.
Does that sound reasonable or can you suggest the right way? Also is there a reasonable limit to how many autocomplete on a page? Thanks for any suggestions!
Edit, here's what I have after the help. data-jsonurl is apparently not being picked up by jquery as it is doing the html request to the url of the main page.
$(document).ready(function () {
var options = {
source: function(request, response) {
$.getJSON($(this).data("jsonurl"), request, function (return_data) {
response(return_data.itemList);
});
},
minLength: 2
};
$('.ac').autocomplete(options);
});
<%= Html.TextBoxFor(
x => x.AssetId,
new {
#class = "ac",
data_jsonurl = Url.Action("AssetSerialSearch", "WoTran", new { q = Model.AssetId })
})
%>
And the emitted html look okay to me:
<input class="ac" data-jsonurl="/WoTran/AssetSerialSearch?q=2657" id="WoTransViewModel_f32dedbb-c75d-4029-a49b-253845df8541__AssetId" name="WoTransViewModel[f32dedbb-c75d-4029-a49b-253845df8541].AssetId" type="text" value="2657" />
The controller is not a factor yet, in firebug I get a request like this:
http://localhost:58182/WoReceipt/Details/undefined?term=266&_=1312892089948
What seems to be happening is that the $(this) is not returning the html element but instead the jquery autocomplete widget object. If I drill into the properties in firebug under the 'element' I eventually do see the data-jsonurl but it is not a property of $(this). Here is console.log($this):
You could use the jQuery UI Autocomplete plugin. Simply apply some know class to all fields that require an autocomplete functionality as well as an additional HTML5 data-url attribute to indicate the foreign key:
<%= Html.TextBoxFor(
x => x.Name,
new {
#class = "ac",
data_url = Url.Action("autocomplete", new { fk = Model.FK })
})
%>
and then attach the plugin:
var options = {
source: function(request, response) {
$.getJSON($(this).data('url'), request, function(return_data) {
response(return_data.suggestions);
});
},
minLength: 2
});
$('.ac').autocomplete(options);
and finally we could have a controller action taking two arguments (term and fk) which will return a JSON array of suggestions for the given term and foreign key.
public ActionResult AutoComplete(string term, string fk)
{
// TODO: based on the search term and the foreign key generate an array of suggestions
var suggestions = new[]
{
new { label = "suggestion 1", value = "suggestion 1" },
new { label = "suggestion 2", value = "suggestion 2" },
new { label = "suggestion 3", value = "suggestion 3" },
};
return Json(suggestions, JsonRequestBehavior.AllowGet);
}
You should also attach the autocomplete plugin for newly added rows.

Categories

Resources