Vue optionMergeStrategies Methods has no access to 'this' keyword - javascript

I am trying to build a plugin for Vue.
My plugin has a custom method caller customMethod for every component, I want it to run on after the page is mounted/created.
In a simple way, this is working as I want but I am having trouble accessing this inside customMethod.
It logs 'undefined' when I am trying to console.log(this).
so how can I access this inside my customMethod ?
var defaultParms = Object.freeze({
start : function(){},
leave : function(){},
});
const myPlugin = {
install(Vue, options = []) {
var ref = Vue.util;
var extend = ref.extend;
var assets = Object.create(null);
extend(assets, defaultParms);
Vue.options.customMethod = assets;
// set option merge strategy
var strats = Vue.config.optionMergeStrategies;
if (strats) {
strats.customMethod = (parent, child, vm)=>{
if (!child) return parent;
if (!parent) return child;
var ret = Object.create(null);
extend(ret, parent);
for (var key in child) {
ret[key] = child[key];
}
return ret
};
}
Vue.mixin({
customMethod:{
start: function(){
console.log('hi') // log 'hi'
console.log(this.$appName) // log 'undefined'
}
},
created: function () {
if(this.$options.customMethod){
this.$options.customMethod.start && this.$options.customMethod.start();
}
}
});
Vue.prototype.$appName = 'vikash';
}
}
Vue.use(myPlugin)
new Vue().$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

This is part of how the context this works in JS. You need to set it with bind, call or apply:
this.$options.customMethod.start.call(this)

Related

MutationObserver Wrapper

