Rx.Observable.bindCallback with scope in rxjs - javascript

It seems in rxjs 4.x, Rx.Observable.fromCallback accept scope as the second parameter, but in 5.0, this method is changed to Rx.Observable.bindCallback and doesn't accept scope parameter. How to add scope parameter in bindCallback. For example in ES6.
class Test {
constructor(input) {
this.input = input;
}
callback(cb) {
return cb(this.input);
}
rx() {
// this works on rx 4.x
// var observable = Rx.Observable.fromCallback(this.callback, this)();
// this doesn't work, because this.callback function doesn't use original this, so cannot get this.input
var observable = Rx.Observable.bindCallback(this.callback)();
// Work around: Rx.Observable.bindCallback(this.callback)();
// var me = this;
// var observable = Rx.Observable.bindCallback((cb) => {me.callback(cb);})();
observable.subscribe(
input => console.log('get data => ' + input),
err => console.log('get error =>' + err),
() => console.log('complete')
);
}
}
new Test(100).rx();

There is an example at http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#static-method-bindCallback which shows how to do this.
Use bindCallback on object method
const boundMethod = Rx.Observable.bindCallback(someObject.methodWithCallback);
boundMethod.call(someObject) // make sure methodWithCallback has access to someObject
.subscribe(subscriber);
You can call it immediately without declaring a variable, and also pass args like this:
Rx.Observable.bindCallback(someObject.callback).call(someObject,<args>)
So to bind to this you can simply call
Rx.Observable.bindCallback(this.callback).call(this,<args>)

It works for me, when I add this to the constructor
constructor(input) {
this.input = input;
this.callback = this.callback.bind(this)
}

Related

Get the names of the parameters of the wrapped function

I want to use the methods of the Minio class without specifying all their parameters, but substituting some of the parameters automatically. How do I do it...
I get all the class methods from the prototype of the Minio class and dynamically create wrappers for them in my class.
For each wrapper method, I get the parameter names from the original method of the Test class.
If there is one in the list of parameters that I want to omit when calling my wrapper method, then I add it to the list of arguments and call originalMethod.apply(this.minioClient, args).
Everything was fine until there were methods that were already wrapped.
I need to get the parameter list of the bucketExists method from outside the Minio class. Any idea how to get parameter names from such a wrapped method?
// minio/dist/main/helpers.js
exports function promisify(fn){
return function(){
const args = [...arguments];
fn.apply(this, args);
}
}
// minio/dist/main/minio.js
class Minio{
bucketExists(bucketName){
return bucketName;
}
methodThatNotWrappedByPromisifyAndWorksFine(bucketName){
return bucketName;
}
}
module.exports = Minio;
Minio.prototype.bucketExists = (0,helpers.promisify)(Minio.prototype.bucketExists)
I want to give an instance of my class with methods wrapped from the original class link the ability to work with only one bucket, that was passed to the my class constructor, without the ability to specify some other one after initialize.
My wrapper
const proxyHandler = () => {
return {
apply: (target, thisArg, argumentsList) => {
const funcParams = getMethodParamNames(target.source ? target.source.functionForWrap : target);
const bucketNameIndex = funcParams.indexOf("bucketName");
const regionNameIndex = funcParams.indexOf("region");
if (bucketNameIndex >= 0) {
argumentsList.splice(bucketNameIndex, 0, this.bucket.name);
}
if (regionNameIndex >= 0) {
argumentsList.splice(regionNameIndex, 0, this.bucket.region);
}
try {
return target.apply(this.minioClient, argumentsList);
} catch (error) {
logger.engine.error(`S3 '${this.bucket.name}' ${target} error: ${error.message}`, error.stack);
}
},
}
}
getMethods(this.minioClient).forEach(func => {
this[func] = new Proxy(this.minioClient[func], proxyHandler());
})
Solved the problem by overriding the method wrapper like this.
const MinioHelpers = require('minio/dist/main/helpers');
const origMinioPromisify = MinioHelpers.promisify;
MinioHelpers.promisify = (functionForWrap) => {
console.log("PATCHED!", functionForWrap);
var fn = origMinioPromisify(functionForWrap);
//from this i'll get all need information about wrapped function
fn.source = {
functionForWrap,
promisify: origMinioPromisify,
}
return fn
}
var Minio = require('minio');

