Knockout JS - update ViewModel data by custom script - javascript

I would like to implement knockout functionality to an already existing page. Therefore I want to manipulate with the ViewModel data from existing script. I have mocked up an example but it doesn't work correctly.
A ViewModel is correctly bound to the UI (try typing in the input). It functions also when I modify the ViewModel data in backend (by pressing a button). However when I modify the input value again by typing into the input box, the data is not changed.
How can I propperly modify ViewModel data from backend (some existing code manipulates the data). Please note, that I have used jQuery click event as an example. Existing code may manipulate the data in the different manner.
Here is the code (HTML):
<!-- View -->
<p>First name: <strong data-bind="text: myName"></strong></p>
<input data-bind="value: myName"></input>
<button>Click me</button>
And JS:
// ViewModel
var AppViewModel = function() {
this.myName= ko.observable("John Doe");
}
// ViewModel instance
var app = new AppViewModel();
// Activates knockout.js
ko.applyBindings(app);
// Custom external code that changes the data in the ViewModel instance
$("button").click(function() {
app.myName= ko.observable("Steve Peterson");
ko.applyBindings(app);
});

Instead of creating new observable in click handler
app.myName= ko.observable("Steve Peterson");
modify value of existing observable
app.myName("Steve Peterson");
Working example
http://jsfiddle.net/S9HBq/

Related

update values in jquery based on angular change

In my application I have used calendar widget using fullcalender.js:
$(document).ready(function() {
$('#profile-calendar').fullCalendar({
events: $scope.calendarEvent
});
});
What I wanted to do is, I need to give different values to the event attribute based a angular event. the below code snippet is what I have used to update the $scope.calendarEvent attribute.
$scope.$on('profile_selection',function(event, args){
$scope.calendarEvent = $rootScope.calendarEvent;
$scope.$apply();
});
But this is not working as I expected, the calendar is showing the initial value given to the $scope.calendarEvent. What should I do to make the $scope.calendarEvent take the changed value by the broadcast message and update dynamically.

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>

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

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!

Many to many relationship events with backbone.js

I have a many-to-many relationship with two of my backbone.js models implemented using a pivot table on the serverside. I'm trying to figure out how to structure it clientside. My current structure is:
1) I have a Tag model, and a TagView which renders a checkbox and the tag label, I have a checked event on the checkbox which does nothing at the moment.
I have a TagsCollection, which holds a bunch of Tags.
I have a TagsCollectionView, which binds add, reset etc of the TagsCollection, and adds TagViews for the added Tags, renders them, and appends the html to its current html (on reset, the html is reset).
I have a global TagCollection instance which contains all the possible tags
I have a Notes Model which contains an (empty) TagCollection called selectedtags on init.
The server returns an array of selected tagids for each Notes, which I add to its TagCollection.
Now comes the hard part, tying it all together.. my NotesView has its own TagsCollectionView which is bound to the global TagsCollection (so it can list all the Tags).. now, how do I get a checked event on the checkedbox of its sub TagViews to trigger an add to this Notes model's selectedtags? Should I provide a reference to the this Notes model instance to the TagsCollectionView on init which then provides it to all the TagViews it creates, whose checked event then adds/removes items from that model? That's the best way I can figure out how to do this, any other thoughts would be appreciated.
View is only for visual display of model. Please specify the need for TagsCollectionView in more details:
Use change event for checking the checkbox.
I would advise incremental coding. First work with the Tag and TagView, As it works, continue adding collection to hold the Tags. After that you can add Notes. It's a 'divide and conquer' :)
Don't confuse with the whole design, it is very simple when you start.
I can not provide the whole design due to lack of requirement details. but, I think below code should trigger the starting point of your design.
var TagsCollectionView=Backbone.View.extend({
el:$(),
});
var Tag=Backbone.Model.extend({
defaults:{
// define the default properties
}
});
var TagView=Backbone.View.extend({
el:$("body"),
events:{
'change #checkBox':'customFunction'
},
initialize: function(){
_.bindAll(this, 'render','customFunction');
this.render();
},
render:function(){
var tag=new Tag();
// code to render the checkbox and label
},
customFunction:function(){
// whatever you want to do after checking event of checkbox
}
});
// collection to collect all the tags
var TagCollection=Backbone.Collection.extend({
model:Tag
});
var Notes=Backbone.Model.extend({
defaults:{
'tagCollection':TagCollection
}
});
// you do not require TagsCollectionView

Categories

Resources