Adjust ng-repeat to match object structure - javascript

I have an object which looks like this:
$scope.hobbies = {
list: [
{
"PersonId": 23,
"PersonName": "John Smith",
"Hobbies": [
{
"HobbyTitle": "Paragliding",
"HobbyId": 23
},
{
"HobbyTitle": "Sharking",
"HobbyId": 99
}
]
}
]
};
I'm trying to develop a view which allows users to make a selection of each person's hobby.
I have a plunker here
My problem is that all selected hobbies are displayed under every person. This is because I'm just pushing all selected hobbies to a selectedHobbies Array.
$scope.addHobbyItem = function (item) {
var index = $scope.selectedHobbies.list.indexOf(item);
if (index === -1) {
$scope.selectedHobbies.list.push(item);
}
};
This of course doesn't work, as once a hobby is selected, it is shown under every person. How could I adjust the code to work with the way I'm ng-repeating over the selectedHobbies?
The HTML is below. I'm also using a directive to listen to click on the hobby container and trigger addHobbyItem()
<div data-ng-repeat="personHobby in hobbies.list">
<div>
<div style="border: 1px solid black; margin: 10px 0 10px">
<strong>{{ personHobby.PersonName }}</strong>
</div>
</div>
<div class="">
<div class="row">
<div class="col-xs-6">
<div data-ng-repeat="hobby in personHobby.Hobbies" data-ng-if="!hobby.selected">
<div data-hobby-item="" data-selected-list="false" data-ng-class="{ selected : hobby.selected }"></div>
</div>
</div>
<div class="col-xs-6">
<div data-ng-repeat="hobby in selectedHobbies.list">
<div data-hobby-item="" data-selected-list="true"></div>
</div>
</div>
</div>
</div>
</div>

your selectedHobbies should be a map in which the key is the person id and the value is a list of his selected hobbies. checkout this plunker
$scope.selectedHobbies = {
map: {}
};
// Add a hobby to our selected items
$scope.addHobbyItem = function(pid, item) {
if(!$scope.selectedHobbies.map[pid]) {
$scope.selectedHobbies.map[pid] = [];
}
var index = $scope.selectedHobbies.map[pid].indexOf(item);
if (index === -1) {
$scope.selectedHobbies.map[pid].push(item);
}
};
in the directive call addHobbyItem with the person id
scope.addHobbyItem(scope.personHobby.PersonId, scope.hobby);
and lastly in you html iterate on each person's selected hobbies
<div data-ng-repeat="hobby in selectedHobbies.map[personHobby.PersonId]">
<div data-hobby-item="" class="add-remove-container--offence" data-selected-list="true"></div>
</div>

something like this:
http://plnkr.co/edit/7BtzfCQNTCb9yYkv1uPN?p=preview
I used the $parent.$index to create an array of arrays containing hobbies for each person.
$scope.addHobbyItem = function (item, index) {
var ine = $scope.selectedHobbies[index].indexOf(item);
if (ine === -1) {
$scope.selectedHobbies[index].push(item);
}
};
function hobbyClickEvent() {
if (!$(element).hasClass('selected')) {
scope.addHobbyItem(scope.hobby, scope.$parent.$index);
} else {
scope.removeHobbyItem(scope.hobby);
}
}
and in the HTML:
<div data-ng-repeat="hobby in selectedHobbies[$index]">
<div data-hobby-item="" class="add-remove-container--offence" data-selected-list="true"></div>
</div>

Related

Implement two conditions in the presentation of data