"Uncaught TypeError: this._isDateType is not a function" within Arrow Function

I've got every time a type error that a function definition could not be found. The code looks as follow:
return BaseController.extend("ch.micarna.weightprotocol.controller.Calendar", {
onInit: function () {
console.log(this._isDateType(new Date()));
let oHbox = this.byId("calendar-container");
let oTodayDate = new Date();
let oEndDate = this._getLastDayOfMonth(oTodayDate);
},
_getLastDayOfMonth: (oBegin) => {
if (this._isDateType(oBegin)) {
throw new TypeError("The given parameter is not type of date.");
}
return new Date(oBegin.getFullYear(), oBegin.getMonth() + 1, 0);
},
_isDateType: (oDate) => {
return Object.prototype.toString.call(oDate) === "[object Date]";
},
});
The problem is the _isDateType function that could not be found when it is called inside the _getLastDayOfMonth function.
I set the break point:
and as you can see, the function is undefined and I do not know why.
At the beginning of the onInit function, I called the _isDateType function:
console.log(this._isDateType(new Date()));
and it supply the result as expected.
What am I doing wrong?
Replace the arrow function
_getLastDayOfMonth: (oBegin) => {
// this....
},
with the normal function expression:
_getLastDayOfMonth: function(oBegin) {
// this...
},
By this, the _getLastDayOfMonth can freely access other methods within the Controller instance.
Why it didn't work with arrow function
First of all, it's important to know that arrow functions bind their context lexically.
An arrow function expression has a shorter syntax than a function expression and does not have its own this. [source]
For example, it's not possible to call .bind on arrow functions. They get their this from the closure when evaluated.
Since this was not an instance of the Controller but rather the window object when BaseController.extend was called, calling this._isDateType inside the arrow function was equivalent to window._isDateType.
What you cannot do is refer to a property of an "under construction" object from elsewhere in the object literal syntax. In cases where you want to do that, you do need one or more separate assignment statements.
For example, move your code as follows:
var temp = BaseController.extend("ch.micarna.weightprotocol.controller.Calendar", {
onInit: function () {
console.log(this._isDateType(new Date()));
let oHbox = this.byId("calendar-container");
let oTodayDate = new Date();
let oEndDate = this._getLastDayOfMonth(oTodayDate);
}
});
temp._isDateType = (oDate) => {
return Object.prototype.toString.call(oDate) === "[object Date]";
};
temp._getLastDayOfMonth = (oBegin) => {
if (this._isDateType(oBegin)) {
throw new TypeError("The given parameter is not type of date.");
}
return new Date(oBegin.getFullYear(), oBegin.getMonth() + 1, 0);
}
return temp;
The idea is to split function assignments into several statements;
The element this can be used inside a function to get the temporary value of the element. To use the _isDateType method you should create an attribute inside the method and fill it with the 'this' value.
return BaseController.extend("ch.micarna.weightprotocol.controller.Calendar", {
var temp= null;
onInit: function () {
temp = this;
console.log(temp._isDateType(new Date()));
let oHbox = temp.byId("calendar-container");
let oTodayDate = new Date();
let oEndDate = temp._getLastDayOfMonth(oTodayDate);
},
_getLastDayOfMonth: (oBegin) => {
if (temp._isDateType(oBegin)) {
throw new TypeError("The given parameter is not type of date.");
}
return new Date(oBegin.getFullYear(), oBegin.getMonth() + 1, 0);
},
_isDateType: (oDate) => {
return Object.prototype.toString.call(oDate) === "[object Date]";
}

How can I map one object usage to route to 2 objects?

I use totango for some usage tracking. Now, while we try to rework the way we track, I want to send trackings into 2 different totango accounts, for a transition period.
I've managed to split the objects into window.totango_old and window.totango_beta.
Now instead of replacing all the old usages of window.totango, I was wondering whether I can make window.totango simply apply any arbitrary method I use on it onto the 2 different objects specified above.
I've tried figuring out the usage with .apply(), but I can't fully grasp how it would work in my case.
I want to avoid doing this:
window.totango = function() {
return {
track: function(event, value) {
window.totango_old.track(event, value);
window.totango_beta.track(event, value);
}
}
}
Because it means I have to map the usable functions one by one. Is there a "catch-all" way that would pass any method I call on an object, and let me get its name and arguments, to pass on to different objects dynamically?
I tried running a test, like so:
window.test2 = function() {
return {
testFunc: function(a, b) {
console.log([a, b]);
}
}
};
window.test = function() {
this.apply(window.test2, arguments)
// also tried: window.test2.apply(window.test2, arguments)
};
window.test.testFunc("1", "2");
But I received the following exception:
Uncaught TypeError: undefined is not a function
You could use something like this:
window.callFunction = function( method, args ) {
var res = { };
//Set the default values
method = method || 'track';
args = args || [ ];
//in case we want the result of the functions
res.old = window.totango_old[ method ].apply( window.totango_old, args );
res.beta = window.totango_beta[ method ].apply( window.totango_beta, args );
return res;
}
Because totango_old is an object, you can use the name of the method as the index, then call apply on the returned function and pass your arguments. The first parameter of apply is the value of "this" in that context. Depending on how the module is set up it is important to have the right value in the first parameter. The second parameter passed to apply is the arguments to pass to the function.
You could possibly do something like this
function TotangoFill () {
}
TotangoFill.prototype = {
callFunction: function ( method, args ) {
var res = { };
//Set the default values
args = args || [ ];
//in case we want the result of the functions
res.old = window.totango_old[ method ].apply( window.totango_old, args );
res.beta = window.totango_beta[ method ].apply( window.totango_beta, args );
return res;
},
track: function( event, value ) {
// arguments is an array of all the arguments passed to this function
return this.callFunction( 'track', arguments );
},
anotherMethod: function( ) {
//You don't need to define the parameters, just pass them on
return this.callFunction( 'anotherMethod', arguments );
},
customEvent: function( value ) {
//Add another layer of abstraction
return this.track( 'customEvent', value );
}
}
window.totango = new TotangoFill( )
You have a problem with your attempt to wrap totango which explains the error in your test, and can be easily resolved without changing your invocations.
Specifically, you need to actually invoke the function that you assigned to window.totango such that totango contains the returned object, and not the function itself, i.e.:
window.totango = (function() {
return {
track: function(event, value) {
window.totango_old.track(event, value);
return window.totango_beta.track(event, value);
}
}
})(); // invocation

Rivets.js event handlers with custom arguments

I've just started with Rivets.js, which looks promising as simple data-binding framework.
I've arrived at the point that I don't know how to pass "custom arguments" to the rv-on-click binder, so I tried to take the idea from this: https://github.com/mikeric/rivets/pull/34
My code:
rivets.binders["on-click-args"] = {
bind: function(el) {
model = this.model;
keypath = this.keypath;
if(model && keypath)
{
var args = keypath.split(' ');
var modelFunction = args.shift();
args.splice(0, 0, model);
var fn = model[modelFunction];
if(typeof(fn) == "function")
{
this.callback = function(e) {
//copy by value
var params = args.slice();
params.splice(0, 0, e);
fn.apply(model, params);
}
$(el).on('click', this.callback);
}
}
},
unbind: function(el) {
$(el).off('click', this.callback);
},
routine: function(el, value) {
}
}
This code is working, my question is: is this the correct way?
If you want to pass a custom argument to the event handler then this code might be simpler:
rivets.configure({
// extracted from: https://github.com/mikeric/rivets/issues/258#issuecomment-52489864
// This configuration allows for on- handlers to receive arguments
// so that you can onclick="steps.go" data-on-click="share"
handler: function (target, event, binding) {
var eventType = binding.args[0];
var arg = target.getAttribute('data-on-' + eventType);
if (arg) {
this.call(binding.model, arg);
} else {
// that's rivets' default behavior afaik
this.call(binding.model, event, binding);
}
}
});
With this configuration enabled, the first and only argument sent to the rv-on-click handler is the value specified by data-on-click.
<a rv-on-click="steps.go" data-on-click="homePage">home</a>
This is not my code (I found it here), but it does work with Rivets 0.8.1.
There is also a way to pass the current context to the event handler. Basically, when an event fires, the first argument passed to the handler is the event itself (click, etc), and the second argument is the model context.
So lets say that you are dealing with a model object that is the product of a rv-each loop...
<div rv-each-group="menu.groups">
<input rv-input="group.name"><button rv-on-click="vm.addItem">Add item</button>
___ more code here ___
</div>
Then you can access the current "group" object in the event handler like this:
var viewModel = {
addItem: function(ev, view) {
var group = view.group;
}
};
More details on this technique can he found here https://github.com/mikeric/rivets/pull/162
I hope this helps.
There is another answer here:
https://github.com/mikeric/rivets/issues/682
by Namek:
You could define args formatter:
rivets.formatters.args = function(fn) {
let args = Array.prototype.slice.call(arguments, 1)
return () => fn.apply(null, args)
}
and then:
rv-on-click="on_click | args 1"
Please note that args is not a special id, you can define anything instead of args.
To pass multiple arguments use space: "on_click | args 1 2 3 'str'"

knockout pass additional parameters to subscription function

What I want to achieve is to create subscription for model properties. This subscription function should call WebApi via Ajax updating property value in database. For ajax call I need three paramaters: "fieldName", "fieldValue" and "modelId", ajax will update database row based on those three parameters.
I have many properties and all of them need the same functionality, so I do not want to subscribe for each property individually, so I found a following suggestion:
ko.subscribable.fn.withUpdater = function (handler) {
var self = this;
this.subscribe(handler);
//support chaining
return this;
};
Add this is how it is "attached" to observables:
self.ModelId= ko.observable();
self.CompanyName = ko.observable().withUpdater(update);
where update is some js function outside model.
However, I have problem, because I am not able to pass three paramaters to update functions (or also I can say in another words - I need to be able to get viewModel.ModelId property value inside update, as well as propertyName).
function update (propertyName, propertyNewValue, anotherPropertyValue) {
//do ajax update
}
As an example for CompanyName property it will be:
update("CompanyName", "New Company value here", 3),
where
3 == viewModel.ModelId
There might be a better way to do this, but the following will work:
First, add a target object to the withUpdate method:
ko.subscribable.fn.withUpdater = function (handler, target, propname) {
var self = this;
var _oldValue;
this.subscribe(function (oldValue) {
_oldValue = oldValue;
}, null, 'beforeChange');
this.subscribe(function (newValue) {
handler.call(target, _oldValue, newValue, propname);
});
return this;
};
The update subscribe function will get scoped to the target property:
var update = function (propertyName) {
console.log('propname is '+ propname + ' old val: ' + oldvalue + ', new val: ' + newvalue + ', model id: ' + this.ModelId());
}
Now you will need to use it a little differently.
self.CompanyName = ko.observable().withUpdater(update, self, "CompanyName");
An example http://plnkr.co/edit/HhbKEm?p=preview
I couldn't get the scope of the withUpdater function to be that of the object without explicitly passing in the target and a string for the company name.
You can declare your function as a variable outside of the 'fn' scope.
var dataservice = 'my class that has the data calls';
var altFunc = function () {
return ko.pureComputed(function () {
var currentItem = this().filter(function (item) {
// Do knockout stuff here and return your data
// also make calls to the dataservice class
}, this, dataservice);
};
ko.observableArray.fn.someNewFunctionality = altFunc;

Categories