Can you monkey-patch methods of YUI modules? - javascript

In YUI3 is it possible to overwrite a method from e.g. the Node module? For example, I want to do something like this:
Y.Node.prototype.get = function () {
// Do some stuff then call the original function
};
That works perfectly (as you would expect) when Y is the globally available instance of YUI that I presume is created by the library. It does not work when you use the module loader and pass a callback:
YUI().use("*", function (DifferentY) {
DifferentY.Node.prototype.get === Y.Node.prototype.get; // false
});
I've spent a while digging through the YUI source but have so far failed to work out where and how DifferentY in the previous example is created (and by extension, where DifferentY.Node is created).
I have never used YUI before so it may be that I'm going about this in the completely wrong way.

Ok If I look at that example there seems to be a misunderstanding about Y. In YUI3 every thing is sandboxed, so you can have multiple instances of YUI running simultaneously. Y is not a global variable, it will be instantiated when you call the YUI().use method and only exists inside that function. That's why in the code of SO only DifferentY exists, but not Y.
YUI().use('node', 'event', function (Y) {
// The Node and Event modules are loaded and ready to use.
// Y exists in here...
});
So if you want to enhance YUI "from outside" I would build on YUI's module strategy and create a YUI module with YUI.add()
if (YUI) {
YUI.add('node-enhancement', function (Y) {
Y.Node.prototype.get = function () {
// Do some stuff then call the original function
};
}, '0.0.1', {
requires: [Node]
});
}
and let the developer load the enhancement as a module (how he would do it anyway with yui3)
YUI().use('node-enhancement'), function(Y) {
// just use YUI as allways
});
for an explanation of how the global YUI object works, this overview might help: http://yuilibrary.com/yui/docs/yui/

