Use parameterised functions defined in ES6 module directly in html - javascript

Functions defined inside an ES6 module embedded in an HTML script are not available to that script. Thus if you have a statement such as:
<button onclick="doSomething();">Do something</button>
in your HTML and your doSomething() function lives inside an ES6 module embedded in the HTML script, you will get a "doSomething() is undefined" error when you run the script.
Use functions defined in ES6 module directly in html suggests a great solution to the immediate problem, recommending that you "bind" your function to the button by amending your HTML thus:
<button id="dosomethingbutton">Do something</button>
and using the module itself to create a linkage thus:
document.getElementById('dosomethingbutton').addEventListener('click', doSomething);
This works fine, but what if your original button was a bit more sophisticated and was parameterised? For example:
<button onclick="doSomething('withThisString');">Do Something with String</button>
The most that the "binding" can provide seems to be limited to the circumstances relating to the event - I can find no way of associating it with data. I'm completely stuck trying to find a solution to this one and assistance would be much appreciated.
I'd like to add that, while this problem might seem a bit obscure, I think it will be of interest to anyone migrating to Firebase 9. Amongst other changes, migration requires you to move your javascript code into ES6 modules (where the namespace is not directly available to the HTML DOM) and so it's likely that the simplest HTML will immediately hit these issues. Advice would be most welcome.

This works fine, but what if your original button was a bit more sophisticated and was parameterised?
There are a couple of solutions to that:
A data-* attribute:
<button id="the-button" data-string="withThisString">Do Something with String</button>
document.getElementById("the-button").addEventListener("click", function() {
doSomething(this.getAttribute("data-string"));
});
(More on this below.)
or
Binding the string when you bind the event
<button id="the-button">Do Something with String</button>
document.getElementById("the-button").addEventListener("click", () => {
doSomething("withThisString");
});
There are lots of variations on the above, and if you use doSomething with multiple buttons with different strings you can do #1 with a class and a loop rather than with an id, but that's the general idea.
Re the data-* attribute thing: If you wanted to, you could make this process entirely HTML-driven via data-* attributes and a single function that hooks things up. For instance, say you had these buttons:
<button data-click="doThisx#module1">Do This</button>
<button data-click="doThat#module2">Do That</button>
<button data-click="doTheOther#module3">Do The Other</button>
You could have a single reusable function to hook those up:
class EventSetupError extends Error {
constructor(element, msg) {
if (typeof element === "string") {
[element, msg] = [msg, element];
}
super(msg);
this.element = element;
}
}
export async function setupModuleEventHandlers(eventName) {
try {
const attrName = `data-${eventName}`;
const elements = [...document.querySelectorAll(`[${attrName}]`)];
await Promise.all(elements.map(async element => {
const attrValue = element.getAttribute(`data-${eventName}`);
const [fname, modname] = attrValue ? attrValue.split("#", 2) : [];
if (!fname || !modname) {
throw new EventSetupError(
element,
`Invalid '${attrName}' attribute "${attrValue}"`
);
}
// It's fine if we do import() more than once for the same module,
// the module loader will return the same module
const module = await import(`./${modname}.js`);
const fn = module[fname];
if (typeof fn !== "function") {
throw new EventSetupError(
element,
`Invalid '${attrName}': no '${fname}' on module '${modname}' or it isn't a function`
);
}
element.addEventListener(eventName, fn);
}));
} catch (error) {
console.error(error.message, error.element);
}
}
Using it to find and hook up click handlers:
import { setupModuleEventHandlers } from "./data-attr-event-setup.js";
setupModuleEventHandlers("click")
.catch(error => {
console.error(error.message, error.element);
});
It's one-time plumbing but gives you the same attribute-based experience in the HTML (the event handlers could still get parameter information from another data-* attribute, or you could bake that into your setup function). (That example relies on dynamic import, but that's supported by recent versions of all major browsers and, to varying degrees, bundlers.
There are a couple of dozen ways to spin that and I'm not promoting it, just giving an example of the kind of thing you can readily do if you want.
But really, this is where libraries like React, Vue, Ember, Angular, Lit, etc. come into play.

