angularjs: nested ng-repeat creates blank values in selects - javascript

Ok, first a simplified portion of the Javascript:
//the network values below are later dynamically
//reloaded at runtime from a remote API
$scope.networks = [{id: 5, name: "Network 1"}];
$scope.campaign = {
paths: [
{offers: [
{name: "Site1",
network: $scope.networks[0],
uri: "http://uri1.com"},
{name: "Site2",
network: $scope.networks[0],
uri: "http://uri2.com"}]}]};
And the relevant part of the HTML:
<div ng-repeat "path in campaign.paths">
<div ng-repeat="offer in path.offers">
<input type="text" ng-model="offer.name" />
<input type="text" ng-model="offer.uri" />
<select ng-model="offer.network"
ng-options="n.id as n.name for n in networks">
</select>
</div>
</div>
The problem is that this always gives me a blank option as the first option whenever I try to set the model for the to offer.network. I have read
Why does AngularJS include an empty option in select?
and
AngularJS - extra blank option added using ng-repeat in select tag, but they only seem to apply to selects that are at the top level (i.e., not using a model inside an ng-repeat). My problem is that I need to update a model that is part of a nested ng-repeat.
EDIT: I should have also included the below code; this is what is actually breaking everything. This code runs after the original array is set up:
$scope.getNetworks = function () {
$http.get('/api/networks/nameid'
).success(function (data, status, headers, config) {
$scope.networks = data;
});
};
$scope.getNetworks();
The above backend call returns an array of objects of the form {id: id, :name "name"}.
And yes, I could populate the select statically from the server side, but I would really like to know how to make this work in the general case.

Since you use the n.id in the comprehension expression of the drop down list, you should pre-populate the offer.network with the id value like this
network: $scope.networks[0].id
And since the data is populated dynamically, you can do something like this. (For the demo purpose, I just use a very dumb way to populate the values. But you get the idea.)
$scope.getNetworks().then(function(data){
$scope.networks = data;
//populate the default value
$scope.campaign.paths[0].offers[0].network = $scope.networks[0].id;
$scope.campaign.paths[0].offers[1].network = $scope.networks[0].id;
})
Working Demo

Related

Access Object Properties of ko.observable item selected in HTML drop-down list and use them in View Model

