I have a list of "assets" that I am displaying using foreach bind.
Each asset has a delete button which calls $parent.removeAsset, which all works fine.
However, I want to add the option to select several "assets" by checking a checkbox, and then remove all the "assets" that are checked.
I am still learning the ropes of knockoutjs so I would really appreciate any help.
Here is the code I am using to display the "assets
<div style="height: 100%; overflow: auto;" data-bind="foreach: assets">
<!-- AssetList AssetItem Tmpl BEGIN -->
<div class="asset-item action" data-tooltip="Select Asset">
<div class="asset-type" data-bind="css: type"> </div>
<div class="asset-select"><input type="checkbox" /></div>
<!-- ko if: type() === 'Text' -->
<div class="asset-name" data-bind="text: content"></div>
<!-- /ko -->
<!-- ko if: type() === 'Image' -->
<div class="asset-name">Image</div>
<!-- /ko -->
<!-- ko if: type() === 'Video' -->
<div class="asset-name">Video</div>
<!-- /ko -->
<div class="asset-remove-cell">
<div class="asset-remove action" data-tooltip="Remove Asset" data-bind="click: $parent.removeAsset"></div>
</div>
</div>
<!-- AssetList AssetItem Tmpl END -->
</div>
And this is my delete function:
self.removeAsset = function(asset){
if (!confirm("Are you sure you want to delete this asset?")) {
event.stopImmediatePropagation();
return false;
}
self.selectedIndex(0);
$.ajax({
url: "/Assets/delete/"+asset.id(),
type: "POST",
success: function(response) {
self.assets.remove(asset);
//notify('good',response);
}
});
};
As #Kenneth suggested I added a boolean to my assets and then looped through the observable array and deleted each asset that was set to true. Here is the code I used for anyone looking to do something similar:
The array:
function Asset() {
var self = this;
self.id = ko.observable("");
self.type = ko.observable("");
self.selected = ko.observable(false);
};
The delete checkbox:
<input type="checkbox" data-bind="checked: selected" />
The function called when the delete button is pressed:
$('#deleteMultipleAssets').click(function(){
if (confirm('Are you sure you want to delete the selected asset?')) {
ko.utils.arrayForEach(viewModel.assets(), function(asset) {
if(asset.selected()){
viewModel.removeMultipleAsset(asset);
}
});
}
});
The removeMultipleAssets function:
self.removeMultipleAsset = function(asset){
self.assets.remove(asset);
};
You should add a field to your assets called selected and then bind that field to the checkbox you're displaying inside the foreach-template.
When you click on the general delete-button, you can loop over your assets and delete all the ones that have their selected-field equal to true and remove them.
Related
I am trying to show and hide a div section based on the corresponding buttons that are created through foreach loop. At the moment, whenever I click the button it shows all div sections rather than the one the button is under. I am quite new to knockout and I have spent many hours trying different methods and tutorials to resolve this issue but still unsuccessful.
This is the view section:
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="firstDiv">
<!-- ko if: $root.filteredAvailabilities().length > 0 -->
<!-- ko foreach: $root.filteredAvailabilities -->
<div class="secondDiv">
<div class="thirdDiv">
<div class="fourthDiv">
<div class="fifthDiv">
<!-- ko with: Items -->
<div class="sixthDiv">
<!-- ko if: !$root.viewPrices() -->
<input class="actionButton" type="button" data-bind="upperCaseValue: $parents[1].ViewPrices, click: $root.ViewPrices" />
<!-- /ko -->
<!-- ko if: $root.viewPrices() -->
<input class="actionButton" type="button" data-bind="upperCaseValue: $parents[1].HidePrices, click: $root.HidePrices" />
<!-- /ko -->
</div>
<!-- /ko -->
</div>
<!-- ko if: $root.viewPrices() -->
<!-- ko foreach: Rooms -->
<div class="seventhRoomDiv">
<table class="roomPriceTable">
<tr>
<td><span class="roomPriceTableRoomLabel" data-bind="text: Room.Name"></span></td>
</tr>
</table>
</div>
<!-- /ko -->
<!-- /ko -->
</div>
</div>
<!-- ko if: $root.viewPrices() -->
<div class="eighthBottomDiv">
<input class="actionButton chooseRoomButton" type="button" data-bind="upperCaseValue: $parent.ChooseRoom, click: $root.ChooseRoom" />
</div>
<!-- /ko -->
</div>
<!-- /ko -->
<!-- /ko -->
</div>
In the view model all its doing is setting viewPrices to true:
/// <summary>
/// View the prices.
/// </summary>
self.ViewPrices = function ()
{
self.viewPrices(true);
};
I just want the corresponding seventhDivRoom to display after I click the button that's attached to it rather than displaying all.
Before expanding:
After expanding - Expanded all three rather than the second one only:
EDIT
I have tried using Rafael Companhoni example and apply it to my versions, but I am coming across some difficulties to display the div. I have added
self.ShowRoomPrice = ko.observable(false);
to the view model. Then added
availability.ShowRoomPrice = false;
to the availability callback which is similar to how you created the observable array. Furthermore I have added
self.Test = function (availability){
availability.ShowRoomPrice = !availability.ShowRoomPrice
model.availability(availability);
};
Finally the view looks like this
<!-- ko if: ShowRoomPrice === true -->
<input class="actionButton" type="button" data-bind="upperCaseValue: 'Show / Hide Prices', click: $root.ChooseHotel" />
<!-- /ko -->
It does change the state of ShowRoomPrice between true and false but the div does not appear. Is there something that's still missing?
You are using the property 'viewPrices' which is in the root view model to decide if the divs should be rendered. In the example below I've added a simple view model with an observableArray of FilteredAvailabilty objects. It demonstrates how you could use each sub-view model 'viewPrices' property in the 'if' binding:
var FilteredAvailability = function (viewPrices, items, rooms) {
var self = this;
self.viewPrices = ko.observable(viewPrices);
self.Items = ko.observableArray(items);
self.Rooms = ko.observableArray(rooms);
}
var ViewModel = function() {
var self = this;
// initialize with some data
self.filteredAvailabilities = ko.observableArray([
new FilteredAvailability(true, items, rooms),
new FilteredAvailability(true, items, rooms),
new FilteredAvailability(false, items, rooms),
]);
};
And then in the HTML
<div>
<!-- ko if: filteredAvailabilities().length > 0 -->
<!-- ko foreach: filteredAvailabilities -->
<div>
<!-- ko if: viewPrices -->
<div>Depends only on each the property 'viewPrices' of each item</div>
<!-- /ko -->
</div>
<!-- /ko -->
<!-- /ko -->
</div>
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>
I have a jquery knockout template which renders a foreach.
Inside each item, i am using the same function many times (for css binding and for visibility of other child elements)
Is it possible that instead of calling the same function many times for each item in the foreach, to temporary save it and then re-use it inside the template?
PS: i know i can set the visibility of the <i class="fa " /> tags using css selectors, but this doesn't answers the question.
<script type="text/html" id="properties-template">
<!-- ko foreach: Groups -->
<!-- saving the result in a variable instead of calling it so many times -->
<!-- var isValid = isGroupValid($data); !-->
<div class="group" data-bind="css: { 'valid': isGroupValid($data) }">
<div class="iconContainer">
<!-- ko if: $root.isGroupValid($data) === false -->
<i class="fa fa-square-o"></i>
<!-- /ko -->
<!-- ko if: $root.isGroupValid($data) === true -->
<i class="fa fa-check-square"></i>
<!-- /ko -->
</div>
</div>
<!-- /ko -->
</script>
As $data is itself the viewmodel, you could just have a computed observable there which does whatever logic isGroupValid currently does. So I assume your view model at the moment looks something like
function ViewModel(){
this.isGroupValid = function(data){
// some logic returning boolean
}
}
Here's a live example demonstrating the "before": http://jsfiddle.net/hbSj7/
change it to this:
function ViewModel(){
this.isGroupValid = ko.computed(function(){
// some logic returning boolean
// just use "this" where you used to use "data"
}, this);
}
Then just change your template to, eg/
<div class="group" data-bind="css: { 'valid': isGroupValid() }">
<div class="iconContainer">
<!-- ko if: !isGroupValid() -->
<i class="fa fa-square-o"></i>
<!-- /ko -->
<!-- ko if: isGroupValid() -->
<i class="fa fa-check-square"></i>
<!-- /ko -->
</div>
</div>
Here's a live example having changed to this method: http://jsfiddle.net/ug9ax/
The important difference is in your current method the function gets executed every time you call it, with the changed method the computed only gets re-evaluated if anything it depends on changes, like other observable properties in your viewmodel.
The goal
Switch between add/remove button based on availability using KnockoutJS.
The problem
I need to add a product/item to my summary. If the product is already on summary, then changes the "add button" into "remove button" or else changes from "remove button" to "add button"
Until here, just concept, right? Yea, but I think I'm missing the logic of the trouble.
Look:
<!-- ko foreach: products -->
<!-- ko if: isAdded -->
<button class="btn btn-small btn-success action remove">
<i class="icon-ok"></i>
</button>
<!-- /ko -->
<!-- ko ifnot: isAdded -->
<form action="#" data-bind="submit: add">
<button class="btn btn-small action add">
<i class="icon-plus"></i>
</button>
</form>
<!-- /ko -->
<!-- /ko -->
As you can see, there is conditions to check if a specific product is added or not. If the list is empty, nothing appears; If I add something manually by code, the two buttons appears — remove and add button.
I made this CodePen to simulate the scenario.
Can someone help me?
Some details
I can use jQuery; I'm already working on it for about three weeks and until now, no success.
In my opinion you don't take full advantage of knockoutjs (but that could just be because I don't see the full application). I however rewrote it a bit and put up an example here (jsfiddle).
function Product(name, desc) {
var self = this;
self.name = ko.observable(name);
self.desc = ko.observable(desc);
self.isAdded = ko.observable(false);
};
function SummaryViewModel(products) {
var self = this;
self.products = ko.observableArray(products);
self.add = function (item) {
var i = self.products.indexOf(item);
self.products()[i].isAdded(true);
};
self.remove = function (item) {
var i = self.products.indexOf(item);
self.products()[i].isAdded(false);
};
};
var p = [new Product("One", "Yep one"), new Product("Two", "nope, two")];
ko.applyBindings(new SummaryViewModel(p));
I'm trying to render a new div with the class row-fluid on every 4th object in my observableArray. Unfortunately putting html snippets into an if block doesn't seem to accomplish anything.
Is there another way to accomplish this?
<!-- ko foreach: detailsVm.addresses -->
<!-- ko if: $index() % 3 === 0 -->
<div class="row-fluid">
<!-- /ko -->
<div class="span4">
My Content
</div>
<!-- ko if: $index() % 3 === 0 -->
</div>
<!-- /ko -->
<!-- /ko -->
A bunch of (not awesome) ways to go with this one:
Normally, I would suggest mapping your array to a structure that is more suitable for binding in your view. So, you would map it to a row/cell structure. Then, you can easily loop through the rows/cells to produce your output. Like: http://jsfiddle.net/rniemeyer/EdXA2/
A little messier, but you could even do:
<div class="container" data-bind="foreach: items">
<!-- ko if: $index() % 3 === 0 -->
<div class="row-fluid">
<div class="span4" data-bind="text: name"></div>
<!-- ko with: $parent.items()[$index() + 1] -->
<div class="span4" data-bind="text: name"></div>
<!-- /ko -->
<!-- ko with: $parent.items()[$index() + 2] -->
<div class="span4" data-bind="text: name"></div>
<!-- /ko -->
</div>
<!-- /ko -->
</div>
Like: http://jsfiddle.net/rniemeyer/pfzpq/
One idea for a custom binding would be to create a binding that takes in a array and a number of columns and splits it into smaller arrays for you. Something like this: http://jsfiddle.net/rniemeyer/sHDnR/.
ko.bindingHandlers.arrayToRows = {
init: function(element, valueAccessor, all, vm, context) {
var rows = ko.computed(function() {
var options = ko.toJS(valueAccessor()),
data = options.data,
count = options.count,
rows = [], row;
for (var i = 0, j = data.length; i < j; i++) {
if (i % count === 0) {
if (row) {
rows.push(row);
}
row = [];
}
row.push(data[i]);
}
//push the final row
if (row) {
rows.push(row);
}
return rows;
}, null, { disposeWhenNodeIsRemoved: element });
ko.applyBindingsToNode(element, { foreach: rows });
return { controlsDescendantBindings: true };
}
};
I have one other thought where the custom binding would wrap the elements after the fact. Might give that a shot when I have more time.
I know you've fixed it, but a thought for the future: would it be harmful in your context to have that wrapper div always present, but only have a "fluid" class present on every 4th row? Because if not, even though I hate rendering needless markup, in iteration situations like this sometimes I find it easier to just use CSS. Something like:
.container .row:nth-child(4n) { Your fluid styles }