I have a Knockout Viewmodel that uses the mapping plugin. After mapping the JSON object, I create some computed values, like so:
/* viewmodel setup and mapping */
myViewModel
.formattedGiftAmount = ko.computed({
read: function () {
return parseInt(this.giftAmount(), 10).formatMoney();
}
, write: function (value) {
this.giftAmount(value.unformatMoney());
}
, owner: this
})
.formattedGoal = ko.computed({
read: function () {
return parseInt(this.goalAmount(), 10).formatMoney();
}
, write: function (value) {
this.goalAmount(value.unformatMoney());
}
, owner: this
});
Don't worry so much about what the code does, I'm more concerned with the pattern. As you can see, the two properties formattedGiftAmount() and formattedGoal() have nearly identical objects that define them. The only difference is what properties they modify. I'm potentially going to have many more instances like this, so I was wondering if there was any way to make this more reusable. I can imagine doing something like this[prop](), but I can't quite figure out how to inject that into the object and get it to work.
PS: I've seen this but it still doesn't quite do what I'm looking for.
You can also modify fn to add a function to your ko.observable to create it, this will allow you to add the properties in your constructor in a descriptive way (fiddle):
ko.observable.fn.formatAsMoney = function() {
var base = this;
return ko.computed({
read: function() {
return formatMoney(parseFloat(base()));
},
write: function(newValue) {
base(unformatMoney(newValue));
}
});
};
function ViewModel() {
var self = this;
self.number = ko.observable(10.5);
self.money = self.number.formatAsMoney();
};
You can add a function to your view model that creates a formatted property based on an unformatted property:
myViewModel.addFormattedProperty = function(formattedName, unformattedName) {
this[formattedName] = ko.computed({
read: function() {
return parseInt(this[unformattedName](), 10).formatMoney();
},
write: function(value) {
this[unformattedName](value.unformatMoney());
},
owner: this
});
};
Then you could call it for your properties:
myViewModel.addFormattedProperty('formattedGiftAmount', 'giftAmount');
myViewModel.addFormattedProperty('formattedGoalAmount', 'goalAmount');
Related
I have two IFFE:
var Helper = (function () {
return {
number: null,
init: function (num) {
number = num;
}
}
})();
var Helper2 = (function () {
return {
options: {
number: [],
},
init: function(num){
this.options.number = num;
},
getData: function () {
return this.options.number;
}
}
})();
Helper2.init(Helper.number);
console.log(Helper2.getData());
Helper.init(5);
console.log(Helper2.getData());
What I want is
Helper2.init(Helper.number);
console.log(Helper2.getData()); // null
Helper.init(5);
console.log(Helper2.getData()); // 5
what I get is
Helper2.init(Helper.number);
console.log(Helper2.getData()); // null
Helper.init(5);
console.log(Helper2.getData()); // null
What techniques can be done to have it pass by reference, if it can?
JSBIN: https://jsbin.com/gomakubeka/1/edit?js,console
Edit: Before tons of people start incorporating different ways to have Helper2 depend on Helper, the actual implementation of Helper is unknown and could have 100's of ways they implement the number, so Helper2 needs the memory address.
Edit 2: I suppose the path I was hoping to get some start on was knowing that arrays/objects do get passed by reference, how can I wrap this primitive type in such a way that I can use by reference
Passing by reference in JavaScript can only happen to objects.
The only thing you can pass by value in JavaScript are primitive data types.
If on your first object you changed the "number:null" to be nested within an options object like it is in your second object then you can pass a reference of that object to the other object. The trick is if your needing pass by reference to use objects and not primitive data types. Instead nest the primitive data types inside objects and use the objects.
I altered you code a little bit but I think this works for what you were trying to achieve.
var Helper = function (num) {
return {
options: {
number: num
},
update: function (options) {
this.options = options;
}
}
};
var Helper2 = function (num) {
return {
options: {
number: num,
},
update: function(options){
this.options = options;
},
getData: function () {
return this.options.number;
}
}
};
var tempHelp = new Helper();
var tempHelp2 = new Helper2();
tempHelp2.update(tempHelp.options);
tempHelp.options.number = 5;
console.log(tempHelp2.getData());
First of all why doesn't it work:
helper is a self activating function that returns an object. When init is called upon it sets an number to the Helper object.
Then in Helper2 you pass an integer (Helper.number) to init setting the object to null. So you're not passing the reference to Helper.number. Only the value set to it.
You need to pass the whole object to it and read it out.
An example:
var Helper = (function () {
return {
number: null,
init: function (num) {
this.number = num; //add this
}
}
})();
var Helper2 = (function () {
return {
options: {
number: [],
},
init: function(obj){
this.options = obj; //save a reference to the helper obj.
},
getData: function () {
if (this.options.number)
{
return this.options.number;
}
}
}
})();
Helper2.init(Helper); //store the helper object
console.log(Helper2.getData());
Helper.init(5);
console.log(Helper2.getData());
I don't think you're going to be able to get exactly what you want. However, in one of your comments you said:
Unfortunately interfaces aren't something in javascript
That isn't exactly true. Yes, there's no strong typing and users of your code are free to disregard your suggestions entirely if you say that a function needs a specific type of object.
But, you can still create an interface of sorts that you want users to extend from in order to play nice with your own code. For example, you can tell users that they must extend from the Valuable class with provides a mechanism to access a value computed property which will be a Reference instance that can encapsulate a primitive (solving the problem of not being able to pass primitive by reference).
Since this uses computed properties, this also has the benefit of leveraging the .value notation. The thing is that the .value will be a Reference instead of the actual value.
// Intermediary class that can be passed around and hold primitives
class Reference {
constructor(val) {
this.val = val;
}
}
// Interface that dictates "value"
class Valuable {
constructor() {
this._value = new Reference();
}
get value() {
return this._value;
}
set value(v) {
this._value.val = v;
}
}
// "Concrete" class that implements the Valuable interface
class ValuableHelper extends Valuable {
constructor() {
super();
}
}
// Class that will deal with a ValuableHelper
class Helper {
constructor(n) {
this.options = {
number: n
}
}
getData() {
return this.options.number;
}
setData(n) {
this.options.number = n;
}
}
// Create our instances
const vh = new ValuableHelper(),
hh = new Helper(vh.value);
// Do our stuff
console.log(hh.getData().val);
vh.value = 5;
console.log(hh.getData().val);
hh.setData(vh.value);
vh.value = 5;
I implement a class (I call it RowsEditor) and its subclass (I call it DateRowsEditor) with the jQuery code similar to this:
function create_RowsEditor(tableId) {
var rowsEditor = {
tableId: tableId,
quit_row: function(ctl) { /*...*/ }
};
$('#'+tableId).click(function(event) {
var tr = event.delegateTarget;
rowsEditor.quit_row(tr);
});
return rowsEditor;
}
function create_DateRowsEditor(tableId) {
var rowsEditor = $.extend({}, create_RowsEditor(tableId), {
quit_row: function(ctl) { /*...*/ }
};
return rowsEditor;
}
Then I create an object of type DateRowsEditor():
var rowsEditor = create_DateRowsEditor('come'):
The trouble is that on user's click quit_row() is called with an object created by create_RowsEditor() not with the derived object created with create_DateRowsEditor() as it should.
How to do it properly in a structured and object oriented way?
I've solved the problem by adding the .init() method:
var rowsEditor = {
tableId: tableId,
init: function() {
var self = this;
$('#'+tableId).handleRowsBlur(function(event) {
var tr = event.delegateTarget;
self.quit_row(tr);
});
return this;
},
...
and creating objects like this:
/*var rowsEditor =*/ create_ComeEditor('come').init();
It is the proper way to solve my problem because this way initialization of event handlers is called only when it is should be called.
The trouble is that on user's click quit_row() is not called with the derived object
Then don't use extend for that. Just create the object, and overwrite its quit_row method:
function create_DateRowsEditor(tableId) {
var rowsEditor = create_RowsEditor(tableId);
rowsEditor.quit_row = function(ctl) { /*...*/ };
return rowsEditor;
}
I have been using Knockout.js for a lot of projects lately, and I am writing a lot of repetitive code. I would like to be able to define a BaseViewModel class and have my page-specific ViewModels inherit from it. I am a bit confused about how to do this is Javascript. Here is my basic BaseViewModel:
(function (ko, undefined) {
ko.BaseViewModel = function () {
var self = this;
self.items = ko.observable([]);
self.newItem = {};
self.dirtyItems = ko.computed(function () {
return self.items().filter(function (item) {
return item.dirtyFlag.isDirty();
});
});
self.isDirty = ko.computed(function () {
return self.dirtyItems().length > 0;
});
self.load = function () { }
};
}(ko));
I would like to be able to list signatures for methods like load in the BaseViewModel and then give them definitions in the inheriting ViewModel. Is any of this possible? I have found a few solutions online but they all rely on defining functions/classes to make the inheritance work.
Since your BaseViewModel is just adding all of the properties/methods to this (and not using prototype) then it is pretty easy:
In your new view models, just call BaseViewModel:
var MyVM = function () {
var self = this;
ko.BaseViewModel.call(self);
self.somethingElse = ko.observable();
self.itemCount = ko.computed(function() { return self.items().length; });
self.items([1, 2, 3]);
};
// ...
var vm = new MyVM();
Javascript inheritance is done in two pieces. The first is in the constructor, and the second is on the prototype (which you aren't using, so you could skip).
var ViewModel = function(data) {
BaseViewModel.call(this);
};
//you only need to do this if you are adding prototype properties
ViewModel.prototype = new BaseViewModel();
To your last point, about overriding load, its no different that putting a load function on your viewmodel normally. Javascript allows you to override any objects properties with anything, there are no special steps here.
Here is a fiddle demonstrating the inheritance.
Using Knockout.JS, I'm trying to determine how to best extend objects in the view model when they will be both loaded via the mapping plugin and dynamically added. In this example, I'm adding a method addChild to the Person object.
Extending during mapping:
var myPersonModel = function (data) {
ko.mapping.fromJS(data, {}, this);
this.addChild = function () {
this.children.push(new Child());
} .bind(this);
}
var mapping = {
'people': {
create: function (options) {
return new myPersonModel(options.data);
},
}
}
var viewModel = ko.mapping.fromJS(data, mapping);
Extending during dynamic creation:
function Person(id, name, children) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.children = ko.observable(children);
this.addChild = function () {
this.Forms.push(new Child());
} .bind(this);
}
But it seems to me there must be an easier way to do so such that I don't need to repeat myself, and both mapped and new objects get the addChild method.
Take a look at this answer, especially the live example on JSFiddle.
It looks like your code is close to what it needs to be.
I think you just need to fold mapping and ko.mapping.fromJS into your Person constructor.
var JavascriptHelper = Backbone.Model.extend("JavascriptHelper",
{}, // never initialized as an instance
{
myFn: function() {
$('.selector').live('click', function() {
this.anotherFn(); // FAIL!
});
},
anotherFn: function() {
alert('This is never called from myFn()');
}
}
);
The usual _.bindAll(this, ...) approach won't work here because I am never initializing this model as an instance. Any ideas? Thanks.
You could do it by hand:
myFn: function() {
$('.selector').live('click', function() {
JavascriptHelper.anotherFn();
});
}
Or, if anotherFn doesn't care what this is when it is called (or if it wants this to be what live uses):
myFn: function() {
$('.selector').live('click', JavascriptHelper.anotherFn);
}
As an aside, live has been deprecated in favor of on. Also, if you're not instantiating your JavascriptHelper, then why is it a Backbone.Model at all? Why not use a simple object literal:
var JavascriptHelper = {
myFn: function() {
//...
},
anotherFn: function() {
//...
}
};
And what are you expecting this construct:
var JavascriptHelper = Backbone.Model.extend(string, {}, {...})
to leave you in JavascriptHelper? Extending a string is strange but passing three arguments to Backbone.Model.extend is pointless, it only cares about two arguments. If you want static properties then you should be passing them as the second argument:
var JavascriptHelper = Backbone.Model.extend({}, { myFn: ... });