How to use unstructured data with knockoutjs mapping plugin?
For example source json:
[
{
"id": 1,
"name": "Store #1",
"address": "City #1"
},
{
"id": 2,
"name": "Store #2"
}
]
Store #2 without address.
My template:
<ul data-bind='foreach: data'>
<li data-bind='with: id'>
<a href data-bind='text: name, click: function () { $parent.view($data, $index()) }'>
</a>
<span data-bind='text: address'></span>
</li>
</ul>
My viewModel
Module.store = function () {
var self = this;
self.data = ko.mapping.fromJS([]);
self.init = function () {
$.getJSON('json/stores.json', function (stores) {
ko.mapping.fromJS(stores, self.data);
});
};
};
If I run this code, I get error:
Uncaught ReferenceError: Unable to process binding "text: function
(){return address }" Message: address is not defined
For Store #2
How can I set null or empty string for Store #2 address property?
If your view shows the address, then your viewmodel must contain that property.
Make a viewmodel for the individual stores:
Module.Store = function (data) {
this.id = null;
this.name = null;
this.address = null;
ko.mapping.fromJS(data, {}, this);
}
and a use mapping definition (see documentation) in your store list:
Module.StoreList = function () {
var self = this,
mappingDefinition = {
create: function (options) {
return new Module.Store(options.data);
}
};
self.stores = ko.observableArray();
self.viewStore = function (store) {
// ...
};
self.init = function () {
$.getJSON('json/stores.json', function (stores) {
ko.mapping.fromJS(stores, mappingDefinition, self.stores);
});
};
};
Modified view (as a general rule, try to avoid inline functions in the view definition):
<ul data-bind='foreach: stores'>
<li>
<a href data-bind='text: name, click: $parent.viewStore'></a>
<span data-bind='text: address'></span>
</li>
</ul>
It doesn't seems to me that knockout-mapping-plugin has that functionality out of the box. Probably you should consider to try another workaround for that issue, I can see at least two:
1) returning json with null from server
2) displaying that span conditionally like:
<ul data-bind='foreach: data'>
<li data-bind='with: id'>
<a href data-bind='text: name, click: function () { $parent.view($data, $index()) }'>
</a>
<!-- ko if: address -->
<span data-bind='text: address'></span>
<!-- /ko -->
</li>
</ul>
Related
I used the foreach method to create markup foreach item in an observable array to create a treeview.
output example
category name1
content
content
category name 2
content
content
when I click on the category name I want just its content to show/hide, currently when I click on the category name it shows and hides all the categories.
var reportFilters = [
{ Text: "Campaign", Value: primaryCategories.Campaign },
{ Text: "Team", Value: primaryCategories.Team },
{ Text: "Agent", Value: primaryCategories.Agent },
{ Text: "List", Value: primaryCategories.List },
{ Text: "Inbound", Value: primaryCategories.Inbound },
{ Text: "Daily", Value: primaryCategories.Daily },
{ Text: "Services", Value: primaryCategories.Services },
{ Text: "Occupancy", Value: primaryCategories.Occupancy },
{ Text: "Data", Value: primaryCategories.Data }
];
self.showCategory = ko.observable(false);
self.toggleVisibility = function (report) {
var categoryName = report.PrimaryReportCategory;
var categoryContent = report.ID;
if (categoryName == categoryContent ) {
self.showCategory(!self.showCategory());
};
}
<div class="report-category-treeview" data-bind="foreach: $root.categories, mCustomScrollBar:true">
<ul class="column-list" >
<li class="report-category-heading" data-bind="click: $root.toggleVisibility"><span class="margin-top10" ><i class="fas fa-chevron-down"></i> <span class="report-category-name" data-bind="text: categoryName"></span></span></li>
<li id="panel" class="report-category-container" data-bind="foreach: reports, visible: $root.showCategory">
<div class="column-list-item" data-bind="click: $root.report_click, css: { 'selected': typeof $root.selectedReport() != 'undefined' && $data == $root.selectedReport() }">
<span class="column-list-text" data-bind="text: ReportName"></span>
</div>
</li>
</ul>
</div>
currently, when I click on the category name, it shows and hides all the
categories.
It's because showCategory is your single observable responsible for showing\hiding. What you really want is one show\hide observable per category.
I'm not sure how your entire data model looks like, but since you specifically asked about categories, then you should create a category view model, and probably some container view model, which I'll name here master:
var categoryVM = function (name) {
var self = this;
self.name = ko.observable(name);
self.isVisible = ko.observable(false);
self.toggleVisibility = function () {
self.isVisible(!self.isVisible());
}
// ... add here your other observables ...
}
// name 'masterVM' whatever you like
var masterVM = function () {
var self = this;
self.categories = ko.observables([]);
// ... probably add here other observables, e.g. 'reports' ...
self.init = function (rawCategories) {
rawCategories.forEach(function (item) {
categories.push(new categoryVM(item.name)); // replace 'name' with your property
}
}
}
var master = new masterVM();
master.init(getCategories()); // pass in your categories from wherever they come from
ko.applyBindings(master);
Then, in your html, this would be your outer foreach:
<div class="report-category-treeview" data-bind="foreach: categories ... />
and your lis (for brevity, I'm ommiting nested tags under your lis):
<li class="report-category-heading"
data-bind="click: toggleVisibility">
<li id="panel" class="report-category-container"
data-bind="foreach: $root.reports, visible: isVisible">
Goal:
use KO to show/hide folder, sub-folder, and files, as recursive UL LI list. When a user click on the folders, the child items under that folder will toggle hide/show.
Problem:
The recursive part is ok. But it does not do toggle. console.log says error that 'show' is undefined. Any idea what went wrong ?
Code
<script type="text/javascript">
$(function() {
ko.applyBindings(viewModel,document.getElementById('resources-panel'));
});
var viewModel = {
treeRoot: ko.observableArray()
};
var FileElement = function(ppp_name, ppp_type, ppp_children) {
var self = this;
self.ppp_children = ko.observableArray(ppp_children);
self.ppp_name = ko.observable(ppp_name);
self.ppp_type = ko.observable(ppp_type);
self.show = ko.observable(false);
self.toggle=function() {
self.show(!self.show());
}
}
var tree = [
new FileElement("IT Dept", "folder",[
new FileElement("IT Overview.docx", "file",[]),
new FileElement("IT Server1", "folder",[
new FileElement("IT Server1 Configuration Part 1.docx", "file", []),
new FileElement("IT Server1 Configuration Part 2.docx", "file", []),
]),
new FileElement("IT Server2", "folder",[])
]),
new FileElement("HR Dept", "folder", [])
];
viewModel.treeRoot(tree);
</script>
<script id="FileElement" type="text/html">
<ul>
<li>
<a href="#" data-bind="click: toggle" class="action-link"><br/>
<span data-bind="text: ppp_name"></span>
</a>
<ul data-bind="template: { name: 'FileElement', slideVisible: show, foreach: ppp_children }" ></ul>
</li>
</ul>
</script>
<div id="resources-panel" data-bind="template: { name: 'FileElement', slideVisible: show, foreach: $data.treeRoot }"></div>
Your top level binding context is the treeRoot, and treeRoot doesn't have a "show" property it's just a simple array so you probably want to remove that first show binding altogether
<div id="resources-panel" data-bind="template: { name: 'FileElement', foreach: $data.treeRoot }"></div>
Then within the FileElement template you'll want to move the show binding to the outside of the template binding like f_martinez suggested
<ul data-bind="slideVisible: show, template: { name: 'FileElement', foreach: ppp_children }" ></ul>
Here's an example jsFiddle
So what exactly is the ko.observable() doing? Here's the situation. I have a boolean ko.observable(), as you can see. I have click set to that value, so it SHOULD toggle the value of the true false contained within it's method call.
When I watch the array get populated in the developer tools, I see that selected does not = true or false, it instead = a pretty extensive function, and I can't find the true or false value anywhere inside of that, so I have no idea what exactly is happening when ko.observable() is used
What I expected is for tab.selected to be the value of tabArray[tab].selected, and when the page loads, that is correct. However, after clicking, tabArray[tab].selected = [Object object] when the text value is written out. I attempt to use:
<pre data-bind="text: JSON.stringify(ko.toJS(tab.selected)"></pre>
(found here: http://www.knockmeout.net/2013/06/knockout-debugging-strategies-plugin.html) and that prints out either true or false, do I need to do this for the other places where i need that value? Because I'm not sure exactly what ko.observable is doing.
define(['knockout', 'text!../Content/SSB/PartialViews/MainContent.html'], function (ko, MCTemplate) {
ko.components.register('MainContent', {
template: MCTemplate
});
var MainViewModel = {
tabArray: [
{ name: 'bob', selected: ko.observable(true) },
{ name: 'bib', selected: ko.observable(false) },
{ name: 'bab', selected: ko.observable(false) },
{ name: 'bub', selected: ko.observable(false) },
{ name: 'beb', selected: ko.observable(false) },
]
};
ko.applyBindings(MainViewModel);
return {
viewModel: MainViewModel
}
});
the HTML
<div id="tab">
<ul class="nav nav-tabs" role="tablist">
<!--ko foreach: {data: $parent.tabArray, as: 'tab'}-->
<li data-bind="click: tab.selected, css: { 'active': tab.selected}">
<a data-bind="attr: {href: '#' + tab.name}, text: name"></a>
<div data-bind="text: tab.name"></div>
<div data-bind="text: tab.selected"></div>
</li>
<!--/ko-->
</ul>
<!--ko foreach: {data: $parent.tabArray, as: 'tab'}-->
<div class="ui-tabpanel" role="tabpanel" data-bind="visible: tab.selected">
<p data-bind="text: name"></p>
</div>
<!--/ko-->
</div>
The click binding calls the provided function, passing it the current view model (also called $data). That's why you see [Object object] as the observable's value after the click. Since you want the click to toggle the observable, you need to create a function to do that. A nice, clean way to do this is through a custom binding, which I'll call toggle:
ko.bindingHandlers.toggle = {
init: function(element, valueAccessor) {
ko.utils.registerEventHandler(element, 'click', function () {
var obs = valueAccessor();
obs(!obs());
});
}
};
Now you bind using toggle instead of click: <li data-bind="toggle: tab.selected...
I have a a collection of panels each with a simple list of items that needs to either be sorted by 'computedLoad' or 'Name'. I have the following objects and methods to accomplish this generically over all of the panels (only showing one panel among many).
scope.orderBy = {
name: {
displayName: "Name",
sort: "Name",
reverse: false
},
load: {
displayName: "Load",
sort: "-computedLoad",
reverse:false
}
};
scope.selectOrder = function (panel, order) {
timeout(function () {
panel.activeOrder = order;
});
};
scope.panels = {
methods: {
activeOrder: scope.orderBy.name
}
};
I have the following html:
<div>
<ul class="nav nav-pills">
<li class="list-label"><a>Order By:</a></li>
<li ng-repeat="order in orderBy">{{order.displayName}}</li>
</ul>
<ul class="nav nav-pills nav-stacked">
<li ng-repeat="item in suite.Methods | orderBy:panel.methods.activeOrder.sort"><span class="text">{{item.Name}}</span></li>
</ul>
</div>
The selectOrder method doesn't seem to work. Any ideas? Am I missing something?
Here is an example: http://jsbin.com/puxoxi/1/
Setting panel.activeOrder happens asynchronously, so it is outside of angulars so called "digest cycle".
To make angular re-evaluate your scope, use the $apply function:
It could look like this:
scope.$apply(function() {
panel.activeOrder = order;
});
Hi I have been getting investing alot of time in learning Knockout and have come to a point where I have to many properties in my application and I am in need to use the mapping pluggin.
It seems easy enought how it should be used but I mussed be missing something because it does not work.I have created a test example.This is my code:
function vm() {
var self = this;
this.viewModel = {};
this.getData = function() {
$.getJSON('/api/Values/Get').then(data)
.fail(error);
function data(ajaxData) {
console.log(ajaxData);
self.viewModel = ko.mapping.fromJS(ajaxData);
console.log(self.viewModel);
}
function error(jError) {
console.log(jError);
}
};
};
ko.applyBindings(new vm());
This is my html:
<ul data-bind="foreach: viewModel">
<li data-bind="text:FirstName"></li>
<input type="text" data-bind="value: FirstName"/>
</ul>
<button data-bind="click : getData">Press me!</button>
My ajax call succesfully retrieves this data from the server:
[
{
FirstName: "Madalina",
LastName: "Ciobotaru",
hobies: [
"games",
"programming",
"hoby"
]
},
{
FirstName: "Alexandru",
LastName: "Nistor",
hobies: [
"games",
"programming",
"movies"
]
}
]
It seems that after data function is called viewModel get's converted into an array but with no items in it.
What am I doing wrong?
I have taken your expected server data and created a jsfiddle here. You needed to change the viewModel property to be an observable array, and change the way the mapping is performed.
Here is a version of your script that will work:
function vm() {
var self = this;
this.viewModel = ko.observableArray([]);
this.getData = function() {
$.getJSON('/api/Values/Get').then(data)
.fail(error);
function data(ajaxData) {
console.log(ajaxData);
ko.mapping.fromJS(ajaxData, {}, self.viewModel);
console.log(self.viewModel);
}
function error(jError) {
console.log(jError);
}
};
};
ko.applyBindings(new vm());