Show different content if 'ko with' is not displayed - javascript

I'm using the following knockout code to display properties from an object.
Using the with i can check if the properties in this object exist.
<!-- ko with: Bunk1 -->
<div data-bind="css: Color">
<div class="row no-margin">
<div>
<div data-bind="text: Name"></div>
<div data-bind="text: FirstName"></div>
</div>
</div>
</div>
<!-- /ko -->
This is the model:
var viewModel = function () {
var self = this;
self.Bunk1 = ko.observable();
self.Bunk2 = ko.observable();
...
...
// 'val' is loaded with $.ajax
// this code might not be executed and Bunk1 can fail to initialize.
var model = new BunkModel();
model.initModel(val);
self.Bunk1(model);
...
}
function BunkModel() {
var self = this;
self.Color = ko.observable();
self.Name= ko.observable();
self.FirstName= ko.observable();
self.initModel = function (values) {
self.Color(self.mapColor(values.color));
self.Name(values.name);
self.FirstName(values.firstName);
}
}
What i would like to do is to display an alternative div if there is no data, something like an else to the ko with. How can i bind the object properties but display alternative data if they don't exist.

When creating a new observable without passing a value, its value will be undefined. This means you can simply add a conditional block below the existing with block:
<!-- ko if: Bunk1() === undefined -->
content here
<!-- /ko -->
Note that you need to use parentheses when doing a comparison like this, Bunk1 === undefined would check if the observable itself is undefined instead of its underlying value.

In addition to #Stijn answer.
You can use "ifnot" binding:
<!-- ko ifnot: Bunk1 -->
content here
<!-- /ko -->

Related

ko.computed property to determine visibility not working

In my KncokoutJS ViewModel, I have the follow computed property:
self.SelectedUserHasRoles = ko.computed(function () {
if (self.isLoaded()) {
return self.selectedUser().roles().length > 0;
}
return false;
});
And in my HTML, I have the following:
<!-- ko if: isLoaded() -->
<!-- ko if: !SelectedUserHasRoles -->
<div>
<p>User has no Roles.</p>
</div>
<!-- /ko -->
<!-- ko if: SelectedUserHasRoles -->
<div class="roles-wrapper" data-bind="foreach: $root.selectedUser().roles()">
<div class="role-token" data-bind="text: Name"></div>
</div>
<!-- /ko -->
<!-- /ko -->
In my code, I was to say this:
If data from AJAX call has finished loading (isLoaded is true), then for the currently selected user, check and see if he/she has any roles. If yes, then loop through them and show them, if not, show a bit of text saying 'User has no Roles.'
All seems to work, except for the showing User has no Roles text snippet. I've no idea why that isn't showing! I'm putting breakpoints into my computed property and can see that when I select a user with no roles, the expression (in console window) is false, and I'm negating that, so I should see that text snippet!
What am I doing wrong? I've created a screencast to make things easier to understand.
When you want to negate an observable or computed value in a binding, you have to call it explicitly:
<!-- ko if: !SelectedUserHasRoles() -->
In the case of the if binding, there's also the ifnot counterpart:
<!-- ko ifnot: SelectedUserHasRoles -->
I think it's useful to understand why this is needed, since I see it happening a lot.
You could see the data-bind attribute as a comma separated string of key value pairs. Knockout wraps each of the values in a function, which it calls the valueAccessor.
Essentially, you'll go from:
data-bind="if: SelectedUserHasRoles"
to
{
"if": function() { return SelectedUserHasRoles }
}
SelectedUserHasRoles is an observable instance, which evaluates as truthy. When you negate this value using an !, it will always be false.
var myObs = ko.observable("anything");
var valueAccessor = function() { return myObs; };
var valueAccessorNeg = function() { return !myObs; };
console.log(valueAccessor()); // Returns the observable
console.log(valueAccessorNeg()); // Always prints false
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
The valueAccessor function is passed to the init method of a binding. Usually, it is retrieved by calling it, and then unwrapped. Because the unwrap utility doesn't care about whether you pass it an observable or a plain value, you'll not see any errors when you make this mistake.
var myObs = ko.observable(false);
var va1 = function() { return myObs; };
var va2 = function() { return !myObs; };
var va3 = function() { return !myObs(); };
console.log(ko.unwrap(va1())); // false
console.log(ko.unwrap(va2())); // always false
console.log(ko.unwrap(va3())); // true
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
I hope this small peek under the hood might help you (and others that have made this mistake) to be able to determine when the () are needed in the future.
Because you are not binding to a variable but to an expression, you need to add parenthesis here:
<!-- ko if: !SelectedUserHasRoles() -->
//^^ here
See the following snippet
function CreateVM() {
var self = this;
this.isTrue = ko.observable(false);
this.selectedUser = ko.observable();
this.isLoaded = ko.observable();
self.SelectedUserHasRoles = ko.computed(function () {
if (self.isLoaded()) {
return self.selectedUser().roles().length > 0;
}
return false;
});
}
var vm = new CreateVM();
ko.applyBindings(vm);
var userWithRoles = { roles: ko.observableArray([1,2]) };
var userWithoutRoles = { roles: ko.observableArray([]) };
vm.selectedUser(userWithoutRoles);
vm.isLoaded(true);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<!-- ko if: isLoaded() -->
<!-- ko if: !SelectedUserHasRoles() -->
<div>
<p>User has no Roles.</p>
</div>
<!-- /ko -->
<!-- ko if: SelectedUserHasRoles -->
<div class="roles-wrapper" data-bind="foreach: $root.selectedUser().roles()">
<div class="role-token" data-bind="text: $data"></div>
</div>
<!-- /ko -->
SelectedUserHasRoles: <span class="role-token" data-bind="text: SelectedUserHasRoles"></span>
<!-- /ko -->
See user3297291's answer for more details.
You have the same check for isLoaded() twice, actually
<!-- ko if: isLoaded() -->
<!-- ko if: !SelectedUserHasRoles -->
if isLoaded() evalutes to false, your SelectedUserHasRoles() won't even be evaluated.

