Jquery plugin private data and $(this) - javascript

I am developing a JQuery plugin that stores private data in the object's data field (as was recommended in an article I found):
$.fn.awesomify = function (schema, data) {
$(this).data('schema', schema);
}
I can then retrieve this value in a private method:
function rebuild() {
var schema = $(this).data('schema');
}
Now the problem I have is that the value of $(this) is different when the method gets called from a different object. For example, the onclick event of an href:
var a = ...;
a.click(function () {
rebuild(); // Now $(this) is the a-object
});
How should I solve this?
Thanks!

This is because the value of this is determined at invocation time, and is set to the object which the method belongs to, or window if the method is not attached to an object*; rebuild() is not attached to an object, so this is window.
You can either pass the value of this in as an argument, or use the Function.prototype.call/Function.prototype.apply methods;
rebuild.call(this);
or:
rebuild(this);
function rebuild(that) {
var schema = $(that).data('schema');
}
* -> Exception is if you're in strict mode, where its undefined, but this isn't relevant here.

You can store "this" object to some variable and then use it from everywhere where this variable is visible
var currentObj = $(this);
function rebuild() {
var schema = currentObj.data('schema');
}

Related

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

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>

When is it appropriate to create a static property instead of an instance in javascript objects?

I don't really understand how/when to use javascript prototypes. I understand the difference between these two ways of assigning properties:
Static property
var Date = function () {};
Date.setTime = function (key) {
};
Date.updateTime = function (key, value) {
};
setTime and updateTime are properties directly added to the Date object.
Assigning property to an instance prototype
var Date = function () {};
Date.prototype.setTime = function (key) {
};
Date.prototype.updateTime = function (key, value) {
};
setTime and updateTime are defined properties that instances of Date will inherit.
My question: How do you know when you should be assigning properties to an instance/prototype or directly to the object (static variable)? When should I decide to assign a variable directly to an object vs making it available to all instances?
Depends completely on what you are wanting to do with the object. If you want instances of the object to be able to use the method with their own properties, you'll want the prototype to have the methods. Otherwise, if you're creating a utility class of some sort where you don't want the class to keep any state, then you should probably just add static methods.
Basically, if you want instances of the object to be able to use their own state in the method, then set the method on the prototype.
var MyObj = function(prop) {
this.myProp = prop;
}
MyObj.static = function() {
console.log('This is static ' + this.myProp);
}
MyObj.prototype.dynamic = function() {
console.log('This is dynamic ' + this.myProp);
}
var myInstance = new MyObj('wow!');
MyObj.static(); // 'This is static undefined'
// MyObj.dynamic(); // Not a function
// myInstance.static(); // Not a function either
myInstance.dynamic(); // 'This is dynamic wow!'

jQuery: use dynamic object based on variable value

I have several objects and all of them have some methods that are called the same but do different things.
When I click a button, I want to call the init() method, but the
object is different based on what button I clicked.
Here is a snippet
$btn.on('click', function(e){
e.preventDefault();
var $trigger = $(this);
var objectName = $trigger.data('object');
/*
if objectName is 'user', I want to call user.init(),
if it's 'product' I want to call product.init() and so on...
right now i get an error if I call like his
*/
objectName.init($trigger);
});
Is it possible to dynamically call an object like this ? I know it is for its properties and methods, but I din't find anything about this issue.
Thank you.
It's better to do mapping
var entities = {
user: user,
entity: entity
}
var objectName = $trigger.data('object');
entities[objectName].init($trigger);
In case your objects (or functions) defined in the global scope, you can access them using the window object:
var funcT = function() {
console.log('funcT was called');
}
var objT = {
'a': 1,
'b': 2
}
function a() {
var funcName = 'funcT'
window[funcName]();
var objName = 'objT'
console.log(window[objName]);
}
a()
With window[variable] you can access variables based on another variable.
So all that you need to do is to replace objectName.init($tr‌​igger); with: window[objectName].‌​init();

Object assignment does not work in this angularjs controller

Inside my angularjs controller, I try to assign an object into a $scope.XX object. For some reason, it cannot work. Here is a simplified version of the code inside the angularjs controller.
$scope.XXX = {};
polling_interval_ms = 100;
var poll = function (ChartObj, polling_interval_ms) {
var processedObj = {};
processedObj = processDataChart(data_Chart); //data_Chart is object that contains data that changes in real-time
ChartObj = Object.assign(processedObj);
console.log(ChartObj);
$timeout(function () {
poll(ChartObj, polling_interval_ms)
}, polling_interval_ms);
};
poll($scope.XXX, polling_interval_ms);
console.log($scope.XXX);
The strange part is the output of console.log(ChartObj); shows that data has been assigned to the object. However, the output of console.log($scope.XXX) is empty. I was expecting $scope.XXX to contain same data as ChartObj. What did I do wrong?
In javascript all parameters in function is reference. So when you change reference - you not change referenced object.
In your case you can use Object.assgin in a bit different way
Object.assign(ChartObj, processedObj);
because
The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
Or pass wrapper for object XXX, in this case it a $scope
$scope.ChartObj = {};
polling_interval_ms = 100;
var poll = function (wrapper, polling_interval_ms) {
var processedObj = {};
processedObj = processDataChart(data_Chart); //data_Chart is object that contains data that changes in real-time
wrapper.ChartObj = Object.assign(processedObj);
console.log(wrapper.ChartObj);
$timeout(function () {
poll(wrapper, polling_interval_ms)
}, polling_interval_ms);
};
poll($scope, polling_interval_ms);
console.log($scope.ChartObj);
Use
$scope.XXX
instead of ChartObj because
your are assigning value to
ChartObj
and
$scope.XXX
is not a refrence type

creating one var object vs multiple var

For better code structure, I want to use a javascript object holding all properties instead of using multiple vars:
// 1. WAY
// This returns an error, as _inp cannot be accessed by input_value
// Uncaught TypeError: Cannot read property 'value' of undefined
var ref = {
_inp: input.target,
input_value: _inp.value,
....
};
// 2. WAY
// When using this, it works
var ref = {
_inp: input.target,
input_value: input.target.value,
....
};
// 3. WAY
// This obviously works, too.
var
_inp = input.target,
input_value = _inp.value,
My Question is, why does 3. Way works and 1.Way doesnt?
In example 1, _inp will be a property of an object. It isn't a variable. You can only access it from a reference to the object (and it won't be a property of the object until the object exists, which will be after the object literal has been evaluated, see also Self-references in object literal declarations).
Because _inp will only be filled in with the input.target value after passing through the entire var ref = { ... }; statement. This means that when you try to use it, it doesn't exist yet.
The 1st way don't work because you refers to "_inp" which is not an existing var. and the ref object is not fully created (that's why input_value: this._inp.value won't work either)
To create objects and assigning values to its properties, you can use a function (I keep most of your code):
var ref = {
_inp: input.target,
input_value: null,
init: function()
{
this.input_value = this._inp.value;
}
};
ref.init();
console.log(ref.input_value); // will contains the same as input.target.value
but usually, people create objects with all property with default values, and pass an argument to their init function:
var ref = {
_inp: null,
input_value: null,
init: function(input)
{
if (input)
{
this._inp = input.target;
this.input_value = input.target.value;
}
}
};
var input = {target:{value:"foo"}};
ref.init(input);

Categories