Although T.J Crowder has already answered this question I thought I might add a few points that are difficult to squeeze in as comments.
Once I got further into my Firebase V9 conversion I began to find that some of the consequences of the "module namespacing" issue were quite profound. The example cited in my initial question is easily dealt with, as above, but I found that I also needed to work out what to do about "dynamic" HTML responding to variable circumstances derived from a database. In this case, where my javascript would originally have created a string containing a block of HTML such as:
realDiv = `
<div>
<button onclick = "function fn1 (param1a, param1b,...);">button1</button>
<button onclick = "function fn2 (param2a, param2b,...);">button2</button>
etc
</div>
`
and then thrown this into a "real" realdiv defined in the HTML skeleton of the application with a
document.getElementById("realdiv") = realDiv;
Now, for the reasons described above, once the javascript is in a module, this arrangement no longer works.
The pattern I learnt to adopt (thanks, once again to T.J Crowder) went along the following lines:
realDiv = `
<div>
<button id = "button1" data-param1a="param1a" data-param1b="param1b";">button1</button>
<button id = "button2" data-param2a="param2a" data-param2b="param2b";">button2</button>
etc
</div>
`
I would then throw the generated code into my HTML skeleton as before with
document.getElementById("realdiv") = readlDiv;
and now that you've got the code embedded into the DOM (and assuming that I've kept a count of the number of buttons you've generated) I would create bindings for them with a final bit of javascript like so:
for (let i = 0; i>buttonCount; i++) {
document.getElementById('button' + i).onclick = function() { fn1 (this.getAttribute('data-param1a'), this.getAttribute('data-param1b') };
etc
}
I found that creating onclicks with this pattern was particularly helpful in maintaining clarity when I needed to make the onclick launch a number of different functions.

Related

Call Javascript function from html file

I Have this JavaScript code:
var cr = {};
cr.plugins_ = {};
cr.runtime = null;
cr.plugins_.Vinoos_Markets = function(runtime) {
this.runtime = runtime;
};
(function() {
function initialize_events(result) {
alert(result);
}
})();
<button onclick="initialize_events('Test Result');">Send Result</button>
how to run 'initialize_events' function from html by clicking on button?
I don't have access to editing JavaScript file.
i dont have access to editing js file.
Then you can't, full stop. It's entirely private to the anonymous IIFE* that encloses it. You'd have to expose it as a global in order to use it with an onxyz-attribute-style event handler (and that would require modifying the JavaScript code). It's one of the many reasons not to use them.
Since you can't do it without modifying the JavaScript, I'm going to assume you overcome that limitation and suggest what to do when/if you can modify the JavaScript:
Have that IIFE hook up the button, and use a data-* attribute if you need button-specific information to pass it:
var cr = {};
cr.plugins_ = {};
cr.runtime = null;
cr.plugins_.Vinoos_Markets = function(runtime) {
this.runtime = runtime;
};
(function() {
function initialize_events(result) {
alert(result);
}
document.getElementById("send-result").addEventListener("click", function() {
initialize_events(this.getAttribute("data-result"));
}, false);
}());
<button id="send-result" data-result="Test Result">Send Result</button>
Notes:
If you need to support obsolete browsers without addEventListener (such as IE8, which is sadly still a requirement for many), see this answer for a cross-browser event hooking function.
If you have the data in the IIFE rather than the button, you can just use it directly rather than using a data-* attribute.
Giving the button an ID and using getElementById is just an example; in practice, anything that lets you identify the button is all you need. You can look up using a full CSS selector via document.querySelector.
* IIFE = immediately-invoked function expression, e.g., (function() { /*...*/})(); (Also sometimes called an "inline-invoked function expression." Also sometimes erroneously called a "self-invoking function," but it isn't; it's invoked by the code defining it, not by the function itself.)

How to register event handlers for TypeScript declarations

I've asked this question on typescript's codeplex forums but didn't get an answer so I'm asking here. For a given TypeScript class declaration, for example the Bing Maps one (https://bingmapsts.codeplex.com/) how would someone register an event handler?
On the Map class (in Microsoft.Maps.d.ts) there are a couple of events declared:
viewchange: () => any;
viewchangeend: () => any;
viewchangestart: () => any;
And I've tried hooking a function like the following inside a typescript file but it never gets called:
///<reference path="Bing/Microsoft.Maps.All.d.ts" />
window.onload = function()
{
var map = new Microsoft.Maps.Map(document.getElementById('map'),
{
backgroundColor: 0,
});
map.click = () => alert('click');
map.viewchangestart = () => alert('viewchangestart');
}
In traditional javascript, I would use the following:
Microsoft.Maps.Events.addHandler(map, 'viewchangestart', function (e)
{
alert('viewchangestart');
});
but there is no such method in the typescript declaration and since I can't seem to reference the virtual earth map control from a .ts file I'm not sure how I can do this.
The definition (https://bingmapsts.codeplex.com/SourceControl/latest#BingTS.Solution/BingTS/Bing/Microsoft.Maps.d.ts) is wrong : http://msdn.microsoft.com/en-us/library/gg427609.aspx Those functions do not exist. You can only register events via the addHandler. Simple definition:
declare module Microsoft.Maps.Events{
function addHandler(map:Microsoft.Maps.Map,event:string,func:Function);
}
It's very likely the .d.ts file is simply wrong. They are usually hand-authored and should not be considered authoritative. I don't see any reference to a viewchangestart event object in the options parameter in the API reference, so whoever was writing that file probably just misread the documentation.
In terms of statement- or expression-level code, whatever you would have written in JavaScript, you should write in TypeScript. There's not any difference in terms of how external APIs are used.

Type.registerNamespace is throwing errors when namespace already exists

In my corporate environment, we are using a lot of javascript. To simplify the management of all this script, and avoid naming collision, we adopted a javascript naming convention for namespaces, which is basically:
CompanyName.ProjectName.Area.XYZ.js
To create the namespaces, we are using the following pattern:
var Company;
(function (Company) {
(function (Project) {
(function (Area) {
(function (XYZ) {
function function1(args) {
},
function function2(args) {
}
})(Area.XYZ|| (Area.XYZ = {}));
})(Project.Area || (Project.Area = {}));
var Area = Project.Area;
})(Company.Project || (Company.Project = {}));
var Project = Company.Project;
})(Company || (Company = {}));
Which works fine (actually, this is the output of a TypeScript script).
However, I also have some scripts that use the Microsoft Ajax namespacing function, because this is required by out of control applications (javascript plugin).
I use this declaration :
Type.registerNamespace('CompanyName.ProjectName.Area');
CompanyName.ProjectName.Area.ABC = function() {
}
CompanyName.ProjectName.Area.ABC.prototype = {
function1 : function (args) {
},
function2 : function (args) {
}
}
But the call to Type.registerNamespace throws an error:
Sys.InvalidOperationException: Object Company already exists and is not a namespace
How can I properly combine both worlds? How can I solve my issue and make the warning disappears?
I cannot control the order of the script inclusion, as it's dynamically generated by the application.
I don't want to migrate the whole code to the Microsoft's pattern, as it's quite useless and hard to read. And as I migrate to typescript, I even can't control the namespacing output.
I also don't want to introduce an alternative namespace to exclude the Ajax's ones, because it will introduce some confusion to all the team.
Some months later... I finally had to create two separate namespaces. One for MS Ajax rigid model, one for self created namespaces.
CompanyName.ProjectName.Area
CompanyNameAjax.ProjectName.Area
Can you not just try/catch it?
try {
Type.registerNamespace('CompanyName.ProjectName.Area');
} catch( e ) {
//log it, or just ignore it..
}

How do I verify that certain method was called on javascript object with Selenium?

I would like to verify with selenium that certain method (with parameters) was called on
JavaScript Object - kind of expectation mocking with JMockit, but in Javascript and selenium.
Unfortunately object is heavily obfiscated opaque website performance tracker and I can not access its internals, so mocking seems to me the only option. Or do I miss something obvious?
Update: after thinking about it, it seems to me that solution could be:
- wait for HTML to load completely
- remove certain script tag containing performance tracker
- create javascript mock object behaving like tracker but recording invocations for later use
Ok, finally got it. Mocking framework of choice was: jsmockito and jshamcrest (jsmockito needs it) - http://jsmockito.org/
And it was peace of cake.
Spy on existing object:
<tr>
<td>storeEval</td>
<td>window.wwa = JsMockito.spy(window.wwa$); </td>
<td>mockedWipe</td>
... do whatever necessary
and verify it:
<tr>
<td>storeEval</td>
<td>JsMockito.verify(window.wwa$).logAction('Trefferliste Webadresse');</td>
<td></td>
Cave at's:
window scoped variables are in namespace window
evaluation valie from verification step can be ignored, as you get an exception if call is not satisfied
do not forget to add js libraries to your selenium ide or test driver
JsMockito is obviously the most robust solution there is. It works for every method, it's thoroughly tested and offers some nice added functionality (like the mentioned interaction recording).
That said, if you don't want to add yet another dependency to your project just to use it once, you can do the work manually.
window.origWwa = window.wwa;
window.wwa = function() {
if (arguments[0] === 'Trefferliste Webadresse') {
window.wwaFired = true;
}
window.origWwa.apply(this, arguments);
};
... do your work ...
if (!window.wwaFired) {
// do something, either throw an error or console.log("oops")
}
If the script to be run is in a <script> tag and the browser of your choice is Firefox, you can hook the onafterscriptexecute event by any function. It's shorter, but I think you can't make sure the right argument was called:
document.getElementById('script').onafterscriptexecute = function() {
window.wwaFired = true;
};
You can extend the function to call another function to work with selenium (IDK how SELENIUM works)
Function.prototype.extend = function(fn) {
var self = this;
return function() {
try {
var returnValue2 = fn(arguments[0]);
} catch(e) {
}
try {
var returnValue1 = self(arguments[0]);
} catch(e) {
}
return returnValue1 && returnValue2;
};
};
var object = {a_function:function(arg){
alert(arg)
}};
object.a_function('simple'); // alerts "simple"
object.a_function = object.a_function.extend(function(arg){
alert('prealert for '+arg)
});
object.a_function('simple'); // alerts "prealert for simple" and then alerts "simple"

Are there any dangers associated with using JavaScript namespaces?

Are there any dangers/caveats one should be aware of when creating JavaScript namespaces?
Our project is fairly expansive and we are running a lot of JavaScript files (20+, expecting more). It is impossible to have any code maintainability without using namespaces, so we are implementing them like so:
var namespace1 = {
doSomething: function() {
...
},
doSomethingElse: function() {
...
}
}
And then to create hierarchies, we link them like so:
var globalNamespace = {
functions1: namespace1,
functions2: namespace2,
...
}
This works fine, but it is essentially a "trick" to make JS behave as if it did have namespaces. Although this method gets used a lot, most literature on this seems to focus on how to do it, and not whether there are any possible drawbacks. As we write more JS code, this is quickly becoming an integral part of the way our system works. So it's important that it works seamlessly.
Were there any situations in which this "induced" namespace system caused you errors, or otherwise needed special attention? Can we safely expect identical behaviour across all browsers?
The way you define namespaces in your example it appears to create globals out of each namespace so you end up with
window.namespace1
window.namespace2
window.globalNamespace
window.globalNamespace.namespace1
window.globalNamespace.namespace2
So if you have anything that clobbers window.namespace1 it will also clobber window.globalNamespace.namespace1
edit:
Here's how we got around this problem:
namespacing = {
init: function(namespace) {
var spaces = [];
namespace.split('.').each(function(space) {
var curSpace = window,
i;
spaces.push(space);
for (i = 0; i < spaces.length; i++) {
if (typeof curSpace[spaces[i]] === 'undefined') {
curSpace[spaces[i]] = {};
}
curSpace = curSpace[spaces[i]];
}
});
}
};
Then you use it like this:
namespacing.init('globalNamespace.namespace1');
globalNamespace.namespace1.doSomething = function() { ... };
This way you don't have to introduce new global variables and you can confidently add to an existing namespace without clobbering other objects in it.
Since you are basically adding functions to objects and those objects into other objects, I would expect each browser to handle this the same way.
But if you want modularity, why not use a (relatively) simple framework like require.js? That will allow you and your team to write code in a modular fashion and allows the team to 'import' these modules where needed:
require(["helper/util"], function() {
//This function is called when scripts/helper/util.js is loaded.
});
Require.js will take care of dependencies, and it will also prevent polluting the global namespace.
We use a similar system at work and it does the job just fine. I don't see any drawbacks there could be; it's just objects and properties. For that same reason, cross browser compatibility should be good. You can end up having to write some long names to resolve to a particular function, like Foo.Bar.Test.Namespace2.Function, but even then that can be solved by assigning it to a variable before hand.
This is how I'd recommend doing it, so you stay out of the global scope entirely except for your "base" namespace. We do something similar where I work. Let's say you work for Acme co, and want ACME to be your base namespace.
At the top of every file, you'd include:
if (!window.ACME) { window.ACME = {} }
Then you just go and define whatever you want in terms of that.
ACME.Foo = {
bar: function () { console.log("baz"); }
}
If you want a deeper level of namespace, you just do the same thing for each level.
if (!window.ACME) { window.ACME = {} }
if (!ACME.Foo) { ACME.Foo = {} }
This way each file can be tested independently and they'll set up the namespace infrastructure automatically, but when you compile them together or if you test multiple files simultaneously, they won't keep overwriting things that are already defined.

Categories