Loading js modules dynamically and provide instant access to the functions inside - javascript

I want to create a module loader for javascript files using $.getScript but since the loading of the scripts is then asynchronously when I put a function call of a module inside the document they may be called before the module was loaded. Is there any way to avoid this situation maybe by putting the function call on hold until the module was loaded successfully?
framework.core.js:
var Framework = $.extend(Framework, Framework.Core = {
modules: [
'Module1',
'Module2'
],
init: function () {
$.each(modules, function (index, value) {
$.getScript('framework.' + value.toLowerCase() + '.js', function () {
});
});
}
});
Framework.Core.init();
site.html:
<html>
<head>
<script src="framework.core.js"></script>
<script>Framework.Module1.functionCall();</script> // Call a function independent of the completion of the framework.core.js loader
</head>
...

You will need to open the success callback for the depending functions to hook on it. You will not be able to defer the execution of those following functions to wait for the Module (unless you would insert the script via document.write), so the callback is necessary. Best, just make the Deferred object (returned by the ajax function) public. Also, you should not use jQuery.getScript/ for that task at all, because it prevents caching.
var Framework = $.extend(Framework, Framework.Core = {
// The "Core" property seems pretty useless, by the way ^^
modules: [
'Module1',
'Module2'
],
loads: {},
init: function () {
$.each(this.modules, function(index, value) {
this.loads[value] = $.ajax({
url: 'framework.' + value.toLowerCase() + '.js',
cache:true,
dataType:"script"
});
});
}
});
Framework.init();
<html>
<head>
<script src="framework.core.js"></script>
<script>Framework.loads.Module1.then(function() {
functionCall();
}); // Call a function as soon as the module is loaded
</script>
</head>
...

Related

External JS file function cannot see page function

