How to access a Stimulus JS controller method from inside a nested function? - ruby-on-rails

I have a Stimulus controller inside which I have a setSegments function and then this code in the connect() method:
connect() {
const options = {
overview: {
container: document.getElementById('overview-container'),
waveformColor: 'blue',
},
mediaElement: document.querySelector('audio'),
dataUri: {
arraybuffer: document.getElementById('normal-audio-button').dataset.waveform
},
emitCueEvents: true,
};
Peaks.init(options, function (err, peaks) {
window.instance = peaks;
window.speed = "normal";
setSegments()
instance.on('segments.enter', function (segment) {
const segmentCard = document.getElementById(segment.id)
segmentCard.focus({preventScroll: true})
window.currentSegment = segment
});
});
}
setSegments() {
alert("segment set up)
}
I'm tryng to call setSegments() inside the Peaks.init function but it doesn't work because of the function's scope. I'm just not sure how to get around this. I tried calling this.setSegments() instead but it doesn't help.
What's the correct way of accessing the function in this case?
Thanks

The problem is that this is a bit confusing when working with JavaScript, however a way to think about it that it is the current context.
For example, when your code is running in the browser console or not in another function this is the global window object. When you are directly in the controller's connect method this is the controller's instance.
However, when you pass a function to Peaks.init that function creates it's own new context where this is the function's context and no longer the controller instance.
There are three common workarounds to calling this.setSegments;
1. Set a variable that is outside the function scope
As per your solution, const setSegments = this.setSegments; works because you are creating a reference outside the function scope and functions have access to this.
connect() {
const options = {}: // ... Options
// this is the controller's instance
const setSegments = this setSegments;
Peaks.init(options, function (err, peaks) {
// this is the peaks init handler context
window.instance = peaks;
// this.setSegments(): - will not work
setSegments();
instance.on('segments.enter', function (segment) {
// this is the event (usually)
});
});
}
2. Use bind to override the function'sthis
You can pull your function out to a variable and then add .bind(this) to it so that when the function is called it will use the this from the controller instance instead.
connect() {
const options = {}: // ... Options
// this is the controller's instance
const myFunction = function (err, peaks) {
// this is the BOUND this provided by the bind command and will be the controller's instance
window.instance = peaks;
this.setSegments():
instance.on('segments.enter', function (segment) {
// this is the event (usually)
});
};
myFunction.bind(this);
Peaks.init(options, myFunction);
}
3. Use an arrow function (easiest)
You should be able to use an arrow function in modern browsers or if you have a build tool running it may transpiled this for older browsers.
Instead of function() {} you use () => {} and it grabs the this from the parent function context instead.
connect() {
const options = {}: // ... Options
// this is the controller's instance
Peaks.init(options, (err, peaks) => {
// this is now the controller's instance and NOT the peak handler context
window.instance = peaks;
this.setSegments():
instance.on('segments.enter', function (segment) {
// this is the event (usually) and is NOT the controller instance as arrow function not used here.
});
});
}
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this for more details

I don't know if it's the best way to do it but adding the following right after the beginning of the connect method did the trick:
let setSegments = this.setSegments

Related

How to pass Model as an argument in ipcMain.handle

I want to create a reusable function in Electron.js to handle Saving data irrespective of the model(e.g User, Employee, Product),so I passed Model as an argument, then call the specific Model during when the function is called.
but I get this error
Error: Expected handler to be a function, but found type 'object'
This is my code
const User = require( '../database/models/Users.js');
ipcMain.handle('user:create', saveData(User));
async function saveData(_, data,Model) {
try {
const user = await Model.insert(data);
return user;
} catch (e) {
console.log(e.message);
}
}
ipcMain.handle('user:create', saveData(User)); call function saveData(User) after app is started and it returns object. if you want to assign function to 'user:create' then without parameters it's ipcMain.handle('user:create', saveData); but with parameters it's.
ipcMain.handle('user:create', () => saveData(User));
is the same as
ipcMain.handle('user:create', function () {
return saveData(User)
});

How to write tests using the service scope

My app is accessing object from the service scope (package:gcloud/service_scope.dart), like the storageService and additional services that I put inside the scope with ss.register().
Now I want to unit test a function that accesses this scope, and uses mock objects that I want to put in the service scope.
Is the only way to do so, to register them for every test, like this:
var withServiceScope = (callback()) => ss.fork(() {
// Register all services here
return callback();
});
test('the description', () => withServiceScope(() async {
// Call my function that can now access the service scope
}));
Or is there are way that allows me to do that in the setUp() function so I don't need to add this line for each test?
This might make it simpler to write your tests (code not tested)
import 'package:test/test.dart' as t;
import 'package:test/test.dart' show group;
var withServiceScope = (callback()) => ss.fork(() {
// Register all services here
return callback();
});
test(String description, Function testFunction) {
t.test(description, () => withServiceScope(() async {
testFunction();
}));
}
main() {
test('the description', () async {
// Call my function that can now access the service scope
}));
}

