Edit ng-repeated item using ngclick - angularjs

Im learning Angular and am trying to build bits and bobs to learn. Ive tried to explain myself as clear as possible below - any help will be much appreciated.
Example:
http://jsbin.com/micasafetise/2/
http://jsbin.com/micasafetise/2/edit?html,js,console,output
I've created myself this bit of data
$scope.people =
[
{
"personID": 1,
"first_name": "Sam",
"last_name": "Stimpson",
"attending": false
},
{
"personID": 2,
"first_name": "Alison",
"last_name": "van Schoor",
"attending": true
},
{
"personID": 3,
"first_name": "Lindsay",
"last_name": "van Schoor",
"attending": false
}
];
I've created an output in my view like this:
<div ng-repeat="person in people">
<a href="" ng-click="isAttending()">
{{person.first_name}}{{person.last_name}} - {{person.attending}}
</a>
</div>
Now the bit I'm stuck on. I want to be able to click on a person and update their attending field from false to true. I understand I can use ng-click like this
$scope.isAttending = function() {
alert("is attending");
};
but do not have a clue how to update the person Ive clicked to change false to true in the $scope.people.
When I have achieved this I plan to have another ng-repeat with a filter to show those attending but Ill be able to do that part I believe.
can anyone help me or give me some advise, anything will be much appreciated at the moment.
Thanks in advanced.
I wrote out an example of what I'm trying to do here:
http://jsbin.com/micasafetise/2/
http://jsbin.com/micasafetise/2/edit?html,js,console,output

You could just pass the repeated person <a href="" ng-click="isAttending(person)"> and have your handler method take that object, so you can update its property (well you could do that inline as well in the view, but it is better to separate out and place the logic in the controller as you originally had).
$scope.isAttending = function(person) {
person.attending = true;
};
Just add a filter as well on both the sections to show who are all attending and who are not.
<div ng-repeat="person in people | filter:{attending:false} track by person.personID">
<a href="" ng-click="toggleAttending(person)">
{{person.first_name}}{{person.last_name}} - {{person.attending}}
</a>
</div>
and
$scope.toggleAttending = function(person) {
person.attending = !person.attending;
};
Demo
In your case you could probably optimize better (you have 2 filters on same list of people) by filtering out from the controller and populate to 2 list of people people.invited and people.attendance.
function updatePeopleAttendance(){
$scope.people.attending = [];
$scope.people.invited = [];
angular.forEach(people, function(person){
$scope.people[person.attending ? 'attending' : 'invited'].push(person);
});
}
Demo
or even better you could pass the id, index and status in the method and pull the item out of one array and push it the other one.

Related

AngularJs - Select checkboxes by binding list of selected objects to list of all objects

