How to patch JS function that doesn't belong to a class - javascript

I'm a complete JS n00b and I'm really struggling to figure this out. I'm trying to change the default colors used in graphs. There's a 'getColors' functions in web/views/graph/colors.js that simply returns an array of colors that are used for this and it seems best to me to just override this function with my own array of colors but I can't for the life of me figure out how to patch it because this function does not belong to a class.
The original code (that I want to patch) looks like this (notice that none of this is inside a class):
/** #odoo-module **/
const COLORS_BRIGHT = [
"#1f77b4",
// rest removed for brevity
];
const COLORS_DARK = [
"#00ffff",
// rest removed for brevity
];
// this is the function I want to replace with my own
export function getColors(colorScheme) {
return colorScheme === "dark" ? COLORS_DARK : COLORS_BRIGHT;
}
// a few more functions that I don't care about
I've created my own colors.js like this
/** #odoo-module **/
import { patch } from '#web/core/utils/patch';
import { getColors } from '#web/views/graph/colors';
const MY_COLORS_BRIGHT = [
"#1f77b4",
// rest removed for brevity
];
const MY_COLORS_DARK = [
"#00ffff",
// rest removed for brevity
];
patch(getColors, 'my_module.colors', {
getColors(colorScheme) {
return colorScheme === "dark" ? MY_COLORS_DARK : MY_COLORS_BRIGHT;
}
}
Obviously this doesn't work - it adds a new getColors function inside the old getColors function instead of replacing it. Normally I think I would patch the Class with getColors function and that would work I think but there is no class to patch in this case. How can I go about replacing this function without it having a parent to patch?

I've solved my own problem. The key here is importing the entire colors.js file with import * as colors and then patching colors with the new function. I'm not sure why, but when doing it this way I was unable to patch the getColors function directly (the original one would still be called). But patching getColor (which is what all other modules actually call) and using my own getMyColors function it works.
/** #odoo-module **/
import { patch } from '#web/core/utils/patch';
import * as colors from '#web/views/graph/colors';
const MY_COLORS_BRIGHT = [
"#1f77b4",
// rest removed for brevity
];
const MY_COLORS_DARK = [
"#00ffff",
// rest removed for brevity
];
function getMyColors(colorScheme) {
return colorScheme === "dark" ? MY_COLORS_DARK : MY_COLORS_BRIGHT;
}
patch(colors, 'my_module.colors', {
getColor(index, colorScheme) {
const colors = getMyColors(colorScheme);
return colors[index % colors.length];
}
});

Related

have separate js objects in a webpack project which interact with same variable

I wanted to know if it was possible by combining webpack and js' oop to arrive at a functional code like the one presented below.
The goal is to be able to isolate each of the elements of my site (sidebar, main,...) in different files while making sure that they can interact between.
Is this possible with webpack and pure js or not?
import ApplePicker from "./my_path/applePicker.js";
import NiceFarmer from "./my_path/niceFarmer.js";
const orchard = function () {
const appleNumber = 10;
const jack = new ApplePicker();
const daniel = new NiceFarmer();
jack.eatAnApple();
daniel.eatAnApple();
// appleNumber have to be now === 2
}
// Example of applePicker.js structure
const ApplePicker = function () {
this.eatAnApple = function () {
// Do something
}
}
export default ApplePicker;
yes you can.
If you simply want to consume a variable from applePicker.js:
// applePicker.js
export const apples = 10
If you want to be able to reassign that variable you might want to add a simple facade layer on top of that:
// applePicker.js
let apple = 5
export function getApple() {
return apple;
}
export function setApple(newValue) {
apple = newValue;
}
Even better, if you want other functions to "fire" when a variable is changed, use the Observer pattern

Extend Highcharts Export

I need to set the resources object within the Highcharts exporting module's exportChart method but can't seem to overwrite it.
The export module source code is located at http://code.highcharts.com/modules/exporting.src.js and the specific subsection of this that I am overwriting looks like this:
'use strict';
(function (factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory;
} else {
factory(Highcharts);
}
}(function (Highcharts) {
(function (H) {
// create shortcuts
var Chart = H.Chart,
merge = H.merge,
extend = H.extend;
//... Removed extra code not needed for example
extend(Chart.prototype, /** #lends Highcharts.Chart.prototype */ {
exportChart: function (exportingOptions, chartOptions) {
var svg = this.getSVGForExport(exportingOptions, chartOptions);
// merge the options
exportingOptions = merge(this.options.exporting, exportingOptions);
// do the post
H.post(exportingOptions.url, {
filename: exportingOptions.filename || 'chart',
type: exportingOptions.type,
// IE8 fails to post undefined correctly, so use 0
width: exportingOptions.width || 0,
scale: exportingOptions.scale,
svg: svg
}, exportingOptions.formAttributes);
}
//... Removed extra code not needed for example
});
//... Removed extra code not needed for example
}(Highcharts));
}));
To test the ability to overwrite the method I am using the following code:
(function (H) {
var Chart = H.Chart,
extend = H.extend;
extend(Chart.prototype, /** #lends Highcharts.Chart.prototype */ {
exportChart: function (exportingOptions, chartOptions) {
alert('changed it');
}
});
}(Highcharts));
The alert never fires but instead the normal export is still happening.
I've created a JSFiddle showing this issue here: https://jsfiddle.net/j005v79j/
Can anyone tell me why the overwriting of this method isn't working?
I realized I was also loading code.highcharts.com/modules/offline-exporting.js on my side which was not in the fiddle. This script overwrote the export buttons to use a different method called exportChartLocal(). Since the buttons were now tied to a method that would never be called my alert was not firing.
I've removed the call to offline-exporting for now and will write overwrites into my code for this version at a later date.
Thanks to #Kamil for pointing out the issue.

