knockout.js - data-bind auto update after function call - data-binding

I have a case where I databind to a date field inside model in a list:
function Model(data) {
var self = this;
ko.mapping.fromJS(data, {}, this);
}
<div id="fieldOnPage" data-bind="text: formatDate(myDateField())"></div>
Then, in a modal, I display the same date field so it can be edited:
<div id="fieldInModal" data-bind="text: formatDate(myDateField())"></div>
However, since I'm calling the formatDate function does its work on the unwrapped observable, I'm unable to see the changes get written real-time back onto the main page when I edit the value in the modal.
Another caveat is that I using the ko.mapping plugin so I don't necessarily have a specific ko.computed field on myDateField. Is this possible to do with an external function like this? If not, how would I do it using the ko.computed if I had to specifically override the myDateField binding?

You could do something like
function Model(data) {
var self = this;
ko.mapping.fromJS(data, {}, this);
this.formattedDate = ko.computed(function () {
return formatDate(ko.utils.unwrapObservable(self.myDateField));
});
}
The bind to the formatted Date
<div id="fieldInModal" data-bind="text: formattedDate"></div>
Hope this helps.

Related

Can a Meteor method call get invoked via Collection.findOne

In my Meteor code. Can I define a method "or a function" on the server and call it on collectionName.findOne({id: 'someId'}).methodName; on the client?
Being new to Meteor, I don't know if this is possible and if so, what would the syntax look like? Thanks
This code is just a brain dump.
//server
Meteor.method({
doWork1: function (args) {
//do work
return something;
}
});
MyCol = new Mongo.Collection('myCol');
MyCol.insert({item: "envelopes", qty : 100, myFunc: doWork1});
//client
Meteor.call(MyCol.findOne({item: 'envelops'}).myFunc;
Edited:
Blaze Sahlzen comments made me think and add the following.
The reasons why I thought to give a mongo collection a try is this:
Stage one: the user fills up a form and click a button, the input values need to be used as arguments for a method which when successfully returns, the form gets modified to show different input fields for the user to fill up again and click the SAME button.
Stage two: same as stage one but with different input fields again.
Stage n: same as stage n-1 but with different input fields again.
I need to store all input values and group them by their stage identifier.
Each method may add/remove different kind of input controls for the next stage.
Since the SAME button will be used thus I don't have different buttons for different methods, so I came up with my original question "brain dump code". You are welcome to reinvent or change it and offer yours. :) Thanks again.
edited 2
A practical example could look like this:
var stageOne = {};
//for each DOM input
stageOne[inputName][i]= [inputValue][i];
myCol.insert({'stageOne': stageOne});
//then an observer for stageOne
But I just can't get my head around how to "link" each stage with the correct method to call without having to use a long if or switch conditional statement.
Alright, if I understand what you mean, I don't think you need observeChanges. The following solution might be a bit extensive, so bear with me.
Firstly you need a session variable to control on the client side which form values need to be shown. You could introduce this variable in your Template.name.onRendered.
Session.set('stage',1);
Then you have your input fields
<form>
<label id="label1">{{label1}}</label>
<input id="field1" type="text"/>
<label id="label1">{{label2}}</label>
<input id="field2" type="text"/>
<label id="label1">{{label3}}</label>
<input id="field3" type="text"/>
<button id="form-submit" type="submit"/>
</form>
I can imagine that you want to switch up the names of their labels to reflect the change in forms as you go to different stages. As a result you can write helpers as such:
'label1': function(){
var myStage = Session.get('stage');
if (myStage == 1){return '<label-title-for-stage-1>';
} else if (myStage == 2){return '<label-title-for-stage-2>';}
} else if .... etc.
}
Any changes to the session variable 'stage' will force the helper to reload, making it ideal to update form titles as you go through your stages.
You can then write an event for the button click event as such:
'submit #form-submit': function(){
var options = {
stage: Session.get('stage'),
values: [
{ value: $('#field1').val(), name:$("#label1").text() },
{ value: $('#field2').val(), name:$("#label1").text() },
{ value: $('#field3').val(), name:$("#label3").text() }]
}
Meteor.call('storeValues', options, function(error, result) {
if (error) {
console.log(error);
}
Session.set('stage',result);
});
}
This will essentially combine the filled fields into one object and call upon a server method, waiting for a callback from the server that tells the client which stage to move to.
Now, on the server side you can insert the values for this particular user in your collection and see if a particular stage has filled up and return whether the client can move on to the next stage.
Meteor.methods({
'storeValues': function(options){
for (var i = 0; i < options.values.length; i++){
myCol.insert({
value:options.values[i].value,
name:options.values[i].name,
stage: options.stage
});
}
if (options.values.length > 'amount-per-stage'){
return options.stage + 1;
} else {
return options.stage;
}
}
});
This way you store the data that gets entered in each form, while moving up one stage each time if all fields have been entered.
What you could do is use observeChanges:
var query = MyCol.find({item: 'envelops'});
var handle = query.observeChanges({
added: function () {
somethingHappened();
},
changed: function () {
somethingHappened();
}
});
var somethingHappened = function(){
// do something
}
Query contains your collection, and the handle function automatically checks whether any changes are being made to that collection, triggering the somethingHappened function if there are.
Inside somethingHappened you can place the behaviour that you would normally place in your method.
You can use observeChanges both client side and server side, but in this case you only need it on the server side.