How to Refresh Knockout ViewModel Using Mapping Plugin

I have the following Knockout ViewModel:
var EditorViewModel = function () {
var self = this;
self.addData = function (_data) {
ko.mapping.fromJS(_data, {}, self);
};
};
which is used as such:
var viewModel = new EditorViewModel();
viewModel.addData(model);
ko.applyBindings(viewModel);
For info, the data for viewModel.addData() comes from an AJAX call.
The View/ViewModel is populated with no problem the first time round when ko.applyBindings is called. However, if I later pull new data from the server via AJAX and then call either:
viewModel.addData(someNewAjaxData)
or
ko.mapping.fromJS(someNewAjaxData, {}, viewModel);
Then Knockout does not update the view with the new data. Hopefully this is a trivial problem...?!
KnockoutJS Mapping has the ability to specify a key for an item. This lets KnockoutJS know when an item needs to be created versus updated.
var ItemViewModel = function(d){
var self = this;
self.id = ko.observable(d.id);
self.text = ko.observable(d.text);
// We assign it when the object's created to demonstrate the difference between an
// update and a create.
self.lastUpdated = ko.observable(new Date().toUTCString());
}
var EditorViewModel = function(){
var self = this;
self.items = ko.observableArray();
self.addData = function(d){
ko.mapping.fromJS(d, {
'items': {
'create': function(o){
return new ItemViewModel(o.data);
},
'key': function(i){
return ko.utils.unwrapObservable(i.id);
}
}
}, self);
console.info(self);
}
self.addMoreData = function(){
self.addData({
'items': [
{id:1,text:'Foo'},
{id:3,text:'Bar'}
]
});
}
}
var viewModel = new EditorViewModel();
viewModel.addData({
items:[
{id:1,text:'Hello'},
{id:2,text:'World'}
]
});
ko.applyBindings(viewModel);
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Hello, world!</h3>
</div>
<ul class="list-group">
<!-- ko foreach:items -->
<li class="list-group-item">
<span class="text-muted" data-bind="text:id"></span>
<span data-bind="text:text"></span>
<span class="text-danger pull-right" data-bind="text:lastUpdated"></span>
</li>
<!-- /ko -->
<li class="list-group-item" data-bind="visible:!items().length">
<span class="text-muted">No items.</span>
</li>
</ul>
<div class="panel-footer">
Update
</div>
</div>
</div>
</div>
</div>
Observe, in the attached snippet, how the first item's lastUpdated value is assigned when it's created, but is left unchanged when it's updated. This is because the mapper sees the ID already exists in items and simply updated the properties. But, for item with the id 3, a new model is created.
All knockout bibdings were subscribed to the first created model. You recreate the bound model on every ajax response. Your problem seems similar that was discussed in this stackoverflow article. The same osbervable should be bound to the markup.
Try this (may be you should support an empty model binding):
// in the start of app
var observableModel = ko.observable();
ko.applyBindings(observableModel);
// on every ajax call
var viewModel = new EditorViewModel();
viewModel.addData(model);
observableModel(viewModel);