Basic ES6 Javascript Plugin - reuse variable between functions

I'm attempting to build a basic JS plugin that can be called after a click event to disable a button (to prevent users firing multiple API calls) and to give feedback that something is loading/happening. Here is how it looks:
This works great on an individual basis, but I want to re-write it as a plugin so I can reuse it across the site.
Here is a cut down version of the JS from file loader.plugin.js.
let originalBtnText;
export function showBtnLoader(btn, loadingText) {
const clickedBtn = btn;
const spinner = document.createElement('div');
spinner.classList.add('spin-loader');
originalBtnText = clickedBtn.textContent;
clickedBtn.textContent = loadingText;
clickedBtn.appendChild(spinner);
clickedBtn.setAttribute('disabled', true);
clickedBtn.classList.add('loading');
return this;
}
export function hideBtnLoader(btn) {
const clickedBtn = btn.target;
clickedBtn.textContent = originalBtnText;
clickedBtn.removeAttribute('disabled');
clickedBtn.classList.remove('loading');
return this;
}
export function btnLoader() {
showBtnLoader();
hideBtnLoader();
}
And here is an example of how I would like to use it.
import btnLoader from 'loaderPlugin';
const signupBtn = document.getElementById('signup-btn');
signupBtn.addEventListener('click', function(e) {
e.preventDefault();
btnLoader.showBtnLoader(signupBtn, 'Validating');
// Call API here
});
// Following API response
hideBtnLoader(signupBtn);
The issue I have is that I want to store the originalBtnText from the showBtnLoader function and then use that variable in the hideBtnLoader function. I could of course achieve this in a different way (such as adding the value as a data attribute and grabbing it later) but I wondered if there is a simple way.
Another issue I have is that I don't know the correct way of calling each individual function and whether I am importing it correctly. I have tried the following.
btnLoader.showBtnLoader(signupBtn, 'Validating');
btnLoader(showBtnLoader(signupBtn, 'Validating'));
showBtnLoader(signupBtn, 'Validating');
But I get the following error:
Uncaught ReferenceError: showBtnLoader is not defined
at HTMLButtonElement.<anonymous>
I have read some good articles and SO answers such as http://2ality.com/2014/09/es6-modules-final.html and ES6 export default with multiple functions referring to each other but I'm slightly confused as to the 'correct' way of doing this to make it reusable.
Any pointers would be much appreciated.
I would export a function that creates an object with both show and hide functions, like this:
export default function(btn, loadingText) {
function show() {
const clickedBtn = btn;
const spinner = document.createElement('div');
spinner.classList.add('spin-loader');
originalBtnText = clickedBtn.textContent;
clickedBtn.textContent = loadingText;
clickedBtn.appendChild(spinner);
clickedBtn.setAttribute('disabled', true);
clickedBtn.classList.add('loading');
}
function hide() {
const clickedBtn = btn.target;
clickedBtn.textContent = originalBtnText;
clickedBtn.removeAttribute('disabled');
clickedBtn.classList.remove('loading');
}
return {
show,
hide,
};
}
Then, to use it:
import btnLoader from 'btnloader';
const signupBtn = document.getElementById('signup-btn');
const signupLoader = btnLoader( signupBtn, 'Validating' );
signupBtn.addEventListener('click', function(e) {
e.preventDefault();
signupLoader.show();
// Call API here
});
// Following API response
signupLoader.hide();
If you need to hide it from a different file from where you showed it, then you can export the instance:
export const signupLoader = btnLoader( signupBtn, 'Validating' );
And later import it.
import { signupLoader } from 'signupform';
function handleApi() {
signupLoader.hide();
}
Youre maybe overriding the Element.prototype, to make it accessible right from that element. However, i wouldnt set values onto that element, i would rather return an object with all the neccessary stuff:
export function implementBtnLoader(){
Element.prototype.showBtnLoader=function( loadingText) {
const clickedBtn = this;
const spinner = document.createElement('div');
spinner.classList.add('spin-loader');
var originalBtnText = clickedBtn.textContent;
clickedBtn.textContent = loadingText;
clickedBtn.appendChild(spinner);
clickedBtn.setAttribute('disabled', true);
clickedBtn.classList.add('loading');
return {
text:originalBtnText,
el:this,
hideBtnLoader: function() {
const clickedBtn = this.target;
clickedBtn.textContent = this.text;
clickedBtn.removeAttribute('disabled');
clickedBtn.classList.remove('loading');
return this;
}
};
};
}
export function btnLoader() {
implementBtnLoader();
}
When imported, and implementBtnLoader was called, one can do:
var loader=document.getElementById("test").showBtnLoader();
console.log(loader.text);
loader.hideBtnLoader();

