How to bind a function with sub-observables to an observable array? - javascript

I have following ViewModel:
var Order = function(data) {
this.fruits = ko.observableArray(ko.utils.arrayMap(data.Fruis, function(item) { return item; }));
this.vegetables = ko.observableArray(ko.utils.arrayMap(data.Vegetables, function(item) { return item; }));
};
I need to define some sub-properties and sub-observables bound to the specific instance, and some common methods for fruits and vegetables,:
var Items = function(data, type) {
var self = this;
self.type = type;
self.choice = ko.observable(-1);
self.choice.select = ko.computed(function(){
var id = self.choice();
// do stuff
});
self.choice.remove = function() {
var id = self.choice.peek();
// do stuff
};
self.add = function(code) {
//do stuff
self.choice(id);
};
};
Which is the right way to bind the function containing my set of methods and sub-observables, so that i can use the methods as follows:
orderViewModel.fruits.add("apples");
orderViewModel.fruits.add("bananas");
orderViewModel.fruits.choice(0);
orderViewModel.fruits.choice.remove();
console.log(ko.tpJSON(orderViewModel));
// prints: {fruits: [bananas], vegetables: []};
I think there is no need to use extenders, as the properties and methods aren't generic, and don't need to be common to all observables.
I tried by returning an observable array from my Item function, but i wasn't able to get this to work, as sub-properties and sub-observables have been lost. How can i bind Items to my observable arrays?