I have an existing legacy aspx page loading an external JS file. I am adding functionality and have added an async function in a script block on the page. The external JS file has been modified to call the async function. No matter where on the page I load the external script it still continues to complain that the page function is not defined. I'm seriously stuck! Thanks
UPDATE:
///loading scripts
<script src="../_scripts/jquery-3.4.1.min.js"></script>
<script src="../_scripts/bootstrap-4.6.0.min.js"></script>
<script src="../_scripts/jquery.datatables.min.js"></script>
<script src="../_scripts/datatables.select.min.js"></script>
//page function
<script type="text/javascript">
$(document).ready(function () {
async function providerPopUp() {
await $.ajax({
url: '../Provider/PreCert_PrvSearch.aspx',
method: 'get',
data: { typeOfSearch: typeOfSearch, coIdNbr: coIdNbr },
dataType: 'json',
success: function (response) {.......
//load external script after page script
<script src="../_scripts/PreCert_Create.js"></script>
//call to page function added to external js file
function Pop_Modal_Window_NPI (){
providerPopUp()
.then((result) => {
console.log('result: ' + result);
retPrv = result;
})
External JS file function Pop_Modal_Window_NPI is triggered onblur of a text box
Result is Uncaught ReferenceError: providerPopUp is not defined
at Pop_Modal_Window_NPI (PreCert_Create.js:169)
at HTMLInputElement.onblur (PreCert_Create.aspx?...parameters)
Pop_Modal_Window_NPI() calls the function providerPopUp(), but the latter is inside an enclosure and so isn't in the scope of the call.
You can get around this by adding the function to the window namespace:
window.providerPopUp = async function() {
...
};
And then the call inside Pop_Modal_Window_NPI becomes:
window.providerPopUp()
(You don't even need to prefix window to the function call, I just do it for consistency)

How to mock-up the .load function in Qunit?

I have a javascript function I want to test, containing .load. The function looks like this :
function getPane(divId) {
$("#" + divId).load(
"Pane.html",
function () {
//do some work here
});
});
}
I want to test this using Qunit but I'm not sure how to mock this behaviour.
I also do not know how to mock a function that has both .load and .get -
function getPane(divId) {
$("#" + divId).load("Pane.html", function () {
$.get("/Config/Pane", function (data) {
//do work here
}
});
});
}
I'm using only QUnit, no Mockjax or Sinon.js or anything ( I know,I know I should).
Any help would be appreciated. Thanks.
Since the OP suggested they might go with Mockjax, I figured I'd add that solution. Note that I'm adding the mocks in a setup method and tearing them down after. This allows for each test to be idempotent. Additionally, your getPane() function needs a callback so you can add assertions in your tests.
function getPane(divId, cb) {
$("#" + divId).load("Pane.html", function () {
$.get("/Config/Pane", function (data) {
// do work here
cb(); // callback executed for any additional actions (like tests)
// you may want to add some error handling with callback as well
});
});
}
Then in your #qunit-fixture of the qunit test file add the div to put stuff into:
<html>
...
<body>
<div id="qunit"></div>
<div id="qunit-fixture">
<div id="foobar"></div> <!-- our test element -->
</div>
...
</body>
</html>
Now write your mocks and tests:
QUnit.module("some tests", {
setup: function() {
$.mockjax({
url: "Pane.html",
responseText: "<div>Some HTML content</div>"
});
},
teardown: function() {
$.mockjax.clear(); // new in 1.6
}
});
QUnit.asyncTest("test it out", function(assert) {
getPane("foobar", function() {
assert.equal($("#foobar div").length, 0, "A new div was added to the page!");
QUnit.start();
});
});

Getting requirejs to work with Jasmine

I first want to say that I am new to RequireJS and even newer to Jasmine.
I am having some issues with the SpecRunner and require JS. I have been following the tutorials of Uzi Kilon and Ben Nadel (along with some others) and they helped some but I am still having some issues.
It seems that, if there is an error that is thrown in the test (I can think of one in particular, a type error) the spec runner html will display. This tells me that I have some issues in the javascript. However, after I fix those error no HTML is displayed anymore. I cannot get the test runner to display at all. Can someone find something wrong with my code that would cause this issue?
Here is my directory structure:
Root
|-> lib
|-> jasmine
|-> lib (contains all of the jasmine lib)
|-> spec
|-> src
|-> jquery (jquery js file)
|-> require (require js file)
index.html (spec runner) specRunner.js
Here is the SpecRunner (index) HTML:
<!doctype html>
<html lang="en">
<head>
<title>Javascript Tests</title>
<link rel="stylesheet" href="lib/jasmine/lib/jasmine.css">
<script src="lib/jasmine/lib/jasmine.js"></script>
<script src="lib/jasmine/lib/jasmine-html.js"></script>
<script src="lib/jquery/jquery.js"></script>
<script data-main="specRunner" src="lib/require/require.js"></script>
<script>
require({ paths: { spec: "lib/jasmine/spec" } }, [
// Pull in all your modules containing unit tests here.
"spec/notepadSpec"
], function () {
jasmine.getEnv().addReporter(new jasmine.HtmlReporter());
jasmine.getEnv().execute();
});
</script>
</head>
<body>
</body>
</html>
Here is the specRunner.js (config)
require.config({
urlArgs: 'cb=' + Math.random(),
paths: {
jquery: 'lib/jquery',
jasmine: 'lib/jasmine/lib/jasmine',
'jasmine-html': 'lib/jasmine/lib/jasmine-html',
spec: 'lib/jasmine/spec/'
},
shim: {
jasmine: {
exports: 'jasmine'
},
'jasmine-html': {
deps: ['jasmine'],
exports: 'jasmine'
}
}
});
Here is a spec:
require(["../lib/jasmine/src/notepad"], function (notepad) {
describe("returns titles", function() {
expect(notepad.noteTitles()).toEqual("");
});
});
The notepad source:
define(['lib/jasmine/src/note'], function (note) {
var notes = [
new note('pick up the kids', 'dont forget to pick up the kids'),
new note('get milk', 'we need two gallons of milk')
];
return {
noteTitles: function () {
var val;
for (var i = 0, ii = notes.length; i < ii; i++) {
//alert(notes[i].title);
val += notes[i].title + ' ';
}
return val;
}
};
});
And the Note source (JIC):
define(function (){
var note = function(title, content) {
this.title = title;
this.content = content;
};
return note;
});
I have made sure that, as far as the app is concerned, the paths are correct. Once I get this working I can play with configuring that paths so that it isn't so yucky.
I managed to get this working with some trial and error. The main issue was that when you write specs it isn't a require that you want to create, you want to use define:
Original:
require(["/lib/jasmine/src/notepad"], function (notepad) {
describe("returns titles", function() {
expect(notepad.noteTitles()).toEqual("pick up the kids get milk");
});
});
Working:
define(["lib/jasmine/src/notepad"], function (notepad) {
describe("returns titles", function () {
it("something", function() {
expect(notepad.noteTitles()).toEqual("pick up the kids get milk ");
});
});
});
After doing some research it became clear that, when using RequireJS, Anything that you want the require() to use must be wrapped in a define (seems obvious now I guess). You can see that, in the specRunner.js file, a require is used when executing the tests (therefore the need to "define" the specs.
The other issue is that, when creating specs, the describe() AND the it() are necessary (not just the describe like I had in the posted example).
Original:
describe("returns titles", function() {
expect(notepad.noteTitles()).toEqual("pick up the kids get milk");
});
Working:
describe("returns titles", function () {
it("something", function() {
expect(notepad.noteTitles()).toEqual("pick up the kids get milk ");
});
});
I also changed around where the test runner exists but this was a refactor and did not change the outcome of the tests.
Again, here are the files and the changed:
note.js: stayed the same
notepad.js: stayed the same
index.html:
<!doctype html>
<html lang="en">
<head>
<title>Javascript Tests</title>
<link rel="stylesheet" href="lib/jasmine/lib/jasmine.css">
<script data-main="specRunner" src="lib/require/require.js"></script>
</head>
<body>
</body>
</html>
specRunner.js:
require.config({
urlArgs: 'cb=' + Math.random(),
paths: {
jquery: 'lib/jquery',
'jasmine': 'lib/jasmine/lib/jasmine',
'jasmine-html': 'lib/jasmine/lib/jasmine-html',
spec: 'lib/jasmine/spec/'
},
shim: {
jasmine: {
exports: 'jasmine'
},
'jasmine-html': {
deps: ['jasmine'],
exports: 'jasmine'
}
}
});
require(['jquery', 'jasmine-html'], function ($, jasmine) {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function (spec) {
return htmlReporter.specFilter(spec);
};
var specs = [];
specs.push('lib/jasmine/spec/notepadSpec');
$(function () {
require(specs, function (spec) {
jasmineEnv.execute();
});
});
});
notepadSpec.js:
define(["lib/jasmine/src/notepad"], function (notepad) {
describe("returns titles", function () {
it("something", function() {
expect(notepad.noteTitles()).toEqual("pick up the kids get milk");
});
});
});
Just adding this as an alternate answer for people who are you using Jasmine 2.0 standalone. I believe this can work for Jasmine 1.3 also, but the async syntax is different and kind of ugly.
Here is my modified SpecRunner.html file:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Jasmine Spec Runner v2.0.0</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-2.0.0/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.0/jasmine.css">
<!--
Notice that I just load Jasmine normally
-->
<script type="text/javascript" src="lib/jasmine-2.0.0/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-2.0.0/jasmine-html.js"></script>
<script type="text/javascript" src="lib/jasmine-2.0.0/boot.js"></script>
<!--
Here we load require.js but we do not use data-main. Instead we will load the
the specs separately. In short we need to load the spec files synchronously for this
to work.
-->
<script type="text/javascript" src="js/vendor/require.min.js"></script>
<!--
I put my require js config inline for simplicity
-->
<script type="text/javascript">
require.config({
baseUrl: 'js',
shim: {
'underscore': {
exports: '_'
},
'react': {
exports: 'React'
}
},
paths: {
jquery: 'vendor/jquery.min',
underscore: 'vendor/underscore.min',
react: 'vendor/react.min'
}
});
</script>
<!--
I put my spec files here
-->
<script type="text/javascript" src="spec/a-spec.js"></script>
<script type="text/javascript" src="spec/some-other-spec.js"></script>
</head>
<body>
</body>
</html>
Now here is an example spec file:
describe("Circular List Operation", function() {
// The CircularList object needs to be loaded by RequireJs
// before we can use it.
var CircularList;
// require.js loads scripts asynchronously, so we can use
// Jasmine 2.0's async support. Basically it entails calling
// the done function once require js finishes loading our asset.
//
// Here I put the require in the beforeEach function to make sure the
// Circular list object is loaded each time.
beforeEach(function(done) {
require(['lib/util'], function(util) {
CircularList = util.CircularList;
done();
});
});
it("should know if list is empty", function() {
var list = new CircularList();
expect(list.isEmpty()).toBe(true);
});
// We can also use the async feature on the it function
// to require assets for a specific test.
it("should know if list is not empty", function(done) {
require(['lib/entity'], function(entity) {
var list = new CircularList([new entity.Cat()]);
expect(list.isEmpty()).toBe(false);
done();
});
});
});
Here is a link the async support section from the Jasmine 2.0 docs: http://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support
Another option for Jasmine 2.0 standalone is creating a boot.js file and setting it up to run your tests after all of your AMD modules have been loaded.
The ideal end user case for writing tests in our case was to not have to list out all of our spec files or dependencies in once explicit list, and only have the requirement of declaring your *spec files as AMD modules with dependencies.
Example ideal spec: spec/javascript/sampleController_spec.js
require(['app/controllers/SampleController'], function(SampleController) {
describe('SampleController', function() {
it('should construct an instance of a SampleController', function() {
expect(new SampleController() instanceof SampleController).toBeTruthy();
});
});
});
Ideally the background behaviour of loading the dependency in and running the specs would be totally opaque to anyone coming on to the project wanting to write tests, and they won't need to do anything other than create a *spec.js file with AMD dependencies.
To get this all working, we created a boot file and configured Jasmine to use it (http://jasmine.github.io/2.0/boot.html), and added some magic to wrap around require to temporarily delay running tests until after we have our deps loaded:
Our boot.js' "Execution" section:
/**
* ## Execution
*
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
*/
var currentWindowOnload = window.onload;
// Stack of AMD spec definitions
var specDefinitions = [];
// Store a ref to the current require function
window.oldRequire = require;
// Shim in our Jasmine spec require helper, which will queue up all of the definitions to be loaded in later.
require = function(deps, specCallback){
//push any module defined using require([deps], callback) onto the specDefinitions stack.
specDefinitions.push({ 'deps' : deps, 'specCallback' : specCallback });
};
//
window.onload = function() {
// Restore original require functionality
window.require = oldRequire;
// Keep a ref to Jasmine context for when we execute later
var context = this,
requireCalls = 0, // counter of (successful) require callbacks
specCount = specDefinitions.length; // # of AMD specs we're expecting to load
// func to execute the AMD callbacks for our test specs once requireJS has finished loading our deps
function execSpecDefinitions() {
//exec the callback of our AMD defined test spec, passing in the returned modules.
this.specCallback.apply(context, arguments);
requireCalls++; // inc our counter for successful AMD callbacks.
if(requireCalls === specCount){
//do the normal Jamsine HTML reporter initialization
htmlReporter.initialize.call(context);
//execute our Jasmine Env, now that all of our dependencies are loaded and our specs are defined.
env.execute.call(context);
}
}
var specDefinition;
// iterate through all of our AMD specs and call require with our spec execution callback
for (var i = specDefinitions.length - 1; i >= 0; i--) {
require(specDefinitions[i].deps, execSpecDefinitions.bind(specDefinitions[i]));
}
//keep original onload in case we set one in the HTML
if (currentWindowOnload) {
currentWindowOnload();
}
};
We basically keep our AMD syntax specs in a stack, pop them off, require the modules, execute the callback with our assertions in it, then run Jasmine once everything is done loading in.
This set up allows us to wait until all of the AMD modules required by our individual tests are loaded, and doesn't break AMD patterns by creating globals. There's a little hackery in the fact that we temporarily override require, and only load our app code using require (our `src_dir: in jasmine.yml is empty), but the overall goal here is to reduce the overhead of writing a spec.
you can use done in combo with before filters to test asynchronous callbacks:
beforeEach(function(done) {
return require(['dist/sem-campaign'], function(campaign) {
module = campaign;
return done();
});
});
This is how I do to run a jasmine spec in a html using AMD/requirejs for all my sources and specs.
This is my index.html file that loads jasmine and then my 'unit test starter' :
<html><head><title>unit test</title><head>
<link rel="shortcut icon" type="image/png" href="/jasmine/lib/jasmine-2.1.3/jasmine_favicon.png">
<link rel="stylesheet" href="/jasmine/lib/jasmine-2.1.3/jasmine.css">
<script src="/jasmine/lib/jasmine-2.1.3/jasmine.js"></script>
<script src="/jasmine/lib/jasmine-2.1.3/jasmine-html.js"></script>
<script src="/jasmine/lib/jasmine-2.1.3/boot.js"></script>
</head><body>
<script data-main="javascript/UnitTestStarter.js" src="javascript/require.js"></script>
</body></html>
and then my UnitTestStarter.js is something like this:
require.config({
"paths": {
....
});
require(['MySpec.js'], function()
{
jasmine.getEnv().execute();
})

RequireJS define callback returning wrong module dependencies

I'm having a very strange and frustrating problem with RequireJS. When I call require for a module with a list of dependencies, all dependencies available in the callback reference a single module. This is probably easier to explained with code:
Including require.js script (with no data-main attribute)
<script type="text/javascript" src="/js/common/require.min.js" ></script>
Below that I include require my main.js (used in all pages of the site) which in the callback requires my page specific js.
<script type="text/javascript">
require(['/js/require/main.js'], function () {
require(['page/home_page']);
});
</script>
main.js
requirejs.config({
baseUrl: 'js/require'
});
requirejs(['base'],
function() {
var base = require('base');
base.init();
});
home_page.js
define(['home','searchbar'], function (home,searchbar){
console.log(home.init == searchbar.init); // This is always true !!!
home.init();
searchbar.init();
});
home.js
define(function(){
this.init = function(){
console.log('in home page');
}
return this;
});
searchbar.js
define(function(){
this.init = function(){
console.log('Now in the searchbar init')
}
return this;
});
The issue is in home_page.js both modules home and searchbar reference the same thing. What's strange is that now that I've simplified this example, it seems pretty random which one it chooses. Most times it's searchbar but every few refreshes it will be home.
Anyone have an ideas? Is it something terribly obvious?
EDIT: Simplified example and provided all module source.
You are assigning to this in both modules. This is not a good idea (excuse the pun). this will possibly be the window object in both cases. You could check by adding a
window.init === searchbar.init
test.
Rewrite the modules to return unique objects, like so:
define(function() {
return {
init: function() {
console.log('in home page');
}
};
});
and
define(function() {
return {
init: function() {
console.log('Now in the searchbar init');
}
};
});

Unable to run javascript using Ext.Ajax in Sencha-Touch2?

I am using htmlPanel.js in Sencha-Touch which was discussed in the Forum here to display local html content. I am able to load the html content with normal html tag, but not the javascript.
Below is the htmlPanel.js I used:
Ext.define('HTMLPanel', {
extend: 'Ext.Panel',
// We are using Ext.Ajax, so we should require it
requires: ['Ext.Ajax'],
config: {
listeners: {
activate: 'onActivate'
},
// Create a new configuration called `url` so we can specify the URL
url: null
},
onActivate: function(me, container) {
Ext.Ajax.request({
// we should use the getter for our new `url` config
url: 'htmlTest.html',//this.getUrl(),
method: "GET",
success: function(response, request) {
// We should use the setter for the HTML config for this
//Ext.Msg.alert('Alert', 'Success!!!', Ext.emptyFn);
me.setHtml(response.responseText);
},
failure: function(response, request) {
//Ext.Msg.alert('Alert', 'Failure!!!', Ext.emptyFn);
me.setHtml("failed -- response: " + response.responseText);
}
});
}
});
Below is my htmlTest.html:
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas">Your browser does not support the HTML5 canvas tag.</canvas>
<script type="text/javascript" charset="utf-8">
var c=document.getElementById('myCanvas');
var ctx=c.getContext('2d');
ctx.fillStyle='#FF0000';
ctx.fillRect(0,0,640,1280);
</script>
<h1>This is some text in a paragraph.</h1>
</body>
</html>
And below is my index.js:
Ext.application({
name: 'SampleLoad',
launch: function () {
//loadURL('htmlTest.html');
Ext.Viewport.add({
url: 'htmlTest.html',
xclass: "HTMLPanel",
});
// Add the new HTMLPanel into the viewport so it is visible
Ext.Viewport.add(HTMLPanel);
}
});
I am able to see the text "Some text is here.", but not the canvas that I tried to create using javascript.
Is there any config that needs to be specified? Or any other cause?
Thanks.
The problem is that HTML is implanted in the DOM but scripts aren't executed. This is the case for assignment to innerHtml and probably also for setHtml().
You can solve this by explicitly executing the scripts in the rendered element, immediately after splicing the HTML in. Your htmlPanel.js would then look like:
...
// We should use the setter for the HTML config for this
//Ext.Msg.alert('Alert', 'Success!!!', Ext.emptyFn);
me.setHtml(response.responseText);
var scriptArray = me.renderElement.dom.getElementsByTagName("script");
for(var i=0;i<scriptArray.length;i++) {
eval(scriptArray[i].text);
}
},
...

Categories

Resources