How to detect changes besides the change event in knockout.js - javascript

I am using knockout.js. I created a view model say testViewModel with only 1 observable property testProperty.
function testViewModel()
{
var self = this;
self.testProperty = ko.observable("Initial");
}
than i created a span in which the changed value of testProperty is reflected and a input text field by which we can change the testProperty value.
<span data-bind="text: testProperty"></span><br />
<input type="text" data-bind="value: testProperty" />
I created an Example Fiddle.It seems that the observable property value is updated when the focusout event is executed on the input text field.
Now my question is that can we change the observable property value update event from focusout to something else. I created a save button also. Is there any way to update the observable property value only on save button press.
I am trying to create an application in which a user can create and save its profile and can edit the saved profile.I am using the same observable properties in create and edit form and these properties are observable. So when user edit its profile the ui should not
be updated until user press the save button. This is my goal. Please help me to solve this issue ?

I would suggest have testProperty and testProperty_temp. Bind the input to temp and when the button is clicked, set testProperty to the testProperty_temp
function testViewModel()
{
var self = this;
self.testProperty = ko.observable("Initial");
self.testProperty_temp = ko.obserable("");
self.save = function() { self.testProperty(self.testProperty_temp()); }
}
Hope this helps

Another means, along the same lines of what Matt Burland suggested:
http://jsfiddle.net/mori57/PQxJC/
Basically, wrap your input and button in a form, and bind the form to submit: which is handled by a method on your ViewModel. See the comments I've made inline, but here it is for people who don't want to go out to jsFiddle:
<span data-bind="text: testProperty"></span><br />
<!-- wrap the input and button in a form and
data-bind to submit, with a reference
to a handler on your viewmodel -->
<form data-bind="submit: updateProfile">
<!-- this must be bound to your shadow value -->
<input type="text" data-bind="value: _tmpTestProperty" />
<button type="submit">save</button>
</form>​
and in your javascript
function testViewModel()
{
var self = this;
self.testProperty = ko.observable("Initial");
// Create the "shadow" property
// and prepopulate it with testProperty's value
self._tmpTestProperty = ko.observable(self.testProperty());
// Create our form handler
self.updateProfile = function(val){
// set the testProperty value to the
// value of the shadow property
self.testProperty(self._tmpTestProperty());
};
}
ko.applyBindings(new testViewModel());​
In this way, your value doesn't change when you lose focus on the text input box, but is only updated when you submit the form.

Your simplest approach would be to have a shadow property for each of your properties. So you bind one to your text boxes and only copy the value to the other property, the one bound to the other UI elements, when save is clicked.
See here: http://jsbin.com/aguyud/5/edit
An easier way using two models and $.extend to copy from one to the other:
http://jsbin.com/aguyud/7/edit
Update, actually scratch that, that doesn't seem to work. I tried this instead:
http://jsbin.com/aguyud/22/edit
which works the first time, but after copying the model with $.extend it seem it's copied all the bindings too, so it only works once!

Related

Knockout.js: Set radio button value onload

Please see my fiddle here
I have a couple of radio buttons and depending if one of them is selected I want a text box to then show. I have been able to achieve this using knockout.
What I want to happen is when the page loads, if the value of the "Timesheet" radio button is checked I want the text box to show. But I've been unable to work out how to do this. Thanks is advance.
See below my knockout code:
function K2ConsultantApprovalViewModel() {
var self = this;
self.timeSheetSelected = ko.observable("");
}
ko.applyBindings(new K2ConsultantApprovalViewModel());
If the checked binding is applied to a radio button, it will set the element to be checked when the parameter value equals that of the radio button element's value attribute. So you need to slightly change your way of thinking and create a "payment type" observable that stores the chosen payment type, rather than the boolean "is timesheet selected?" observable that you have now. You can then initially give this observable the value "Timesheet", and that will be what is selected on page load. It also makes it trivial to show or hide any other elements based on the current selection.
function K2ConsultantApprovalViewModel() {
var self = this;
self.paymentType = ko.observable("Timesheet");
}
ko.applyBindings(new K2ConsultantApprovalViewModel());
And binding would look like this:
<input id="DisbursementsOrTimeSheet_ChoiceField0" type="radio" name="DisbursementsOrTimeSheetChoice" value="Disbursements" data-bind="checked: paymentType">
Update Fiddle here.
Update
I would not recommend this since it's a backwards way of working, but if the initial value of your checked binding has to come from the input element itself, you could create a small binding handler that is executed before the checked binding.
ko.bindingHandlers['initChecked'] = {
init: function (element, valueAccessor, allBindings) {
var checked = valueAccessor();
if (element.checked) {
checked(element.value);
}
}
};
Then bind like this:
<input id="DisbursementsOrTimeSheet_ChoiceField1" type="radio" name="DisbursementsOrTimeSheetChoice" value="Timesheet" data-bind="initChecked: paymentType, checked: paymentType" checked="checked">
It will work (proof). But the proper way to do this would be to get the data in the view model and, as someone well put it in the comments, cut out the middle man.

