(Using KnockoutJs 2.0.0)
I have a list of phone numbers in my viewmodel. Each phone number has a type (home, work, mobile, etc). I want to display an icon (based on a fontawesome class) next to each phone number.
If I hardcode the icons in the css binding, everything works:
<tbody data-binding="foreach: phoneList">
<tr>
<td><span data-bind="css: {'icon-home' : TypeId() == 1, 'icon-building': TypeId() == 2, ... , 'icon-phone': TypeId() >= 7></span></td>
...
</tbody>
I wanted to replace the hardcoded list with a call to a function. I initially tried adding the function to the parent but had no success, so then I tried adding the function directly to the phone object itself both as a function and as a ko.computed() -- but neither of these work for me.
I've dummied up some code here that demonstrates the problem. If you inspect the span element of the table items, you'll see that it almost appears as if the data-biding is treating the returned string as an array of characters and setting the class based on indexes rather than treating the returned string as a class.
I'm sure this is something completely obvious, but I've been beating my head to no avail.
A computed observable should work just fine. The problem is what what you're returning from that computed observable. You need to return the definition of classes in the same format as the hard-coded version:
me.getClass = ko.computed(function() {
return me.typeId() == 1 ? { 'mobile': true } : { 'business': true };
});
See the updated version here: http://plnkr.co/edit/qDjgMlZpXHjn5ixY3OCt
Or, you could define a custom binding to clean up the computed function a bit, though it should be noted that in this case all classes will be replaced by the output of the binding. This is probably not necessary in Knockout 3.0.0, as alluded to in the comments and other answers.
Binding:
ko.bindingHandlers.setClass = {
update: function(element, valueAccessor, allBindings) {
var value = ko.utils.unwrapObservable(valueAccessor());
element.className = value;
}
};
Observable:
me.setClass = ko.computed(function() {
return me.typeId() == 1 ? "mobile" : "business";
});
HTML:
<td data-bind="setClass: setClass, text: typeId"></td>
A version using a custom binding is here: http://plnkr.co/edit/ryaA4mIf7oh5Biu8bKj0?p=info
Fix
Example
I updated your version of KO to 3.0.
Next, I changed your ko.computed binding for getClass from:
me.getClass = ko.computed(function() { return me.typeId == 1 ? "mobile" : "business"; });
to:
me.getClass = ko.computed(function() { return this.typeId() == 1 ? "mobile" : "business"; }, me);
Note
There may be a way to do this with KO 2.0, but I couldn't find documentation for previous versions. I imagine the issue is related to syntax if the feature exists.
An alternate way to do this is use an attr data-bind, instead of using a custom binding handler to set the class on the element.
So, you would still need to use a computed to set the observable:
me.setClass = ko.computed(function() {
return me.typeId() === 1 ? "mobile" : "business";
});
Then use an attr binding to set the class on the html element:
<td data-bind="attr: { class: setClass }, text: typeId"></td>
Related
I read VueJs docs, It recommends to use computed property to calculate or toggle an element class, but I couldn't do it inside v-for loop. What I have done instead is this:
Template
<li v-bind:click="onClick(word)" v-bind:class="calculateClass(word)" v-for="word in words">{{ word.name }}</li>
Code
data: {
words : [{name:'ali', clicked : 1},{name:'sara', clicked : 0},{name:'marya', clicked : 1}]
},
methods: {
calculateClass : function (word) {
return {
"classA": word.clicked=== 1,
"classB" : word.clicked === 0,
'test' : true // allways return 'test' class
}
},
onClick: function (word) {
// changing the `clicked` property of related object in this.words array
for (var i in this.words) {
if (this.words[i].name === word.name) {
this.$set(this.words[i], 'clicked', 1)
break; //Stop this loop, we found it!
}
}
}
},
It is working, but is there a problem with this approach? I didn't see other examples using a method to calculate a class. Should I do this with computed? How? Is there a better way?
You are correct that a computed property cannot accept an argument and, as such, doesn't really fit in this case.
There is nothing wrong with using a method the way you are.
I'm new to using Knockout and am doing a very basic implementation that changes the color with a observable. Is there a cleaner way to write the following code?
<div class="selected" data-bind="style: { background: fullHexCode(mainScreenNavigationSelector()) !== false ? fullHexCode(mainScreenNavigationSelector()) : 'white' }"></div>
I have this in multiple spots on my page and they all use different params for the fullHexCode() function. It looks extremely messy. Any help would be great, thanks!
It looks like the logic depends on another observable so you could use a computed observable -- in the snippet below the backgroundColor computed observable depends on the mainScreenNavigationSelector observable.
That's just a simple example, you'll have to adjust it to your specific situation.
var MyViewModel = function () {
this.mainScreenNavigationSelector = ko.observable(false);
this.backgroundColor = ko.computed(function() {
return this.mainScreenNavigationSelector() ? 'green' : 'red';
}, this);
this.toggleColor = function() {
this.mainScreenNavigationSelector(!this.mainScreenNavigationSelector());
}
}
var viewModel = new MyViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="selected" data-bind="style: { 'background-color': backgroundColor }">
TEST
</div>
<button data-bind="click: toggleColor">Toggle Color</button>
You may deduplicate your HTML code by defining methods in your viewmodel. Named computeds are even better as they are naturally memoized, that is evaluated only once if used repeatedly in your HTML.
You may also factorize heavy expressions as with: or let: bindings in the parent node. For example: <div class='some-container' data-bind="let: { v: mainScreenNavigationSelector() }">... bindings based on v here... </div>.
Notice: let is better than with for this purpose. But it's a new binding which will be available in the next release of KO. You can polyfill it with a custom binding.
When JS expressions can't be avoided in your HTML code, try to make them as slick as possible. For example:
<div data-bind="style: {
background: fullHexCode(mainScreenNavigationSelector()) || 'white'
}"></div>
In Javascript, logical operator doesn't return true or false but the actual value of the last evaluated arguments. So:
a || b return a if not "falsy", otherwise b
a && b return b if a not "falsy", otherwise a
The last idiom is useful in KO bindings because contrary to Angular, KO bindings are regular JS expressions. They fail if some null/undefined occurs in a dot sequence (like a.b if a is undefined).
So instead of some tertiary operator hell like data-bind="text: object != null ? (object.a != undefined ? object.a : 'None') : 'None'", just write data-bind="text: object && object.a || 'None'"
Also [] and {} are not falsy, and it's actually a good thing. It allows to write things like data-bind="foreach: foo.optionalArray || ['default', 'stuff']"
However, Number(0) is false. So beware of something like data-bind="with: object.id && 'saved' || 'new'" which may not work as expected if 0 is a valid object id.
Also this last trick. If data-bind="text: name" fails because name is undefined , then "data-bind="text: name || 'anonymous'" will still fail, but "data-bind='text: $data.name || 'anonymous'" will work as expected. As a convention, I write $data.attribute instead of attribute to convey the info about dealing with a optional attribute.
I have one set of JSON data consisting of the following two objects:
{
"date":"20160118",
"entity":"01",
"security":{
"securityID":"191457",
"identifiers":[
{
"value":"345342532",
"type":"fii"
},
{
"value":"32453452",
"type":"isin"
},
{
"value":"48125D886",
"type":"cusip"
}
]
}
}
And:
{
"date":"342534543",
"entity":"01",
"security":{
"securityID":"3425435",
"identifiers":[
{
"value":"32543543",
"type":"fii"
}
]
}
}
I am creating a table using only AngularJS expressions and HTML.
I'm running into trouble when accessing security.identifiers[#].value. Depending on how many identifiers a security has - they may be in different array positions - meaning an "fii" could end up in the same column as a "cusip" for the previous row. Is there a way within my angular expression I can search through an array and find a string? In this case identifiers.type will be the key to knowing which column it is placed in. I've tried looping through in my javascript and providing an index to my expression, but I seem to be running into closure issues - I'm hooping there is a simpler way.
So far I have this for my identifiers columns - I'm aware they are wrong but hopefully will give you an idea of what I'm trying to do.
<td>{{data.security.identifiers.type === "fii" ? data.security.identifiers.value : ""}}</td>
<td>{{data.security.identifiers.type === "isin" ? data.security.identifiers.value : ""}}</td>
<td>{{data.security.identifiers.type === "cusip" ? data.security.identifiers.value : ""}}</td>
I can understand what do you want to do, the below code could work like you expect.
<td>{{data.security.identifiers.filter(function(v){return v.type == 'fii'})[0].value}}</td>
And to improve you can create a function to do the filter (probably in the Controller or Service):
$scope.identifierFilter = function(type) {
var filtered = this.data.security.identifiers.filter(function(v) {
return v.type == type;
});
return filtered && filtered[0] ? filtered[0].value : '';
};
and in the html
<td>{{identifierFilter('fii')}}</td>
I'm look at your example, I think the above code would work.But If you can change your JSON structor it will be more better and easy.
"identifiers":{
fii : 'dfdfdf',
isin : '32453452',
cusip: '48125D886'
}
html can simple like
<td>{{data.security.identifiers.fii}}</td>
And if you want to do this transform, I can give you further answer.
with could help
I update your example, sorry about some syntax error about above code.
please check: https://plnkr.co/edit/NgvJKrfOGmKHfOap08xU?p=preview
This is reconstructor data before template use.It's better if the data structor can change, choice which you like.
https://plnkr.co/edit/7eHAvSaBcOiTajOomNEs?p=preview
Probably, your best option is to use a directive, or filter depending on your requirements. A directive will make your mark-up clear and you will encapsulate the functionality you need. For example.
<table border="1" style="width:100%">
<tr>
<th>Entity</th>
<th>SecurityID</th>
<th>Security1</th>
<th>Security2</th>
<th>Security3</th>
<th>date</th>
</tr>
<tr ng-repeat="data in JSONData">
<td>{{data.entity}}</td>
<td>{{data.security.securityID}}</td>
<td><identifier identifiers="data.security.identifiers" render-type="fii"></identifier></td>
<td><identifier identifiers="data.security.identifiers" render-type="isin"></identifier></td>
<td><identifier identifiers="data.security.identifiers" render-type="cusip"></identifier></td>
<td>{{data.date}}</td>
</tr>
</table>
The plunker:
https://plnkr.co/edit/6VJuQob1jfDzsR8XPKKM?p=preview
Hope this helps.
Short Question:
How to create a <input type="text"> which contains a custom-format string serialization of an object in a way that editing the string updates the model and vice versa?
I think AngularJS’ directives are the way to go, but i can’t get it pinned down.
Long Question:
Prequel
I have a object which is my application’s “master model”. it can be serialized to a string of a specific format:
it has 2-3 attributes, whose serializations are joined by “;” (no trailing “;” if the third is missing)
attributes 2 and 3 are lists of objects, and serialized by joining those with “,”.
the serialization of the objects is just one of their string attributes, or two ow them with “x” between.
so i have a constructor (accepting a spec string), and a toString function. Following; the latter for clarity:
World.prototype.toString = function() {
var spec = [];
spec[0] = this.version;
spec[1] = this.layers.map(function(layer) {
var c = (layer.c > 1) ? layer.c + 'x' : '';
return c + layer.id; //e.g. 'T' or '2xT'
}).join(',');
//spec[2] in python: ','.join(option.id for option in options if option.checked)
var options = this.options.filter(function(option) {
return option.checked;
});
if (options.length > 0)
spec[2] = options.map(function(option) {
return option.id;
}).join(',');
return spec.join(';');
};
The directive i tried to use looks thusly, but the $watch only fires once.
angular.module('layersApp', []).directive('spec', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.$watch('world', function(val) {
element.val(val.toString());
console.log('object updated', element.val());
}, true);
element.blur(function(e) {
scope.world = new World(element.val());
});
}
};
});
Actual long question
What i want is an easy way to make this work,
<input type="text" data-ng-model="theWorld" spec>
where spec is the custom directive shown above, setting up the two-way binding
Outlook
it would be awesome if this could result in a generic “serialization” directive used like that:
<input type="text" data-serialization="string2Foo, foo2String" data-ng-model="foo">
Which would look up the object foo, and the functions string2Foo and foo2String to setup custom (de)serialization.
I think you can use of $parsers and $filters of ngModel controller.
Here is the simplest example of doing it.
http://plnkr.co/edit/13PJN2
It should be easy to add validation, too.
I tried to make it accept custom serializer from parent scope, but failed to do so. Not sure about it.
I'm using ExtJs 3.4 and have the following code to create a hidden field:
box.hidden = this.el.insertSibling({
tag: 'input',
type: 'hidden',
value: itemVal,
name: (this.hiddenName || this.name)
}, 'before');
However, when itemVal is a json-string (or a string with quotation characters) it creates an element that looks like:
<input type="hidden" value="[" 635f7ede-7add-415f-8461-548d17027cac.group","bbe2x:101"]"="" name="selector_account_ef8e33ca71e749dca21997f51b404e23" id="ext-gen1766">
The problem is that it cocatenates the html for performance. So I want to, in this case, create the element by setting Ext.DomHelper.useDom to true. Should be an easy fix, right? But the inner code that checks the useDom variable checks against the private object that is passed to Ext.apply function instead of using Ext.DomHelper.useDom. So it doesn't matter if i set Ext.DomHelper.useDom to true inside the function that checks it, it is never true. Se the ExtJs code here:
http://docs.sencha.com/ext-js/3-4/source/DomHelper-more.html
// private
function doInsert(el, o, returnElement, pos, sibling, append){
el = Ext.getDom(el);
var newNode;
if (pub.useDom) {
...
} else {
...
}
return returnElement ? Ext.get(newNode, true) : newNode;
}
I found an old bug report for this that was closed, (http://www.sencha.com/forum/showthread.php?76966-CLOSED-3.0.0-DomHelper-s-useDom-bug) but I don't understand why and HOW I can set useDom to true.
Of course it's simple to fix it by replacing " to " but I want to understand it.