Even though you might not want to create an extender, what you're doing here is extending an observable array...
If you don't want to register an extender, you can create a small helper function to create an observableArray and add some methods and properties to it before you return.
In the example below you can see some example code. Some important advice:
If you use this approach, I'd suggest not overwriting the default methods in observableArray. E.g.: remove takes an item by default; you want it to work with an external choice index... It's best to pick a different name so you keep supporting both.
If you end up using the extension a lot, it might be worth it to create a clean viewmodel that stores the observable array internally. You can define a toArray method for exporting to a plain array.
var obsCollection = function(initialItems) {
var items = ko.observableArray(initialItems);
items.choice = ko.observable(-1);
items.add = items.push;
var ogRemove = items.remove.bind(items);
// I'd rename this to "deleteChoice"
items.remove = function() {
var index = items.choice();
ogRemove(items()[index]);
// Reset choice to -1 here?
};
return items;
};
var fruits = obsCollection(["Apple"]);
log(fruits);
fruits.add("Banana");
fruits.choice(0);
fruits.remove();
log(fruits);
fruits.remove();
fruits.add("Mango");
fruits.add("Lemon");
log(fruits);
function log(d) {
console.log(JSON.stringify(ko.unwrap(d)));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
Edit to clarify the (lack of) use of this:
Since we don't use the new keyword, there's no real need to use this. Internally, the observableArray creates a new instance, but our only way of referring to this instance is through items. When detaching prototype methods from the array, we need to make sure we call them with the right context, by either bind or .call(items) (or apply).
If you want the code to look like a "class", you can either do: var self = items; and continue with the self keyword, or rewrite it to use the new keyword (last bullet point in my answer).
var myArray = ko.observableArray([1,2,3]);
try {
// Reference the function without binding `this`:
var removeFromMyArray = myArray.remove;
// Internally, the observableArray.prototype.remove method
// uses `this` to refer to itself. By the time we call it,
// `this` will refer to `window`, resulting in an error.
removeFromMyArray(2);
} catch(err) {
console.log("ERROR:", err.message);
console.log(myArray());
}
// By binding to the array, we ensure that the function reference
// is always called in the right context.
var boundRemoveFromMyArray = myArray.remove.bind(myArray);
boundRemoveFromMyArray(2);
console.log(myArray());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

Related

How to iterate through instances of a prototype "class" JS

I want a "for" or "while" loop to iterate through all the instances/objects in the prototyp/"class".
like "hasNext()" in array.
Then I wanna implement a function. for instance alertname("obj") this will then return the name of obj. the problem is that I dont know the specific obj. the function only gets a string and then it'll search in the prototypes which one is the right one.
function Product(id, name) {
this.id = id;
this.name = name;
}
Product.prototype.getid = function() {
i = this.id;
return i;
};
Product.prototype.getname = function() {
i = this.name;
return i;
};
balloon = new Product(0, "Balloon");
var text = "balloon";
//doesnt work
function getname(obj) {
i = Product.prototype.getname(obj);
window.alert(i);
}
getname(text);
It looks like you want to keep track of all the objects you create with new Product. This is something you need to implement yourself.
Just create an array:
const stock = [];
stock.push(new Product(0, "Balloon"));
// ...
stock.push(new Product(0, "Monopoly"));
Then you can simply iterate them:
for (const product of stock) {
console.log(product.getname());
}
It is for a good reason that JS does not provide you with such an array out-of-the-box: if that were done, then none of the created objects could ever be garbage-collected; they will always be regarded as something you still need to use. So it is a good thing that there is no built-in mechanism for this.
Concerning your own attempt
Product.prototype.getname(obj);
This does not make sense: getname does not take an argument. You usually call prototype functions like methods:
obj.getname()
In some cases you would want to use Product.prototype.getname, but that is only needed when obj is not an instance of Product, but is so similar that it would work to call getname on it. In that case use .call():
Product.prototype.getname.call(obj);

JavaScript Function Get Object

Is there a way for a function to get the object value before the function name? Below is an example of what I am kinda trying to achieve. I want to be able to read the myElement variable within the function but not pass it as a parameter but pass it before the function with the dot.
Any simple examples or explanations would be most helpful.
var myElement = document.getElementById('myID');
myElement.myFunction();
function myFunction() {
alert(myElement);
}
The only way you could do this is to add myFunction to HTMLElements prototype (which is what gets returned by document.getElementById(). That's usually frowned upon, but if it's your own project and you know what you do, you could do that.
var myElement = document.getElementById('myID');
HTMLElement.prototype.myFunction = function() {
console.log(this);
}
myElement.myFunction();
<div id="myID"></div>
With this prototype in place, you can call myFunction on every HTMLElement in your code.
In regards to your last comment, the function could be
HTMLElement.prototype.myFunction = function() {
alert(this.id);
}
I don't see why you should do it, as it's much easier to just do
alert(myElement.id);
In regards to the comments, here's what I'd do. Instead of extending anything, create your own class (or function), that takes a HTMLElement. Now on this class, you can add whatever method you want, manipulate your element and then return the plain HTMLElement from a getter. You can obviously change that to whatever return you want.
class MyHtmlElement {
constructor(htmlElement) {
this._htmlElement = htmlElement;
}
alertId() {
alert(this._htmlElement.id);
// optional
return this;
}
logId() {
console.log(this._htmlElement.id);
// optional
return this;
}
setId(newId) {
this.htmlElement.id = newId;
// optional
return this;
}
setStyle(prop, val) {
this._htmlElement.style[prop] = val;
// optional
return this;
}
get htmlElement() {
return this._htmlElement;
}
set htmlElement(value) {
this._htmlElement = value;
}
}
const el = new MyHtmlElement(document.getElementById('foo'));
el
.setId('bar')
.logId()
.alertId()
.setStyle('background-color', 'red')
.setStyle('width', '100vw')
.setStyle('height', '100vh');
// If you need the plain element, return it
const plainHTMLElement = el.htmlElement;
console.log(plainHTMLElement);
<div id="foo"></div>
When a function is stored in an object and then called with theObject.theFunction(), the value of this within the function will be theObject.
function sayHello() {
alert('Hello, my name is ' + this.name);
}
let myObject = { name: 'Bob', speak: sayHello };
myObject.speak(); // shows the message 'Hello, my name is Bob'
Now if you want to be able to create your own function and let it be used by an Element, you either need to store the function in the Element instance first or to add it to the Element prototype, both of which I highly discourage. If you feel like you have to do this, there's a flaw in your design.
Still, if you have a good reason to add a custom method to an existing object, I recommend you look up lessons about prototype inheritance in JavaScript, or read my old answer about it here if you're not sure how it works. You could say, make a function which adds methods to an object when it is called, like this:
function addMethods(elem) {
elem.speak = sayHello;
}
let myElement = document.getElementById('myID');
addMethods(myElement);
myElement.speak(); // Hello, my name is <value of the element's name attribute>
Or you could add the method to the prototype of all elements:
Element.prototype.speak = sayHello;
let myElement = document.getElementById('myID');
myElement.speak();
While browsers have let people do this since forever ago, there is technically no guarantee that Element is publicly available, or that its prototype is modifiable, or that you can add methods to Element instances. The Prototype framework (an inconveniently named third party library) has been using these techniques for a long time, but it did cause them a couple issues. jQuery prefers using a different approach, wrapping elements in another object on which custom methods are put.

Is there a way to change/redirect "indirect" self-references?

I'm developing a website using Knockout.js, but my question is way more general, so don't focus on that.
I reference the object within itself by its variable name, in this case viewModel, like so:
var viewModel = {
propA: "propA",
fnA: function () {
alert("I am " + viewModel.propA); //the same as this.propA
}
};
That way, I don't run into any trouble when it comes to sub-objects and other "fancy" stuff.
Now, I need to merge another object into my viewModel, but I run into a reference problem:
g_test = {}; //The way with this global variable is just for demonstration purposes
(function () {
var viewModel = {
propA: "propA",
fnA: function () {
alert("I am " + viewModel.propB);
}
};
g_test.vmA = viewModel;
})();
(function () {
var viewModel = {
propB: "propB",
fnB: function () {
alert("I am " + viewModel.propA);
}
}
/** merge viewModel "A" into viewModel "B" */
var vmA = g_test.vmA;
for (var key in vmA) {
if (vmA.hasOwnProperty(key)) {
viewModel[key] = vmA[key];
}
}
viewModel.fnA(); //I am undefined
viewModel.fnB(); //I am propA
})();
As you can see, fnB knows propA, but fnA does not know propB.
Using this as reference to the viewModel object however does the job. But I don't want to run through my whole object and replace every viewModel with this or introduce respective "helper variables" where needed, because this would take me ages. Plus, to my eyes it would mean that it's impossible to merge two objects which don't have bidirectional knowledge of each other.
Is there a senseful way of letting the viewModel variable in fnA point to viewModel "B" without having to know viewModel "B"?
Creating multiple viewModels and then trying to merge them seems like a problematic approach, and I don't know what you're trying to achieve with it. There is probably a better way (at the very least, using this would have been the right choice for creating mixins). But assuming that either there's a good reason or it's too much effort to rework it now, let's look at your issue.
You have two viewModels, and you want to merge them in such a way that each knows about the members of the other. You merge them in one direction, so B knows about A, but A doesn't know about B. You need to merge the other direction, as well. Object.assign is a convenient way to do what your loop does, but if you are on a pre-Object.assign browser, you can add another loop that goes through viewModel and copies properties from it into vmA.
The result is that you have two viewModels whose methods refer to each other. Each has propA, propB, fnA, and fnB, but fnA (in both objects) refers to the member of object A and vice-versa.
g_test = {}; //The way with this global variable is just for demonstration purposes
function mergeObjects(obj1, obj2) {
Object.assign(obj1, obj2);
Object.assign(obj2, obj1);
}
(function() {
var viewModel = {
propA: "propA",
fnA: function() {
alert("fnA: I am " + viewModel.propB);
}
};
g_test.vmA = viewModel;
})();
(function() {
var viewModel = {
propB: "propB",
fnB: function() {
alert("fnB: I am " + viewModel.propA);
}
}
mergeObjects(g_test.vmA, viewModel);
// to demonstrate which value is outputted by which function
g_test.vmA.propA = "A.propA"; // not used
g_test.vmA.propB = "A.propB"; // used by fnA
viewModel.propA = "B.propA"; // used by fnB
viewModel.propB = "B.propB"; // not used
viewModel.fnA(); //I am A.propB
viewModel.fnB(); //I am B.propA
// also
g_test.vmA.fnA();
g_test.vmA.fnB();
})();

Convert arguments into new object parameter without cloning the object?

I want to create a new object with parameters from 'arguments', but I don't know
how to or even possible to convert it directly without cloning. Here's how it is possible using a clone:
function theClass(name, whatever) {
this.name = name;
this.whatever = whatever;
}
// I need to use the arguments passed from this function but without using clone
// as shown.
function doSomething()
{
function clone(args) {
theClass.apply(this, args);
}
clone.prototype = theClass.prototype;
return new clone(arguments);
}
// Usage expectation.
var myNewObject = doSomething("Honda", "motorbike");
console.log(myNewObject.name);
However, this suffers on performance because each time you call doSomething, you have to create a clone just to pass that arguments to be applied in it from theClass.
Now I want to pass that arguments without passing to a cloned object, but I don't know
how to convert it directly.
Any idea?
Note: As clarified by kaminari, the parameters passed are not strictly 'name' and 'whatever', but could be anything depends on the object I want to create. 'theClass' in the code is merely an example.
Thanks.
EDIT: In light of the intended use of these functions:
Probably your best option on maintaining your intended behavior is to implement your function in the following way:
function theClass(options){
this.name = options.name || ''; //or some other default value
this.whatever = options.whatever || '';
};
function doSomething(options){
options = options || {};
return new theClass(options);
};
With this implementation in mind, the code you supplied in "usage expectation" would look like this:
var myNewObject = doSomething({name: "honda", whatever: "motorbike"});
console.log(myNewObject.name);
In this manner, theClass can support as many or as few parameters as need be (only depends on what's supplied in the object and what you choose to extract from it) and similarly, the wrapper doSomething can be given as many or as few options as desired.
this suffers on performance because each time you call doSomething, you have to create a clone just to pass that arguments to be applied in it from theClass.
Simply define the clone function outside of doSomething, and it won't get recreated every time:
function theClass(name, whatever) {
this.name = name;
this.whatever = whatever;
}
function clone(args) {
theClass.apply(this, args);
}
clone.prototype = theClass.prototype;
function doSomething() {
return new clone(arguments);
}

Is this a good decorator pattern for javascript?

I need some simple objects that could become more complex later, with many different properties, so i thought to decorator pattern.
I made this looking at Crockford's power constructor and object augmentation:
//add property to object
Object.prototype.addProperty = function(name, func){
for(propertyName in this){
if(propertyName == name){
throw new Error(propertyName + " is already defined");
}
}
this[name] = func;
};
//constructor of base object
var BasicConstructor = function(param){
var _privateVar = param;
return{
getPrivateVar: function(){
return _privateVar;
}
};
};
//a simple decorator, adds one private attribute and one privileged method
var simpleDecorator = function(obj, param){
var _privateVar = param;
var privilegedMethod1 = function(){
return "privateVar of decorator is: " + _privateVar;
};
obj.addProperty("privilegedMethod1", privilegedMethod1);
return obj;
}
//a more complex decorator, adds public and private properties
var complexDecorator = function(obj, param1, param2){
//private properties
var _privateVar = param1;
var _privateMethod = function(x){
for(var i=0; i<x; i++){
_privateVar += x;
}
return _privateVar;
};
//public properties
var publicVar = "I'm public";
obj.addProperty("publicVar", publicVar);
var privilegedMethod2 = function(){
return _privateMethod(param2);
};
obj.addProperty("privilegedMethod2", privilegedMethod2);
var publicMethod = function(){
var temp = this.privilegedMethod2();
return "do something: " + temp + " - publicVar is: " + this.publicVar;
};
obj.addProperty("publicMethod", publicMethod);
return obj;
}
//new basic object
var myObj = new BasicConstructor("obj1");
//the basic object will be decorated
var myObj = simpleDecorator(obj, "aParam");
//the basic object will be decorated with other properties
var myObj = complexDecorator(obj, 2, 3);
Is this a good way to have Decorator Pattern in javascript?
Are there other better ways to do this?
There are various implementations of the Decorator pattern in Javascript on Wikipedia and other sites - (1), (2), (3). The pattern is defined as:
the decorator pattern is a design pattern that allows new/additional behaviour to be added to an existing object dynamically.
Object extension is already build into the language itself. Objects can be easily extended, and properties can be added anytime. So why should you have to jump through hoops to achieve this? Shouldn't something like this suffice:
var person = { name: "Jack Bauer" };
// Decorated the object with ability to say a given phrase
person.say = function(phrase) {
alert(phrase);
}
// Using the decorated functionality
person.say("Damn it!");
If you want a method to apply to all objects that were created using this function, then add that method/properties to the function's prototype.
Update: If you have clearly defined pieces of functionality which can be mixed and matched as needed into certain types of objects, then the MooTools approach of extending and mixing in behavior into objects is nicely done. To give an example, consider a UI component that can be resized, dragged around with a handle, and deleted by clicking a tick mark at the top-right corner. You may not want to create each component with these behaviors but define all these behaviors separately each into their own object. And later mix in these behaviors into each type of component as needed.
var Resizable = {
...
};
var Draggable = {
...
};
var Deletable = {
...
};
var someLabel = new Label("Hello World");
// one way to do it
someLabel.implement([Resizable, Draggable, Deletable]);
// another way to do it
someLabel.implement(Resizable);
someLabel.implement(Draggable);
someLabel.implement(Deletable);
It looks better and more intuitive (to me) than doing something like
var awesomeLabel = new Resizable(new Draggable(new Deletable(someLabel)));
because we are still dealing with a label, and not some resizable, or some draggable, or some deletable object. Another small point, but still worth mentioning is that the parentheses start getting unmanageable after 3 or 4 decorators, especially without good IDE support.

Categories