knockout condition in HTML page - javascript

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.

Related

Losing scope when using ng-include for recursion ( Updating object at run time, not reflecting the changes on child template) in angularjs

I'm using ng-include for recursion. it's loading correct at first time but when I change anything in the object at run time, it doesn't reflect the changes on html. ng-include will create a child scope for that, so its not getting changes from parent scope. How can I bind the scope or reflect the changes in the html? Below is the code snippet for main.html
<div ng-switch on="value.length>0">
<div ng-switch-when="true">
<div ng-init="item = value[0];" ng-include="'test/views/partialTemp.html'">
</div>
</div>
</div>
In Partial Template
<div class="row" ng-repeat="(key, result) in item" ng-if="item ">
.......
<div ng-switch on="result.length > 0 ">
<div ng-repeat="tempValue in result">
<div ng-switch-when="true">
<div ng-init="item = tempValue;" ng-include="'test/views/partialTemp.html'"></div>
</div>
</div>
</div>
....
</div>
In JS File
onRadioButtonChange = function(key, value, item) {
var self = this;
self.serviceObj.Response.values = self.updateServiceObject(self.serviceObj.Response.values, key, value, false); // Updating the actual object which came from service
self.objectToShow = JSON.parse(JSON.stringify(self.serviceObj)); // Taking the actual object which will show in the HTML
self.objectToShow.Response.values = self.filterObjectToShow(self.objectToShow.Response.values); // it'll add/remove the child radio button
}
objectToShow is the object which I'm using to render the html

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

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>

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>

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>

Knockout.js using open ended tags in if statement

I have the following code...
<div data-bind="foreach:profiles">
<!-- ko if: $index() % 3 === 3 -->
<div class="form-group">
<!-- /ko -->
<div class="col-xs-3 col-md-1 no-padding-left">
<img class="img-circle text-center" src="\Content\images\fake_profile_pics\md.png" alt=".." style="opacity: 1.9" />
<h5 class="text-center"><span data-bind="text:Points"></span><span> points</span></h5>
</div>
<div class="col-xs-9 col-md-3">
<span class="display-block" data-bind="text:Division"></span>
<span class="display-block"><span data-bind="text:NominationsWritten"></span><span> Nominations Written</span></span>
</div>
<!-- ko if: $index() % 3 === 1 -->
</div>
<!-- /ko -->
</div>
I am trying to create a row of objects, 3 objects wide, that all start at the height of the lowest element of the row above it. The problem with this code is that knockout cannot find an end tag to the first div, and breaks without errors. If you place an end tag within the first 'ko if' statement everything works fine.
Is this possible to do using the knockout commenting method, or is there a more accepted way of tackling this problem with knockout?
You don't show your view model, but for this kind of problem, I think it is much easier to use a computed property in your view model and bind to that rather than mix a lot of view model logic into your view. For example, if you have this:
function ViewModel() {
var self = this;
self.someStuff = ko.observableArray();
//... other props
}
I'd just add a computed property like this:
function ViewModel() {
var self = this;
var numCols = 3;
self.someStuff = ko.observableArray();
self.columns = ko.computed(function () {
var source = self.someStuff();
if (source && source.length) {
var cols = [];
for (var i=0; i < source.length; i+=numCols) {
cols.push(source.slice(i,numCols));
}
return cols;
}
});
//... other props
}
Note: you might be able to come up with better ways to partition the array.
Then you can just bind it like:
<div data-bind="foreach:columns">
<div class="form-group" data-bind="foreach:$data>
<div>
<!-- bind whatever properties you want here -->
</div>
</div>
</div>
And you keep that ugly logic out of your view. And you computed property will be reevaluated any time your observable array changes.
Rather than the inline if why not always render the wrapping div with the conditional class?
<div data-bind="css: { 'form-group': $index() % 3 === 3 }">
...
</div>

Categories