I'm trying to make a nested list of items where user can hide either a certain item or a group of nested items. So far, I'm making use of $indexand I've got this:
<div ng-controller="ItemCtrl">
<div ng-repeat="x in xs" ng-hide="hidden == $index">
<span>{{ x.name }}</span>
<button ng-click="hide($index)">Collapse</button>
<div ng-repeat="y in x.ys" ng-hide="hidden == [x.$index, $index]">
<span>{{ y.name }}</span>
<button ng-click="hide([x.$index, $index])">Collapse</button>
<div ng-repeat="z in y.zs" ng-hide="hidden == [x.$index, y.$index, $index]">
<span>{{ z.name }}</span>
<button ng-click="hide([x.$index, y.$index, $index])">Collapse</button>
</div>
</div>
</div>
</div>
With this controller:
angular.module("app", [])
.controller("ItemCtrl", function($scope) {
$scope.xs = [...]; // My data here
$scope.hidden = -1; // Nothing hidden yet
$scope.hide = function(item) {
$scope.hidden = item;
};
});
It does work. The downside is, there will be too many $index to mantain while the nested list is going deeper. Plus, I have to write all the conditional on every nest level.
My question is, is there any alternative which is simpler, more reliable, and if possible, will generate automatically no matter how many nested item that I have?
please check ui-tree, I think it's what you looking for.
github ui-tree
and the demo
demo
Related
I have some angular code that is supposed to assign a class to only the parent element of the link clicked. This seems to work initially, but then after clicking around a bit, the code seems to get stuck. Here is some sample code of what I'm working with:
<div ng-repeat="item in items track by item.id" class="row" ng-class="{'active': selectItem.this == item.id}">
<a ng-click="selectItem.this = item.id">Move to top</a> {{item.name}}
$scope.selectItem = { this: -1 };
http://plnkr.co/edit/jhahff7OyVTVt615BBp3?p=preview
Any help would be great!
You just need to make the whole text a clickable DOM
<div ng-repeat="item in items track by item.id" class="row" ng-class="{'active': selectItem.this == item.id}">
<a ng-click="selectItem.this = item.id">Move to top <span ng-bind="item.name"></span></a>
</div>
I replaced the a tag with p tag and bind an click event to the p tag. With this refactoring, I don't see any lagging or weird behaviour.
<body ng-controller="MainCtrl">
<div ng-repeat="item in items track by item.id" class="row"
ng-class="{'active': selectItem.this == item.id}">
<p style="cursor: pointer;" ng-click=selectedItem()>
Move to top {{item.name}}
</p>
</div>
</body>
In controller
$scope.selectItem = {};
$scope.selectedItem = function () {
$scope.selectItem.this = this.item.id;
};
Let me know if you still see any issues.
I want to trigger automatic loadProduct function when products.length==1
Bellow my angular code.
<div class="prodItem prodItem-nos-{{products.length}} prodItem-item-number-{{$index}}"
ng-repeat="product in products track by $index"
ng-click="loadProduct(product.id)"
uib-tooltip="{{ product.name }}">
<div class="prodMeta">
<div class="prodName" ng-bind="product.name"></div>
<div class="prodDescr" ng-bind="product.description"></div>
</div>
<div class="prodBuyNow">
<button ng-click="loadProduct(product.id)">Choose</button>
</div>
</div>
If you want to trigger function when item in scope changes you can use scope.$watch()
for example :
scope.$watch('products',function(oldValue,newValue){
if(newValue.length === 1){
executeFunction();
}
});
See :
https://docs.angularjs.org/api/ng/type/$rootScope.Scope
ng-click="if(products.length === 1){ loadProduct(product.id); }"
I don't like too much conditions inside ng-click. Being products a scope variable, you can just add this logic inside your loadProduct function, adding a condition at the beginning like
if($scope.products.length === 1)
and then execute your code.
If, you want to call loadProduct() is only one product is there call it in api which fetched the products
Eg:
$http.get("/api/products")
.then(function(response) {
$scope.products = response.data;
if($scope.products.length == 1)
{
$scope.loadProduct($scope.products[0].id)
}
});
you can use ng-init function
and check your prodt list in the function
<div ng-repeat="product in products track by $index" ng-init="yourFunctionName()">
<div class="prodMeta">
<div class="prodName" ng-bind="product.name"></div>
<div class="prodDescr" ng-bind="product.description"></div>
</div>
<div class="prodBuyNow"><button ng-click="loadProduct(product.id)">Choose</button></div>
</div>
</div>
Edit: Thanks to Simon Schüpbach, I was able to resolve the issue by changing the template. See the end for the solution.
Let's preface this by saying that we are beginner to soft-intermediate in Angular.
On one of our project, we are using angularjs 1.4.x and also ng-cart (https://github.com/snapjay/ngCart). It worked great but then we were confronted with a demand from our client that created new weird issues.
We added fsCounter, as a directive, to the cart page so user can add or remove items. This all work great but the users also have the option to delete an item from the cart view. Deletion works as expected BUT it seems to affect the scope to the item that takes it place.
Let me make it clearer :
Let's say we have 2 products in our cart page, it displays something like that
Product_1 {price} {counter} {total} delete_btn
Product_2 {price} {counter} {total} delete_btn
Each fsCounter is its own scope
return {
restrict: "A",
scope: {
value: "=value",
index: "=index"
},
link: //other logic
However when we delete the first item, visually and in the directives, the data seems to shift. So our second row will now inherit the first row's counter.
Directive's data looks like this:
Product_1:
itemId:3,
quantity:2,
{other data}
Product_2:
itemId:8,
quantity:5,
{other data}
But once we delete the first directive (We get the scope, remove the DOM element, destroy the scope) the second directive will now have this data:
Product_2:
itemId:3,
quantity:2,
{other data}
Here is the template code :
<div class="unItem" ng-repeat="item in ngCart.getCart().items track by $index">
<div class="photo"><img src="{[{ item.getImage() }]}" alt=""></div>
<div class="details">
<h3>{[{ item.getName() }]} <span>{[{ item.getPrice() | currency:$}]}</span></h3>
<md-select ng-model="attributes" placeholder="Attribut" class="select-attribut" ng-show="item.hasAttributes()" ng-change="item.updateSelected(attributes)">
<md-option ng-repeat="attr in item.getAttributes()" ng-selected="attr == item.getSelected()" ng-value="attr">{[{ attr }]}</md-option>
</md-select>
</div>
<div class="quantity">
<div fs-counter-dynamic value="itemQuantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="{[{ item.getId() }]}"
data-editable
ng-model="itemQuantity"
name="quantity" id="quantity-{[{ item.getId() }]}",
index="{[{ item.getId() }]}"
></div>
</div>
<div class="total">Total : {[{ item.getTotal() | currency }]}</div>
<div class="delete"><a ng-click="ngCart.removeItemById(item.getId());"></a></div>
</div>
Is this normal behavior? Is there any way to force the directive to keeps its own data? From what I've understood, each directive has its own scope, so what I think happens is that, when we remove the first one, it keeps the data stored in some kind of array that says "directive 1 data is : " and when we delete the first directive, the second one becomes the first.
So basically, are we doing anything wrong or is there anyway to remap the data?
Hope it was clear enough,
Thanks!
Edit: added html code
Edit2: Answer :
New FsCounter template looks like this:
<div fs-counter-dynamic value="item._quantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="{[{ item.getId() }]}"
data-editable
ng-model="item._quantity"
name="quantity" id="quantity{[{ item.getId() }]}"
></div>
Do you know ng-repeat, then you don't have such problems
<div ng-repeat="product in products">
<fs-counter index="product.index" value="product.value"></fs-counter>
</div>
and in your controller
$scope.products = [
{index:1, value:"Cola"},
{index:2,,value:"Fanta"}
]
to remove an element you just have to do
$scope.products.splice(0,1);
Edit:
I suggest to save all necessary data inside the item you use inside ng-repeat. Your problem is, that you mix data from array with other data from your $scope. It is possible to $watch changes in your directive, but if you set them with ng-repeat everything is done automatically.
$scope.products = [
{index:1, name:"Cola", price:1.50, image:"your/path.png", attributes:{...}},
{index:2, name:"Fanta", price:1.40, image:"your/path.png"}
]
And then in your html
<div class="unItem" ng-repeat="item in ngCart.products track by $index">
<div class="photo"><img ng-src="item.image" alt=""></div>
<div class="details">
<h3>{{item.name}} <span>{{item.price | currency}}</span></h3>
</div>
<div class="quantity">
<div fs-counter-dynamic value="item.quantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="item.index"
data-editable
ng-model="item.quantity"
name="quantity" id="{{'quantity-' + $index}}",
index="item.index"
></div>
</div>
<div class="total">Total : {{ item.price * item.quantity | currency }}</div>
<div class="delete"><a ng-click="ngCart.removeItemById(item.index);"></a></div>
</div>
I am displaying the ng-repeat content in two columns.
Using this code works fine:
<div class=storerow ng-repeat="store in stores track by $index" ng-if="$index%2==0">
<div ng-repeat="i in [$index,$index+1]" ng-if="stores[i]!=null" class="ngrepeatstore">
<div class="image-container" style="background-image: url({{stores[i].image}})" ng-click="tileClicked({{stores[i].id}})">
</div>
</div>
However, when I add a filter- it breaks the NG repeat and no content appears:
<div class=storerow ng-repeat="store in stores track by $index" ng-if="$index%2==0">
<div ng-repeat="i in [$index,$index+1] | filter: greaterThan('order', 0) | orderBy:'order'" ng-if="stores[i]!=null" class="ngrepeatstore">
<div class="image-container" style="background-image: url({{stores[i].image}})" ng-click="tileClicked({{stores[i].id}})">
</div>
</div>
the .js for greaterThan
$scope.greaterThan = function(prop, val){
return function(item){
return item[prop] > val;
}}
I tried adding the filter to the first ng-repeat- however that doesn't work as it just applies the filter to the overall content (ie if just one item is greatThan 0, it shows all items- not just the ones greater than 0).
This is because you're actually telling Angular to filter the property order on [$index, $index + 1], an array of two integers, which makes no sense.
I.E. With your delegate comparing index.prop > val, what you're really doing is comparing index['order'] > someValue.
This Plunker demonstrates: http://plnkr.co/edit/FkSrmZuuK4B1ToGNgAnq?p=preview You need to move filter:greaterThan(prop, val) up to the parent ng-repeat. Only there will your filter work.
<div ng-repeat="store in stores | filter:greaterThan2('id', 0) | orderBy:'name'" ng-if="$even">
{{store.name}}
<div ng-repeat="i in [$index, $index+1] | orderBy:angular.identity:true" ng-if="stores[i] !== null">
<strong>store.id</strong> {{i}}
</div>
</div>
My angular is 1.0.8-stable
My objective is to display data in rows of 3.
My html should look like
<div class="table-row">
<div class="item">item1</div>
<div class="item">item2</div>
<div class="item">item3</div>
</div>
My pseudo code is if the $index mod 3 == 0 then I will display <div class="table-row">.
if the $index mod 3 == 2, then I will display </div>.
So far, I have this.
<div ng-repeat='item in my_works.items'>
<!-- if index mod 3 == 0 show <div>-->
<!-- if index mod 3 == 2 show </div>-->
</div>
Perhaps I was thinking it in the wrong way as there may be a more efficient way to do this in angularjs?
You can use (key, value) in expression – where key and value can be any user defined identifiers, and expression is the scope expression giving the collection to enumerate.
You can try something like this
<div ng-repeat='(index, item) in my_works.items'>
<div class="table-row" ng-show="index % 3 == 0">
</div>
</div>
Reference
Since you can't use ng-if in 1.0.8 here are two variations that solve the problem. They both wrap 3 items into a table-row div
The outer loop counts off in groups of three, so it fires once per outer table-row div. The second approach uses a filter to control the loop range but it should have better performance. The first approach doesn't require a new filter.
Then the inner ng-repeat loops through the 3 items within a row. It uses slice to get just the 3 array items needed for that particular row.
Here's a fiddle with both of the variations working: http://jsfiddle.net/f8D8q/4/
Option 1: Solution without a new filter:
<div ng-repeat='itemtmp in items|limitTo:(items.length/3)+1'>
<div class="table-row">
<span ng-repeat='item in items.slice($index*3,($index*3+3))'>
{{item.name}} - {{$index}}
</span>
</div>
</div>
Option 2: More performant solution using range filter from http://www.yearofmoo.com/2012/10/more-angularjs-magic-to-supercharge-your-webapp.html#more-about-loops
<div ng-repeat="n in [] | range:(items.length/3)+1">
<div class="table-row">
<span ng-repeat='item in items.slice($index*3,($index*3+3))'>
{{item.name}} - {{$index}}
</span>
</div>
</div>
and the associated range filter:
app.filter('range', function () {
return function (input, total) {
total = parseInt(total);
for (var i = 0; i < total; i++) {
input.push(i);
}
return input;
};
});
Both are tested (down to 1.0.2) and work.
Convert your flat list of items to a list of lists of 3 items.
Iterate over the list of lists.
<div class="table-row" ng-repeat="list in listOfLists">
<div class="item" ng-repeat="item in list">
{{ item }}
</div>
</div>
If you already have a list, items, you could add a filter chunked and replace listOfLists with items|chunked:3, assuming you implement chunked something like this:
app.filter('chunked', function(){
return function(list, chunkSize){
var chunks = [];
angular.forEach(list, function(element, i){
if(i % chunkSize === 0){
currentChunk = [];
chunks.push(currentChunk);
}
currentChunk.push(element);
});
return chunks;
};
});
I think that matches what you are trying to do; here's a plunker: http://plnkr.co/edit/3h7JprbXFVwnNZErj7hl
I didn't get a chance to test with old Angular though.