I created an image gallery. This gallery may or may not have several types `(A, B ...).
To present the types I am missing the following array: Cats = ["A", "B"].
The images only appear when this array is filled, that is, the size is different from 0 or undefined.
How can I display images, usually, when the size of the Cat array is 0 or undefined?
When the cat array has these conditions, the images appear normally, when the array is different from undefined or greater than 0, it presents the images separated by types as shown in the figure.
Is there a way to implement this without creating two "html"? Can someone help me?
<div style="margin-left: 16px; margin-right: 16px;" class="first" *ngIf="Cats != undefined">
<div *ngFor="let cat of Cats">
<div *ngIf="counts[cat]">
<div class="row">
<span class="nomeCategoria">{{cat}}</span>
</div>
<ul class="mdc-image-list my-image-list">
<ng-container *ngFor="let it of items">
<li class="mdc-image-list__item" *ngIf="it.Cat == cat">
<div class="mdc-image-list__image-aspect-container">
<ng-container *ngIf="it.image == null; else itImage">
<img src="./assets/image-not-found.svg" class="mdc-image-list__image imagenotfound">
</ng-container>
<ng-template #itImage>
<img [src]="it.image" class="mdc-image-list__image">
</ng-template>
</div>
</li>
</ng-container>
</ul>
</div>
</div>
</div>
I'll check if Cats array is empty and if it's then I'll populate an array with all items Cat. Something like this:
export class AppComponent {
ngOnInit(){
this.checkCatsArray()
}
Cats=[]
items=[
{
ID:1,
Cat:"A",
image:"https://material-components-web.appspot.com/images/photos/2x3/3.jpg",
},
{
ID:2,
Cat:"B",
image:"https://material-components-web.appspot.com/images/photos/3x2/10.jpg",
},
{
ID:3,
Cat:"M",
image:"https://material-components-web.appspot.com/images/photos/2x3/6.jpg",
},
]
get counts() {
return this.items.reduce((obj, value) => {
if (value.Cat in obj) {
obj[value.Cat]++;
} else {
obj[value.Cat] = 1;
}
return obj;
}, {});
}
checkCatsArray() {
if (this.Cats.length == 0) {
for (let cat of this.items) {
this.Cats.push(cat.Cat)
}
}
}
}
In this scenario, I didn't touch HTML and I got the desired result - show every Cat if Cats array is empty. Maybe you need to configure checkCatsArray() at your own but I believe that this is something that you are looking for.
Hope that will help!

Angular on click event for multiple items

What I am trying to do:
I am trying to have collapsible accordion style items on a page which will expand and collapse on a click event. They will expand when a certain class is added collapsible-panel--expanded.
How I am trying to achieve it:
On each of the items I have set a click event like so:
<div (click)="toggleClass()" [class.collapsible-panel--expanded]="expanded" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
<div (click)="toggleClass()" [class.collapsible-panel--expanded]="expanded" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
and in the function toggleClass() I have the following:
expanded = false;
toggleClass() {
this.expanded = !this.expanded;
console.log(this.expanded)
}
The issue im facing:
When I have multiple of this on the same page and I click one, they all seem to expand.
I cannot seen to get one to expand.
Edit:
The amount of collapsible links will be dynamic and will change as they are generated and pulled from the database. It could be one link today but 30 tomorrow etc... so having set variable names like expanded 1 or expanded 2 will not be viable
Edit 2:
Ok, so the full code for the click handler is like so:
toggleClass(event) {
event.stopPropagation();
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
console.log("contains class, remove it")
} else {
event.target.classList.add(className);
console.log("Does not contain class, add it")
}
}
and the code in the HTML is like so:
<div (click)="toggleClass($event)" class="collapsible-panel" *ngFor="let category of categories" >
<h3 class="collapsible-panel__title">{{ category }}</h3>
<ul class="button-list button-list--small collapsible-panel__content">
<div *ngFor="let resource of resources | resInCat : category">
<span class="underline display-block margin-bottom">{{ resource.fields.title }}</span><span class="secondary" *ngIf="resource.fields.description display-block">{{ resource.fields.description }}</span>
</div>
</ul>
</div>
you could apply your class through javascript
<div (click)="handleClick($event)">
some content
</div>
then your handler
handleClick(event) {
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
} else {
event.target.classList.add(className);
}
}
In plain html and js it could be done like this
function handleClick(event) {
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
} else {
event.target.classList.add(className);
}
console.log(event.target.classList.value);
}
<div onclick="handleClick(event)">
some content
</div>
Try to pass unique Id. (little modification)Ex: -
in component.ts file:
selectedFeature: any;
categories:any[] = [
{
id: "collapseOne",
heading_id: "headingOne",
},
{
id: "collapseTwo",
heading_id: "headingTwo",
},
{
id: "collapseThree",
heading_id: "headingThree",
}
];
toggleClass(category) {
this.selectedFeature = category;
};
ngOnInit() {
this.selectedFeature = categories[0]
}
in html:-
<div class="collapsible-panel" *ngFor="let category of categories">
<!-- here you can check the condition and use it:-
ex:
<h4 class="heading" [ngClass]="{'active': selectedFeature.id==category.id}" (click)="toggleClass(category)">
<p class="your choice" *ngIf="selectedFeature.id==category.id" innerHtml={{category.heading}}></p>
enter code here
-->
.....
</div>
Try maintaining an array of expanded items.
expanded = []; // take array of boolean
toggleClass(id: number) {
this.expanded[i] = !this.expanded[i];
console.log(this.expanded[i]);
}
Your solution will be the usage of template local variables:
see this: https://stackoverflow.com/a/38582320/3634274
You are using the same property expanded to toggle for all the divs, so when you set to true for one div, it sets it true for all the divs.
Try setting different properties like this:
<div (click)="toggleClass("1")" [class.collapsible-panel--expanded]="expanded1" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
<div (click)="toggleClass("2")" [class.collapsible-panel--expanded]="expanded2" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
TS:
expanded1 = false;
expanded2 = false;
toggleClass(number:any) {
this["expanded" + number] = !this["expanded" + number];
console.log(this["expanded" + number])
}

AngularJS: Update all objects in $scope simultaneously

Say I have an object stored in $scope like so:
$scope.todo = [
{
"title" : "Groceries",
"todoItems" : [
{
"title" : "Milk",
"status" : "Not Done"
},
{
"title" : "Eggs",
"status" : "Not Done"
},
{
"title" : "Bread",
"status" : "Done"
}
]
},
{
"title" : "Medical",
"todoItems" : [
{
"title" : "Make eye doctor appointment",
"status" : "Not Done"
},
{
"title" : "Go to pharmacy",
"status" : "Not Done"
},
{
"title" : "Take vitamins",
"status" : "Done"
}
]
}
];
I am creating a feature that allows inline editing of each todo item, like so:
I achieve this by toggling a property on the todo list item called editMode. See lines 11-14 in the following code block:
<div ng-app="myApp">
<div ng-controller="dashBoard">
<div class="panel panel-default list-[(listID)]" ng-repeat="(listID, todoList) in todo" ng-cloak>
<div class="panel-heading">[( todoList.title )]</div>
<ul class="list-group">
<li ng-repeat="(itemID, todoItem) in todoList.todoItems" data-as-sortable="board.dragControlListeners" data-ng-model="items" class="status-[(todoItem.status)] todo-item todo-item-[(itemID)]" data-as-sortable-item>
<div class="input-group">
<span data-as-sortable-item-handle class="input-group-addon">
<input ng-click="toggleStatus(listID, itemID, todoItem.status)" type="checkbox" ng-checked="todoItem.status == 1">
</span>
<span ng-if="!todoItem.editMode" class="todo-item-label-wrapper">
<div ng-click="toggleEditMode(listID, itemID, 1)" class="todo-item-label">[(todoItem.value)]</div>
</span>
<span ng-if="todoItem.editMode" class="todo-input-wrapper">
<input show-focus="todoItem.editMode" ng-keyup="$event.keyCode == 13 && toggleEditMode(listID, itemID, 0)" type="text" ng-model="todoItem.value" class="form-control">
</span>
</div>
</li>
</ul>
</div>
</div>
</div>
When any given todo item is clicked, it goes into edit mode. The todo item stays in edit mode until the user hits enter. I'd like to make it impossible to have multiple todo items in edit mode at the same time. If you click on todo item "foo" and then click on todo item "bar", todo item "foo" should switch back to read-only mode.
I am currently achieving this by individually switching every todo item with angular.forEach(), e.g.:
$scope.toggleEditMode = function(listID, itemID, editMode) {
$scope.todo[listID].todoItems[itemID].editMode = editMode;
//Turn off edit mode on every todo item other than the one that was just clicked
angular.forEach($scope.todo[listID].todoItems, function(todoItem, foreignItemID) {
if (foreignItemID !== itemID) {
$scope.todo[listID].todoItems[foreignItemID].editMode = 0;
}
});
}
But I wonder if angular has some utility for this usecase that I should be using.
What I do in such a case is not having an editMode property on each item, but instead using a scope variable like $scope.currentEditItemId. Then you do something like this:
$scope.toggleEditMode = function (listID, itemID, enableEdit) {
if (enableEdit === 1) {
$scope.currentEditItemId = itemId;
// ... whatever you need to do here
}
}
And the HTML would look like this:
<span ng-if="itemId != currentEditItemId" class="todo-item-label-wrapper">
<div ng-click="toggleEditMode(listID, itemID, 1)" class="todo-item-label">[(todoItem.value)]</div>
</span>
<span ng-if="itemId == currentEditItemId" class="todo-input-wrapper">
<input show-focus="todoItem.id == currentEditItemId" ng-keyup="$event.keyCode == 13 && toggleEditMode(listID, itemID, 0)" type="text" ng-model="todoItem.value" class="form-control">
</span>
Revisiting this a bit, I realize one way you could update everything in the scope simultaneously is to instantiate each item in $scope through a constructor, and then update the constructor prototype. This doesn't really solve for the original use case I posed above (which is perhaps better stated as "update all items in scope except one") but I think it still has some useful applications.
So, if you want to have an item which, when clicked, updates lots of other items, you could do something like this:
HTML:
<div ng-app="myApp" ng-controller="toDo">
<div ng-click="toggleEdit(index)" ng-class="{{item.editable}}" ng-repeat="(index, item) in items"> {{item.title}} </div>
</div>
JS:
var app = angular.module('myApp', []);
function newItem(title) {
this.title = title;
}
newItem.prototype.editable = 'foo';
function toggleAll() {
newItem.prototype.editable = 'bar';
}
app.controller('toDo', function($scope) {
$scope.items = []
for (var i = 0; i <= 10; i++) {
var item = new newItem("item" + i);
$scope.items.push(item);
}
$scope.toggleEdit = function(index) {
toggleAll();
}
});
Result:
Here we see the class foo toggle to the class bar on all items when any given item is clicked:

Dynamic ng-switch inside of ng-repeat

I am trying to create a switch based on a dynamic array of objects...
For example:
<div ng-switch on="currentItem">
<div ng-repeat="item in myItems" ng-switch-when="item.name">
<p>{{item.name}}</p>
<button ng-click="nextItem(item)">Next Item</button>
</div>
</div>
And then in my controller...
$scope.myItems = [{
"name": "one"
}, {
"name": "two"
}]
// Default first item
$scope.currentItem = $scope.myItems[0].name;
$scope.nextItem = function(med) {
for (var i = 0; i < $scope.myItems.length; i++) {
if ($scope.currentItem === $scope.myItems[i].name) {
if ($scope.myItems[i + 1] !== undefined) {
$scope.currentItem = $scope.myItems[i + 1].name
}
}
}
}
Basically, the dom should render a div for each of the items, and when a user clicks the Next Item button, currentItem should be updated, and the switch should trigger based on that.
I am not seeing the first result as I should (nothing is being rendered). Any help would be greatly appreciated.
Plunk: http://plnkr.co/edit/PF9nncd1cJUNAjuAWK22?p=preview
I have forked your plunkr: http://plnkr.co/edit/A9BPFAVRSHuWlmbV7HtP?p=preview
Basically you where not using ngSwitch in a good way.
Just use ngIf:
<div ng-repeat="item in myItems">
<div ng-if="currentItem == item.name">
<p>{{item.name}}</p>
<button ng-click="nextItem(item)">Next Item</button>
</div>
</div>
I've forked your plunkr: http://plnkr.co/edit/2doEyvdiFrV74UXqAPZu?p=preview
Similar to Ignacio Villaverde, but I updated the way your getting the nextItem().
$scope.nextItem = function() {
var next = $scope.myItems[$scope.myItems.indexOf($scope.currentItem) + 1];
if(next) {
$scope.currentItem = next;
}
}
And you should probably keep a reference in currentItem to the entire object, not just the name:
<div ng-repeat="item in myItems">
<div ng-if="item == currentItem">
<p>{{item.name}}</p>
<button ng-click="nextItem(item)">Next Item</button>
</div>
Much simpler!

WinJs List View display:none tile with ID

So what's the problem. I want to exclude item from WinJS List View with specific parameter (ID - passed from JSON). How to do that?
Things i've tried:
a) Before pushing data to someView.itemDataSource process it with this function (It work's, but looks dirty).
fldView.itemDataSource = this._processItemData(Data.items.dataSource);
....
_processItemData: function (data) {
for (var i = data.list.length; i >= 1; i--) {
if (data.list._groupedItems[i]) {
if (data.list._groupedItems[i].groupKey == 'Folders')
continue;
else {
if (data.list._groupedItems[i].data.folderID) {
data.list.splice(i - 1, 1);
}
}
}
}
return data;
}
b) The traditional way with two conditional templates (Doesn't work):
fldView.itemTemplate = this.getItemTemplate;
....
getItemTemplate: function(promise){
return promise.then(function(item){
var
itemTemplate = null,
parent = document.createElement("div");
if(item.data.folderID){
itemTemplate = document.querySelector('.hideItemTemplate')
}else{
itemTemplate = document.querySelector('.itemTemplate')
}
//console.log(item.data.folderID);
itemTemplate.winControl.render(item.data, parent);
return parent;
})
}
2 HTML templates
<div class="itemTemplate" data-win-control="WinJS.Binding.Template">
<div class="item">
<img class="item-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
<div class="item-overlay">
<h4 class="item-title" data-win-bind="textContent: title" style="margin-left: 0px;"></h4>
<h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle" style="margin-left: 0px; margin-right: 4.67px;"></h6>
</div>
</div>
</div>
<div class="hideItemTemplate" data-win-control="WinJS.Binding.Template">
<div class="display-none"></div>
</div>
and CSS display: none
.hideItemTemplate, .display-none{
display:none;
}
Thank's in advance!
Suggest to filter the item either before building the WinJS.Binding.List using array.filter or do a filter projection on the list after it is built. if grouping is required, grouping can be done on the filtered list.
var list; // assuming this is all data items
var filteredList = list.createFiltered(function filter(item)
{
if (item.FolderID)
return false;
else
return true;
});
var groups = filteredList.createGrouped(...);

Categories