I am building a single page web application with Knockout that will display a variety of different information based on the user's selections. At the top there is a drop down list populated from a array of JSON objects using the options binding. Once the user makes a selection from the list how do I access the properties of the specified object in my View Model JavaScript code?
My specific app is about college football teams. The drop down list at the top has a list of team names that are pulled from an array of JSON objects that contain details about each team. This array comes from an AJAX request to the server. I know this part of my code works as I am able to use other properties in the selected object to change the look of the HTML page. However I can't figure out how to be able to access the properties of the selected object (school name, mascot, conference, etc) in my View Model Java Script so I can use those details to make further AJAX requests that will provide the user more information about the selected team (such as rosters, schedules, and stats).
There is a "value: selectedSchool" in the data-bind for my select menu in HTML that connects to a self.selectedSchool = ko.observable(); in my view model. I have tried a variety of ways to access the properties in that self.selectedSchool including dot and bracket notation, a ternary operator to check if it is null before accessing the property, and more. But as far as I can tell that variable doesn't actually contain an object that I can access, so how do I get around this?
I have tried to start small by just trying to access the 'school' property from that object and use it to get the rosters (which have the same file names as the school name) via AJAX, but eventually I want to do a lot more with it.
my HTML View:
<div>
<select data-bind="options: schools,
optionsText: function(item) {
return item.school + ' ' + item.mascot
},
value: selectedSchool,
optionsCaption: 'Choose a team...'"></select>
</div>
<div>
<!-- ko with: selectedSchool -->
<p>
School: <span data-bind="text: school"></span>
</p>
<p>
Conference: <span data-bind="text: conference"></span>
</p>
<img data-bind="attr: { src: logos[0], alt: school}">
<!-- /ko -->
</div>
The above HTML is the only part that works now. I also have a table for the team rosters with columns for things like name, position, and size once I can get the below AJAX request to work.
my JavaScript View Model:
function SchoolsViewModel() {
// Data
var self = this;
self.schools = ko.observableArray([]);
self.selectedSchool = ko.observable();
self.selectedRosterData = ko.observableArray([]);
// Behaviours
$.getJSON("/schools", function(data) {
self.schools(data);
});
//the below part works doesn't work. this is one of many tries
self.selectedRoster = function(roster) {
self.selectedSchool(roster);
$.getJSON("/rosters", { school: self.selectedSchool['school'] }, function(data) {
self.selectedRosterData(data);
});
ko.applyBindings(new SchoolsViewModel());
};
I want to make the second AJAX request return the JSON file from the server with the same name as the 'school' property from the selected JSON object in the schools array. However self.selectedSchool doesn't seem to contain a JSON object at all as far as I can tell. I don't want the AJAX request to activate until after the user selects a team from the drop down menu.
Also, for what it's worth, I eventually want to do a lot more with this than just request the roster data. The ideas is that after the user selects a team from the drop down then a list of folders will appear with several options like roster, schedule, stats, and news. When the user clicks on one of the folders they will receive the corresponding information. If I can access the properties of the selected team object from the drop-down menu then I think I can figure the rest out, but any answers with an eye on future expandability would be appreciated.
Thank you all so much for your time!
Remember selectedSchool is an observable (which, in Knockout, is technically a function), so this:
self.selectedSchool['school']
will not work. You need to "unwrap" the observable first by calling it:
self.selectedSchool()['school']

Get file paths for multiple uploaded files with angular-file-upload within ng-repeat

I am using Angular 1 and the angular-file-upload plugin.
I want to create an array populated in the following way:
result = [{
name: "whatever string",
uploadedFilePath: "/whatever/path/image.png"
}, {
name: "other string",
uploadedFilePath: "/whatever/path/otherImage.png"
}]
In the view I have an ng-repeat with a name input and a file-input field and a button that creates another card with the same fields in the ng-repeat.
I can add the names to the array in the corresponding objects without much issue.
If there is a single upload input and not in the ng-repeat (either file input or multiple) I can easily get the path by taking it through the event:
vm.uploader.onCompleteItem = function(fileItem, response, status, headers) {
vm.uploadedFilePaths.push(response.filePath);
};
and then getting the paths from vm.uploadedFilePaths.
I don't know any good practices for showing the file input in the view so I'm using:
<input type="file" ng-file-model="files" multiple />
<p ng-repeat="file in files">
{{file.name}}
</p>
I don't know how to get the paths corresponding to each of the uploaded files and adding them to the corresponding object in the array.
I'm somewhat confident I should be using the event vm.uploader.uploadAll(); at the end to put them in the corresponding place ? But how do I find out the corresponding place for each, I'm not sure how I can utilize $index here.
I'm very new to using angular-file-upload so I'd appreciate it very much if you could explain it like it's for a newbie.
Declare a object as
vm.fileDetails = {
name:"",
path:""
}
and then on the completion of upload
vm.uploader.onCompleteItem = function(fileItem, response, status, headers) {
vm.fileDetails.name = response.fileName;
vm.fileDetails.path = response.filePath;
vm.uploadedFilePaths.push(vm.fileDetails);
};
Now you can use this array in your view to get details.

Show second checkbox if return value of first is true

I got a question and I am also accepting to getting downvotes for this because I have not really tried something yet. The problem is I don't know how to name the problem and for what I should look for around the internet.
It's like this, I got a link to an api which (in my case) contains all provinces of china in this format:
{
"result":{
"version":"1.0",
"status":100101,
"msg":"got provinces successfully"
},
"data":[
{"province":"\u9999\u6e2f"},
{"province":"\u5317\u4eac\u5e02"}
and some more. Now I want to make a dropdown select menu which contains all this provinces as dropdown values and if one dropdown is selected it should check another URL which says if the selected province is valid or not (in my case it only can be valid because the user cannot enter something himself)
?action=api&i=getCityForProvince&data=北京市
This would be the url for checking this, if it is successful it shows me the cities of the province in the same format like the code above. With this i want to make another select box which only appears if the first is true. In this select box you then select your city and that's it.
I sadly have absolutely no idea how to start with this problem and for what i should look for to solve this.
I wonder if the fact that it's chinese has anything to do with your problem? I bet, it doesn't. With jquery it's pretty easy to accomplish such tasks. It's like building blocks you need to put together.
Learn how to make ajax calls with JQuery. It's quite easy, also it should process your Json result, making it a object or array. So in the callback, you can build up your select box like described here. Another block is to bind to the change event of the select box, which is doing another Ajax call (you already know that now) using the value from the Select input. And in the result of that callback, you can also check the result json and if the result was successful, you can easily fill up another select box using already known methods now, or change its visiblity according to your results.
I think you will want to learn those things, and was not supposed to get a ready coded solution :)
To make your work easier, I recommend you to use:
a template library
an MVVM framework
The difference between using jQuery directly and an MVVM library, or template library, like handlebars or mustache, is that with jQuery you have to take care of handling the elements, and with the other solutions, you leave this work to the framework.
ANyway, I recommend using knockout over using the template libraries, because:
it includes the templates
it can provide a two-way binding
it can handle events
it can apply classes, modify visibility, enable and disable elements...
Here I add a simple example of what it can do:
// This is the ko part:
// This is the view model using Revealing Module Pattern to build it
var vm = (function(){
// The list of provinces which will be shown when available
var provinces = ko.observableArray([]);
// The province selected in the list
var selectedProvince = ko.observable();
// This is what you'd call when the provinces are loaded using AJAX
var loadProvinces = function(data) {
provinces(data);
};
// This functions will be triggered when the selected province changes...
var updateCities = function() {
console.log("Here you'd update cities");
};
// ... because of this subscription
selectedProvince.subscribe(updateCities);
// return the object with the desired properties:
return {
provinces: provinces,
selectedProvince: selectedProvince,
loadProvinces: loadProvinces,
updateCities: updateCities
};
})();
ko.applyBindings(vm);
// AJAX call simulation:
// the returned data
var data = [
{"province":"\u9999\u6e2f"},
{"province":"\u5317\u4eac\u5e02"}
];
// a time out to load the data (simulate AJAX call)
setTimeout(function() { vm.loadProvinces(data);}, 1000);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="visible: !(provinces().length)">
Please wait while loading provinces
</div>
<div data-bind="visible: provinces().length">
<select data-bind="options: provinces,
optionsText: 'province',
optionsValue: 'province',
value: selectedProvince">
</select>
<p>Selected province: <span data-bind="text: selectedProvince"></span></p>
</div>
As you can see, it handles not only creating the DOM elements, bu also handling events, two way-bindig, detecting changes in values...
You could originally write the HTML for your second checkbox and give it a display: none; property. Then on the JS:
if (firstcheckboxValue === true) {
document.getElementById('secondCheckboxId').style='display: block';
}
You could use display: inline-block; or display: inline; etc, whatever suits your layout better.
Things would drastically get easier if you used jQuery. Since there's no code to start working with, I'll just list out steps I'd go through.
1) Write DOM elements for dropdowns, say #dd_provinces #dd_cities. #dd_cities would be hidden.
2) From $().ready(function(){...}) I'd make the web API call.
3) From result callback of the API call in #2, make the second API call(one to fetch cities of the province).
4) Result callback of the second API callback will populate the DOM element #dd_cities
5) Unhide #dd_cities
Sample code:
HTML
<select id="dd_provinces">
</select>
<select id="dd_cities" style="visibility: hidden">
</select>
JS
$(document).ready(function() {
$.ajax({
url: "/echo/json/",
data: "",
success: function(evt) {
var mData = ["City 1", "City 2", "City 3", "City 4"];
for(var i = 0; i < mData.length; i++){
var optionElem = "<option>" + mData[i] + "</option>";
$("#dd_provinces").append(optionElem);
}
$.ajax({
url: "/echo/json",
data: "",
success: function(evt) {
$("#dd_cities").css("visibility", "visible").animate('5000');
}
});
},
error: function(evt) {
console.log(evt);
}
});
});