Parameter declaration expected (typescript)

#Vinay in this TypeScript + AngularJS 1: How to connect enum with select directive? question shows a relatively simple way to get a array for building a select drop-down in angular.
Unfortunately, I try to ape this code and I get errors ... first upon declaring the 'colors' array if I use var or let... (but it works if I don't). Unfortunately, that just moves the error to the next variable declaration in the setup of the for loop. Unfortunately, here, I can't not put in a let or a var.
I'm sure this is simple, but I'm just banging me head and missing it.
enum Color {
Green = <any>"Green",
Red = <any>"Red",
Blue = <any>"Blue"
}
export class ClassName {
colors: string[] = []; // <-- get error here if I declare var or let
for (var item in Color) { // <-- get error here
if (Color.hasOwnProperty(item)) {
this.colors.push(item);
}
}
}
Property declarations belong in the body, but executable code goes in the constructor:
export class ClassName {
colors: string[] = []; // <-- get error here if I declare var or let
constructor() {
for (var item in Color) { // <-- get error here
if (Color.hasOwnProperty(item)) {
this.colors.push(item);
}
}
}
}

Select collection by variable name: window[type] doesn't work

I need to keep the helpers dynamic to get a cursor. That means, the user can change the collection - by click event - which is used to get a list.
But in my console.log I get a undefined for window[type]. What am I doing wrong?
So article.find() would work, but window[type] not...
imports/api/example/index.js
export const article = new Mongo.Collection('articles');
export const images = new Mongo.Collection('images');
imports/api/example/client/example.js
import { article, images } from '../';
Template.example.helpers({
list() {
const type = Template.instance().section.get();
console.log(type, window[type]); // result: 'article', undefined
return window[type].find(); // <- therefore this is NOT working
}
});
Template.example.onCreated(function() {
this.section = new ReactiveVar('article');
});
Template.example.events({
'click .target': function(event, template) {
const $this = $(event.currentTarget),
type = $this.attr('data-type');
template.section.set(type);
}
});
This goes against the zen of Python ("explicit is better than implicit"), but it is reasonable enough here.
You can use the import * as name from "module-name" variant to get all of the collections (provided that they are the only thing that is exported from that file, otherwise be explicit).
import * as collections from '../index'; //collections includes all of your collections
Template.example.helpers({
list() {
const type = Template.instance().section.get();
return collections[type].find();
}
});
which will get you what you want.
The window object will only contain global variables as properties.
However, JavaScript modules are implicitly given their own scope that variables are bound to. And, only the global scope is automatically accessible as a variable.
You can use the bracket syntax, object[property], but you'll need to establish a different object containing article and images.
import { article, images } from '../';
const collections = { article, images };
// ...
Or, you can import all named exports using import * as name:
import * as collections from '../';
// ...
Then, use that object to lookup by type:
Template.example.helpers({
list() {
const type = Template.instance().section.get();
console.log(type, collections[type]);
return collections[type].find();
}
});

Categories

Resources