AngularJS - Cannot set scope value into template

I have following function:
$scope.setDetailToScope = function(data) {
$scope.$apply(function() {
//$scope.order = data;
$rootScope.order = data;
setTimeout(function() {
$scope.$apply(function() {
//wrapped this within $apply
$scope.order = data[0];
console.log('message:' + $scope.order);
console.log($scope.order);
});
}, 100);
});
};
"
console.log($scope.order);
Gives me values which has been set into scope.
But i cannot get these values in template.
<!-- DEBUG DIV -->
<div class="debugDiv" ng-show="$root.debugable == true">
{{columns}}
</div>
<div data-ng-controller="OrdersCtrl" ng-init="initData()">
<div id="orders_grid" >
</div>
</div>
<!-- GRID TOOLBAR BUTTON TEMPLATE -->
<script id="template" type="text/x-kendo-template">
<a class="k-button" href="\#/orders/create">Add</a>
</script>
<!-- ORDER DETAIL DIV -->
<div class="container" id="orderDetail" data-ng-controller="OrdersCtrl" ng-if="'detailSelected == true'" xmlns="http://www.w3.org/1999/html">
<!-- DEBUG DIV -->
<div class="debugDiv" ng-show="$root.debugable == true">
{{order}} <!--NOT WORKING-->
</div>
If i tried to add values into rootscope it works, but in this case i cannot get value into ng-model.
What i'm doing wrong please?
Many Thanks for any help.
EDIT:
If i tried solution wit $timeout i got on console.log($scope.order);
following object which is not passed into the template:
_events: ObjectacrCrCode: "interlos"actionName: ht.extend.initarchived: falsebaseStationInfo: ht.extend.initbsc: "bsc1"btsRolloutPlan: "plan1"candidate: "B"costCategory: ht.extend.initcreatedBy: ht.extend.initdirty: falsefacility: ht.extend.initid: 3location: ht.extend.initmilestoneSequence: undefinednetworkType: "Fix"note: "poznamka"orderNumber: 111113orderType: ht.extend.initotherInfo: ht.extend.initparent: function (){return e.apply(n||this,r.concat(h.call(arguments)))}partner: ht.extend.initpersonalInfo: ht.extend.initproject: ht.extend.initpsidCode: "psid1"sapSacIrnCode: "sap1"uid: "924c0278-88d0-4255-b8ac-b004155463fa"warehouseInfo: ht.extend.init__proto__: i
well I'm not sure why you are using a setTimeout with scope apply, to me is safer to use a $timeout since it fires another digest cycle,
try something like
$scope.setDetailToScope = function(data) {
$timeout(funtion(){
//$rootScope.order = data; try either data or data[0]
$scope.order = data[0];
},100);
};
please note that calling nested apply methods can run into some problems with the angularjs digest cycle you may get an error like "digest already in progress" so put attention to it.
NOTE:
it seems like you got some dirty data there, so try to do a map between the data and the scope
$scope.order ={};
$scope.order.uid = data.uid;
$scope.order.orderNumber = data.orderNumber //and so on
in your template try something like:
<div class="debugDiv">
<p> {{order.uid}} </p>
<p> {{ order.orderNumber}} </p>
</div>
this could be a little bit rustic but it worth to try it out.

