I have a handful of simple checkbox lists that I need to create for an application. I built a "Check All" button for my initial test and it worked beautifully. But, when I changed the code to fetch a subset of the list via a Node call, the list still appeared as expected, but the Check All functionality no longer did. In my initial test, the list was just an array of objects with "description" and "value" but after inserting Node into the middle, the objects also had a $$hashkey property. I'm not sure if this is the source of the problem, but if someone could take a look and tell me what's wrong, I'd appreciate it.
My HTML looks like this:
<div class="row">
<div class="col-md-3" id="semRushApiList_None">
<input type="checkbox" value="semRushCheckAll_None" name="semRushCheckAll_None" ng-click="toggleSemRushApiTypes_None()" /><strong>Check All</strong>
<div ng-repeat="apiCall in semRushApiTypes_None">
<input type="checkbox" name="selectedSemRushApiTypes_None[]" value="{{apiCall.apiName}}" ng-checked="selectedSemRushApiTypes_None.indexOf(apiCall) > -1" ng-click="toggleSemRushApiSelection_None(apiCall)" /> {{apiCall.description}}
</div>
</div>
</div>
My angular js looks like this:
$scope.semRushCheckAll_None = false;
$scope.semRushApiTypes_None = [];
fetchApiTypesByCategory("none").then(function(types){
$scope.semRushApiTypes_None = types;
});
$scope.selectedSemRushApiTypes_None = [];
$scope.toggleSemRushApiTypes_None = function() {
$scope.semRushCheckAll_None = !$scope.semRushCheckAll_None;
if ($scope.semRushCheckAll_None) {
$scope.selectedSemRushApiTypes_None = angular.copy($scope.semRushApiTypes_None);
} else {
$scope.selectedSemRushApiTypes_None = [];
}
};
$scope.toggleSemRushApiSelection_None = function(apiCall) {
var idx = $scope.selectedSemRushApiTypes_None.indexOf(apiCall);
if (idx > -1) {
$scope.selectedSemRushApiTypes_None.splice(idx, 1);
} else {
$scope.selectedSemRushApiTypes_None.push(apiCall);
console.log(JSON.stringify($scope.selectedSemRushApiTypes_None));
}
};
function fetchApiTypesByCategory(category) {
var deferred = $q.defer();
$http.get(rootApiUrl + "/fetchSemRushApiTypesByCategory?category=" + category).then(function(response) {
deferred.resolve(response.data);
}, function(response){
deferred.reject("Error: " + response.data);
});
return deferred.promise;
}
The node call looks like this:
server.route({
method:"GET",
path:"/fetchSemRushApiTypesByCategory",
handler:function(request,reply){
var q = Qs.parse(request.query);
return reply(factory.createApiTypeList(q["category"])).code(200);
}
});
and the factory looks like this:
exports.createApiTypeList = function(category) {
var result = [];
for (var i = 0; i < semRushApiJson.length; i++) {
if (semRushApiJson[i].category === category) {
var description = semRushApiJson[i].description;
var apiName = "";
for (var p = 0; p < semRushApiJson[i].params.length; p++) {
if (semRushApiJson[i].params[p].key == "type") {
apiName = semRushApiJson[i].params[p].value;
break;
}
}
result.push({
"description": description,
"apiName": apiName
});
}
}
return result;
};
Some simple console.log statements have proven that things are being populated as expected, with the exception of the $$hashkey property on the objects coming out of the Node call.
When I check the checkboxes individually, the selected array is populated with a value that doesn't have the $$hashkey and when I check the check all, the selected list gets all of the appropriate values including the $$hashkey, but the checkboxes do not get updated on the UI like they did before I moved the populating of the list to a Node call.
Can anyone tell me what I'm doing wrong here?
V
Related
I know this was asked multiple times already, but none of that answered my question.
I have the following:
I get data over JSON to Javascript into a two dimensional array.
When I load the site, the table shows up like wanted.
Now when I click a button (just for testing), it is updating one value from the array and logging that array in the console, where I see the changed array.
The problem is that the change is not showing up in the table.
When I just add a value to the array, it is showing up in the table.
What am I doing wrong?
HTML:
<table id="sortsTable">
<tbody data-bind="foreach: sorts">
<tr>
<td data-bind="text: $data.name"></td>
<td data-bind="text: $data.ingName"></td>
</tr>
</tbody>
</table>
<button data-bind="click: addPerson">Add</button>
JS:
var sorts = ko.observableArray([]);
$(function() {
var request = new XMLHttpRequest();
var formData = new FormData();
var responseElements = [];
request.open("POST", "scripts.php", true);
formData.append("action", "getSorts");
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
responseElements = JSON.parse(request.responseText);
sorts = convertList(responseElements);
ko.applyBindings(new AppViewModel(sorts));
}
}
request.send(formData);
});
function convertList(response) { //just the function to convert the json object to a more useful array
var names = [];
var ingredients = [];
var sorts = [];
for (var index = 0; index < response.length; index++) {
var name = response[index]['name'];
var ing = response[index]['ingName'];
if (names.indexOf(name) == -1) {
names.push(name);
}
if (ingredients.indexOf(ing) == -1) {
var nameIndex = names.indexOf(name);
if (ingredients[nameIndex] == undefined) {
ingredients[nameIndex] = ing;
} else {
ingredients[nameIndex] = ingredients[nameIndex] + ", " + ing;
}
}
}
for (var i = 0; i < names.length; i++) {
sorts[i] = {};
sorts[i]['name'] = names[i];
sorts[i]['ingName'] = ingredients[i];
}
return sorts;
}
function AppViewModel(data) {
var self = this;
self.sorts = data;
self.addPerson = function() {
console.log("click");
self.sorts[0]["name"] = "test"; //doesn't update table
//self.sorts.push({name: "qwer", ingName: "we"}); //works like expected
console.log(self.sorts);
};
}
Thanks.
The observable array only monitors which items are added to it, not the items themselves, so
self.sorts.push({name: "qwer", ingName: "we"}); //works like expected
works because you're getting the observable array to add to it's items, but
self.sorts[0]["name"] = "test"; //doesn't update table
doesn't work because the observable array has no way of knowing that an item inside it has changed. For this to work the properties of the items in the array will need to be observable.
In convertList switch to:
for (var i = 0; i < names.length; i++) {
sorts[i] = {};
sorts[i]['name'] = ko.observable(names[i]);
sorts[i]['ingName'] = ko.observable(ingredients[i]);
}
And they must be set by calling the observable setter method like so:
self.addPerson = function() {
console.log("click");
self.sorts[0]["name"]("test");
...
Also as an aside, you seem to have some other issues here. You define sorts as an observable array on the first line, but you overwrite it with the return value of convertList which is a normal array, not an observable one.
sorts = convertList(responseElements);
ko.applyBindings(new AppViewModel(sorts));
I'd remove the first line and create sorts as an observable array
function convertList(response) { //just the function to convert the json object to a more useful array
var names = [];
var ingredients = [];
var sorts = ko.observableArray([]);
...
The issue is that when you bind this:
<td data-bind="text: $data.name"></td>
$data.name is not observable, it's a simple property on an object, created here:
sorts[i]['name'] = names[i];
Knockout will quite happily bind properties like this, and display them, but any updates to them are not visible to knockout. Instead, as well as your observableArray, you also need to make any individual properties you want the ability to update observable as well:
sorts[i]['name'] = ko.observable(names[i]);
Then when you update it, knockout will see the change. Note however that you can't simply just assign to the property, as you'll just overwrite the knockout observable and it will be lost, instead you need to call the observable with the update:
self.sorts[0]["name"]("test");
I'm using ngRepeat in my angular application and for some reason the ngRepeat did not populate even though the collection that it is connected to is populated with the correct data.
What i'm doing is sending http get request to a node server to request the data,
go over the result from the server and populate a collection on the scope that is connected to that specific ngRepeat.
The ngRepeat part of the Html file:
<div id="cellRow" ng-repeat="obj in rowsCollection track by obj.index">
<div class="inputsContainer">
<input ng-model="obj.col1"></input>
<input ng-model="obj.col2"></input>
<input ng-model="obj.col3"></input>
</div>
</div>
The Ctrl code:
angular.module('App').controller('Ctrl', ['$scope','dataUtils', function($scope,dataUtils) {
$scope.dataObj = null;
$scope.rowsCollection = [];
dataUtils.getDataObj()
.then($scope.initializeObjects)
.catch($scope.showError);
$scope.initializeObjects = function(data) {
if( data && data.length > 0 ) {
for(var index = 0; index < 21; index++) {
$scope.dataObj = {};
$scope.dataObj.index = index + 1;
$scope.dataObj.col1 = data[0][index];
$scope.dataObj.col2 = data[1][index];
$scope.dataObj.col3 = data[2][index];
$scope.rowsCollection.push($scope.dataObj);
}
}
};
$scope.showError = function(errorMsg) {
console.log(errorMsg);
};
}]);
The dataUtils.getDataObj calls an http get request from the server.
When using the controller in this form i see that the initializeObjects function is called and the rowCollection collection is populated but the ngRepeat stays empty.
After i changed the Ctrl ro the following code:
angular.module('App').controller('Ctrl', ['$scope','dataUtils', function($scope,dataUtils) {
$scope.dataObj = null;
$scope.rowsCollection = [];
dataUtils.getDataObj()
.then(initializeObjects)
.catch(showError);
function initializeObjects(data) {
if( data && data.length > 0 ) {
for(var index = 0; index < 21; index++) {
$scope.dataObj = {};
$scope.dataObj.index = index + 1;
$scope.dataObj.col1 = data[0][index];
$scope.dataObj.col2 = data[1][index];
$scope.dataObj.col3 = data[2][index];
$scope.rowsCollection.push($scope.dataObj);
}
}
}
function showError(errorMsg) {
console.log(errorMsg);
}
}]);
The ngRepeat did populate, why didn't the ngRepeat populate in the first Ctrl configuration but did in the second ?
If you want to use the first way, since you're calling a function, it must have () at the end.
So to achieve what you want you should change this:
.then($scope.initializeObjects)
for:
.then($scope.initializeObjects())
Note: The second way is better, since you need to reuse this $scope function in another moment, otherwise, keep it as a normal function().
In ur first implementation, it should work when u put the $scope.initializeObjects =... before using it in the promise.
$scope.initializeObjects = function(data) {
if( data && data.length > 0 ) {
for(var index = 0; index < 21; index++) {
$scope.dataObj = {};
$scope.dataObj.index = index + 1;
$scope.dataObj.col1 = data[0][index];
$scope.dataObj.col2 = data[1][index];
$scope.dataObj.col3 = data[2][index];
$scope.rowsCollection.push($scope.dataObj);
}
}
};
$scope.showError = function(errorMsg) {
console.log(errorMsg);
};
dataUtils.getDataObj()
.then($scope.initializeObjects)
.catch($scope.showError);
However, if u declare your function as your second attempt, it will be defined before the code in this closure get executed.
You may check this question to understand the difference.
This has been driving me crazy as I'm sure it's simple, but I've tried a multitude of different approaches over the last 2 days and nothing worked properly.
I have two select-dropdowns in my view HTML:
<p>Spend between
<select
id ='minSpend'
ng-model ="cheapest"
ng-options="offer.PriceAUD as '$'+Math.round(offer.PriceAUD) for offer in offers | uniquePrice:'PriceAUD'">
</select>
and
<select
id ='maxSpend'
ng-model ="dearest"
ng-options="offer.PriceAUD as '$'+Math.round(offer.PriceAUD) for offer in offers | uniquePrice:'PriceAUD'" >
</select>
They populate nicely with unique values once data is received.
The uniquePrice filter looks like this:
app.filter('uniquePrice', function() {
return function(input, key) {
if (typeof input == 'undefined'){return;}
var unique = {};
var uniqueList = [];
for(var i = 0; i < input.length; i++){
if(typeof unique[input[i][key]] == "undefined"){
unique[input[i][key]] = "";
input[i][key] = Math.round(input[i][key]);
uniqueList.push(input[i]);
}
}
return uniqueList;
};
});
What I like is the lowest price and highest price to be selected initially. I managed to write a fn that does it "on-click":
$scope.setValues = function(){
$scope.cheapest = $scope.offers[0].PriceAUD;
$scope.dearest = $scope.offers[ $scope.offers.length-1].PriceAUD;
}
[ $scope.offers comes from the DB sorted by PriceAUD ASC ]
Triggered in the HTML by a simple:
<button ng-click="setValues()">Set Values</button>
Which works fine. But how can I trigger this when data-loading is complete? I tried:
$scope.offers = Items.query();
$scope.offers.$promise.then(function (result) {
$scope.setValues();
});
But it doesn't work.
And when I set $scope.dearest = $scope.offers[0].PriceAUD in the controller directly it doesn't work because the promise isn't resolved. I'm sure I just need to put this in the right place, but where?
Thanks for your help.
EDIT: The code to get Items:
var appServices = angular.module('appServices', ['ngResource']);
appServices.factory('Items', ['$resource',
function($resource){
var url = '/api/get/offers.json';
var paramDefaults = {};
var actions = "{ query: {method:'GET', params:{id:'Items'}, isArray:true}";
var optns = '';
return $resource(url, paramDefaults, actions , optns);
}]);
I've been beating myself up over this for the past two days and I cant find a solution. I'm trying to filter a set of articles based off of their tags. I've used an ng-repeat to repeat the article and display the data coming from my API. But cant seem to get the filter correct.
Here is an example of the Article Object
{"id":"05390344-57f8-4f2a-ada0-0705edacd0ef","type":"Article","title":"Lorem title","topics":[{"Id":"bb2a6222-34b1-4abd-8d19-c07eb7ff4d0c","Name":"Pajamas"},{"Id":"7f752e06-092b-473a-9f4e-b79796c7ab7b","Name":"Undies"},{"Id":"bd533c42-90e9-4dce-9316-e3be6c8fc55c","Name":"Briefs"}],"synopsis":"<p>Lorem Ipsem synposis</p>","date":"August 20, 2014","time":"9:28 AM","source":"Localhost","url":"#"}
My controller contains
$scope.filterTopics = {};
function getChecked(obj) {
var checked = [];
for (var key in obj) if (obj[key]) checked.push(key);
return checked;
}
$scope.searchFilter = function (row) {
var topicChecked = getChecked($scope.filterTopics);
if (topicChecked.length == 0)
return true;
else {
if ($scope.filterTopics[row.topics])
return true;
}
};
$scope.$watch('cards', function (cards) {
$scope.count = 0;
angular.forEach(cards, function (card) {
if (card.filterTopics) {
$scope.count += 1;
}
})
});
My view to create the ng-repeat
<div ng-repeat="article in articles | filter: searchFilter">
Then I have checkboxes that I can click to pass search filter the topic Id
ng-model="filterTopics['#topic.Id']
Any help would be great!!!!
Solution was to add a for loop to grab each object
for (var i in row.topics) {
if (topicChecked.indexOf(row.topics[i].Id) != -1) {
return true;
}
}
I have a page where you can invite teams. Clicking "Invite teams" makes a popup box appear showing a search input. The search-function is AJAX based. When a team is found through your search word(s), you'll have to click on the team whereupon the team will be showed in a "Invited-teams"-box.
It works in a way that when you "add" the team, a hidden input field is generated containing the team's ID as a value. The problem is that with my current code, it is possible to add the same team as many times as you wish. I should be possible to check, if the team can be found in the hidden-input-data. If it already exists, it should not be possible to add the sane team.
My current javascript-code can be found beneath here. Please notice that I have tried to make the code that checks the team, but it doesn't work.
function addTeam(tid) {
// Grab the input value
var teamName = document.getElementById(tid).innerHTML;
var teamID = document.getElementById(tid).id;
// If empty value
if(!teamName || !teamID) {
alert('An error occured.');
} else {
//Tried to do the "team-adlready-added"-test, but it doesn't work
var stored_teams = $t('#store-teams').getElementsByTagName('input');
for (var i = 0; i < stored_teams.length; i++) {
var stored_team = stored_teams[i];
if(stored_team.value == teamID) {
break;
var team_already_added = 1;
}
alert(team_already_added);
}
if((team_already_added) || team_already_added != 1) {
// Store the team's ID in hidden inputs
var store_team = document.createElement('input');
store_team.type = 'hidden';
store_team.value = teamID;
// Append it and attach the event (via onclick)
$t('#store-teams').appendChild(store_team);
// Create the teams with the value as innerHTML
var div = document.createElement('div');
div.className = 'team-to-invite';
div.innerHTML = teamName;
// Append it and attach the event (via onclick)
$t('#teams').appendChild(div);
}
div.onclick = removeTeam;
}
return false;
}
Thanks in advance.
I just want to give you a hint for a possible solution without html elements.
You can create a new functional object for team:
var Team = function (id, name) {
this.name = name;
this.id = id;
}
Create an array which will contain teams:
var TeamList = [];
Add you Teams:
TeamList.push(new Team(1, "Team 1"));
TeamList.push(new Team(2, "Team 2"));
TeamList.push(new Team(3, "Team 3"));
TeamList.push(new Team(4, "Team 4"));
Write a function which loops trough the list of teams and checks with the id if a team already exists:
function containsTeam(id) {
for (var i = 0; i < TeamList.length; i++) {
if (TeamList[i].id == id) {
return true;
}
}
return false;
}
Just check it:
containsTeam(1); //returns true
containsTeam(5); //returns false
Have a look at the jsFiddle DEMO and open the console to see the output.
EDIT: In addition, to remove an element you can write a function which looks pretty much the same as the containsTeam function. Just use array.splice instead of returning true:
function removeTeam(id) {
for (var i = 0; i < TeamList.length; i++) {
if (TeamList[i].id == id) {
TeamList.splice(i, 1);
}
}
}
And remove a team:
removeTeam(3);
Your variable scope is off.
You declare team already added in the wrong spot.
Declare it with team name and team id and it will get you in the right direction