Here is an usage example of monkey patching technique.
Check out the console output here: http://jsfiddle.net/jslayer/XmF6L/
YUI.add('node-custom-module', function(Y){
console.warn('Override Y.Node');
Y.Node.YOUR_NODE = 'custom Node';
});
YUI.add('widget-custom-module', function(Y){
console.warn('Override Y.Widget');
Y.Widget.YOUR_WIDGET = 'custom Widget';
});
YUI.GlobalConfig = {
modules : {
'node-custom-module' : {
condition : {
name : 'node-custom-module',
trigger : 'node',
test : function(){
return true;
}
}
},
'widget-custom-module' : {
condition : {
name : 'widget-custom-module',
trigger : 'widget',
test : function(){
return true;
}
}
}
}
};
YUI().use('node', function(Y) {
console.group('Node');
console.log('Y.Node.YOUR_NODE : ', Y.Node.YOUR_NODE);
console.groupEnd('Node');
});
YUI().use('widget', function(Y) {
console.group('Widget');
console.log('Y.Node.YOUR_NODE : ', Y.Node.YOUR_NODE);
console.log('Y.Widget.YOUR_WIDGET : ', Y.Widget.YOUR_WIDGET);
console.groupEnd('Widget');
});
Surely it is not necessary to use YUI.GlobalConfig.
Also, it is comfortably to use groups (http://yuilibrary.com/yui/docs/yui/loader.html) inside YUI config's
groups : {
patches : {
modules : {
/* Define your mp modules here */
}
}
}

Related

Is there a way to override all the viewcontroller listeners in ExtJs

Version ExtJs - 6.2.1
Considering the sample code specified below, i'm curious to know if there is a better approach for implementing where i can handle some checks.
Ext.define('MainApp.view.main.MainController', {
extend: 'Ext.app.ViewController',
...
listen: {
controller: {
// listen to some components events
'componentController':{
'event1': 'onEvent1',
'event2': 'onEvent2'
}
}
},
onEvent1: function(){
// can i avoid this and do something better ??
this.commonEventHandlingChecks();
// event 1 handling logic
},
onEvent2: function(){
// can i avoid this and do something better ??
this.commonEventHandlingChecks();
// event 2 handling logic
},
commonEventHandlingChecks: function(){
// some logic to do some custom validations
}
});
Instead of calling the method "commonEventHandlingChecks" on each and every listener i have in my controller, is there a better way to do all the common event handling checks. Probably by overriding some methods in the controller or Ext.util.Event
Ext.Mixin does have a mixinConfig before API that can add a function on the mixin and execute it. If that returns false then it won't execute the function is was put before. This is documented in the class description here (link to the 6.2.1 version since you said you were using it).
That would work except the mixin would have to know what methods on the class it's being mixed into need to be protected. This wouldn't scale very well if you were wanting to use a mixin in different classes. For this, I would do something a little more advanced but keep the same functionality as the before API gives you. This mixin would look like:
Ext.define('MyAuthMixin', {
extend: 'Ext.Mixin',
onClassMixedIn: function (targetClass) {
const proto = targetClass.prototype
const config = proto.config
const protectedMethods = config.protectedMethods || proto.protectedMethods
// change this method name if you want something else
const checkAuth = this.prototype.checkAuth
if (protectedMethods) {
Ext.Object.each(protectedMethods, function (key, value) {
if (value && proto[ key ]) {
targetClass.addMember(key, function () {
// execute the checkAuth methods
// change this variable to change the method name
if (checkAuth.apply(this, arguments) !== false) {
return this.callParent(arguments);
}
});
}
});
}
},
checkAuth: function () {
// return false to stop calling
return !!MyApp.$user
}
})
Don't be scared by that onClassMixedIn function. Basically it's putting the checkAuth method before the method it's being told to protect and if you return false in the checkAuth then it will not execute that protected method.
For an example of how to use it and see it in action, I have created this fiddle. The implementation in classes would be this part:
mixins: [
'MyAuthMixin'
],
config: {
// put in a config object so subclass and superclass merging
// which is also why it's an object as a subclass can disable
// a protected method
protectedMethods: {
'onEvent1': true,
'onEvent2': true
}
},
To not protect a method, you can leave it out or set it to false. Reason for setting to false would simply be a subclass could disable the check if it extends a class that has it turned on. This mixin will work for any class, not just a controller. It can be a component or singleton or store, any.

RequireJS - Cannot Access External Module Function

I'm having an issue with RequireJS. Essentially, I'm not able to access a function defined inside another file from another one.
I need to do that because I want to export a given subset of functions like
define('submodule', [], function() {
let myFunction1 = function(){ return "Hello"; }
let myFunction2 = function(){ return " From"; }
let myFunction3 = function(){ return " Submodule!"; }
return {
myFunction1 : myFunction1,
myFunction2 : myFunction2,
myFunction3 : myFunction3,
};
});
And accessing them from another file
define('main', ['config', 'sub1', 'sub2', 'submodule'],
function(config, sub1, sub2, submodule) {
//Config
alert(config.conf);
//Submodule
let callSubmodule = function() {
alert(submodule.myFunction1() +
submodule.myFunction2() +
submodule.myFunction3());
}
//sub1
let callSub1 = function() {
alert(sub1.myFunction1());
}
//sub2
let callSub2 = function() {
alert(sub2.myFunction1());
}
});
The fact is that usually I'm able to do this with sub1 and
sub2, but, with submodule, I simply can't. I think it's somehow caused by the dependencies in require.config.js.
My require.config.js:
require(['common'], function () { //contains vendors
require(['config'], function () { //contains a js config file
require(['main'], function () { //main file
require(['sub1', 'sub2'], function () { //some subfiles
require(['submodule']);
});
});
});
});
For submodule.myFunction1() and othe two related functions I'm getting:
Uncaught (in promise) TypeError: Cannot read property 'myFunction1' of undefined
This is weird since I'm able to do that in other situations and I really can't understand why this is happening. For instance, I'm able to call sub1 and sub2 functions from main and other files but not submodule in particular.
Index.html
//Taken from Plunker
. . .
<script data-main="common" data-require="require.js#2.1.20" data-semver="2.1.20" src="http://requirejs.org/docs/release/2.1.20/minified/require.js"></script>
<script src="require.config.js"></script>
. . .
<button onclick = "callSubmodule()">Call Submodule</button>
<button onclick = "callSub1()">Call Sub1</button>
<button onclick = "callSub2()">Call Sub2</button>
common.js contains vendors, here's just an example
requirejs.config({
baseUrl : "",
paths : {
"jquery" : "http://code.jquery.com/jquery-latest.min.js"
}
});
sub1.js
define('sub1', ['submodule'], function(submodule) {
let myFunction1 = function(){ return "called sub1"; }
return {
myFunction1 : myFunction1
};
});
sub2.js
define('sub2', ['submodule'], function(submodule) {
let myFunction1 = function(){ return "called sub2"; }
return {
myFunction1 : myFunction1
};
});
I set up a Plunker with #SergGr help that tries to replicate application's structure but all the modules get undefined on click. On the real application this does not happen.
How can I solve this?
This is your code:
define('main', ['submodule'], function(submod) {
console.log(submodule.myFunction());
});
You have submod in the parameter list. But you then try to access submodule. Note that you return the function straight from your module (return myFunction), so your module has the value of the function myFunction and thus the module is what you should call. The code should be:
define('main', ['submodule'], function(submod) {
console.log(submod());
});
I Managed to solve this issue. Essentially, it was caused by a circular-dependency between the modules. So, a needed b and b needed a leading to one of them being undefined on the dependency resolution.
I found a solution to that on the answer provided by #jgillich at requirejs module is undefined.
So, I managed to solve using, in main
define('main', ['config', 'sub1', 'sub2', 'require'],
function(config, sub1, sub2, submodule, require) {
//Config
alert(config.conf);
//Submodule
let callSubmodule = function() {
alert(require('submodule').myFunction1() +
require('submodule').myFunction2() +
require('submodule').myFunction3());
}
});
As #jgillich said:
If you define a circular dependency ("a" needs "b" and "b" needs "a"), then in this case when "b"'s module function is called, it will get an undefined value for "a". "b" can fetch "a" later after modules have been defined by using the require() method (be sure to specify require as a dependency so the right context is used to look up "a"):
//Inside b.js:
define(["require", "a"],
function(require, a) {
//"a" in this case will be null if "a" also asked for "b",
//a circular dependency.
return function(title) {
return require("a").doSomething();
}
}
);
http://requirejs.org/docs/api.html#circular
The way you've named your modules I would expect they all came from a require config file. I would not expect that requirejs would know how to load those files without some sort of explicit compilation process. I also suspect that your server is returning something due to a 404 that JS is almost able to interpret without exploding.
Your setup seems and naming scheme seems quite strange. If you have the ability to start from scratch below are my recommendations.
Recommendations:
I'm noticing that you're using absolute paths. I highly recommend using relative paths for everything. There are many reasons for this.
Your data-main should be what you call "require.config.js". Your common.js is actually a require.config.js.
You load require.config.js (which is your main) separately using a script tag. You can do this but it's strange.
You can use the "commonjs" style syntax to require files without needing to use the array to define all your dependencies. I recommend that.
This is my recommendation for a set-up:
index.html
<script src="/js/config.js" />
<script src="http://requirejs.org/docs/release/2.1.20/minified/require.js" />
<script>
require('/js/main', function(main) {
main({});
});
</script>
/js/config.js
// setting requirejs to an object before its loaded will cause requirejs to use it as the config
window.requirejs = {
baseUrl : "/",
paths : {
"jquery" : "http://code.jquery.com/jquery-latest.min.js"
}
};
/js/main.js
define(function(require) {
const sum = require('./sum');
return (a, b) => sum(a, b);
});
/js/sum.js
define(function(require) {
return (a, b) => a + b;
});
Update (March 02, 2017)
Your plunker obviously will not work because you have direct calls from HTML to your module functions.
<button onclick = "callSubmodule()">Call Submodule</button>
<button onclick = "callSub1()">Call Sub1</button>
<button onclick = "callSub2()">Call Sub2</button>
RequireJS doesn't work that way. One of key purposes of RequireJS is to provide modules isolation and thus it just can't work that way: imagine if several different modules had functions callSubmodule.
To the best of my knowledge there is no way to bind calls from HTML back to the code in a RequireJS module, it should be other way around: module binds to HTML. And if you fix those issues, everything works fine for me as you can see at this fork of your plunker.
Old Answer
The bug is in your subModule.js
define('submodule', [], function() {
let myFunction = function(){ return "Hello"; }
//return myFunction; // old, wrong
return { myFunction: myFunction };
});
Even if you want to return just 1 function you should not return it as is, you should wrap it into an object and give it an explicit name.
P.S. if this is not your real issuse, please provide us real Minimal, Complete, and Verifiable example

Referencing a parent object in callback functions with jQuery

I've a page that is generated dynamically, and that includes certain number (user-dynamically-defined) of advanced scatter plot charts. I intend to create a JavaScript object which defines the scatter plot itself, i.e. which takes some parameters, some data, and some container ID, and which will create the various elements needed to obtain the visualisation: canvas elements, toolbar, etc.. To do so, I started with the following (simplified) class:
(function () {
if (!this.namespace) { this.namespace = {};}
this._instances = { index: 0 };
this.namespace.ScatterPlot = function (containerId, file, options) {
_instances.index ++;
this.id = this.containerId+"-"+_instances.index ;
this.containerId = containerId ;
_instances [this.id] = this;
// ... Do stuffs with file and options ...
// Initialize elements once the DOM is ready
$(this.updateDOM);
}
namespace.ScatterPlot.prototype = {
updateDOM: function() {
$("<canvas>")
.click(clickCallback)
.appendTo("#"+this.containerId);
//(...)
},
clickCallback: function() {
alert("Some click: "+this.id);
}
}
})();
Each object can be created with:
var v1 = new namespace.ScatterPlot("container1", "foo", "foo");
var v2 = new namespace.ScatterPlot("container2", "foo", "foo");
There are two problems here: (1) in updateDOM, 'this' does not make reference to my initial ScatterPlot object, which means that this example will never work, and (2) similarly, the clickCallback will not be able reference the scatterplot with 'this' either.
I'm new to javascript, and I'm still struggeling to understand the logic of OO programming in javascript, so the question is: I'm I taking the wrong direction here ? After some digging, I could roughly achieve what I wanted by passing this to updateDOM:
$(this.updateDOM(this)); // This blows my eyes but does the trick, at least partially
updateDOM: function(that) {
$("<canvas>")
.click(that.clickCallback)
.appendTo("#"+that.containerId);
//(...)
},
clickCallback: function() {
// Not working either... Should pass 'that' to the function too
alert("Some click: "+this.id);
}
But I don't feel this patters to be very elegant... And the problem is not fixed either regarding the click callback.
Thoughts ?
Have a look at MDN's introduction to the this keyword.
The standard ways of dealing with that issue are using a that variable - not as an argument, but in a separate function:
var that = this;
$(function() {
that.updateDOM();
});
// or
$(this.getClickCallback());
...
namespace.ScatterPlot.prototype.getClickCallback = function() {
var that = this;
return function clickCallback(e) {
alert("Some click: "+that.id);
};
};
Alternatively, you can always use .bind() (or $.proxy for older browsers) which do quite what the second example does in a more generic way:
$(this.clickCallback.bind(this));

Is there any way to use Jasmine default matchers within custom matchers?

I have a custom matcher in some Jasmine test specs of the form:
this.addMatchers({
checkContains: function(elem){
var found = false;
$.each( this.actual, function( actualItem ){
// Check if these objects contain the same properties.
found = found || actualItem.thing == elem;
});
return found;
}
});
Of course, actualItem.thing == elem doesn't actually compare object contents- I have to use one of the more complex solutions in Object comparison in JavaScript.
I can't help but notice, though, that Jasmine already has a nice object equality checker: expect(x).toEqual(y). Is there any way to use that within a custom matcher? Is there any general way to use matchers within custom matchers?
Yes, it is slightly hacky but entirely possible.
The first thing we need to do is make the Jasmine.Env class available. Personally I have done this in my SpecRunner.html since its already setup there anyway. On the load of my SpecRunner I have the following script that runs:
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var trivialReporter = new jasmine.TrivialReporter();
jasmineEnv.addReporter(trivialReporter);
jasmineEnv.specFilter = function(spec) {
return trivialReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
};
})();
So after the execJasmine function declaration I push the jasmineEnv into the global namespace by adding this:
this.jasmineEnv = jasmineEnv;
Now, in any of my spec files I can access the jasmineEnv variable and that is what contains the matchers core code.
Looking at toEqual specifically, toEqual calls the jasmine.Env.prototype.equals_ function. This means that in your customMatcher you can do the following:
beforeEach(function(){
this.addMatchers({
isJasmineAwesome : function(expected){
return jasmineEnv.equals_(this.actual, expected);
}
});
});
Unfortunately, using this method will only give you access to the following methods:
compareObjects_
equals_
contains_
The rest of the matchers reside the jasmine.Matchers class but I have not been able to make that public yet. I hope this helps you out in someway or another