Angular select multiple not refreshing on model change

I have the following issue with angular and the html select-input:
When the user loads the page my select is empty until the data is loaded and returned from the corresponding service.
In this case the select is populated correctly.
However, the user should be able to filter the model with a click on a button.
When I press the button a new request is send to the REST-API and the response contains the new model for the select.
Unfortunately, the select won't update correctly even when I change it's model
Here is some code to illustrate my problem:
// This happens in my controller
EventService.getAvailableRooms(requestObject).then(function successCallback(response){
// sanatizeRoomTypes is used to generate user-friendly names instead of [1, 2,..]
vm.rooms = DataSanatizer.sanatizeRoomTypes(response.data);
}, function errorCallback(response){
console.log(response)
});
vm.rooms is the model of my select:
<select id="roomSelect" ng-model="eventCtrl.selectedRooms"
ng-options="room.name group by room.type for room in eventCtrl.rooms track by room.id" multiple required>
</select>
In some cases the select duplicates it's model or looses entries. It seems like there is some sort of data binding problem.
Thanks,
Try wrapping your model changes in $scope.apply :
$scope.$apply(function() {
$scope.data.myVar = "Another value";
});
Read here for more info:
http://tutorials.jenkov.com/angularjs/watch-digest-apply.html
You can also use $scope.$watch on the changing $scope variables to update them in your view.

