When I use knockout-secure-binding with ko, and I try to use ternary operators inside data-bind, an error occurs.
Sample code:
<div data-bind="attr: { class: itemSelected() ? 'selected' : 'unselected' }"></div>
KSB init:
var options = {
attribute: "data-bind", // default "data-sbind"
globals: window, // default {}
bindings: ko.bindingHandlers, // default ko.bindingHandlers
noVirtualElements: false // default true
};
ko.bindingProvider.instance = new ko.secureBindingsProvider(options);
Error:
Uncaught #<Object> knockout-secure-binding.js: 4
If I use the regular binder, without including the secure one, everything works as expected. Why is this happening? What can I do to keep using secure binding?
It's natural that it doesn't work. If you see the expressions section of the specs of sbind, , which is the language that you can use in data bindings when you use the secure bind binder, you'll see that the ternary operator isn't in the list.
You'll have to use a computed or pure computed that evaluates that expression, and bind the computed instead of the expression.
The computed could be like this:
self.selClass = ko.pureComputed(function() {
return self.itemSelected() ? 'selected' : 'unselected';
}, self);
NOTE: you can use a pure computed. This example shows the case of a view model created with a constructor that uses the self=this pattern. If you use other patter for your view model, like RMP, the computed will be slightly different
So, the binding woul be this one:
<div data-bind="attr: { class: selClass }"></div>
which is a valid sbind expression.
Related
I am aware of the JavaScript ternary operator condition? optionA : optionB and now I am seeing {{option: condition}} in Vue, and the behaviour seems to be that if condition holds the assignment is option, otherwise the assignment is empty. Is this 'binary' operator : something general for JS or particular to Vue?
The actual code:
<span :class="{ done: todo.done }">{{ todo.text }}</span>
where
.done { text-decoration: line-through; }
is a CSS and todo.done is a boolean.
It's not an operator but regular object literal syntax. Vue class attribute supports objects for conditional class lists, where boolean value determines whether class name from a key is added to a list or omitted.
This behaviour is similar to popular classnames package that serves the same purpose in React ecosystem.
If you wanted a binary operator, you should choose ||. This object syntax is not specific to vue but the usage is: What this code is saying is "Set the attribute done of the object bound to the class to todo.done, and vue will infer to add or not to add the class of the object parameter (in your case it is done).
I've come across this way of isolate binding specification:
scope: {
property: "=*"
}
What does the asterisk mean here? Can someone please provide an example?
The isolate binding with =* is to shallow watch for the change in the collection.
Let me explain a bit:
Normally watchCollection variables we use in the scripts like below:
$scope.arr = ['foo', 'bar', 'lorem', 'ipsum'];
$scope.arrCount = 4;
$scope.$watchCollection('arr', function(new, old) {
$scope.arrCount = new.length;
});
But what if you want to do object binding in html attribute itself?
<my-directive my-attr="arr"><!--$scope.arr-->
And, if you do this:
scope: {
myAttr: "=*"
}
Now, the directive attributes are assigned as it should shallow watch. And the use of watchCollection is good of use this time.
So, the =* is to be used when we need to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use =* or =attr (=? or =*?attr if the property is optional) as described in the docs.
Although, we can use = for watchCollection to deeply watch the object or array we can use =* for the same. But only difference is that using =* method the collection becomes true and when collection becomes true angularjs use the $watchCollection to remove the watcher and it uses the $watch to remove the watcher when collection is false:
if (definition.collection) { //using `=*`
removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
} else { //using `=`
removeWatch = scope.$watch($parse(attrs[attrName],
parentValueWatch), null, parentGet.literal);
}
So, when you want to shallow watch for the collection then you need to use =*.
The asterisk is used to change the default watch behavior.
From the angular docs:
By default, the $watch method is used for tracking changes, and the equality check is based on object identity. However, if an object literal or an array literal is passed as the binding expression, the equality check is done by value (using the angular.equals function). It's also possible to watch the evaluated value shallowly with $watchCollection: use =* or =attr (=? or =*?attr if the attribute is optional).
According to Doc:
= or =attr - sets up a bidirectional binding between a local scope property and an expression passed via the attribute attr. '
By default $watch method is used for tracking changes, and the equality check is based on object identity.
In the case of =* is used to watch evaluated value shallowly using $watchCollection.
There is difference in evaluation using $watch vs $watchCollection.
The following link gives down the difference between them.
In Polymer 0.5, one could use the tokenList filter with expressions on an elements class attribute to apply classes conditionally based on object values. What is the v1.0 replacement or equivalent technique? I can't find anything on the subject beyond handling it entirely in code.
Polymer 1.0 made quite a few cuts in favor of performance gains, expressions being one of those.
Using the example from the 0.5 documentation:
<div class="{{ {active: user.selected, big: user.type == 'super'} | tokenList}}">
You could re-write for 1.0 like so:
<div class$="{{getClassList(user.selected, user.type)}}">
Then in your element's js:
getClassList: function(selected, type) {
var classList = '';
if (selected) classList += ' active';
if (type == 'super') classList += 'big';
return classList;
}
Make sure that any properties that are subject to change (and that the resulting value depends on) are passed as parameters to the function. If these properties are updated, polymer will re-compute the value. Also make sure that each property passed to the function are initialized in some way -- Polymer won't compute the property if any arguments are undefined.
Another thing to be aware of is that any occurrence of {{}} must take up the whole attribute or text content, so you can't have things like class="foo {{bar}}". If you need to declaratively add a class name to your element, you could do something like this:
<div class$="{{getClassList('user-item', user.selected, user.type)}}">
I saw others' solution but mine didn't work, probably there are snytax error because I'm applying multiple class.
ng-class="{'col-md-9 col-sm-8 col-xs-8':item.lp_image!='', item.lp_image='','col-md-12'}"
What you are defining in the ng-class attribute is a JavaScript object. You can define several rules, just like you define several properties of an object. Each key is a class, each value is a boolean or an expression that gives a boolean.
E.g.
{
'class1': false,
'class2': true,
'class3': 2 == 2,
'class4': 3.1415 > 0
}
I am a beginner in Knockout and I must say I often get confused regarding when to use (). Is there any general tip/trick regarding when would you use () against when you would not because at the moment I am just doing trial and error. If the binding throws error or doesn't update values I remove () else I put.
I feel like the existing answers skip over a very important point of confusion: data-bind attributes.
It is true that you use the parens when you are in Javascript, and getting or setting observables. But when you are writing data-bind="text: property", you leave out the parens even when working with observables.
Edit
As noted in the comment below, bindings that are expressions, or access properties of observbles, require parens
visible: personName().length > 0
visible: person().Name().length > 0
visible: person().isVisible
Note that the last one person and isVisisble are both observables, but the last property doesn't use parens! The reason for this is that we would be passing a value to the binding instead of an observable, and it wouldn't update.
You use () in knockout when using observables or when executing any other method.
Knockout observables are functions, invoked to return you what you looking for or allow you to assign new values.
In knockout you use object.property() to retrieve a value and object.property(newValue) to assign a value to that property.
On the knockout website checkout the documentation, specifically the section on observables, which shows you the use of the () when querying and writing observables.
To quote:
var myViewModel = {
personName: ko.observable('Bob'),
personAge: ko.observable(123)
};
To read the observable’s current value, just call the observable with no parameters. In this example, myViewModel.personName() will
return 'Bob', and myViewModel.personAge() will return 123.
To write a new value to the observable, call the observable and pass the new value as a parameter. For example, calling
myViewModel.personName('Mary') will change the name value to 'Mary'.
To write values to multiple observable properties on a model object, you can use chaining syntax. For example,
myViewModel.personName('Mary').personAge(50) will change the name
value to 'Mary' and the age value to 50.
Knockout's interactive tutorial is also quite nice and well worth going through.
Basically whenever you're working with an observable value (array or otherwise) you should use the parentheses to get the value and set the value.
var something = ko.obserbable();
something(5); //set value
console.log(something()); //get value: 5
The reason being that most JS implementations do not support getters and setters for properties yet, so observables were implemented like this to get around this limitation.