I have a list of WebsiteOwners. I'm trying to build a UI which will display more information about the owners when I click on them.
this.toExpand = ko.observableArray(); //initialize an observable array
this.invertExpand = ko.observable("");
this.invertExpand = function (index) {
if (self.invertExpand[index] == false) {
self.invertExpand[index] = true;
alert(self.invertExpand[index]); //testing whether the value changed
}
else {
self.invertExpand[index] = false;
alert(self.invertExpand[index]); //testing whether the value changed
}
};
Here's the HTML code :
<div data-bind="foreach: WebsiteOwners">
<div>
<button data-bind="click: $root.invertExpand.bind(this,$index())" class="label label-default">>Click to Expand</button>
</div>
<div data-bind="visible: $root.toExpand()[$index]">
Primary Owner: <span data-bind="text:primaryOwner"></span>
Website Name : <span data-bind="text:websiteName"></span>
//...additional information
</div>
</div>
You can store one of your WebsiteOwner items directly in your observable. No need to use an index.
Don't forget you read an observable by calling it without arguments (e.g. self.invertExpand()) and you write to it by calling with a value (e.g. self.invertExpand(true))
I've included 3 examples in this answer:
One that allows only a single detail to be opened using knockout
One that allows all details to be opened and closed independently using knockout
One that does not use knockout but uses plain HTML instead 🙂
1. Accordion
Here's an example for a list that supports a single expanded element:
const websiteOwners = [
{ name: "Jane", role: "Admin" },
{ name: "Sarah", role: "Employee" },
{ name: "Hank", role: "Employee" }
];
const selectedOwner = ko.observable(null);
const isSelected = owner => selectedOwner() === owner;
const toggleSelect = owner => {
selectedOwner(
isSelected(owner) ? null : owner
);
}
ko.applyBindings({ websiteOwners, isSelected, toggleSelect });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: { data: websiteOwners, as: 'owner' }">
<li>
<span data-bind="text: name"></span>
<button data-bind="
click: toggleSelect,
text: isSelected(owner) ? 'collapse' : 'expand'"></button>
<div data-bind="
visible: isSelected(owner),
text: role"></div>
</li>
</ul>
2. Independent
If you want each of them to be able to expand/collapse independently, I suggest adding that state to an owner viewmodel:
const websiteOwners = [
{ name: "Jane", role: "Admin" },
{ name: "Sarah", role: "Employee" },
{ name: "Hank", role: "Employee" }
];
const OwnerVM = owner => ({
...owner,
isSelected: ko.observable(null),
toggleSelect: self => self.isSelected(!self.isSelected())
});
ko.applyBindings({ websiteOwners: websiteOwners.map(OwnerVM) });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: websiteOwners">
<li>
<span data-bind="text: name"></span>
<button data-bind="
click: toggleSelect,
text: isSelected() ? 'collapse' : 'expand'"></button>
<div data-bind="
visible: isSelected,
text: role"></div>
</li>
</ul>
3. Using <details>
This one leverages the power of the <details> element. It's probably more accessible and by far easier to implement!
const websiteOwners = [
{ name: "Jane", role: "Admin" },
{ name: "Sarah", role: "Employee" },
{ name: "Hank", role: "Employee" }
];
ko.applyBindings({ websiteOwners });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: websiteOwners">
<li>
<details>
<summary data-bind="text: name"></summary>
<div data-bind="text: role"></div>
</details>
</li>
</ul>
Related
kinda new to vue, I have mapped out some data from my initial data object in vue.js I am trying to hide and show only the items within that iteration of the mapping when the user selects the heading. I am using the isHidden prop in vue to hide and show my list items but when selecting any heading it shows all the tags instead of those associated with that specific header.
anyone know the proper way to do this? should I use some index or id from e.target? or should I give each list item a 'hidden' property and change it that way somehow?
here's my list that I mapped out
<div v-for="item in list">
<h4 v-on:click="viewItemsinCat()">{{item.category}}</h4>
<ul>
<li v-if="!isHidden" v-for="item in item.food">
{{item}}
</li>
</ul>
</div>
then I have my data like so:
data: {
list: [{
category: 'Baked goods',
food: ['bread', 'cookie', 'butter', 'powder']
}, {
category: 'meats',
food: ['chicken', 'turkey', 'beef']
}, {
category: 'fruits',
food: ['bannana', 'apple', 'pineapple']
}, {
category: 'canned goods',
food: ['tomatoes', 'green beans', 'corn']
}, {
category: 'veggies',
food: ['broccoli', 'celery', 'lettuce']
}, {
category: 'pantry',
food: ['broom', 'mop', 'dried beans']
}, ],
isHidden: true,
}
then I have my method to alter isHidden
viewItemsinCat: function(){
this.isHidden = false
},
Add a selected property to contain the currently selected item when the user clicks:
data() {
return {
selected: null,
list: [...]
}
}
<div v-for="item in list">
<h4 v-on:click="selected=item.category">{{item.category}}</h4>
<ul v-if="selected==item.category">
<li v-for="food in item.food">
{{ food }}
</li>
</ul>
</div>
Here's a demo
so there are a ton of references in StackOverflow in relation to ng-repeat and checkboxes. However, I have tried them and still can't get this to work.
What I am trying to do is that on click of {{address.bookName}} all the contacts that are populated via ng-repeat are selected by default. I tried adding ng-checked = true and while it did superficially tick the checkboxes, it did not create the property of receivers.selected[contact.firstName] and it's corresponding value of {{contact.contact}}.
<ul data-ng-repeat='address in addressBook'>
<li>
<a data-ng-click='showContacts = !showContacts'>{{address.bookName}}</a>
<ul>
<li data-ng-show='showContacts' data-ng-repeat='contact in address.addresses'>
<label>
<input
type='checkbox'
ng-model='receivers.selected[contact.firstName]'
name='selectedReceivers[]'
ng-true-value = '{{contact.contact}}'
ng-false-value='null'/>
{{contact.firstName}} {{contact.lastName}} - {{contact.contact}}
</label>
</li>
</ul>
</li>
</ul>
In my controller I have an object:
$scope.receivers = {
selected: {}
};
In summary: on click of {{address.bookName}}, I would like all the contacts in address.addresses to be selected and therefore $scope.receivers to be populated with all the contacts' firstName and contact.
data-ng-show='showContacts == $parent.$index', This will show the particular address of clicked book-name.
You can make the check-boxes selected by ng-checked= true.
In your case, you can just apply the ng-model dynamically by using $index (To get current / inner loop index) and $parent.$index (To get parent loop index).
<input type='checkbox'
ng-model="receivers.checked[$parent.$index][$index]"
ng-checked="true"
ng-true-value="{{contact}}"
ng-false-value="null" />
Finally, copy of your scope variable using angular.copy(element).
//Copy the scope variable
$scope.receivers.checked[index] = angular.copy($scope.addressBook[index].addresses);
function LoginController($scope) {
//Declare ng-model
$scope.receivers = {
selected: {},
checked: {}
};
$scope.showSelected = function(parentIndex, index)
{
//Show hide particular contacts
$scope.showContacts = index;
//Copy the scope variable
$scope.receivers.checked[index] = angular.copy($scope.addressBook[index].addresses);
}
$scope.addressBook = [{
"_id": 1,
'bookName': 'Family',
'companyID': 1234,
'creator': 1234,
'addresses': [{
'contact': '1234567890',
'firstName': 'Bob',
'lastName': 'Smith'
}, {
'contact': '1234567890',
'firstName': 'Amy',
'lastName': 'Smith'
}, {
'contact': '1234567890',
'firstName': 'John',
'lastName': 'Smith'
}]
}, {
'_id': 2,
'bookName': 'Friends',
'companyID': 1234,
'creator': 1234,
'addresses': [{
'contact': '1234567890',
'firstName': 'Bruce',
'lastName': 'Wayne'
}, {
'contact': '1234567890',
'firstName': 'Clark',
'lastName': 'Kent'
}, {
'contact': '1234567890',
'firstName': 'Peter',
'lastName': 'Parker'
}]
}];
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller="LoginController">
<h5 class="form-title">Select contacts:</h5>
<ul data-ng-repeat='address in addressBook'>
<li>
<a data-ng-click='showSelected($parent.$index, $index)'>{{address.bookName}}</a>
<ul>
<li data-ng-show='showContacts == $parent.$index' data-ng-repeat='contact in address.addresses'>
<label>
<input type='checkbox'
ng-model="receivers.checked[$parent.$index][$index]"
ng-checked="true"
ng-true-value="{{contact}}"
ng-false-value="null" /> {{contact.firstName}} {{contact.lastName}} - {{contact.contact}}
</label>
</li>
</ul>
</li>
</ul>
<h5 class="text-bold">Send to: {{receivers.checked}}</h5>
</div>
I am working with Knockout support and now creating tree structured UI component. Here i will create the elements dynamically and there i want to bind the data to newly created elements.
Please check with the below code
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script type="text/html" id="tree">
<li menuid="data bind with attr binding">
<span> </span>
<span><a href="#" name="endnode"></span>
<ul data-bind="template: { name: 'tree', foreach: childNodes }">
</ul>
</li>
below is my script
var viewModel = {
Mytree: ko.observable({
childNodes: [
{
id: 1,name:"node1",
childNodes: [ {id: 2, name:"node2", childNodes: [{id: 3,name:"node3", childNodes: [] }] } ]
},
{
id: 4,name:"node4",
childNodes: [ {id: 5,name:"node5", childNodes: [] } ]
}
]
})
};
ko.applyBindings(new viewModel.Mytree());
Now i want to append the bindable node names to to tree like below:
<span><a href="#" name="endnode" data-bind:"text:childNodes.name"/></span>
<ul data-bind="template: { name: 'tree', foreach: childNodes }">
Can you please any one suggest me to achieve this
If you pass it the view model (you can use the $root syntax for this) you can just use "name" in the binding. The context will change as it goes down the tree ... there's a couple of other bits wrong - for one it's data-bind= (equals, not colon)
Try this template ...
<script type="text/html" id="tree">
<li menuid="data bind with attr binding">
<span> </span>
<ul data-bind="template: { name: 'tree', foreach: childNodes }" />
</li>
</script>
and use this to kick things off (note $root)
<ul data-bind="template: { name: 'tree', data: $root }" />
this will give you ...
I would like to select a specific location by Continent / Country / State / etc.
I get a JSON and I managed to display/select the Continent, but I cannot get the list of Countries to populate.
I am rather new to knockout and JS, so I am probably doing something wrong (I managed to do this based on the live examples from knockout, but those were using select, and I cannot seem to get it to work on ul)
The JSON (composed by hand, so might have errors)
var Continent = [
{ Name: "Europe", Countries: [{ Name: "England", ID: 1 }, { Name: "Wales", ID: 2 }] },
{ Name: "America", Countries: [{ Name: "US", ID: 3 }, { Name: "Canada", ID: 4 }] },
{ Name: "Asia", Countries: [{ Name: "India", ID: 5 }, { Name: "China", ID: 6 }] }
];
The javascript:
var locationVM = function () {
var self = this;
self.selectedContinent = ko.observable("none");
self.selectedCountry = ko.observable();
self.selectedContinent.subscribe(function () {
self.selectedCountry(undefined);
});
self.onClickContinent = function (data) {
self.selectedContinent(data.Name);
};
};
ko.applyBindings(new locationVM());
The HTML
<div>
<ul data-bind="foreach: Continent">
<li data-bind="text: Name, click: $parent.onClickContinent" />
</ul>
</div>Selected Continent : <span data-bind="text: selectedContinent">text</span>
<div data-bind="with: selectedContinent">
<ul data-bind="foreach: Countries">
<li data-bind="text: Name" />
</ul>
</div>
and here is the fiddle http://jsfiddle.net/norbert/WB46V/
Also, if there is anything I missed, or is not needed, please point it out, or provide link for further studies ;)
You're trying to set the continent value to a string and then later trying to iterate thru the string. What you need to do is set the continent to the data.
Something like this:
self.selectedContinent(data);
Working fiddle here.
http://jsfiddle.net/WB46V/6/
Just to add, I haven't used knockout before, so my solution in the fiddle might not be the best one.
I am trying to display descendant elements in array using "with" binding.
But it displays only last items in "exercises" and I want to see all of them. How is it possible to fix this?
And after that, how can I make each item in array editable?
My ViewModel:
function AppViewModel() {
var self = this;
self.workouts = ko.observableArray([
{name: "Workout1", exercises:{
name: "Exercise1.1",
name: "Exercise1.2",
name: "Exercise1.3"
}},
{name: "Workout2", exercises:{
name: "Exercise2.1",
name: "Exercise2.2",
name: "Exercise2.3"
}},
{name: "Workout3", exercises:{
name: "Exercise3.1",
name: "Exercise3.2",
name: "Exercise3.3"
}},
{name: "Workout4", exercises:{
name: "Exercise3.1",
name: "Exercise3.2",
name: "Exercise3.3"
}},
]);
self.removeWorkout = function() {
self.workouts.remove(this);
};
}
ko.applyBindings(new AppViewModel());
The View:
<div class="content">
<ul data-bind="foreach: workouts">
<li>
<span data-bind="text: name"> </span>
Remove
<ul data-bind="with: exercises">
<li data-bind="text: name"></li>
</ul>
</li>
</ul>
</div>
Here's this code at jsfiddle:
http://jsfiddle.net/9TrbE/
Thanks!
The exercises property you declared as an object should be an array.
self.workouts = ko.observableArray([
{name: "Workout1", exercises:[
{ name: "Exercise1.1" },
{ name: "Exercise1.2" },
{ name: "Exercise1.3" }
]},
]);
So you can use this view :
<div class="content">
<ul data-bind="foreach: workouts">
<li>
<span data-bind="text: name"> </span>
Remove
<ul data-bind="foreach: exercises">
<li data-bind="text: name"></li>
</ul>
</li>
</ul>
</div>
Declaring :
var exercises = {
name: "Exercise1.1",
name: "Exercise1.2",
name: "Exercise1.3"
};
Is like doing that :
var exercises = {
name: "Exercise1.1",
};
exercises.name: "Exercise1.2";
exercises.name: "Exercise1.3";
Get it with this
Here's this code at jsfiddle
self.workouts = ko.observableArray([
{name: "Workout1", exercises:[
{ name: "Exercise1.1" },
{ name: "Exercise1.2" },
{ name: "Exercise1.3" }
]},
]);
`http://jsfiddle.net/9TrbE/8/