I started working with knockout a few months ago and so far it is being a very good road. Today when I was working with some inputs in my html I came across a very boring issue that took me a while to figure out. Here is my code:
<div class="add-box" style="display:none;" id="new-user">
<textarea placeholder="Name" data-bind="value : name"></textarea>
</div>
<script>
function UserViewModel() {
var self = this;
self.name= ko.observable('');
}
$(document).ready(function () {
ko.applyBindings(new UserViewModel(), document.getElementById('new-user'));
})
</script>
This code works fine, but the first time that I did was like this:
<textarea placeholder="Name" data-bind="value : name()"></textarea>
The only difference between them are the parenthesis () at the end of the name property. Since this is a observable one I thought that the parenthesis would be necessary in order to make the 2-way-binding. But with them, whenever I change the value of the textarea the viewmodel is not update accordingly, if I remove everything works.
Could you explain why on this case I have to remove the parenthesis, and why in other scenarios, like when I used data-bind="text: I have to put them??
Here is the magic with KO: special "Observable" function-objects.
When you use parenthesis, you evaluate the observable (which is just a special function) which results in a value that breaks "live" data-binding: in this case the underlying value (say, a string) is bound, but not the observable from which the value was obtained.
The underylying bindings are (usually) smart enough to deal with both observables and non-observable values. However, bindings can only update observables and can only detect Model changes through observables.
So, usually, do not include parenthesis when using obervables with declarative data-binding.
Passing the observable will make sure the Magic Just Works and allow the View and Model to stay in sync. Changes to said bound observable will trigger the appropriate binding update (e.g. so that it can update the HTML) even if the binding does not itself need to update the observable/Model.
However, in some rarer cases, you just want the value right then and you never want the binding to update from/to the Model. In these rarer cases, using parenthesis - to force value extraction and not bind the observable itself - is correct.
In my case I was using jquery.tmpl ,
and knockout 2.2.0 works with jquery.tmpl, when I upgrade to knockout 3.0, I got this problem
when I use this one, It somehow get conflict with Knockoutjs builtin template/
Removing jquery.tmpl.js resolves my problem.
Related
I have an angular page, where, during an *ngFor loop, I want to update a variable, then write it to the HTML during each iteration of the loop.
Like so:
HTML:
<table *ngFor="let data of Dataset">
somehowRunThis(data)
<div>{{methodResult}}</div>
</table>
TS:
...
methodResult: any;
...
somehowRunThis(data): {
let a;
...
this.methodResult = a;
}
etc etc.
Is there any way this can work? Attempting to add a method that returns within the curly brackets seems to not work, and there appears to be no effective way to run arbitrary methods from the HTML in Angular.
Thank you for any assistance you can provide.
Is there any particular reason why you want to trigger this update in HTML?
Depending on your needs you can use pipe (https://angular.io/guide/pipes) or transform the data to desired format in your component.
I would say it's not a good idea to have a method with side-call effects invoked in HTML.
There are a lot of ways to do this. A general advice: sometimes we are looking for an answer in the wrong places, be open :)
Instead of forcing ngFor, just run a simple array.map on your data before sending it to the template.
displayData = this.data.map(el => this.somehowRunThis(el))
this way you'll avoid having terrible performance.
If you don't care and still want to do this thing for some reason you can make your function return it and actually call in template:
{{ myFunctionReturnsText() }}
This is a bad idea because the function calls will run on each change detection so something like Pipes/Directives will be better.
I was trying to figure out how to access both a v-for member and the original event data to a bound function on an event in vue.js, but couldn't for the life of me find it in the documentation.
What I want:
<div class="card pickup" v-for="pickup in pickups">
<select v-on:change="locationChanged">
with locationChanged calling the following method in my vue object:
vuePickup = new Vue({
el: '#pickups',
data: {
pickups: pickups,
},
methods: {
locationChanged: (event, pickup) => {
pickup.newLocation = event.target.value == -1;
}
},
});
which requires access to both the element in question and the object that was bound to that portion of the v-model.
By default, using v-on:change="locationChanged" calls locationChanged with the event, and the element can be reached via event.target but I couldn't find a way to access the pickup object I was binding the particular element to.
Using v-on:change="locationChanged(pickup)" instead causes locationChanged to be called with the model I need, but I then lose the reference to the HTML element in question.
What I ended up doing was setting up a local function in the model itself to forward the values:
v-on:change="e => locationChanged(e, pickup)"
which provides me with the information I need.
However, I feel that this is not the correct approach as a) I couldn't find it in the documentation, and b) it's not the most user friendly.
#Lawless pointed me to an existing question by another user who was wondering more generally how to access the event (as compared to my goal of accessing the event target, i.e. the element that triggered the event), but my question about the idiomatic approach here remains as this is a very basic question (callbacks for enumerated elements) and yet both solutions (mine and the linked question) are not very obvious and not covered in the vue.js manual.
Is there a more idiomatic approach prefered by the vue.js community?
Another question on StackOverflow (pointed out to me by #Lawless) was asking the same question I started off with (namely, how to access the event data in a binding for an enumerated element rather than one associated with a top-level vue.js object), providing another, more direct (but imho even more arcane) approach to accomplishing the same.
To recap, starting with an HTML element generated by vue.js bound to a vuejs object obtained via enumeration
<div class="card pickup" v-for="pickup in pickups">
<select v-on:change="locationChanged">
...
</select>
</div>
How do you access both the backing object (here, pickup) and a reference to the select element so you can query its new value and act accordingly.
My approach was to take advantage of the fact that vuejs passes in the event by default and work around the fact that vuejs suppresses the event parameter if you explicitly provide a local object as the parameter in the callback by using a lambda:
<div class="card pickup" v-for="pickup in pickups">
<select v-on:change="e => locationChanged(e, pickup)">
...
</select>
</div>
Explicitly adding the element I'm interested in to the callback, at the cost of an extra function call/layer of indirection.
The previous answer provided a solution that takes advantage of the explicitly defined vuejs $event variable, which is always translated to the native browser event, which can be directly used in addition to whatever other variables you wish to capture, making the solution look like this:
<div class="card pickup" v-for="pickup in pickups">
<select v-on:change="locationChanged($event, pickup)">
...
</select>
</div>
Which requires more intimate knowledge of vuejs, but avoids the closure.
However, I kept searching because it did not feel like this solution was in line with the vuejs ethos (and all I ever heard was everyone raving about how awesome the vuejs docs are, but this answer wasn't explicitly there but rather had to be pieced together).
It seems that I was grappling with a common case of knowing what I want but not how to get there.. and my previous domain knowledge that precluded vuejs led me to the non-idiomatic approach in my question rather than how I would have solved it if I started off learning frontend development with vue.js in the first place.
In the end, I found the obvious answer I was looking for, an answer that felt like it was the correct "vuejs way" of doing it and the proper idiomatic solution to my original question:
The problem is that I was intermingling standard JS/DOM introspection with the vuejs model, and the friction arose in the transition between the two (leaky abstractions and all). I wanted a native handle to the select element along with a vuejs object (pickup), when what I should have been doing was using vuejs exclusively to accomplish what I needed, ultimately by binding the select element to a vuejs object/property, and referencing that directly in my bound callback:
<div class="card pickup" v-for="pickup in pickups">
<select v-model:pickup.location v-on:change="locationChanged(pickup)">
...
</select>
</div>
With the select element now bound to the location property of my vuejs model, I can directly query its state/value in my locationChanged handler and pass in my pickup object without needing to use either $event or a closure:
let vuePickup = new Vue({
el: '#pickups',
data: {
pickups: pickups,
},
methods: {
locationChanged: (pickup) => {
pickup.newLocation = pickup.location == -1;
}
},
});
I am new to Angular and have run into a problem that seems to have a javascript work around but they aren't very elegant.
I have a model with an array property. I ngfor the list property to build some html selection options. This is all working nicely. The problem comes when I am trying to set default value...the html elements don't have a load event.
I tried numerous html elements and they don't appear to have a load event either but I certainly could be doing it wrong.
I have seen a solution to put javascript tag right after the html and I could do that but I was really looking for a more elegant way in Angular.
I saw this SO post and thought that was my answer but there is a warning given that I agree with and thus it doesn't appear to be a good solution.
Regardless I tried it just to see if it would work and I got:
Failed to execute 'setAttribute' on 'Element': '{{loadDefaults()}}' is not a valid attribute name
<span {{loadDefaults()}} ></span>
So how can I fire an AS2 function in the component to load the default values?
HTML (btw this is NOT a full page load so there is no body tag):
<tr>
<td *ngFor="let loc of locOptions;">
<span>{{loc.text}}</span>
<input type="radio" name="radiogroup" [value]="loc.value" (change)="onSelectionChange(loc.value)">
</td>
</tr>
Edit
I thought perhaps mistakenly that ngoninit would fire too soon...before the html elements are rendered.
So perhaps what is being suggested is that I add a boolean is default to the model and bind THAT as the element is rendered.
In your ngonit function set this.locOptions to your default values. The value can be changed later on in any function and the change will be reflected in the view. Hope this helps you.
You should use ngOnInit to init you data, and call retrieve your data from your component :
defaults : any;
ngOnInit {
this.defaults = loadDefaults();
}
loadDefaults() {
//get data
}
HTML :
<span>{{defaults}}</span>
I tried to find on Primefaces Documentation but I have not found how can I customize the filter function for SelectOneMenu.
I add filterMatchMode="custom" filterFunction="#{mainRandevuBean.ilFilter()}"
But I don't know how can I write bean filterFunction.
The filter is a javascript (client-side) function. It all IS in the PrimeFaces documentation, which you should always look into first, carefully, thouroughly.
So use filterFunction="myFilter"
and create a javascript function like
function myFilter(itemLabel, filterValue) {
// return true if this label matches, false otherwise
}
Just as a sidenote: primefaces documentation doesn't say anything semantically about the parameters. It also does not mention where the label comes from (in fact, the docs mention "the item value" which is not very clear).
In fact I used the JavaScript function to debug this in order to figure out what was provided by default as a label.
function filterList(label, filter){
alert("label="+label+" and filter="+filter);
return false;
}
At first I thought it would be anything like the text inside the HTML generated for each list item. But when debugging it I saw the alert said that the label was something like my.package.SomeValueObject#123456 (which is obvously the Java object toString on each item in the list).
You need to define the itemLabel property on the selectItems which is inside the selectManyMenu to generate a proper text value used by the standard filtering mechanisme. As far as I could figure out that is the only reason why you have to put itemLabel there. In the documentation itemLabel is specified before explaining filtering which is confusing.
And as far as I know the itemValue defaults anyhow to just the object value, so I believe following from the documentation is redundant.
itemValue="#{player}"
Hope it helps anyone :.)
I resolve this problem with autocomplete component. Primefaces autocomplete component with dropdown="true" property works like one menu.
For a TV Guide, I am trying to create a dynamic expression within an ng-repeat directive as follows:
<div ng-repeat="programme in programmes['{{channel}}-wed-jan-14']" alt="{{channel}}">
{{channel}} in my controller should evaluate to something like "eTV". The binding is working fine with the alt="{{channel}}" instance but not with the array instance. Angular simply serves up the line of code commented out. If I hardcode the "eTV" string in place of the {{channel}}, it works fine.
Am I trying to ask Angular to do what it is not designed for, or is it possibly my array handling which is dodgy?
Okay, not sure if I just asked a dumb question, but in the absence of responses, I managed to figure out a solution by writing a filter as follows:
Template:
<div ng-repeat="programme in programmes | getChannelDay:channel:dayString" alt="{{channel}}">
Controller filter:
app.filter('getChannelDay', function() {
return function(programmes, channel, dayString) {
return programmes[channel + dayString];
};
});
The issue with my initial problem
<div ng-repeat="programme in programmes['{{channel}}-wed-jan-14']" alt="{{channel}}">
is that I was trying to put {{channel}} inside the expression, but that is the format for markup.
I tried to use the following instead:
<div ng-repeat="programme in programmes['channel + daystring']" alt="{{channel}}">
but I am doing something wrong here. I am pretty sure there is a way to get this to work - if anyone knows, please comment.