Knockout foreach dynamically set ID - javascript

I just started using knockout and having some problems using foreach.
I need to dynamically populate a list with "collapsing" div.
I cant figuer out how to set the
"data-target=""" with the according div id.Is there a kind of $index as in Angular.How do i declare it inside of the data-target?
Thank for the help.
<ul class="nav navbar-nav side-nav">
<li data-bind="foreach: { data: categories, as: 'category' }">
<div id="??" class="collapse">
<h1>Some text</h1>
</div>
</li>
</ul>

Do it within the data-bind:
<a href="javascript:;" data-toggle="collapse" data-bind="attr: { 'data-target': ... }">
<div class="collapse" data-bind="attr: { id: ... }">
Knockout does have a $index context property, too:
<div data-bind="attr: { id: 'foo' + $index() }">

What's data-target being used for? I don't think you not need that in the first place.
If you want to toggle a section, I recommend using a more natural way to solve this. In the context of knockout this means: write a binding handler that encapsulates the behavior you want.
Expressed in simplest terms: You want to click on something and it should toggle something else. For example the visibility of the following <h1>
The minimal thing to do would be: Toggle a CSS class and use that to influence visibility.
Here is a binding handler that switches a CSS class on and off. Together with the simplest CSS you get collapse/expand behavior.
ko.bindingHandlers.toggleClass = {
init: function (element, valueAccessor) {
ko.utils.registerEventHandler(element, 'click', function () {
var cssClass = valueAccessor(),
shouldHaveClass = element.className.indexOf(cssClass) < 0;
ko.utils.toggleDomNodeCssClass(element, cssClass, shouldHaveClass);
});
}
}
ko.applyBindings({
categories: [
{title: 'Category A'},
{title: 'Category B'},
{title: 'Category C'},
{title: 'Category D'}
]
});
.collapsed+.collapse {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: categories">
<li>
<div class="collapse">
<h1>Some text</h1>
</div>
</li>
</ul>
Of course you can use all kinds of more advanced techniques, but the principle would stay the same. Define a behavior, attach that behavior to an element via a binding handler.

Related

AngularJS: my Accordion expands, but does not collapse

I have an accordion object in AngularJS that is data driven. Here is what my html looks like:
HTML:
<div class="field-accordion" ng- if="field.fieldAccordion">
<ul class=a ccordion>
<li ng-repeat="fieldAccordion in field.fieldAccordion" ng- click="accordion.current = fieldAccordion.fieldName">
{{ fieldAccordion.fieldName }}
<ul ng-show="accordion.current == fieldAccordion.fieldName">
<li ng-repeat="fieldSub in fieldAccordion.fieldSub">
{{fieldSub.fieldName}}
</li>
</ul>
</li>
</ul>
</div>
Then in my JS file, I simply use it like this:
JS:
app.controller("myCtrl", function($scope) {
$scope.mySettings({
Header: '',
Title: '',
Img: '',
fieldAccordion: [{
// "this is my accordion list"
This works great for me. Whenever I click on the parent, it expands. The problem that I'm having is I can't get it to collapse. I am also trying to do it while keeping everything data driven like it is now. Is there anything I can change in my HTML to allow the text to collapse after expanding ?
Thank you
So the problem is because of a new scope being created, what you need to know is ng-if and ng-r creates a new scope, I checked your code and the ng-repeat is the root cause of the issue! hence the variable is not updating and the accordion is not toggling. Here is a simple way to update the parent controller using the $parent.
I have created a mockup highlighting the issue. So instead of updating accordion.current which will update the scope created by the ng-if you can just give it as $parent.accordion.current in the ng-click which will update the parent scope and the variable will be available for the ng-show and the accordion toggling works as expected.
Below is a working snippet, let me know if this fix solved your issue!
var app = angular.module('myApp', []);
app.controller('MyController', function MyController($scope) {
$scope.field = {
Header: '',
Title: '',
Img: '',
fieldAccordion: [{
fieldName: "one",
fieldSub: [{
fieldName: "oneone"
}]
}, {
fieldName: "two",
fieldSub: [{
fieldName: "twotwo"
}]
}]
};
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller='MyController' ng-app="myApp">
<div class="field-accordion" ng-if="field.fieldAccordion">
<ul class="accordion">
<li ng-repeat="fieldAccordion in field.fieldAccordion" ng-click="$parent.accordion.current = fieldAccordion.fieldName">
{{ fieldAccordion.fieldName }}
<ul ng-show="accordion.current == fieldAccordion.fieldName">
<li ng-repeat="fieldSub in fieldAccordion.fieldSub">
{{fieldSub.fieldName}}
</li>
</ul>
</li>
</ul>
</div>
</div>

Use single observable for multiple rows in table

<div class="tbody" data-bind="foreach: displayItems">
<div class="t-row">
<div class="t-cell">
<div class="manage-location-buttons">
<a href="javascript:void(0)">
<i class="fa fa-pencil" aria-hidden="true" data-bind="toggleClick: $component.openEditPopup"></i> Edit
</a>
<div class="edit-table-popup" data-bind="visible: $component.openEditPopup">
<ul>
<li><a data-hash="#locationmanagement/managelocations/locationediting" data-bind="click: goToTab">Locations</a></li>
<li><a data-hash="#locationmanagement/managelocations/events" data-bind="click: goToTab">Events</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
It is my sample of custom table.
On Link click I will show edit-table-popup div like popup. Cause I use only one observable openEditPopup for all items, onclick I see popup for each row.
openEditPopup = ko.observable<boolean>(false);
toggleClick - is custom dirrective, which changes boolean value to opposite
Is it possible to use only one observable but to show popup only for clicked row?
Yes, it's possible.
The click binding sends two arguments to an event handler:
The clicked $data,
The click-event.
If your click handler is an observable, this means it calls the observable like so: yourObservable(data, event)
Knowing that an observable is set by calling it with an argument, you can imagine what happens. Note that knockout ignores the second argument.
The solution would therefore be to change the openEditPopup from containing a bool, to containing a displayItem, and changing the visible binding to:
visible: $component.openEditPopup() === $data
An example:
var vm = {
selected: ko.observable("A"),
items: ["A", "B", "C", "D"]
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<p>Tabs</p>
<div data-bind="foreach: items">
<span data-bind="text: $data, click: $parent.selected"></span>
</div>
<p>Content</p>
<div data-bind="foreach: items">
<div data-bind="visible: $parent.selected() === $data">
<h1 data-bind="text:$data"></h1>
</div>
</div>
If I understand correctly, all your rows are bound to one observable and so when you click the row it is setting it to true and all pop ups are showing up?
If so, this would suggest you have a popup per row? I'd recommend changing this and having one popup that is toggled per row and then set it's data to the selected row. Something like this can be achieved with the code below.
var viewModel = function() {
var rows = ko.observableArray([
{id: 1, name: "gimble"}, {id: 2, name: "bob"}, {id: 3, name: "jess"}
]);
var selectedRow = ko.observable();
function showRowPopup(row)
{
//console.log(row.id);
if(selectedRow() == row)
selectedRow(null);
else
selectedRow(row);
}
return {
rows: rows,
showRowPopup: showRowPopup,
selectedRow: selectedRow
}
}
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="foreach: rows">
<div>
<span data-bind="text: id"></span> -
<span data-bind="text: name"></span>
<button data-bind="click: $parent.showRowPopup">Toggle Modal</button>
</div>
</div>
<div data-bind="with: selectedRow">
<h3>My Fake Modal</h3>
<span data-bind="text: id"></span> -
<span data-bind="text: name"></span>
</div>

Having issue to apply second div based CSS to clicked item in first Div

I am very new to angular.Need some assistance with examples as well.
I have two div's . One is created with ng-repeat and have used ng-click to get the clicked item. I have another div below that which has CSS like
style="width:100px;
float:left;
margin-top:-30px;
margin-left:-100px;">
<img src="../../../path to image" in it.
Now I want to apply the below Css div to the ng-clicked item. What is the best way to that? If necessary please provide examples. My code :
<div ng-repeat="p in package ">
<div class="col-md-3 pack-align-lr cursor-poi ticket-top space-buy-pack active-pack " ng-click="testing(p)" >
<span class="pack">{{p}}</span><span class="spaces-font">Spaces</span>
</div>
<div style="width:100px;float:left;margin-top:-30px;margin-left:-100px;"><img src="../../../path to image" class="eventclass"/>
</div>
</div>
Thanks in advance
From my understanding, you want modify the style of a sibling element when you click on the first div.
If that's the case, I suggest you "pack" your css rules in a class, let's say .selected-package.
The next steps would be to store the selected state in a variable (when you ng-click) and use the ng-class directive to toggle the selected-package css class upon the second div.
To wrap it up, here's a simple demo:
angular
.module('fancyApp', [])
.controller('fancyController', function($scope) {
$scope.packages = [
{name:'package 1'},
{name:'package 2'},
{name: 'package 3'}
];
});
.selected-package {
color: red;
}
[ng-click] {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="fancyApp">
<div ng-controller="fancyController">
<h1>My pacakges:</h1>
<ul>
<li ng-repeat="p in packages">
<div ng-click="p.selected = !p.selected">first div: {{p.name}}</div>
<div ng-class="{'selected-package':p.selected}">second div (is red when you click on the first one)</div>
</li>
</ul>
</div>
</div>

Boostrap popovers in Knockout foreach binding

I have a segment of HTML code that is bound to a knockout foreach loop. Inside of this segment, I have a glyphicon that I want to use to trigger a popover with some custom settings that the user can adjust. Here is the segment of code:
<ul class="nav navbar-nav" data-bind="foreach: items">
<li>
<div>
<a data-bind="attr:{id: itemId}" role="button" data-toggle="popover" title="Config Options" data-content="test content" data-placement="auto right">
<span class="glyphicon glyphicon-cog"></span>
</a>
</div>
</li>
</ul>
I can manually put multiple of these glyphicon anchors in the code outside of the foreach loop and the popovers work just fine. They just don't work inside of a knockout foreach binding...
Does anybody know what I need to do to make the popovers work for items contained in a knockout foreach loop? Any suggestions or pointers on what I may be doing wrong would be greatly appreciated.
UPDATE 1:
So after reviewing Buzinas answer and looking into the issue further, it turns out the issue appears to be with the fact that items is a ko.observableArray that is initialized empty. This array is populated based on user selections. When I add items to the array by default upon initialization, those popovers work. The only ones that don't work are the ones that are added on the fly by users. Any thoughts on this would also be appreciated.
I don't know how it's working for you when not using KO, since the Bootstrap documentation says:
Opt-in functionality
For performance reasons, the Tooltip and Popover data-apis are opt-in,
meaning you must initialize them yourself.
One way to initialize all popovers on a page would be to select them
by their data-toggle attribute:
$(function () {
$('[data-toggle="popover"]').popover()
});
Then, I've tried to do that combining with KO, and everything worked fine:
function AppViewModel() {
var self = this;
self.items = ko.observableArray([
{ itemId: 'id1' },
{ itemId: 'id2' },
{ itemId: 'id3' }
]);
self.add = function() {
self.items.push({ itemId: 'id' + self.items.length });
}
}
ko.bindingHandlers.popover = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
$(element).popover();
}
};
ko.applyBindings(new AppViewModel());
li {
margin-left: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<ul class="nav navbar-nav" data-bind="foreach: items">
<li>
<div>
<a data-bind="attr:{id: itemId}, popover" role="button" data-toggle="popover" title="Config Options" data-content="test content" data-placement="auto right">
<span class="glyphicon glyphicon-cog"></span>
</a>
</div>
</li>
</ul>
<button data-bind="click: add">Add</button>
Update
Since you have problems only when you need to add new items, you can create a Custom Binding, and I created one named popover for you:
ko.bindingHandlers.popover = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
$(element).popover();
}
};
And then, you can use it like this:
<a data-bind="popover">My anchor</a>

How do I data-bind = "visible : active" in div

hi this is my code snippet:
<div class="showtimes" data-bind="visible: showHide">
<div data-bind="template: { name: 'movie-grouped-showtimes-template', data: $data }"></div>
</div>
I want to toggle showHide off and on using the following:
<a class="icon arrow" data-bind="click: $parent.showtimes">
can't I just set up a variable showHide in my view Model, like the following:
self.showHide = ko.observable(false) ... Hide
showHide(true); ... show
and can I set it using the click : $parent.showtimes, like in the following:
<a class="icon arrow" data-bind="click: $parent.showtimes"></a>
You just have to set a function in the viewmodel that binds to the button's click (or modify an existing one, like showTimes in this case) to toggle the value of showHide.
Here's a simple example: http://jsfiddle.net/EfrainReyes/LNzDL/
var vm = {
showHide: ko.observable(false),
toggle: function() {
this.showHide( !this.showHide() );
}
};
ko.applyBindings(vm);
I didn't add any other elements to the example because the question seems to be targeted more towards showing/hiding the div.

Categories