Hi I am new to knockoutjs and started learning from the Tutorial in their site.
Observable arrays are supposed to update all the bindings in a page. I am practicing in JsFiddle and my bindings are not being updated dynamically.
What am I doing wrong?
JSFiddle Example here
I am expecting that if we type in the textboxes it should update the table!
JS Code:
var viewmodel = {
posts: ko.observableArray(data)
};
The problem is that calling posts: ko.observableArray(data) will make only data observable, and not its elements. To make an entire object tree observable, you can use the mapping plugin, like in this updated fiddle: http://jsfiddle.net/gyW2k/7/
The only change to the code is:
var viewmodel = {
posts: ko.mapping.fromJS(data)
};
ko mapping docs: http://knockoutjs.com/documentation/plugins-mapping.html
Related
I want to update my DOM element everytime the value on the Firebase changes. I've seen that Angularfire handles three-way data binding, but from what I understood it only works if you take elements from $firebaseArray directly from the DOM.
What I have is an Element on the DOM (chart) that depends on some of the data on a $firebaseArray, but my element gets the data from a function instead of directly from the $firebaseArray. That means I have to do some pre-processing on the $firebaseArray before my element can use it.
This is what I have:
<pie-chart ng-repeat="chart in myCtrl.charts"
data="chart.data"
options="chart.options"></pie-chart>
This is my controller:
function MyCtrl($firebaseArray) {
let myRef = new Firebase(refUrl);
let chartsFirebase = $firebaseArray(myRef);
let getCharts = function() {
let charts = [];
distanceGoals.$loaded().then(function() {
// push some things from chartsFirebase on the charts array
charts.push({
options: { ... },
data: [ ... ]
});
}
return charts;
}
this.charts = getCharts();
}
Turns out that in this way this.charts is only updated one time, after modifications on the data in Firebase I have to refresh the browser.
Has anyone an idea of what I could do to achieve this behavior?
You can add a child-changed event listener to your ref like this:
// Get a reference to our posts
var ref = new Firebase("https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts");
// Get the data on a post that has changed
ref.on("child_changed", function(snapshot) {
var changedPost = snapshot.val();
console.log("The updated post title is " + changedPost.title);
});
This will get called everytime something changes in the location you put the listener on.
For more info take a look at https://www.firebase.com/docs/web/guide/retrieving-data.html and https://www.firebase.com/docs/web/api/query/on.html.
From the AngularFire documentation on $loaded()(emphasis mine):
Returns a promise which is resolved when the initial array data has been downloaded from the database.
That explains the behavior you're seeing.
To solve this, you should extend the $firebaseArray as documented here: https://www.firebase.com/docs/web/libraries/angular/guide/extending-services.html#section-firebasearray
Some related questions:
AngularFire extending the service issue
Joining data between paths based on id using AngularFire (includes a full example by the author of AngularFire)
I've written a component called Upload which allows users to upload files and then report back with a JSON object with these files. In this particular instance, the Upload component has a parameter which comes from a parent view model:
<upload params="dropzoneId: 'uploadFilesDropzone', postLocation: '/create/upload', uploadedFiles: uploadedFiles"></upload>
The one of importance is called uploadedFiles. The parameter binding here means I can reference params.uploadedFiles on my component and .push() new objects onto it as they get uploaded. The data being passed, also called uploadedFiles, is an observableArray on my parent view model:
var UploadViewModel = function () {
// Files ready to be submitted to the queue.
self.uploadedFiles = ko.observableArray([]);
};
I can indeed confirm that on my component, params.uploadedFiles is an observableArray, as it has a push method. After altering this value on the component, I can console.log() it to see that it has actually changed:
params.uploadedFiles.push(object);
console.log(params.uploadedFiles().length); // was 0, now returns 1
The problem is that this change does not seem to be reflected on my parent viewmodel. self.uploadedFiles() does not change and still reports a length of 0.
No matter if I add a self.uploadedFiles.subscribe(function(newValue) {}); subscription in my parent viewmodel.
No matter if I also add a params.uploadedFiles.valueHasMutated() method onto my component after the change.
How can I get the changes from my array on my component to be reflected in the array on my parent view model?
Why do you create a new observable array when the source already is one? You can't expect a new object to have the same reference as another one: simply pass it to your component viewModel as this.uploads = params.uploads. In the below trimmed-down version of your example, you'll see upon clicking the Add button that both arrays (well the same array referenced in different contexts) stay in sync.
ko.components.register('upload', {
viewModel: function(params) {
this.uploads = params.uploads;
this.addUpload = function() { this.uploads.push('item'); }.bind(this);
},
template: [
'<div><button type="button" data-bind="click: addUpload">Add upload</button>',
'<span data-bind="text: uploads().length + \' - \' + $root.uploads().length"></span></div>'].join('')
});
var app = {
uploads: ko.observableArray([])
};
ko.applyBindings(app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="component: {name: 'upload', params: {uploads: uploads}}"></div>
It is only in case your source array is not observable that things get a little more complicated and you need to have a manual subscription to update the source, eg. you would insert the following in the viewModel:
this.uploads.subscribe(function(newValue) { params.uploads = newValue; });
Additionally the output in the text binding would not be updated for the source because it is not observable. If for some reason that I cannot conceive of you would want to have 2 different observableArrays (1 source & 1 component), you should still be able to do with the line above, but replace the function code with params.uploads(newValue)
The problem may be related to this bug (to be confirmed): https://github.com/knockout/knockout/issues/1863
Edit 1: So this was not a bug. You have to unwrap the raw param to access the original observable. In your case, it would be:
params.$raw.uploadedFiles() //this would give you access to the original observableArray and from there, you can "push", "remove", etc.
The problem is that when you pass a param to a component, it gets wrapped in a computed observable and when you unwrap it, you don't have the original observableArray.
Reference: http://knockoutjs.com/documentation/component-custom-elements.html#advanced-accessing-raw-parameters
While Binding Property that involves Parent --> Child Relation
Use Binding in this way
If You want to bind data to Child Property
data-bind='BindingName : ParentViewmodel.ChildViewModel.ObservableProperty'
Here it seems you want to subscibe to a function when any data is pushed in Array for that you can write subscribe on Length of Observable array which can help you capture event that you want.
This should solve your problem.
I'm struggling to understand everything about the ember model lifecycle. I have created this jsfiddle to illustrate my problem. When clicking on one of the entries in the list, editing a value, and clicking the versions link to go back to the list, I get the following error:
Uncaught Error: Attempted to handle event loadedData on while in state rootState.loaded.updated.uncommitted. Called with {}
What is causing this? I get that the object state is now dirty, but how can I force a refresh of all objects when the list is opened?
Also, any suggestions on how to discard all changes to the properties if the form is not saved? I was thinking about cloning the object, using that clone in the edit form, and merging that with the original when saving. Not as easy as I first imagined.
Using latest ember and ember-data.
After quick discussion with #tchak, a solution could be to override the Version route's exit function, and rollback the current model.
App.VersionRoute = Ember.Route.extend({
exit: function() {
var controller = this.controllerFor(this.templateName),
content = controller.get('content');
if (content.get('isDirty')) {
content.get('transaction').rollback();
}
this._super();
}
});
I'm trying to allow a user to create a casting and add an array of categories to this casting object. I was trying to use knockout's foreach binding to the array of categories and let users add new categories to the casting. I have created a jsfiddle to illustrate what I'm trying to explain here.
http://jsfiddle.net/msell/ueNg7/16/
The JSON object gets built up correctly as a user modifies a casting, but I cant quite get the list of castings to display.
You have several problems:
You are using Knockout 1.2.1
The foreach binding was not added until Knockout 2.0.
You are not using an observableArray
You need to modify your categories property to be a ko.observableArray(), instead of just an empty array. Otherwise Knockout will not be able to observe when you push to it, and the remove method will not exist.
Your this binding is wrong.
When called from event handlers, this will be set incorrectly. You can fix this in various ways, discussed in length in the Knockout documentation, but one easy fix is to change the references to viewModel instead of to this.
To fix all these, you should upgrade to Knockout 2.0, and change your view model declaration to be
var viewModel = {
name: ko.observable(''),
description: ko.observable(''),
categories: ko.observableArray(),
categoryToAdd: ko.observable(''),
removeCategory: function(category) {
viewModel.categories.remove(category);
},
addCategory: function() {
viewModel.categories.push(new Category(viewModel.categoryToAdd()));
viewModel.categoryToAdd('');
}
};
Here is a corrected JSFiddle: http://jsfiddle.net/ueNg7/19/
You need to use ko.observableArray for you array otherwise Knockout wont know when you change your array and wont update, also you should use a template instead, read here http://knockoutjs.com/documentation/template-binding.html#note_2_using_the_foreach_option_with_a_named_template
var viewModel = {
name: ko.observable(''),
description: ko.observable(''),
categories: ko.observableArray([]),
categoryToAdd: ko.observable(''),
removeCategory: function(category) {
this.categories.remove(category);
},
addCategory: function() {
this.categories.push(new Category(this.categoryToAdd()));
this.categoryToAdd('');
}
};
I'm currently having problems having the UI refresh when I'm getting new data from the server for a single item which is in an observableArray of wrapper objects which holds an object of several observables.
Consider the following:
var vm = {
....
localEdited: ko.mapping.fromJS(new ItemWrapper(defaultModelSerialised)),
selected: ko.observable(null),
editItem: function(data) {
// clone a temporary copy of data for the dialog when opening (*.localEdited on dialog)
var clonedData = ko.toJS(data);
ko.mapping.fromJS(clonedData, null, this.localEdited);
// selected should now point to the item in the obserable array which will be refreshed
this.selected(data);
// open dialog...
},
submitDialog: function(data) {
// submit data to server...
// (1) commit the data back to UI (new item is return in resp.entity from server)
vm.selected(new ItemWrapper(resp.entity));
// at this point the UI isn't showing the updated value
// (2) however if I do this it reflects the data change in the UI
this.selected().Name("changed"); // updates the UI.
}
Can someone explain why passing in the ItemWrapper into vm.selected isn't updating the UI whereas in (2) it works. I don't want to have to set-up each property like in (2) for every property.
ItemWrapper looks like so:
function PoolWrapper(pool) {
this.Name = ko.observable(pool.Name);
// more properties...
}
OK- the issue is that your clones end up with mapping meta-data on them and eventually this causes recursion when trying calling ko.mapping.fromJS.
The solution is to create your clones using ko.mapping.toJS instead of ko.toJS, so that you get a clean clone (without mapping meta-data).
Here is an updated fiddle: http://jsfiddle.net/rniemeyer/tDDBp/
Something I also stumbled upon today that I thought I'd share:
If you clone using:
var clone = ko.mapping.fromJS(ko.mapping.toJS(itemToClone));
Then the clone will be stripped of any computed observables. They will exist as the last value of the function, but no longer function as a computed observable.
If your item is a complex model with computed observables that you would like to keep on your clone you can do the following:
var clone = ko.mapping.fromJS(ko.mapping.toJS(itemToClone), null, new itemModel());
Where itemModel is your complex model for your item containing your computed observables.