How do you update item value(s) in ko.observableArray and rebind?

I have a ko.observableArray that when the page gets initialized 1 item is added. I then use a and a data-bind="foreach items" to create a div for each item in the ko.observableArray. I have a button and textbox on the page that when you add text to the input and click the button a new item gets pushed on to the ko.observableArray. This works fine I can add a new items with each button click. The items in the ko.observableArray have a price associated with them and the price changes. I want to update the price while still being able to add new items to the ko.observableArray. The price and item name are also ko.observable.
self.items= ko.observableArray(ko.utils.arrayMap(items, function(item) {
return { name: ko.observable(item.name), price: ko.observable(item.price) };
How to I update the underlying item values (price) and not recreate the ko.observable array? Do I have to loop through each item in the ko.observable array? The data is coming from a JSON call. BTW I am new to Knockout.js.
Here is my attempt at a JSFiddle but I could not get it fully working. Adding an item works fine but when I update if I have a different amount of item..like less items the ones not getting updated are destroyed. Anyway around this? I do not want to fetch data that does not have any changes in it.
Do something like this instead
Javascript
var itemObject = function(data){
var self = this;
//I recommend using the mapping plugin
//ko.mapping.fromJS(data, {}, self);
//If you use the mapping plugin, you don't have to hand bind each property
this.Id = ko.observable(data.Id);
.... etc ....
};
var baseViewModel = function(){
var self = this;
this.Items = ko.observableArray();
this.Setup = function(items){
//using underscore.js to map the items.
//This turns each item into an "itemObject", which you can
//then manipulate and have all changes shown on the screen, even
//inside the array.
self.Items(_.map(items, function(item){
return new itemObject(item);
}));
};
};
$(function(){
var myApp = new baseViewModel();
myApp.Setup(items);
ko.applyBindings(myApp);
});
html
<div data-bind="foreach: Items">
<div data-bind="text: Id"></div>
<!-- other binding things here -->
</div>

knockout.js data-bind 'with' conflicts with jQuery change event

For some reason, when I use the data-bind="with: detailedStudent" the jQuery change() binding does not get called. I'm dynamically populating the select options but I'm not sure that should matter. This is some of the code I'm using just to try to give a decent picture of what's going on:
var viewModel;
$(document).ready(function() {
viewModel = new StudentViewModel();
ko.applyBindings(viewModel);
// this change event is not getting called, but if i put the onchange directly into the html as an attribute, it works fine.
$("#accountDialog").find("#mySelect").change(function() {
alert('hi');
}
}
function Student(data) {
var self = this;
ko.mapping.fromJS(data, {}, this);
}
function StudentViewModel() {
var self = this;
this.students = ko.observableArray([]);
this.detailedStudent = ko.observable();
}
<div id="accountDialog" class="modal hide fade" data-bind="with: detailedStudent">
<select id="mySelect" name="mySelect" data-bind="value: GraduationClassId"></select>
</div>
The with binding is a wrapper to the template binding. It copies off the child elements and uses them as the template. So, if your detailedStudent is changing, then KO will be rendering new elements each time that did not have the event handler attached to it.
Some alternatives:
use a binding to attach the event handler (can use event binding)
create a manual subscription against your detailedStudent observable and perform your action in the view model (best option, if your action does not involve DOM manipulation)
try to use a delegated event handler like jQuerys $.on() http://api.jquery.com/on/.
If the action does not involve DOM manipulation, then I agree with RP Niemeyer, the manual subscription is the best option.
However, usually we will have some event with DOM manipulation, for example, to setup the jquery dialog / datepicker plugin to your property. I regard the custom binding would be the best option. The custom binding will work perfectly with the "with" binding clause to setup event handlers to arbitary javascript function.
You could read through this and it is not as hard as it seems to be.
http://knockoutjs.com/documentation/custom-bindings.html

ObservableArray not notifying when item changed

I try to bind observableArray to div on my page and everything is ok. This array contains simple JSON objects, not observable, obtained from WebService.
After that, I want to be able to modify those objects in array and would like view to be refreshed with each modification. For example, when checkbox gets clicked I would like to change the flag on my JSON object (this seems to work automatically all right) and at the same time my UI should get updated, which does not happen. Could anyone provide me with the reason (is this because those objects are simple, not observable?) and solution?
var DocumentContentModel = function () {
var self = this;
self.content = ko.observableArray();
self.ElementApprovalChanged = function (element) {
DocumentService.DoSomething(
element.Id,
function (result) {
if (!result) {
var negatedApproved = !element.Approved;
element.Approved = negatedApproved;
}
},
function (error) {
alert(error);
});
return true;
};
};
$(document).ready(function () {
var contentModel = new ContentModel();
DocumentService.GetContent(1,
function (result) {
contentModel.content(JSON.parse(result));
});
ko.applyBindings(contentModel);
});
UI
<div class="ContentContainer">
<div data-bind='foreach: content'>
<div class="ContentElement" data-bind='css: { NotApproved: !Approved} '>
<div class="ContentValue" data-bind='html: Value'></div>
<div class="Approval">
<input type="checkbox" data-bind='checked: Approved, click: $root.ElementApprovalChanged' />
</div>
</div>
</div>
</div>
What is happening is on checkbox click I send request to webservice and if this call returns false I want to reset element's Approved flag. And even whithout that, selecting checkbox should change div class attribute to mark it as NotApproved when needed. But none of this happens.
An observableArray only tracks the array. So if something is added, removed or replaced in the array this will trigger an update to your view.
An observableArray does NOT track the state of individual properties on the items in the array. So if you have an Approved flag on your objects this needs to be an observable for the UI to reflect changes to that property.
So you would have something like:
element.Approved = ko.observable(false);
....
....
if (!result) {
var negatedApproved = !element.Approved();
element.Approved(negatedApproved);
}
(or if you want to be more consise:
element.Approved(!element.Approved());
)

Knockout.js - javascript function on data-bind

Is there a way i can call JavaScript function on data-bind like this:
<span id="lblSomePropVal" data-bind="text: MySomeFunction(SomeProperty())" ></span>
What i am trying to do is call MySomeFunction with the value of SomeProperty of my viewmodel. My SomeFunction will return some text based on value passed and that will be displayed in the span lblSomePropVal.
I tried it the way i have written in the example but it throws binding error.
Am i missing something here or is there any other way of doing this?
This is the error i am getting:
Microsoft JScript runtime error: Unable to parse bindings.
Message: [object Error];
Bindings value: text: MySomeFunction(SomeProperty())
I had a similar problem trying to calculate table cell entries. What worked for me was including 'MySomeFunction' in my data model, and then data-binding my table cells as:
<td data-bind="text: $root.MySomeFunction(SomeProperty)"></td>
You can use arbitrary JavaScript expressions for bindings, but keep in mind that they are evaluated in the viewmodel's context, so all functions in the expression have to be properties of viewmodel. In your case, MySomeFunction has to be a property of your viewmodel. If you create your viewmodel using mapping plugin, you can attach additional functions to the viewmodel like this:
var viewModel = ko.mapping.fromJS(data.d)
viewModel.MySomeFunction = function(...){...};
Well I am just going through the tutorial myself but I thought you had to set up a property and use ko.computed to give it its value (from the tutorial):
function AppViewModel() {
this.firstName = ko.observable("Bert");
this.lastName = ko.observable("Bertington");
this.fullName = ko.computed(function(){
return this.firstName() + " " + this.lastName();
},this);
}
Then you can have:
Full name: <strong data-bind="text: fullName"></strong>
I have managed to do this by using the context. If you need the whole code I can send it to you.
<h2 class="text" data-bind="html: currentProgram($context)"></h2>
function currentProgram(context){
var title = '<font size="1">' + context.$data.name + '</font>';
return title;
}
You will also need to set this
$.ajaxSetup({
async: false
});
<div style="font-size:18px;float:left;width:95%" data-bind="html: trimString(AccountName,25)"></div>
function trimString(value, maxLen) {
//Return undefined, and short strings
if (value === undefined) return undefined;
if (value.length < maxLen) return value;
return value.substring(0, (maxLen - 1));
}

Resources