How to force dijit/form/TextBox to update the variable bound to it immediately - binding does not work as expected

I have an
<input id="myTextBox" data-dojo-type="dijit/form/TextBox" data-dojo-props="value: at(model, myValue)" />
When I try to copy the value from model.myValue to another input (e.g. by
window.setInterval(
function() { document.getElementById("theOtherInput").value = model.myValue; }, 1000);
I notice, that the value of myTextBox isn't synchronized with model.myValue until the <input> of myTextBox lost focus. I.e. - theOtherInput won't be updated unless I leave the input field myTextBox.
How to force dojo to synchronize the input with the bound variable with every keystroke? I thought that this should have been the default behavior or at least it should be possible with little effort, but I don't find the right attribute to do it. Any ideas?
The main problem for me is, when I submit the form by the enter key, the model.myValue has still the value before the input myTextBox got the focus. (and I don't want a hack with setInterval - I just want to have dojo doing its job of synchronizing the input with the model)
The closest you can get is setting intermediateChanges property to true and attach an onChange event. Something like this:
<input id="myTextBox" data-dojo-type="dijit/form/TextBox"
data-dojo-props="intermediateChanges: true"
data-dojo-attach-event="onChange: _myOnChange"
data-dojo-props="value: at(model, myValue)" />
Now, inside your widget, the _myOnChange function will run with each valuable keystroke or if the content is changed.
_myOnChange: function(newValue){
console.log(newValue);
document.getElementById("theOtherInput").value = newValue;
}

geocomplete with Vue js -- location being erased

I'm trying to use jQuery geocomplete along with Vue.js to populate a form with geo data.
My code contains this form:
<form>
<label for="get-offer-location">location: </label><input id="get-offer-location" v-model="newoffer.location" type="text"/>
<div style="visibility:hidden">
<input name="lat" type="text" value=""/>
<input name="lng" type="text" value=""/>
</div>
</form>
After I click on a suggested location from the get-offer-location input, it looks like the field is filled out -- but then when I start typing in another field, the location input field reverts to just the letters I typed in for the location search. Try it out here by clicking "post", then "news" or "offers":
https://jsfiddle.net/dvdgdnjsph/157w6tk8/
Can anyone tell me what's wrong?
The problem you are having is that v-model binds on input, since the geolocation dropdown is a plugin that changes the value programatically the input event is not fired, so v-model is not updated. As a case, try typing a space after selecting a location, you will see that it sticks.
Fortunately, v-model is nothing more than syntactic sugar for v-on:input, so you can use v-on to fire your event instead. Considering that you are going to need to unfocus to get out of the box, the blur event is likely to be your best option, so you can simply do:
v-on:blur="newarticle.location = $event.target.value"
Unfortunately, JSFiddle won't let me save or update your Fiddle, but I did get it working with the code above.
For completeness, in case you want to use this behavior extensively, and because the Vue docs are fairly limited in this regard, you may write a custom directive to encapsulate the code:
Vue.directive('model-blur', {
bind: function(el, binding, vnode) {
el.addEventListener('blur', function() {
vnode.context[binding.expression] = el.value;
});
}
});
Then you can use like so:
<input v-model-blur="myVar" />
Here's the JSFiddle: https://jsfiddle.net/4vp6Lvmc/
Can't tell for sure. But it looks like jQuery plugin just changes input#get-article-location value, but not the Vue model. So when you trigger model update (e.g. editing headline) it overwrites complete location with whatever you typed in.
I have something like this for catch the geocomplete event and try to set the vueJS value :
$("#get-article-location")
.geocomplete({details: ".details"})
.bind("geocode:result", function (event, result) {
vm.newoffer.location = result.formatted_address;
console.log('done');
});
But something still appears wrong, I think you should really change the name of your vueJS instance (var vm) it may be use by another script and make troubles.
This is because v-model, as two-way binding, on the receiving-user-input way, listens to the input event on the input element, while js plugins (like jquery-geocomplete) obviously set input values via js, which leads to the view model's data not changing as we discussed in other answers.
To fix this, just listen to the geocode:result event of the plugin and manually change the data with code (there seems to be something wrong with jsfiddle so I'm posting it here):
var vueVM = this;
$("#get-offer-location").geocomplete({ details: ".details" });
$("#get-article-location")
.geocomplete({ details: ".details" })
/***** highlight start *****/
.bind("geocode:result", function(event, result){
console.log(result);
//tried result.something but couldn't find the the same thing as `this.value`
vueVM.newarticle.location = this.value;
});
/***** highlight end *****/
extra knowledge: the mechanism of v-model stated above is usually used in making reusable components, by receiving a value prop from the parent, and emitting an input event with the user-input value when detecting change on the input element in the component. And then we can use <my-component v-model='parentData'></my-component> as the child behaves exactly like a simple input element from the parent's perspective. example

How to manipulate data-binding, knockoutJs

I have a customer who is a member of a web site. He has to fill a form every time which is really very often. That's why he wants me to develop an application for him to make this process automatic. When I use the webBrowser control to manipulate it, I am able to login but after that there are fields that contains data-binding. These fields are the ones I need to manipulate. When I push the data to necessary fields, it's not working, because in the html tag, there is no value attribute, instead it has data-binding. So my question is how can I manipulate and push data to these fields?
Thank you so much for your all help in advance.
Knockout uses data-binds to listen to changes in an input and update an underlying model. For example, the value binding listens to change events and writes the new value to a data-bound observable.
If you update a value attribute through code, the change event isn't triggered. You'll see the new value in the UI, but the javascript model won't be updated.
You can combat this by explicitly triggering a change. Here's an example:
Type in the input: you'll see a console.log that shows knockout gets updated
Press the button to inject a new value: you won't see a log: knockout isn't updated
Press the last button to trigger a change event. You'll notice knockout now updates the model.
Of course, you can combine the two click listeners into one function. I've separated them to get the point across.
// Hidden knockout code:
(function() {
var label = ko.observable("test");
label.subscribe(console.log.bind(console));
ko.applyBindings({ label: label });
}());
// Your code
var buttons = document.querySelectorAll("button");
var input = document.querySelector("input");
buttons[0].addEventListener("click", function() {
input.value = "generated value";
});
buttons[1].addEventListener("click", function() {
// http://stackoverflow.com/a/2856602/3297291
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", false, true);
input.dispatchEvent(evt);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="text" data-bind="value: label">
<button>inject value from outside</button>
<button>let knockout know something changed</button>

Why isn't this knockout code picking up changes bound to an ObservableArray?

I've got some strange behavior in (what should be) some relatively straight forward code. I'm trying to prepend an <input> tag onto a list of elements.
HTML
<button data-bind="click: addItem">Add</button>
<div data-bind="foreach: items">
<input data-bind="value: $parent.items()[$index()]" />
<button data-bind="click: $parent.removeItem">Remove</button>
<br>
</div>
Javascript
function ItemViewModel() {
var self = this;
self.items = ko.observableArray();
self.addItem = function() {
self.items.unshift('');
}
self.removeItem = function(name) {
self.items.remove(name);
}
}
ko.applyBindings(new ItemViewModel());
What's going wrong:
RemoveItem is sending an empty string to the function rather than the contents of the input element. However, what's weird is that this only applies to the most recently appended item AND only if no other item has been clicked. For instance, if I have on input type some text, and click remove, an empty string is sent to my function. However, if I add another input element via the add button, Knockout seems to 'reflow' and the original field picks up the changes in the text field and can be removed without a problem.
I'm pretty baffled here. Why isn't Knockout picking up the changes on recently prepended items?
So binding directly to a string doesnt provide the two way binding you would expect, regardless of if you had the value in an observableArray. observableArrays just track changes to a collection (items being added/removed) but not changes to individual items in the array.
To fix this problem, you will need to update your data model a bit:
js
self.addItem = function() {
self.items.unshift({name: ko.observable('')});
}
html
<input data-bind="value: name" />
Fiddle:
http://jsfiddle.net/e2b0089j/

Categories

Resources