Show default value when Knockout observable undefined or JS disabled

Using Knockout.js, is there a way to have an element's original content show if the observable bound to it is undefined?
<p data-bind="text: message">Show this text if message is undefined.</p>
<script>
function ViewModel() {
var self = this;
self.message = ko.observable();
};
ko.applyBindings(new ViewModel());
</script>
I know there are workarounds using visible, hidden or if but I find those too messy; I don't want the same element written out twice, once for each condition.
Also, I don't want to use any sort of default observable values. Going that route, if JS is disabled then nothing shows up. Same for crawlers: they would see nothing but an empty <p> tag.
To summarize, I want to say "Show this message if it exists, otherwise leave the element and its text alone."
The reasoning behind this is that I want to first populate my element using Razor.
<p data-bind="text: message">#Model.Message</p>
And then, in the browser, if JS is enabled, I can do with it as I please. If, however, there is no JS or the user is a crawler, they see, at minimum, the default value supplied server side via Razor.
You can simply use the || operator to show a default message in case message is undefined. Plus put the default text as content:
<p data-bind="text: message() || '#Model.Message' ">#Model.Message</p>
If javascript is disabled, the binding will be ignored and you will have the content displayed instead.
JSFiddle
Try this out
<p data-bind="text: message"></p>
<script>
function ViewModel() {
var self = this;
self.text = ko.observable();
self.message = ko.computed(function(){
if(self.text() != undefined){
return self.text();
}else{
return "Show this text if message is undefined.";
}
});
};
ko.applyBindings(new ViewModel());
</script>
You can set a default value like that in a number of ways, simplest way is to set the default observable value, as your JS file will be incorporated in to your HTML and will be able to access #Model.Message, so you can set a default value:
self.message = ko.observable(#Model.Message);
Here are a few other variations:
var viewModel = {
message: ko.observable(),
message1: ko.observable('Show this text if message is undefined.')
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h2>Option 1</h2>
<i>Set default value of observable: self.message1 = ko.observable(#Model.Message);</i>
<p data-bind="text: message1"></p>
<h2>Option 2</h2>
<i>Check for undefined and replace</i>
<p data-bind="text: message() ? message : message1"></p>
<h2>Option 3</h2>
<i>Use KO if syntax to display content if defined/undefined</i>
<!-- ko if: message() -->
<p data-bind="text: message"></p>
<!-- /ko -->
<!-- ko ifnot: message() -->
<p data-bind="text: message1"></p>
<!-- /ko -->

Accessing objects within viewmodel Knockout

I'm using knockout for a single page app that does some basic calculations based on several inputs to then populate the value of some html . In an attempt to keep my html concise I've used an array of objects in my viewModel to store my form. I achieved the basic functionality of the page however I wish to add a 'display' value to show on html that has formatted decimal points and perhaps a converted value in the future.
I'm not sure of a 'best practices' way of accessing the other values of the object that I'm currently 'in'. For example: If I want my display field to be a computed value that consists of the value field rounded to two decimal places.
display: ko.computed(function()
{
return Math.round(100 * myObj.value())/100;
}
I've been reading through the documentation for knockout and it would appear that managing this is a common problem with those new to the library. I believe I could make it work by adding the computed function outside of the viewmodel prototype and access the object by
viewModel.input[1].value()
However I would imagine there is a cleaner way to achieve this.
I've included a small snippet of the viewModel for reference. In total the input array contains 15 elements. The HTML is included below that.
var ViewModel = function()
{
var self = this;
this.unitType = ko.observable('imperial');
this.input =
[
{
name: "Test Stand Configuration",
isInput: false
},
{
name: "Charge Pump Displacement",
disabled: false,
isInput: true,
unitImperial: "cubic inches/rev",
unitMetric: "cm^3/rev",
convert: function(incomingSystem)
{
var newValue = this.value();
if(incomingSystem == 'metric')
{
//switch to metric
newValue = convert.cubicinchesToCubiccentimeters(newValue);
}
else
{
//switch to imperial
newValue = convert.cubiccentimetersToCubicinches(newValue);
}
this.value(newValue);
},
value: ko.observable(1.4),
display: ko.computed(function()
{
console.log(self);
}, self)
}
]
};
__
<!-- ko foreach: input -->
<!-- ko if: $data.isInput == true -->
<div class="form-group">
<div class="col-sm-6">
<label class="control-label" data-bind="text: $data.name"></label>
</div>
<div class="col-sm-6">
<div class="input-group">
<!-- ko if: $data.disabled == true -->
<input data-bind="value: $data.value" type="text" class="form-control" disabled>
<!-- /ko -->
<!-- ko if: $data.disabled == false -->
<input data-bind="value: $data.value" type="text" class="form-control">
<!-- /ko -->
<!-- ko if: viewModel.unitType() == 'imperial'-->
<span data-bind="text: $data.unitImperial" class="input-group-addon"></span>
<!-- /ko -->
<!-- ko if: viewModel.unitType() == 'metric' -->
<span data-bind="text: $data.unitMetric" class="input-group-addon"></span>
<!-- /ko -->
</div>
</div>
</div>
<!-- /ko -->
<!-- ko if: $data.isInput == false -->
<div class="form-group">
<div class="col-sm-6">
<h3 data-bind="text: $data.name"></h3>
</div>
</div>
<!-- /ko -->
If you want to read/ write to & from the same output, #Aaron Siciliano's answer is the way to go. Else, ...
I'm not sure of a 'best practices' way of accessing the other values of the object that > I'm currently 'in'. For example: If I want my display field to be a computed value that consists of the value field rounded to two decimal places.
I think there's a misconception here about what KnockoutJS is. KnockoutJS allows you to handle all your logic in Javascript. Accessing the values of the object you are in is simple thanks to Knockout's context variables: $data (the current context, and the same as JS's this), $parent (the parent context), $root(the root viewmodel context) and more at Binding Context. You can use this variables both in your templates and in your Javascript. Btw, $index returns the observable index of an array item (which means it changes automatically when you do someth. wth it). In your example it'd be as simple as:
<span data-bind="$data.display"></span>
Or suppose you want to get an observable w/e from your root, or even parent. (Scenario: A cost indicator that increases for every item purchased, which are stored separately in an array).
<span data-bind="$root.totalValue"></span>
Correct me if I'm wrong, but given that you have defined self only in your viewmodel, the display function should output the whole root viewmodel to the console. If you redefine a self variable inside your object in the array, self will output that object in the array. That depends on the scope of your variable. You can't use object literals for that, you need a constructor function (like the one for your view model). So you'd get:
function viewModel() {
var self = this;
self.inputs = ko.observableArray([
// this builds a new instance of the 'input' prototype
new Input({initial: 0, name: 'someinput', display: someFunction});
])
}
// a constructor for your 15 inputs, which takes an object as parameter
function Input(obj) {
var self = this; // now self refers to a single instance of the 'input' prototype
self.initial = ko.observable(obj.initial); //blank
self.name = obj.name;
self.display = ko.computed(obj.fn, this); // your function
}
As you mentioned, you can also handle events afterwards, see: unobtrusive event handling. Add your event listeners by using the ko.dataFor & ko.contextFor methods.
It appears as though KnockoutJS has an example set up on its website for this exact scenario.
http://knockoutjs.com/documentation/extenders.html
From reading that page it looks as though you can create an extender to intercept an observable before it updates and apply a function to it (to format it for currency or round or perform whatever changes need to be made to it before it updates the ui).
This would probably be the closest thing to what you are looking for. However to be completely honest with you i like your simple approach to the problem.

Categories

Resources