Knockout "with" binding - javascript

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/

Related

KnockoutJS - How to hide certain elements inside foreach using Observable Arrays?

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>

Vue.js - Nested v-for Loops

I have a Vue 3.0 app. In this app, I have the following:
export default {
data() {
return {
selected: null,
departments: [
{
name: 'Sports',
items: [
{ id:'1', label: 'Football', value:'football' },
{ id:'2', label: 'Basketball', value:'basketball' },
]
},
{
name: 'Automotive',
versions: [
{ id:'3', label: 'Oil', value:'oil' },
{ id:'4', label: 'Mud Flaps', value:'mud-flaps' },
]
}
]
};
}
};
I need to render these items in a dropdown list. Notably, the items need to be rendered by sections. In an attempt to do this, I'm using Bootstrap's dropdown headers. My challenge is, the HTML needs to be rendered in a way that I can't see how to do with Vue. I need to render my HTML like this:
<ul class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<li><h6 class="dropdown-header">Sports</h6></li>
<li><a class="dropdown-item" href="#">Football</a></li>
<li><a class="dropdown-item" href="#">Basketball</a></li>
<li><h6 class="dropdown-header">Automotive</h6></li>
<li><a class="dropdown-item" href="#">Oil</a></li>
<li><a class="dropdown-item" href="#">Mud Flaps</a></li>
</ul>
Based on this need, I would have to have a nested for loop. In psuedocode, it would be like this:
foreach department in departments
<li><h6 class="dropdown-header">{{ department.name }}</h6></li>
foreach item in department.items
<li><a class="dropdown-item" href="#">{{ item.label }} </a></li>
end foreach
end foreach
I know I could do this with something like Razer. However, I don't see anyway to do this in Vue. Yet, I have to believe I'm overlooking something as rendering a hierarchy is a common need. How do you render a hierachy in Vue without changing the data structure?
Thank you.
just use nested v-for like this:
<template v-for="(department , i) in departments">
<li><h6 class="dropdown-header" :key="`head-${i}`">{{ department.name }}</h6></li>
<template v-for="(item , j) in department.items">
<li><a class="dropdown-item" href="#" :key="`sub-${i}${j}`">{{ item.label }} </a></li>
</template>
</template>
you can have nested v-for with the help of template and also make sure that the keys binding to the elements are unique (this has to be unique so you don't get weird behavior from vue).
run the code below and check the result (click on full screen to see the full list rendered):
// you can ignore this line
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: {
departments: [{
name: 'Sports',
items: [{
id: '1',
label: 'Football',
value: 'football'
},
{
id: '2',
label: 'Basketball',
value: 'basketball'
},
]
},
{
name: 'Automotive',
items: [{
id: '3',
label: 'Oil',
value: 'oil'
},
{
id: '4',
label: 'Mud Flaps',
value: 'mud-flaps'
},
]
}
],
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<template v-for="({name, items}, i) in departments">
<li :key="`header-${i}`">
<h6 class="dropdown-header">{{ name }}</h6>
</li>
<li v-for="{label, id} in items" :key="`link-${id}`">
<a class="dropdown-item" href="#">{{ label }}</a>
</li>
</template>
</ul>
</div>
One approach to this is to use a computed method. I've created a jsfiddle here.
computed: {
departmentItems() {
const items = [];
this.departments.forEach(department => {
items.push({
label: department.name,
class: 'dropdown-header',
});
department.items.forEach(item => {
items.push({
label: item.label,
class: 'dropdown-item'
})
})
})
return items;
}
}
The important part here is, that we have a common model for both dropdown-header and dropdown-item.
You structure your data in a computed property, and then use v-for to iterate over the list. Here is an example:
new Vue({
el: "#app",
data() {
return {
selected: null,
departments: [
{
name: 'Sports',
items: [
{ id:'1', label: 'Football', value:'football' },
{ id:'2', label: 'Basketball', value:'basketball' },
]
},
{
name: 'Automotive',
items: [
{ id:'3', label: 'Oil', value:'oil' },
{ id:'4', label: 'Mud Flaps', value:'mud-flaps' },
]
}
]
}
},
computed: {
departmentsItem: function() {
return this.departments.reduce((acc,department) =>
[
...acc,
{ type: "department", name: department.name },
...department.items.map(item => ({ type: "item", name: item.label }))
]
, []);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<li v-for="(departmentItem,i) in departmentsItem" :key="i">
<h6
v-if="departmentItem.type==='department'" class="dropdown-header"
>{{departmentItem.name}}</h6>
<a
v-else-if="departmentItem.type==='item'" class="dropdown-item" href="#"
>{{departmentItem.name}}</a>
</li>
</ul>
</div>

Observable Array and foreach Lists

I have this code:
<select multiple="multiple" data-bind="options:markerResults, optionsText: function(item) {
return item.name +' '+ item.formatted_address
}">
</select>
And it works. But, This code:
<ul data-bind="foreach: markerResults">
<li>
<stong><span data-bind:"text: name"></span></strong>
<span data-bind:"text: formatted_address"></span>
<span data-bind:"text: rating"></span>
</li>
</ul>
Doesn't. How can I make the code above work?
Thank you!
more code:
for (var j = 0; j < allResults.length; j++) {
createMarker(allResults[j]);
allResults.push(results);
console.log(allResults);
}
}
here is the fiddle http://jsfiddle.net/LkqTU/32252/
function model() {
var self = this;
this.text = ko.observable('hello');
this.markerResults = ko.observableArray([
{ name: "Bungle", formatted_address: "1 My Way", rating: 'A' },
{ name: "George", formatted_address: "2 My Way", rating: 'B' },
{ name: "Zippy", formatted_address: "3 My Way", rating: 'C' }
]);
}
var mymodel = new model();
$(document).ready(function() {
ko.applyBindings(mymodel);
});
here is the html
<ul data-bind="foreach: markerResults">
<li>
<strong><span data-bind="text: name"></span></strong>
<strong><span data-bind="text: formatted_address"></span></strong>
<strong><span data-bind="text: rating"></span></strong>
</li>
</ul>

Filtering array based on selected checkboxes and select fields

How can I store the selected values of checkboxes and select elements and use a combination of these to filter a results array? e.g. think filtering by category Id, or displaying all results in the last X months.
After much research and trial and error I've got as far as this:
View Plunker or see the code below:
HTML within the 'refine' directive
<div class="filters">
<div class="filter">
<label for="maxage">Show results from</label>
<select name="maxage" id="maxage"
ng-options="option.name for option in refine.maxAge.options track by option.id"
ng-model="refine.maxAge.selected"
ng-change="filterResults()">
</select>
</div>
<div class="filter">
<div class="status-filter" ng-repeat="status in refine.statuses">
<label for="statusId{{ status.id }}">{{ status.name }}</label>
<input type="checkbox" name="status" value="{{ status.id }}" ng-change="filterResults()">
</div>
</div>
</div>
HTML of main page
<body ng-app="app">
<div ng-controller="ListCtrl" data-county-parish-id="1478">
...
<main class="page-content columns medium-9 medium-push-3">
...
<spinner name="planningSpinner" show="true">
<div class="loadingPanel block"></div>
</spinner>
<div class="planning">
<div class="no-results ng-hide" ng-show="filteredResults.length === 0">
<p>No results.</p>
</div>
<h4>Number of records: {{ filteredResults.length }}</h4>
<div ng-repeat="appl in filteredResults">
<hf-application info="appl"></hf-application>
</div>
</div>
...
</main>
<aside class="sidebar columns medium-3 medium-pull-9">
...
<div hf-refine-results info="refine"></div>
</aside>
...
</div>
</body>
JS
var app = angular.module('app', []);
// results filter
angular.module('app').filter('results', ['$filter', function($filter) {
return function (input, refine) {
var filterParams = {};
// start off filtering with the outsideBoundary parameter
filterParams.outsideBoundary = refine.outsideBoundary;
// add 'show results from' filter
//var adjustedDate = new Date();
//adjustedDate.setMonth(adjustedDate.getMonth() - refine.maxAge.selected.id);
//filterParams.receivedDate = $filter('date')(adjustedDate, 'yyyy/MM/dd');
return $filter('filter')(input, filterParams);
}
}]);
// Controller
angular.module('app').controller('ListCtrl',
['$scope', '$filter', '$attrs', 'appService', 'resultsFilter', function ($scope, $filter, $attrs, appService, resultsFilter) {
$scope.applications = [];
$scope.refine = {
statuses: {
options: [
{ id: 1, name: 'Unknown' },
...
{ id: 6, name: 'Appealed' }
],
selected: [2, 3]
},
maxAge: {
options: [
{ id: '1', name: 'Last month' },
... // 1 to 12 months
{ id: '12', name: 'Last 12 months' }
],
selected: { id: '6', name: 'Last 6 months' }
},
...
};
$scope.filterResults = function () {
$scope.filteredResults = resultsFilter($scope.applications, $scope.refine);
};
/* get data from appService */
appService.getApplications({
status: 3,
countyparish: parseInt($attrs.countyParishId),
postcode: '',
distance: 5,
pagesize: 100
})
.then(function (data) {
$scope.applications = data;
$scope.filteredResults = resultsFilter(data, $scope.refine);
});
}]);
I appreciate this question has been asked many times, however I haven't found an answer for my question(s) since most examples are very simple expressions within ng-repeat.
This example work with multi checkbox. For filtering with outher select use same logic. Look
'use strict';
var App = angular.module('clientApp', ['ngResource', 'App.filters']);
App.controller('ClientCtrl', ['$scope', function ($scope) {
$scope.selectedCompany = [];
$scope.companyList = [{
id: 1,
name: 'Apple'
}, {
id: 2,
name: 'Facebook'
}, {
id: 3,
name: 'Google'
}];
$scope.clients = [{
name: 'Brett',
designation: 'Software Engineer',
company: {
id: 1,
name: 'Apple'
}
}, {
name: 'Steven',
designation: 'Database Administrator',
company: {
id: 3,
name: 'Google'
}
}, {
name: 'Jim',
designation: 'Designer',
company: {
id: 2,
name: 'Facebook'
}
}, {
name: 'Michael',
designation: 'Front-End Developer',
company: {
id: 1,
name: 'Apple'
}
}, {
name: 'Josh',
designation: 'Network Engineer',
company: {
id: 3,
name: 'Google'
}
}, {
name: 'Ellie',
designation: 'Internet Marketing Engineer',
company: {
id: 1,
name: 'Apple'
}
}];
$scope.setSelectedClient = function () {
var id = this.company.id;
if (_.contains($scope.selectedCompany, id)) {
$scope.selectedCompany = _.without($scope.selectedCompany, id);
} else {
$scope.selectedCompany.push(id);
}
return false;
};
$scope.isChecked = function (id) {
if (_.contains($scope.selectedCompany, id)) {
return 'icon-ok pull-right';
}
return false;
};
$scope.checkAll = function () {
$scope.selectedCompany = _.pluck($scope.companyList, 'id');
};
}]);
angular.module('App.filters', []).filter('companyFilter', [function () {
return function (clients, selectedCompany) {
if (!angular.isUndefined(clients) && !angular.isUndefined(selectedCompany) && selectedCompany.length > 0) {
var tempClients = [];
angular.forEach(selectedCompany, function (id) {
angular.forEach(clients, function (client) {
if (angular.equals(client.company.id, id)) {
tempClients.push(client);
}
});
});
return tempClients;
} else {
return clients;
}
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular-resource.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css">
<div ng-app="clientApp" data-ng-controller="ClientCtrl">
<ul class="inline">
<li>
<div class="alert alert-info">
<h4>Total Filtered Client: {{filtered.length}}</h4>
</div>
</li>
<li>
<div class="btn-group" data-ng-class="{open: open}">
<button class="btn">Filter by Company</button>
<button class="btn dropdown-toggle" data-ng-click="open=!open"><span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu">
<li><a data-ng-click="checkAll()"><i class="icon-ok-sign"></i> Check All</a>
</li>
<li><a data-ng-click="selectedCompany=[];"><i class="icon-remove-sign"></i> Uncheck All</a>
</li>
<li class="divider"></li>
<li data-ng-repeat="company in companyList"> <a data-ng-click="setSelectedClient()">{{company.name}}<span data-ng-class="isChecked(company.id)"></span></a>
</li>
</ul>
</div>
</li>
</ul>
<hr/>
<h3>Clients Table:</h3>
<table class="table table-hover table-striped">
<thead>
<tr>
<th style="width:10%">#</th>
<th style="width:20%">Name</th>
<th style="width:40%">Designation</th>
<th style="width:30%">Company</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="client in filtered = (clients | companyFilter:selectedCompany)">
<td>{{$index + 1}}</td>
<td><em>{{client.name}}</em>
</td>
<td>{{client.designation}}</td>
<td>{{client.company.name}}</td>
</tr>
</tbody>
</table>
</div>

Can knockout.js use templates to generate templates?

I'm trying to use knockout to templates to generate templates.
Along the lines of
Html:
<script id="searchField-template" type="text/html">
<li data-bind="text: name"></li>
</script>
<script id="template-template" type="text/html">
<ul data-bind="template: { name: 'searchField-template', foreach: ${name} }" ></ul>
</script>
JS:
var viewModel = {
Title: [{
name: "Title1"},
{
name: "Title2"},
{
name: "Title3"}],
Manager: [{
name: "Manager1"},
{
name: "Manager2"},
{
name: "Manager3"}],
Defn: [{
name: "Title"},
{
name: "Manager"}]
};
ko.applyBindings(viewModel);
runnable sample here: http://jsfiddle.net/scottwww/yQZUE/2/
It seems that the problem is with how curly braces are interpreted.
Any suggestions?
Here you go >
http://jsfiddle.net/vwP3w/2/
Not sure this is the right way, but a reference to the vm helps.
http://jsfiddle.net/scottwww/vwP3w/1/
HTML:
<div data-bind="template: { name: 'template-template', foreach: Defn }"></div>
<script id="searchField-template" type="text/html">
<li data-bind="text: name"></li>
</script>
<script id="template-template" type="text/html">
<ul data-bind="template: { name: 'searchField-template', foreach: vm[$data.name] }" ></ul>
</script>
JS:
var viewModel = {
Title: [{
name: "Title1"},
{
name: "Title2"},
{
name: "Title3"}],
Manager: [{
name: "Manager1"},
{
name: "Manager2"},
{
name: "Manager3"}],
Defn: [{
name: "Title"},
{
name: "Manager"}]
};
window.vm = viewModel;
ko.applyBindings(viewModel);
use the $data variable inside the nested template.
http://jsfiddle.net/dwick/yQZUE/3/

Categories

Resources