Overview
This MutationObserver Wrapper should be able to provide developers with any changes that have been made in the DOM for security reasons.
Problem
I need a simple way to create a wrapper for MutationObserver. I just want to be informed when a MutationObserver was created on the page. Then I want to make sure the code runs before all other codes so that I can put it in an extension.
Code
This is what I tried:
const dump = console.log.bind(console);
class Observer
{
static started = false
static jacked = {}
static start()
{
Observer.started = true;
Observer.hijack( MutationObserver, function constructor()
{
alert("CAREFUL!! - MutationObserver is running here!");
return (Observer.jacked.MutationObserver).constructor.apply(this, [...arguments]);
});
// if (!Observer.fixes[document.domain])
// {
// dump("not listining on: "+document.domain);
// return;
// };
//
// Observer.fixes[document.domain]()
}
static hijack(target, impose)
{
let waiting = setInterval(()=>
{
if ((typeof target.prototype) !== "object"){ return };
if ((typeof target.prototype.constructor) !== "function"){ return };
clearInterval(waiting);
let name = target.prototype.constructor.name;
dump("hijacking: "+name);
Observer.jacked[name] = target;
Object.defineProperty(target.prototype, "constructor", {writable:false, configurable:false, enumerable:false, value:impose});
},0);
}
let waiting = setInterval(() =>
{
let exists = ((typeof MutationObserver) !== "undefined");
if ( !exists )
{
console.log("observer waiting for `MutationObserver`");
return
};
if ( Observer.started )
{
clearInterval(waiting);
console.log("observer already started .. exiting");
return
};
clearInterval(waiting);
Observer.start();
}, 0);
Try something like this, however, be aware of what globalThis refers to in your extension:
const jacked = {};
jacked.MutationObserver = globalThis.MutationObserver;
delete globalThis.MutationObserver;
globalThis.MutationObserver = (class MutationObserver
{
constructor()
{
console.log("jacked");
}
observe()
{
console.log("busy");
}
});
after running the above, anywhere, like in a function, you can test if your wrapper-test works like this:
let watcher = (new MutationObserver(function(){}));
watcher.observe();
You can use jacked to resume normal operation inside your supplementary class constructor by returning a new jacked.MutationObserver.

Sinon stub not working when trying to mock out an instance of an object

I am trying to mock out every instance that is created with the new keyword for an object.
Here is the Object I am trying to mock out:
var SharedWhiteboardView = function(moduleEl, domService) {
'use strict';
var self;
var sharedWhiteboardProblemImage;
var whiteboardController;
var caller = false;
var toolbarController;
return {
initWhiteboard : function()
{
self = this;
sharedWhiteboardProblemImage = domService.find(moduleEl, '#sharedWhiteboardModule-sharedWhiteboardProblemImage');
var toolbarEL = $('#sharedWhiteboard-toolbar');
toolbarController = new ToolbarController(WhiteboardConstants.SHARED_WHITEBOARD_ID, toolbarEL, null);
toolbarController.init(false);
whiteboardController = toolbarController.getWhiteboardController();
},
enableWhiteboardEdition : function(enabled)
{
if(self.getWhiteboardObject() && self.getWhiteboardObject.hasOwnProperty('enableEdition')) self.getWhiteboardObject().enableEdition(enabled);
whiteboardController.setEnabled(enabled);
}
};
}
This is the file which I am trying to test and it creates a new instance of the above object
Box.Application.addModule('SharedWhiteboardModule', function(context) {
'use strict';
var self;
var moduleEl;
var domService;
var sharedWhiteboardView;
var modal;
var assignmentTimer = 3000;
var sharing = false;
var assignmentImageData = '';
return {
/**
* Initializes the module and caches the module element
* #returns {void}
*/
init: function() {
self = this;
domService = context.getService('DomService');
moduleEl = context.getElement();
sharedWhiteboardView = new SharedWhiteboardView(moduleEl, domService);
sharedWhiteboardView.initWhiteboard();
sharedWhiteboardView.enableWhiteboardEdition(false);
};
}
I am trying to write a unit test to test that the sharedWhiteboardView.enableWhiteboardEdition method is called with 'false'
However I am failing to attach a spy or stub that method out. I have tried these solutions and they did not work
//First Attempt
sinon.stub(SharedWhiteboardView, "enableWhiteboardEdition", function() {return 0})
// Second Attempt
sinon.stub(SharedWhiteboardView.prototype, "enableWhiteboardEdition").returns(0);
//Third Attempt
sandbox.stub(SharedWhiteboardView.prototype, 'enableWhiteboardEdition', checkEnableWhiteboardEdition());
//Fourth Attempt Trying the answer provided by chrmod
it.only('when type is "SharedWhiteboardModule-setEditable" should call sharedWhiteboardView.enableWhiteboardEdition', function (done) {
const view = SharedWhiteboardView();
sinon.stub(view, "enableWhiteboardEdition", function() {
console.log('Hit');
});
module.onmessage('SharedWhiteboardModule-setEditable', true);
done();
});
No error but it does not hit the console.log, I removed the 'new' keyword as suggested
Errors that I got:
-Attempted to wrap undefined property enableWhiteboardEdition as function
-Cannot stub non-existent own property enableWhiteboardEdition
Please any help would be great. I have reached a dead end here.
Here is a codepen: http://codepen.io/anon/pen/bgmNxx?editors=0011
All I am trying to do is to have the Fake method get hit when my module calls enableEdition
SharedWhiteboardView is not a constructor, it is rather a factory function. Once called (without new) it returns new object that has enableWhiteboardEdition as own property.
Thus a stub has to be set on that object:
const view = SharedWhiteboardView();
sinon.stub(view, "enableWhiteboardEdition", function() {return 0});
This did it.
it('when type is "SharedWhiteboardModule-setEditable" should call setEditable with appropriate callback', function (done) {
var mockSharedWhiteboardView = {
enableWhiteboardEdition: function() {},
initWhiteboard: function() {},
initScrollBar: function() {},
refreshScrollBar: function() {},
isMainWhiteboardAvailable: function() {}
};
sandbox.spy(mockSharedWhiteboardView, 'enableWhiteboardEdition');
var tempGlobals = {
SharedWhiteboardView: global.SharedWhiteboardView
};
global.SharedWhiteboardView = function() {
return mockSharedWhiteboardView;
};
module = Box.Application.getModuleForTest('SharedWhiteboardModule', contextFake);
module.init();
var shouldEnable = true;
module.onmessage('SharedWhiteboardModule-setEditable', shouldEnable);
assert(mockSharedWhiteboardView.enableWhiteboardEdition.calledWithExactly(shouldEnable),
'should enable the whiteboard');
shouldEnable = false;
module.onmessage('SharedWhiteboardModule-setEditable', shouldEnable);
assert(mockSharedWhiteboardView.enableWhiteboardEdition.calledWithExactly(shouldEnable),
'should not enable the whiteboard');
// cleanup
global.SharedWhiteboardView = tempGlobals.SharedWhiteboardView;
done();
});

Pass result of a function back to itself in Ember.js controller

I have a function that does what I want it to do using lodash and vanilla javascript. I pass it a value, it checks to see if that value is already mapped, and then it checks to see if the value has specific child properties. If it does, it passes the the child property through the same function.
I'm trying to do essentially the same thing in an ember app, but the function is a property in a controller. When I try to reference the function name, I get an error: "Uncaught ReferenceError: collectHeaders is not a function". How can I pass the result of "collectHeaders(child)" back to the function?
Just lodash & javascript where it works:
function collectHeaders(parent) {
var firstChild = parent.children ? parent.children[0] : {};
if (firstChild.section_type && firstChild.section_value) {
_.forEach(parent.children, function(child) {
collectHeaders(child)
});
} else if (parent.children) {
// more code
}
}
Ember.app controller with lodash:
var mapping = {};
var CompareController = Ember.ObjectController.extend({
collectHeaders: function(parent) {
var firstChild = parent.children ? parent.children[0] : {};
if (firstChild.section_type && firstChild.section_value) {
_.forEach(parent.children, function(child) {
collectHeaders(child);
});
} else if (parent.children) {
// more code
}
},
});
export default CompareController;
Your code is out of scope, your previous function was global
collectHeaders: function(parent) {
var self = this;
var firstChild = parent.children ? parent.children[0] : {};
if (firstChild.section_type && firstChild.section_value) {
_.forEach(parent.children, function(child) {
self.collectHeaders(child);
});
} else if (parent.children) {
// more code
}
},
});

Javascript prototypíng and ''this'' of instantiated object

here's a tricky one. I create a class...
App = function(o) {
var _app = this;
this.events = {
listeners : {
list : new Array(),
add : function(event, fn) {
if (! this.list[event]) this.list[event] = new Array();
if (!(fn in this.list[event]) && fn instanceof Function) this.list[event].push(fn);
if (_app.debug.get()) _app.events.dispatch('log.append','EVENTS:ADD:'+event);
},
remove : function(event, fn) {
if (! this.list[event]) return;
for (var i=0, l=this.list[event].length; i<l; i++) {
if (this.list[event][i] === fn) {
if (_app.debug.get()) _app.events.dispatch('log.append','EVENTS:REMOVE:'+event);
this.list[event].slice(i,1);
break;
}
}
}
},
dispatch : function(event, params) {
if (! this.listeners.list[event]) return;
for (var i=0, l=this.listeners.list[event].length; i<l; i++) {
if (_app.debug.get()) _app.events.dispatch('log.append','EVENTS:DISPATCH:'+event);
this.listeners.list[event][i].call(window, params);
}
}
};
};
and prototype more functionality later. Here's one;
App.prototype.connection = {
source : { 'default' : null },
types : new Array(),
pool : new Array(),
count : function() { return this.pool.length },
active : {
pool : new Array(),
count : function() { return this.pool.length },
add : function(o) { this.pool.push(o) },
remove : function(o) { this.pool.splice(this.pool.indexOf(o), 1); }
},
create : function(o) {
if (! o || ! o.exe) o.exe = this.source.default;
if (! o || ! o.type) o.type = 'xhr';
var c = new this.types[o.type];
App.events.dispatch('connection.created',c);
this.pool.push(c);
return c;
},
remove : function(o) {
App.events.dispatch('connection.removed',o);
this.pool.splice(this.pool.indexOf(o), 1);
},
abort : function(o) {
var i = this.pool.indexOf(o);
if (i===-1) return;
this.pool[i].abort();
}
};
then instantiate this into an object.
app = new App();
The problem is, I have a line called App.events.dispatch('connection.removed',o) which doesn't work. App needs to be the instantiation 'app' which ideally would be 'this', but this refers to App.prototype.connection. How do you get at the root in this case?
Thanks - Andrew
You cannot use the object literal approach to define the connection on the prototype, otherwise there's no way to access the App instance.
Note that when you are referencing App, you are referencing the constructor function, not the App instance. Also, this inside create for instance would not be working because this will point to the connection object, not the App instance either.
There are a few options:
function Connection(eventBus) {
this.eventBus = eventBus;
}
Connection.prototype = {
someFunction: function () {
this.eventBus.dispatch(...);
}
};
function App() {
// this.events = ...
//the instance could also be injected, but you would need to implement
//a setEventBus on the connection object, or simply do conn.eventBus = this;
this.connection = new Connection(this);
}
var App = new App();
Also, please note that all mutable values (e.g. objects) defined on the prototype will be shared across all instances. That's probably not what you want.
Also note that:
listeners : {
list : new Array()
Should be:
listeners : {
list : {}
An array is meant to have numeric indexes only, while a plain object is a better structure to use as a map.

How to create a javascript library using a closure

I have written some javascript that I would to encapsulate in a closure so I can use it elsewhere. I would like do do this similar to the way jQuery has done it. I would like to be able to pass in an id to my closure and invoke some functions on it, while setting some options. Similar to this:
<script type="text/javascript">
_snr("#canvas").draw({
imageSrc : someImage.png
});
</script>
I have read a lot of different posts on how to use a closure to do this but am still struggling with the concept. Here is where I left off:
_snr = {};
(function (_snr) {
function merge(root){
for ( var i = 1; i < arguments.length; i++ )
for ( var key in arguments[i] )
root[key] = arguments[i][key];
return root;
}
_snr.draw = function (options) {
var defaults = {
canvasId : 'canvas',
imageSrc : 'images/someimage.png'
}
var options = merge(defaults, options)
return this.each(function() {
//More functions here
});
};
_snr.erase = function () {};
})(_snr);
When ever I try to call the draw function like the first code section above, I get the following error, '_snr is not a function'. Where am I going wrong here?
EDIT
Here is what I ended up doing:
function _snr(id) {
// About object is returned if there is no 'id' parameter
var about = {
Version: 0.2,
Author: "ferics2",
Created: "Summer 2011",
Updated: "3 September 2012"
};
if (id) {
if (window === this) {
return new _snr(id);
}
this.e = document.getElementById(id);
return this;
} else {
// No 'id' parameter was given, return the 'about' object
return about;
}
};
_snr.prototype = (function(){
var merge = function(root) {
for ( var i = 1; i < arguments.length; i++) {
for ( var key in arguments[i] ) {
root[key] = arguments[i][key];
}
}
return root;
};
return {
draw: function(options) {
var defaults = {
canvasId : 'canvas',
imageSrc : 'images/someimage.png'
};
options = merge(defaults, options);
return this;
},
erase: function() {
return this;
}
};
})();
I can now call:
<script type="text/javascript">
_snr("#canvas").draw({
imageSrc : someImage.png
});
</script>
Because you declared _snr as an object and not a function. Functions can have properties and methods, so there's various ways to achieve what you want, for example one of them would be say...
_snr = function(tag) {
this.tag = tag;
}
_snr.foo = function() {
//Code goes here
}
You can also pass the outer context into a closure to hide your variables from accidentally polluting the global namespace, so like...
(function(global) {
var _snr = function(tag) {
this.tag = tag;
}
_snr.foo = function() {
//Code goes here
}
//export the function to the window context:
global._snr = _snr;
})(window);
window._snr('#tag').foo('wat');
Happy coding.
Because your _snr is an object, not a function. You have to call it like this:
_snr.draw({
canvasId: '#canvas',
imageSrc: 'someImage.png'
});
When you do _snr('#canvas') that is a function call which is why you're getting that error. _snr is an object with some methods attached to it such as draw() and erase(). The reason jQuery is able to pass arguments into the $ is because they return the $ as a function object which is why we're able to pass it various selectors as arguments.
You are going wrong at the first line _snr = {}
It needs to be
_snr = function(){
selector = arguments[0]||false;
//snr init on dom object code
return _snrChild;
}
Im on a mobile phone but when im on a pc I will maybe fix the whole code c:
Here you have a snr object and that has erase and draw methods. What you intend to do is to write a _snr function which will get an id and return a wrapper object. That returned object should have erase and draw methods. so you can do
var returnedObject = _snr("my_id");
returnedObject.draw("image.png");

Categories