How can I emulate the Jquery UI API?

I've written basic jQuery plugins before, but I'm struggling to get my head around something more complex. I'm looking to emulate the API of jQuery UI, which works like this:
$('#mydiv').sortable({name: 'value'}); // constructor, options
$('#mydiv').sortable("serialize"); // call a method, with existing options
$('#mydiv').sortable('option', 'axis', 'x'); // get an existing option
I've tried the following:
(function($){
$.fn.myPlugin = function(cmd){
var config = {
default: 'defaultVal'
};
if(typeof cmd === 'object'){
$.extend(config, cmd);
}
function _foo(){
console.log(config.default);
}
if(cmd==='foo'){
return _foo();
}
this.each(function(){
// do default stuff
});
}
})(jQuery);
$('#myElement').myPlugin({default: 'newVal'});
$('#myElement').myPlugin('foo');
What I would like to see here is 'newval' being logged, but I'm seeing 'defaultVal' instead; the plugin is being called and started from scratch every time I call .myPlugin() on the element.
I've also tried using _foo.call(this) and some other variants. No joy.
In a way, I understand why this is happening, but I know that it must be possible to do it the same way as jQuery UI. I just can't see how!
(I appreciate that jQuery UI uses the widget factory to handle all of this, but I don't want to make that a requirement for the plugin.)
Perhaps what you want is this...
(function($){
var config = {
default: 'defaultVal'
};
$.fn.myPlugin = function(cmd){
if(typeof cmd === 'object'){
$.extend(config, cmd);
}
function _foo(){
console.log(config.default);
}
if(cmd==='foo'){
return _foo();
}
this.each(function(){
// do default stuff
});
}
})(jQuery);
$('#myElement').myPlugin({default: 'newVal'});
$('#myElement').myPlugin('foo');
Move the config variable outside the myPlugin function. This change will cause config to be initialized only once: when your plugin function is created.
You're declaring config during the function call rather than as a closure used by it. Try this:
(function($){
var config = {
default: 'defaultVal'
};
$.fn.myPlugin = function(cmd){
if(typeof cmd === 'object'){
$.extend(config, cmd);
}
function _foo(){
console.log(config.default);
}
if(cmd==='foo'){
return _foo();
}
this.each(function(){
// do default stuff
});
}
})(jQuery);
$('#myElement').myPlugin({default: 'newVal'});
$('#myElement').myPlugin('foo');
In addition, you could look into the jQuery data API for caching data, especially if you aren't going to have just one instance per page.

Categories

Resources