this reference in a jQuery UI Custom widget

I have built a custom widget that contains lots of other widgets.
The problem I am getting is the this. reference when a widget inside my custom widget calls a function in my custom widget. For example:
$(function() {
$.widget("custom.my_widget",
{
_create: function() {
this.variable = "HI";
var that=this;
// A Custom widget
this.button = $("<button>", {html:"PRESS"})
.button()
.click(this.do_it) // I know I can do a function(){ that.do_it() }) but that is not the point
.appendTo(this.element);
},
do_it: function() {
// With the setup like this, how do I get the correct this. reference
// As it stands, this = the button object
alert("TEST: "+ this.variable);
}
})
});
The problem is that the this in the do_it function does not point to my_custom widget, instead it points to the button widget.
Above is symbolic, please don't point out a bug as my actual widget is over 3000 lines of code and has many references like this. I need to get the my_widget instance inside functions like this when other widgets call my widget's functions.
I have tried putting in another parameter, but with some callbacks in some third party widgets this is not possible.
There must be an easy way to get the correct base this value for my_widget.
jsFiddle for reference : http://jsfiddle.net/jplevene/6e7m2q6h/3/
You can either use bind(), instead of click(), specifying the "context", or just reference a local variable and call the function (e.g. self below):
$.widget("custom.my_widget",
{
// the constructor
_create: function() {
var self = this;
this.variable = "HI";
// A Custom widget
this.button = $("<button>").button().click(function(){
self.do_it();
});
},
do_it: function(e) {
alert(this.variable);
}
JSFiddle: http://jsfiddle.net/ewpgv3mt/1/
The only way I found to do it is as follows:
$(function() {
$.widget("custom.my_widget",
{
_create: function() {
this.variable = "HI";
// A Custom widget
this.button = $("<button>", {html:"PRESS"})
.button()
.click(this.do_it) // I know I can do a function(){ that.do_it() }) but that is not the point
.data("widget", this) // <---- New line here
.appendTo(this.element);
},
do_it: function() {
// Get the parent widget
var that = $(this).data("widget");
alert("TEST: "+ that.variable);
}
})
});
What I did was pass the "this" value to a data value of the object. There must be a better way than this.
I have tried $(this).closest(".my_widget_class"), but I then need the widget from the object

JavaScript module pattern with sub-modules cross access or better pattern?

Perhaps this is the wrong approach to my problem, but that is why I'm here. In the code below is a sample of a JavaScript module pattern with sub-modules. As I build this, I realize that some sub-modules need to "call" each other's methods.
I know that it would be wrong to use the full call admin.subModuleB.publicB_2(); but its the only way since the IIFE functions cannot call "themselves" until instatiated, ex. "module" is not available in the primary namespace, etc...
My thought is that this pattern is incorrect for my situation. The purpose of the module encapsulation is to keep things private unless reveled. So what would be a better pattern?
var module = (function($, window, document, undefined) {
return {
subModuleA : (function() {
var privateA = 100;
return {
// We have access to privateA
publicA_1 : function() {
console.log(privateA);
// How do I use a method from publicB_1
// the only way is:
module.subModuleB.publicB_2();
// but I don't want to use "module"
},
publicA_2 : function() {
console.log(privateA);
}
}
})(),
subModuleB : (function() {
var privateB = 250;
return {
// We have access to privateB
publicB_1 : function() {
console.log(privateB);
},
publicB_2 : function() {
console.log(privateB);
// I have access to publicB_1
this.publicB_1();
}
}
})()
}
})(jQuery, window, document);
What you actually have is an issue with dependencies. Sub module A has a dependency on Sub module B. There are two solutions that come to mind.
Define both modules as their own variables inside the function closure, but return them together in a single object.
What you actually want is instantiable classes where Class A has a dependency on Class B.
Since solution #1 is the closest to your current code, let's explore that first.
Define Both Modules Separately Inside the Closure
var module = (function($, window, document, undefined) {
var SubModuleA = function() {
var privateA = 100;
return {
// We have access to privateA
publicA_1 : function() {
console.log(privateA);
// Refer to SubModuleB via the private reference inside your "namespace"
SubModuleB.publicB_2();
// but I don't want to use "module"
},
publicA_2 : function() {
console.log(privateA);
}
};
}();
var SubModuleB = function() {
var privateB = 250;
return {
// We have access to privateB
publicB_1 : function() {
console.log(privateB);
},
publicB_2 : function() {
console.log(privateB);
// I have access to publicB_1
this.publicB_1();
}
};
}();
// Return your module with two sub modules
return {
subModuleA : SubModuleA,
subModuleB : SubModuleB
};
})(jQuery, window, document);
This allows you to refer to your two sub modules using local variables to your module's closure (SubModuleA and SubModuleB). The global context can still refer to them as module.subModuleA and module.subModuleB.
If Sub Module A uses Sub Module B, it begs the question of whether or not Sub Module B needs to be revealed to the global context at all.
To be honest, this is breaking encapsulation because not all the functionality of Sub Module A exists in Sub Module A. In fact, Sub Module A cannot function correctly without Sub Module B.
Given your particular case, the Module Pattern seems to be an Anti Pattern, that is, you are using the wrong tool for the job. In reality, you have two classifications of objects that are interdependent. I would argue that you need "classes" (JavaScript Constructor functions) and traditional OOP practices.
Use JavaScript Constructor Functions ("classes")
First, let's refactor your "module" into two classes:
var module = (function($, window, document, undefined) {
function ClassA(objectB) {
var privateA = 100;
this.publicA_1 = function() {
console.log(privateA);
objectB.publicB_2();
};
this.publicA_2 = function() {
console.log(privateA);
};
}
function ClassB() {
var privateB = 250;
this.publicB_1 = function() {
console.log(privateB);
};
this.publicB_2 = function() {
console.log(privateB);
this.publicB_1();
};
}
// Return your module with two "classes"
return {
ClassA: ClassA,
ClassB: ClassB
};
})(jQuery, window, document);
Now in order to use these classes, you need some code to generate the objects from the constructor functions:
var objectA = new module.ClassA(new module.ClassB());
objectA.publicA_1();
objectA.publicA_2();
This maximizes code reuse, and because you are passing an instance of module.ClassB into the constructor of module.ClassA, you are decoupling those classes from one another. If you don't want outside code to be managing dependencies, you can always tweak ClassA thusly:
function ClassA() {
var privateA = 100,
objectB = new ClassB();
this.publicA_1 = function() {
console.log(privateA);
objectB.publicB_2();
};
this.publicA_2 = function() {
console.log(privateA);
};
}
Now you can refer to module.ClassB using the name within the function closure: ClassB. The advantage here is that outside code does not have to give module.ClassA all of its dependencies, but the disadvantage is that you still have ClassA and ClassB coupled to one another.
Again, this begs the question of whether or not the global context needs ClassB revealed to it.

JQuery UI Widget Inheritance / class method call

I'm trying to write a custom widget in JQuery UI (v 1.9 m8): http://pastebin.com/zua4HgjR
From my site I call it like this: var D = new $.ui.mail({}); Basically it works.
Is there a better method to call doSend on button click?
The question is how to access object instance from function handler?
"this" returns entire html window.
Tried with $.proxy doesn't work: click: $.proxy(this.doSend, this);
Thanks in advice!
Unfortunately if you setup the buttons by using the options member you'll have no reference to the element that you need in order to call doSend. One workaround I was able to come up with is to assign the handler in the _create method where you have the appropriate reference.
_create: function() {
this.options.buttons = [
{
text: 'Send',
click: function(self) {
return function() {
$(self.element).mail('doSend');
};
}(this)
},
{
text: 'Cancel',
click: function() { $(this).remove() }
}
];
this._super(arguments);
}
Live Example - http://jsfiddle.net/hhscm/2/
Spending this weekend finally I finished my plugin: http://agrimarket-blacksea.com/jsc/jquery-mail.js
I decided to call "class function" in classical way: $(this).mail('doSend'), until I'll find something better.
Thanks!

Resources