Trigger ng-click inside ng-repeat when ng-repeat item.length=1 - javascript

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>

Related

Hide elements in nested ng-repeat

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

Directive take other directive's data after deletion

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>

knockout condition in HTML page

Below is my HTML binding to display the records. I, have apply knockout js to perform the condition check as you can see the IF statement.
I want to use count++ as a knockout variable and perform the condition check.
The above code also not working for me.
Please can anyone let me know how to check the condition in knockout.
<div data-bind="foreach: GuidelinesQuestionList" id="problemcollapse">
<div data-bind="foreach: $data.SectionsSet">
<div class="primaryCaseContainer">
<div class="questionHeader" data-bind="text: $data.Heading , attr:{onClick: 'variableName.CollapseExpandCustom.ToggleSection(\''+$data.Uid.replace(/[^\w\s]/gi, '')+'\')'}"></div>
<div data-bind="attr: {id: $data.Uid.replace(/[^\w\s]/gi, '')}">
#{int count = 0;}
<div class="questionContainer" data-bind="foreach: $data.ProblemsSet">
<div data-bind="if: $data.Identified">
#{count++;}
<div>
<br><br>
<div data-bind="attr: {id: 'goalsReplaceDiv'+$data.Uid.replace(/[^\w\s]/gi, '')}"></div>
</div>
</div>
</div>
#if (count == 0)
{
<div id="divNoRecordsMessage">
<div>No Records !! </div>
</div>
}
</div>
</div>
</div>
</div>
You are mixing C# razor code with Knockout bindings. The count variable will not increment in your loop because it's evaluated before being returned to the client. Your loop is being rendered on the client.
Instead of doing that, make your divNoRecordsMessage show/hide based on a KO binding.
Something like this:
<div data-bind="visible: conditionForNoRecords">
No Records
</div>
But you should really make a custom filter for the ProblemSet array, something like this:
self.filteredProblemsSets = ko.computed(function() {
return ko.utils.arrayFilter(this.ProblemsSet(), function(item) {
return item.Identified;
});
}, viewModel);
You could then skip your if condition in the view and you would be able to easily display "No messages" when the array is empty.

ng-repeat: showing an item on click and hiding the others

I have a ng-repeat which display a list of divs and when I click on one it shows an aditionnal div for the clicked item.
This is working
<div ng-repeat="item in items">
<div ng-click="showfull = !showfull">
<div>
<h1>{{item.title}}</h1>
<p>{{item.content}}</p>
</div>
</div>
<div ng-show="showfull">
<p>{{item.info}}</p>
</div>
<hr/>
</div>
My items are loaded from a json containing a list of item and each item have a default attribut showfull set to false in this json. This is working, but now I want to hide all others item in the list when an item is clicked. I've tryied something like this :
This is not working
<div ng-repeat="item in items">
<div ng-click="expand(item)">
<div>
<h1>{{item.title}}</h1>
<p>{{item.content}}</p>
</div>
</div>
<div ng-show="showfull">
<p>{{item.info}}</p>
</div>
<hr/>
</div>
and in the controller I've added a function :
$scope.expand = function(e) {
e.showfull = !e.showfull;
//TODO: put here a foreach logic to hide all other items
}
But even without the foreach logic this is not working, the item don't show the additionnal div when clicked. I have two question :
I suppose this is due to item being "passed by copy" in the expand function or some kind of scope isolation issue but can you explain me why in detail ?
SOLVED
How can I hide all the additional div of other items when I click an item and show the additionnal content for this item ? Do I need to do a directive ?
NOT SOLVED
Thanks
I think you're on the right track. You need to set the showfull on the item and then use ng-show or ng-if to hide it, or as Gavin mentioned, use a class.
On ng-click you can call your expand function, where you pass the item, toggle it and set all others to hidden.
Template:
<div ng-repeat="item in items>
<div ng-click="expand(item)">
<div>
<h1>{{item.title}}</h1>
<p>{{item.content}}</p>
</div>
</div>
<div ng-show="item.showfull">
<p>{{item.info}}</p>
</div>
<hr/>
</div>
Controller:
$scope.expand = function (item) {
angular.forEach($scope.items, function (currentItem) {
currentItem.showfull = currentItem === item && !currentItem.showfull;
});
};
Your expand method is modifying the item, so your ng-show needs to reference the item:
<div ng-show="item.showfull">
<p>{{item.info}}</p>
</div>
To hide all of your items you need to do something like
$scope.expand = function(item) {
angular.forEach(items, function(i) {
if (i === item) {
i.showfull = !i.showfull;
} else {
i.showfull = false;
}
});
};
Shouldn't the second div you want to show be referencing the item?
<div ng-repeat="item in items>
<div ng-click="expand(item)">
<div>
<h1>{{item.title}}</h1>
<p>{{item.content}}</p>
</div>
</div>
<!-- Added item to ng-show -->
<div ng-show="item.showfull">
<p>{{item.info}}</p>
</div>
<hr/>
</div>

trigger ng-click by element id?

Ok i have a ng-repeat iterating over a array passing the $index to a ng-click inside the ng repeat like so:
<div class="animate-repeat row" ng-repeat="items in sortedTypes">
<div class="allinfo" id="idwrap-{{items.id}}" ng-click="shohidifelse($index)">
<div class="allinfo" id="idwrap-{{items.id}}" ng-click="shohidifelse($index)">
<div class="groupcontent row" ng-hide="showhideinfo[$index]">
<div class="large-2 columns">
<p>{{items.phoneTwo}}</p>
</div>
</div>
<div class="row fullprofile" ng-show="showhideinfo[$index]">
<p>{{items.ContactFirst}}</p>
</div>
</div>
</div>
I need to call a click on this
<div class="allinfo" id="idwrap-{{items.id}}" ng-click="shohidifelse($index)">
programatically. i am scroll to this item by the {{items.id}} and i need to call this ng-click from the scroll function.
Is there a clean way to do this, that I've just missed?
answered my own question!
jQuery("#accntselect").on("change", function(e) {
if (!e.added){
}else{
angular.element('#idwrap-'+e.added.id).triggerHandler('click');
jQuery('#idwrap-'+e.added.id).trigger('click').trigger('click');
scrollToAnchor('idwrap-'+e.added.id);
}
});
Where e.added.id is the rest of the element's id!
Try this:
Call the function shohidifelse(), from the jquery scroll function. If you need the $index value to be passed on to shohidifelse(), then pass it on to the jquery function and pass it as a parameter.

Categories