I have searched for a solution to my problem but have not been able to find the right answer.
I have a list of all questions and a list of selected questions. I need to build a list of checkboxes from the list of all questions and check the ones that are in the list of selected questions. When changes are made to the checkboxes, I need the list of selected questions to be updated accordingly. The list of all questions never changes. How can I accomplish this? Here's a very abbreviated version of my situation:
var MyApp = angular.module('MyApp', []);
MyApp.controller('MyController', ['$scope', function ($scope) {
$scope.allQuestions = [
{ q_id: 1, q_txt: 'What time is it?', q_sort: 1},
{ q_id: 2, q_txt: 'What is that?', q_sort: 2},
{ q_id: 3, q_txt: 'Who are you?', q_sort: 4},
{ q_id: 4, q_txt: 'What color is that?', q_sort: 3}
];
$scope.selectedQuestions = [
{ q_id: 1, other_prop: 'yyy' },
{ q_id: 4, other_prop: 'zzz' },
];
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp" ng-controller="MyController">
<div ng-repeat="question in allQuestions"><input type="checkbox" ng-model="x" /> {{ question.q_txt }}</div>
</div>
In my application the lists come from a server and I would like to be able to return the selected questions list as an object back to the server when saving the changes.
I can't figure out the binding to accomplish this, or how to check the right checkboxes. Notice that the two lists are different, but bothe have the q_id value as the key to match them. Any help would be greatly appreciated.
It doesn't sound like you need to worry about which questions are selected until the user submits the form, so I'd just loop through the list of all questions at that time and create an array of the selected questions.
Something like this:
function getSelections() {
var selectedQuestions = []
$scope.allQuestions.forEach( function(question) {
if (question.selected) {
var questionCopy = angular.copy(question)
selectedQuestions.push(questionCopy)
}
})
return selectedQuestions
}
Then call this function before you post and pass the results to your API.
You should just be able to use ng-model to handle preselecting the checkboxes that should be preselected with:
<div ng-repeat="question in allQuestions"><input type="checkbox" ng-model="question.selected" /> {{ question.q_txt }}</div>
Something like this should do the trick. Using array.find() might be more efficient. Using a library like _ would make this easier.
$scope.selectedQuestions.map( function(selectedQuestion) {
var match = $scope.allQuestions.some( function(question) {
if (question.q_id == selectedQuestion.q_id) {
question.selected = true
return true
}
return false
}
}

Is it possible to filter JSON in Angular using multiple drop down menus

I have built an app for a client using my basic / beginner knowledge of Angular. It took me a lot longer than most would but it has been a great learning experience for me.
The app imports JSON data which displays products with each returned result linking to a page with more information.
The products are manually filtered i.e. I Have built pages for all possible filters (the long hard way). This is displayed as 4 initial options then you go to another page with another 4 options based off the previous option then a final page with 4 further options from here a list is returned based on the the previous selections. This way has led to a large collection of JSON files all based off the filter combinations available.
Background story aside my client now wants to be able to filter all data using drop downs on the opening page. Is this possible?
My JSON data looks like below as an example
{"Level":"student","Style":"barb","Handle":"left","ItemCode":"5.25: 606603, 5.75: 606607","title":"Jaguar Prestyle Relax Leftie","Size":"5.25, 5.75","Price":"£50.00","Description":"One micro-serrated to prevent hair bunching or slippage, these are a particularly good choice for barbers. These scissors are ergonomically designed to reduce strain on your hands and wrists, with an offset handle, finger rest and finger ring inserts for extra comfort. Made from stainless steel with a matt satin finish.","picture":"img/stubarbleft/1.jpg"}
What I would like to do is display the returned data which is not an issue I can do that part. At the top of the page I would like to have a set of dropdown filters so the top one filters the returned data but LEVEL then the second one by STYLE (keeping the level the same as selected) and third option by HANDLE (again keeping the previous options in mind)
Is this possible to do. I am out of my depth a bit but trying to learn as I go.
Thanks for your time guys
Sure, it's possible. I've prepared a self explanatory code snippet that should help you out.
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', ['$scope',
function($scope) {
$scope.model = {
selectedStyle: "",
selectedLevel: "",
filterObject: { style : "", level : ""},
recordDetails: undefined,
options: {
styles: ["", "style1", "style2"],
levels: ["", "level1", "level2"]
},
data: [{
"id": 1,
"level": "level1",
"style": "style1",
"price": 100
}, {
"id": 2,
"level": "level2",
"style": "style2",
"price": 200
}, {
"id": 3,
"level": "level1",
"style": "style2",
"price": 300
}, {
"id": 4,
"level": "level2",
"style": "style1",
"price": 400
}]
};
$scope.showDetails = function (record) {
$scope.model.recordDetails = record;
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<select name="styleSelect" id="styleSelect" ng-options="option for option in model.options.styles" ng-model="model.filterObject.style"></select>
<select name="levelSelect" id="levelSelect" ng-options="option for option in model.options.levels" ng-model="model.filterObject.level"></select>
Current filter object: {{model.filterObject}}
<div ng-repeat="record in model.data | filter:model.filterObject">
<a href="#" ng-click="showDetails(record)">
<ul>
<li>{{record.id}}</li>
<li>{{record.level}}</li>
<li>{{record.style}}</li>
</ul>
</a>
</div>
<div>
Record details:
{{model.recordDetails}}
</div>
</div>
Please let me know whether anything needs further explanation.

Filter collection by child

I have the following fictional object which I'm trying to filter:
{
"0":{
"boy":{
"age":"32",
"name":"Daniel Grey"
}
},
"1":{
"boy":{
"age":"23",
"name":"John Doe"
}
}
}
And then, the ng-repeat directive looks like this:
<ul>
<li ng-repeat="person in people">{{person.boy.name}}<li>
</ul>
My question is, how do I filter people by "name"? I've tried:
<input type="text" ng-model="name">
<ul>
<li ng-repeat="person in people | filter:name">{{person.boy.name}}<li>
</ul>
... but nothing happens [ ng-model seems disconnected from the view! ].
Any response is much appreciated!
Thank you!
Updated answer as per OP's updates
Looking at your fiddle, your $scope.people is essentially an array with one big JSON object, with multiple nested boy objects. This is hard to work with. If you have control over the construction of the JSON object, I will suggest converting into an array of multiple JSON objects, which may look something like:
$scope.people = [
{
"name":"Daniel Grey",
"age":"32",
"gender": "male"
},
{
"name":"John Doe",
"age":"23",
"gender": "male"
}
];
Notice how I converted the boy key into the gender attribute.
If you really have absolutely no control over the data structure, you may have to come up with a custom filter to parse through the nested structure.
Take a look at this fiddle. A few things to pay attention to:
I have to specify people[0] in ng-repeat to retrieve the one big JSON object in your array.
The custom nameFilter searches the .boy.name attribute only.
Original Answer
If you want your filter by just name, you will have to specify the specific attributes in your ng-model directive. So in your case, it will be
<input type="text" ng-model="search.boy.name">
<ul>
<li ng-repeat="person in people | filter:search">{{person.boy.name}}<li>
</ul>
But first you will need to fix your JSON object.
UPDATE:
Live demo on fiddle. I did notice that the search-by-name-only filter doesn't work with angularjs 1.2.1, but works with angularjs 1.2.2.

Push rows in a table rendered with ng-repeat in angular

I want to push in an extra row inline in a table when the client clicks on the row. The data should not be prefetched, since I expect there to be at most 30 rows but where each row has associated data that would be unreasonable to fetch in one get.
My approach so far is to use ng-repeat to iterate my collection and render a table. When the client presses the row, the client expects details about the row to be shown inline as an extra row under the pressed row.
<tr ng-repeat="court in courts">
<td>{{court.name}}</td>
<td>{{court.number}}</td>
<td>{{court.nrOfPlayers}}</td>
<td>
<a href ng:click="toggle(court.number)">Details</a> <!-- click toggles extra row with data loaded async -->
</td>
</tr>
<!-- extra row here -->
I have managed to show the details beneath the table with a ng-show in a hacky way, but that is not what I want.
How do you accomplish this with angular.js? What is the angular way to do this?
Here is a fiddle with a stupid squash court example http://jsfiddle.net/HByEv/
I think a possible solution could be http://jsfiddle.net/HByEv/2/.
There is also an alternative for the "No players" message commented in the fiddle if you want to also get rid of the extra <tr ng-show="..."></tr>.
Edit:
As pointed in the comments, in AngularJS 1.2+ you can now use ng-repeat-start and ng-repeat-end to solve this problem.
Jossef Harush provided a fiddle: http://jsfiddle.net/3yamebfw/
One sample
var app = angular.module('test-app', []);
app.controller('MyController', function($scope, $rootScope, $timeout){
$scope.copy = {
p1: ['c1 p1', 'c1 p2'],
p3: ['c3 p1', 'c3 p2', 'c3 p3', 'c3 p4', 'c3 p5']
}
$scope.courts = [
{
"number": 1,
"name": "the best court",
"nrOfPlayers": 2
}, {
"number": 2,
"name": "the bad court",
"nrOfPlayers": 0
}, {
"number": 3,
"name": "the other court",
"nrOfPlayers": 5
}
];
$scope.loadPlayers = function(court){
//Implement your logic here
//Probably using ajax
$timeout(function(){
$scope.players = $scope.copy['p' + court.number] || [];
}, Math.random() * 2000);
}
$scope.shouDetails = function(court){
if(court.nrOfPlayers) {
delete $scope.players;
$scope.loadPlayers(court);
} else {
$scope.players = [];
}
}
})
Demo: Fiddle
Well. In fact, the main issue with your design is that you want to show thousands of rows in the same table. It will work, but it might be hard to render in some browsers (IE). For each rows, you will have a few bindings and each binding add watchers. You should always try to minimize the amount of binding in the page. I suggest you to use a pagination system in your array. Pagination on a list using ng-repeat
If you really want to do what you want without prerendering the rows, you will have to edit the dom, which is not a good practice in angular when avoidable. In my case, i would place the data somewhere else on the page, in a static zone. When i did something like this, I added a twitter bootstrap modal in my page and when the user clicked on "more info" the modal was opened with the info of the selected object.

AngularJS filter on empty string

I have a simple Angular filter setup between a select and a list. When a user selects an item from the dropdown, the list is updated to show the matching result. The problem is that I have a "Select..." option first in the dropdown with no value, and when that is the selected value, all items are shown. I suppose that makes sense, but I want the opposite. If the selected option has no value, I don't want to show the list. Here are some relevant bits, followed by a link to a full fiddle:
The dropdown:
<select class="world-list" ng-model="selectedWorld" ng-options="world.worldId as world.worldName for world in allWorlds">
<option value="">Select...</option>
</select>
The list:
<ul class="unstyled" id="charList">
<li ng-repeat="char in characters | filter:selectedWorld">
{{char.charName}} - {{char.charRace}} {{char.charClass}}
</li>
</ul>
And here is a link to the full fiddle, which contains my JSON structures that drives all this: http://embed.plnkr.co/6XUmC5efO0Y1BRNLRUig
What I'm trying to do is rather simple, and I'm pretty new to Angular so I'm sure I'm missing something obvious. Checked the Jabbr room, nobody on. :(
Anyway, thanks for any and all help!
One way to accomplish this is to filter by specific properties of the iteration object:
<ul class="unstyled" id="charList">
<li ng-repeat="char in characters | filter:{worldId: selectedWorld}">
{{char.charName}} - {{char.charRace}} {{char.charClass}}
</li>
</ul>
Plunker
I noticed in your code "script.js" you don't seem to be accounting for the blank option.
I did not test this (I probably should prior to posing this answer), but it should work by placing the "blank" option within your script.js.
app.controller('WorldCtrl', function ($scope, $http) {
$scope.allWorlds = [{
worldId: "",
worldName: ""
},
{
worldId: "b8ee0530-744b-463e-9428-23178f6c7bff",
worldName: "World 1"
},
{
worldId: "81211982-5613-4f9c-b704-7b6fa35faf84",
worldName: "World 2"
},
{
worldId: "df41208e-e8d2-46c9-8299-8f37632a51f8",
worldName: "World 3"
}];
$scope.characters = [{
charClass: "Rogue",
charName: "Vaes",
charRace: "Human",
worldId: "b8ee0530-744b-463e-9428-23178f6c7bff"
},
{
charClass: "Warrior",
charName: "Azash",
charRace: "Orc",
worldId: "b8ee0530-744b-463e-9428-23178f6c7bff"
},
{
charClass: "Mage",
charName: "Anele",
charRace: "Ogre",
worldId: "81211982-5613-4f9c-b704-7b6fa35faf84"
}];
});

Resources