I'm getting a "newItem() was not passed an identity for the new item" error while trying to add a new item to a JSON store

I've seen other posts in this site regarding the same issue and I've tried the solutions given. I've also visited the links that may offer a solution but I'm still stuck with the same error.
I'm using DOJO and something as simple as this won't even work
myStore.newItem({id: 'test', otherfield: 'otherinfohere'});
myStore.save();
Supposedly the "newItem() was not passed an identity for the new item" error appears when you haven't provided an identifier for the new item, which i have.
The whole purpose of this (Just in case anyone can provide a good idea or has done something similar before) is that i want to create a data grid that shows info from a particular store. The problem is, that in that store all the items may not have the same structure. For instance:
I may have a store that looks like this
{identifier: 'id',
label: 'name',
items: [
{ id:'1', name:'Ecuador', capital:'Quito' },
{ id:'2', name:'Egypt', capital:'Cairo' },
{ id:'3', name:'El Salvador', capital:'San Salvador' , additionalField: 'otherinfohere'},
{ abbr:'gq', name:'Equatorial Guinea', capital:'Malabo', additionalField: 'otherinfohere'},
]}
This is possible because I'm the one constructing the store in a Spring Controller (I'm also using the Spring Framework) from information I have locally stored in a Berkeley DB. So what i need is a data grid with a dynamic layout because I don't want blank spaces to show in the view in the rows with lesser amount of fields, and i need to show all the info in the store at the same time, but i don't know how to do this.
I thought of doing it by creating a simple layout of only 1 field. In it I would load data from a store i create dynamically at runtime. The data in the store would be composed of HTML combined with the values coming from the original store so I could obtain something like this, which is inside an attribute of a JavaScript Object and let the browser parse it for me:
<div><span>id: originalID </span>....</div>
This of course is a simple example, the html layout i'm looking for is far more complicated, but i think that passing it as a string to an object might do the trick.
The problem is that i don't even know if that idea will work because i get that error whenever i try to add values to my secondary store.
rdb.modules.monitor.historicStore.fetch({onComplete: function(items, request){
for (var i = 0; i < items.length; i++){
var item = items[i];
var obj = new Object();
obj.id = rdb.modules.monitor.historicStore.getValue(item, "id");;
var html = "<div><span>";
html += rdb.modules.monitor.historicStore.getValue(item, "sql");
html += "</span></div>";
obj.html = html;
myStore.store.newItem(obj);
}
}});
In this context "historicStore" refers to the JSON store that has the values that i need to convert and add to "myStore" after i added some HTML.
I hope you got the main idea of what I'm trying to do. If anyone can help me we either of these problems i would really appreciate it. Thanks in advance
For the issue regarding store:-
"id" is mandatory for a store, if it is going to be used for a grid(datagrid, EnhancedGrid, etc. whatever). The items are handled only on basis of "id" attribute by the grid data structures.
Usually, id can be a loop variable/ auto incrementation, to avoid any cases like you have said. Before adding the store to the grid, ensure that all items have the id attribute. You can write a function which will loop through each item and check for this, else add an auto-incrementing value for the id attribute of that item.

Categories

Resources