General developer forum

Adding javasvript files

 
Picture of Ludo M
Adding javasvript files
 
Hello,

I am developping  a Moodle plugin and I had lot of problem to add external javascript files.
My code in a simple standalone HTML file works perfectly but when I try the same thing in Moodle I have got bugs.

Here are the file I need:
<script type="text/javascript" src="jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="moment.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" </script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" </script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.4.1/js/bootstrap-datepicker.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.4.1/css/bootstrap-datepicker3.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.0-alpha14/js/tempusdominus-bootstrap-4.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.0-alpha14/css/tempusdominus-bootstrap-4.min.css" />


In Moodle I have included jQuery with
// include jquery
$PAGE->requires->jquery();

For the other js files I have tested:

$PAGE->requires->js( new moodle_url($CFG->wwwroot.'/'.$CFG->admin.'/tool/myplugin/moment.js') );
$PAGE->requires->js(new moodle_url('https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js'));
...

And I get error in the console like:
Error: Mismatched anonymous define() module: function () { 'use strict';....
Error: Bootstrap tooltips require Tether (http://tether.io/)
Error: Tempus Dominus Bootstrap4's requires moment.js. Moment.js must be included before Tempus Dominus Bootstrap4's

It is such a PAIN to add javascript file in Moodle !
Wouldn't have been much simpler to call function loading js files at the end of HTML rendreing?

I have read that AMD can be used for js, but it seems to be a lot of work (install node.js, grunt...) to just add js files !!!

Please can you tell me how to load those file.
 
Average of ratings: -
Davo
Re: Adding javasvript files
Core developersParticularly helpful MoodlersPlugin developers

I would suggest that AMD modules, whilst costing a bit more in terms of initial configuration, are still the right way to go in this case.

The reason is that, unlike a simple webpage where you have 100% control over every bit of javascript that is loaded onto the page, with a Moodle page you could have javascript being loaded by the theme, by the main content of the page, by any blocks being displayed on the side of the page, by filters acting on the page (and probably by other bits of Moodle that I've not thought of). If AMD modules are not used to isolate each bit of javascript, then you are likely to start hitting naming clashes with identically named variables / functions overwriting each other (especially if two different plugins are using different versions of the same library).


 
Average of ratings: -
Picture of Ludo M
Re: Adding javasvript files
 

Ok so I am trying to make this with AMD and I have some problems:


Here are the different folder I have:

Everything is in my plugin directory: admin/tool/myplugin/

amd/src/

boostrap.js
config.js
moment.js
poppser.js
tempusdominus.js
tool_myplugin.js

js/

all the minified js files

Here is amd/src/confi.js

define([], function () {
window.requirejs.config({
paths: {
//Enter the paths to your required java-script files
"moment": M.cfg.wwwroot + '/admin/tool/myplugin/js/moment.min',
"popper": M.cfg.wwwroot + '/admin/tool/myplugin/js/popper.min',
"bootstrap": M.cfg.wwwroot + '/admin/tool/myplugin/js/bootstrap.min',
"bootstrapdatepicker": M.cfg.wwwroot + '/admin/tool/myplugin/js/bootstrap-datepicker141.min',
"tempusdominus": M.cfg.wwwroot + '/admin/tool/myplugin/js/tempusdominus-bootstrap-4.min',

},
shim: {
//Enter the "names" that will be used to refer to your libraries
'moment': {exports: 'moment'},
'popper': {exports: 'popper'},
'bootstrap': {exports: 'bootstrap'},
'bootstrapdatepicker': {exports: 'bootstrapdatepicker'},
'tempusdominus': {exports: 'tempusdominus'},
}
});
});

amd/src/moment.js:

define(['tool_myplugin/config', 'moment'], function(unused,moment) {
return moment;
}
);

amd/src/popper.js

define(['tool_myplugin/config', 'popper'], function(unused,popper) {
return popper;
}
);

amd/src/tool_myplugin.js

define([
'jquery',
'tool_myplugin/moment',
'tool_myplugin/popper',
],
function ($, moment,popper) {
function initManage() {
alert(moment({hour: 10, minute: 00}));

}
return {
init: function () {
initManage();
}
}
});

From my php file i load like this:

$PAGE->requires->js_call_amd('tool_myplugin/tool_myplugin, 'init');

moment Library is loaded as I get the alert message.

But I have a problem with popper as I get error in the console:

Error in source links : request failed with status 404
URL : http://localhost/moodle/admin/tool/myplugin/js/popper.min.js
source link URL : popper.min.js.map
Error in source links : SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
URL : http://localhost/moodle/lib/requirejs.php/-1/core/first.js
source link URL : popper.js.map

The file http://localhost/moodle/admin/tool/myplugin/js/popper.min.js exists I have enabled $CFG->cachejs = false; in config.php to prevent JS caching

I don't understand the problem.

 
Average of ratings: -
Picture of Ludo M
Re: Adding javasvript files
 

I understand why you have used AMD, but something done in a few minutes outside of Moodle takes me hours with AMD, grunt...
I think a tutorial step by step with external js such as Bootsrap components would be a great help to developp Moodle plugins


I have read the doc and PDF about javascript on Moodle.org but now I am stuck... sad((((


 
Average of ratings: -
Picture of Justin Hunt
Re: Adding javasvript files
Particularly helpful MoodlersPlugin developers

It really is hard work  at first. But Davo was right AMD is the right way to go for something like Moodle. Its probably a little simpler than what you are doing there.

Bootstrap is already available so don't bother loading that. You do not need to shim popper. Its in user tours so you should be able to load it from 

'tool_usertours/popper',

I do not know much about the other libs you are loading. If you can wrap them in an AMD wrapper you do not need to shim and that makes life a bit easier.

 
Average of ratings: -
Picture of Justin Hunt
Re: Adding javasvript files
Particularly helpful MoodlersPlugin developers

Just re-reading your post ... Moodle is using Bootstrap 4 alpha for Boost .. and that uses Tether not Popper for popovers. So you need something like this ...

define(['jquery','core/log','theme_boost/tether'], function($,log,Tether) {
"use strict"; // jshint ;_;

log.debug('initialising Tether');

window.Tether = Tether;
return{ };//end of return value
});
That will load tether and put it into a place tooltip and popover can find it, and avoid that error you had in your first post. Test with other themes(eg More) though because they have different versions of Bootstrap. 



 
Average of ratings: -
Picture of Ludo M
Re: Adding javasvript files
 
I have made a standalone version to make requirejs work, and it is ok in the standalone version but still don't work in Moodle.

here are my standalone files:

config.js
requirejs.config({
baseUrl: "assets/js",
paths: {
//the libs
jquery: 'lib/jquery-3.3.1.min',
bootstrap : 'lib/bootstrap.bundle.min', //contains already popper
moment : 'lib/moment.min',
datepicker : 'lib/bootstrap-datepicker141.min',
datetimepicker : 'lib/tempusdominus-bootstrap-4.min',
},
shim : {
"bootstrap" : ["jquery"],
"datepicker" : ["jquery"],
}

});
/*

A



myapp.js
define([
'jquery',
'moment',
'bootstrap',
'datepicker',
],
function($,moment,bootstrap,datepicker){
..
window.moment = moment;
require(["datetimepicker"], function(datetimepicker) {
$('#datetimepicker1').datetimepicker({
format: 'HH:mm',
use24hours: true,
defaultDate: moment({hour: 9,minute:00})
});
}
..
}


And Moodle version:

amd/src/config.js
define([], function () {
window.requirejs.config({

paths: {
"moment": 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min',
"bootstrap": M.cfg.wwwroot + '/admin/tool/myplugin/js/bootstrap.bundle.min',
"datepicker": M.cfg.wwwroot + '/admin/tool/myplugin/js/bootstrap-datepicker141.min',
"datetimepicker":M.cfg.wwwroot + '/admin/tool/myplugin/js/tempusdominus-bootstrap-4.min',
},
shim: {
'bootstrap': {exports: 'Bootstrap'},
'datepicker': { exports: 'datepicker'},
}
});
});
amd/src/myplugin.js

define([
'jquery',
'tool_myplugin/moment',
'myplugin/bootstrap',
'myplugin/datepicker',
],
function ($,moment,bootstrap,datepicker) {

window.moment = moment;
require(['myplugin/datetimepicker'], function(datetimepicker) {
...
});
And I have the following error :

Error: No define call for datetimepicker
I have read on requirejs about this error but I don't understand why it works in standalone and not in Moodle.
I don't need an amd/src/datetimepicker.js file, since I have not shim with datetiempicker exports?

 
Average of ratings: -
Picture of Justin Hunt
Re: Adding javasvript files
Particularly helpful MoodlersPlugin developers

Re boostrap. 

Actually your post did make me wonder about how much we can rely on Bootstrap being available, without explicitly loading it. But it just always has been in my experience.

Re your error. I think its either because

  1.  you have not shimmed datetimepicker . I do not see it in the list of shims. OR
  2. the path is wrong since there is no /amd/src/datetimepicker.js in your distribution. 

Try confirming it is shimmed  and if thats ok try this ..

require(['datetimepicker'], function(datetimepicker) {
...
});


 
Average of ratings: -
Picture of Ludo M
Re: Adding javasvript files
 

Thanks for the help, but I have shimmed datetimepicker and made the file /amd/src/datetimepicker.js and tried with both

require(['datetimepicker'], function(datetimepicker) {
...
});

or

require(['tool_myplugin/datetimepicker'], function(datetimepicker) {
...
});
and I still have the error, I really don't understand why
 
Average of ratings: -
Picture of Justin Hunt
Re: Adding javasvript files
Particularly helpful MoodlersPlugin developers

Well I just looked into it, and datetimepicker is AMD compatible so you do not need to shim anyway. Just put 

datetimepicker.min.js in your amd/build folder

datetimepicker.js in your amd/src folder

And then in your plugin :

require(['tool_myplugin/datetimepicker'], function() {

Because datetimepicker is a jquery plugin, it won't export anything useful to be passed into the function as a parameter. You will instead access it via
$('#someelement').datetimepicker();

I am sure you know all that usage stuff. I don't know anything about datetimepicker in particular. I just loaded it up as a template in Poodll/Generico and came up fine, except that moment.js is a dependency. 

 
Average of ratings: -
Picture of Ludo M
Re: Adding javasvript files
 

I have tried adding tempus dominus files in amd/src  and amd/build from

https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.0-alpha14/js/tempusdominus-bootstrap-4.min.js

and https://github.com/tempusdominus/

But as soon as I put the file in the src folder I have this error:

Error: Script error for "core/first"

I have tried with the cdn in the config path and no shim (like in my standlaone working version)

"datetimepicker":'https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.0-alpha14/js/tempusdominus-bootstrap-4.min',

I get the error:

Error: No define call for tool_myplugin/datetimepicker

Would it be possible for you to send me the amd folders, config.js and app.js files that work for you in Moodle with tempusdominus please?

 
Average of ratings: -
Picture of Justin Hunt
Re: Adding javasvript files
Particularly helpful MoodlersPlugin developers

OK. 

Just post the plugin if its ok (unless that will just confuse me) ... 

If its not something you want to post here, send me a message with a URL or mail address and we can do it from there

 
Average of ratings: -
Picture of Ludo M
Re: Adding javasvript files
 

I have found the solution:

The shim has to be:

datetimepicker: { exports: "$.fn.datetimepicker" }

Here are the files:

amd/src/config.js

define([], function () {
window.requirejs.config({
paths: {
//Enter the paths to your required java-script files
"moment": M.cfg.wwwroot + '/admin/tool/myplugin/js/moment.min',
"moment-fr": M.cfg.wwwroot + '/admin/tool/myplugin/js/moment-fr', // bootsrap 4 bundled with popper
"bootstrap": M.cfg.wwwroot + '/admin/tool/myplugin/js/bootstrap.bundle.min',
"datepickerfr": M.cfg.wwwroot + '/admin/tool/myplugin/js/bootstrap-datepicker.fr',
"datepicker": M.cfg.wwwroot + '/admin/tool/myplugin/js/datepicker',
"datetimepicker":M.cfg.wwwroot + '/admin/tool/myplugin/js/tempusdominus-bootstrap-4',
},
shim: {
//Enter the "names" that will be used to refer to your libraries
bootstrap: { deps: ["jquery"], exports: 'bootstrap'}, datepicker: { deps: ['bootstrap'],  exports: "datepicker" },
datepickerfr: { deps: ['datepicker'],  exports: "datepicker" },
datetimepicker: { exports: "$.fn.datetimepicker" }
}
});
});

amd/src/app.js

define([

'jquery',
'tool_myplugin/moment',
'tool_myplugin/bootstrap',
'tool_myplugin/datepicker',
'tool_myplugin/datetimepicker'

],
function ($,moment) {
//alert('bootstrap'+$.fn.tooltip.Constructor.VERSION);
//alert(moment({hour: 10, minute: 0}));

function initManage() {

$(document).ready(function () {

$('#datepicker').datepicker({
                    weekStart: 1,
                    todayBtn: "linked",
                    language: "fr",
                    locale: 'fr',
                    todayHighlight: true,
                    format: "dd/mm/yyyy",
                    orientation: "auto right"       });                 $('#datetimepicker1').datetimepicker({
                    format: 'HH:mm',
                    use24hours: true,
                    defaultDate: moment({hour: 9,minute:0}) });           

amd/src/datetimepicker.js

define(['tool_myplugin/config',  'datetimepicker'], function(unused,datetimepicker) {
return datetimepicker;
}
);

amd/src/moment.js

define(['tool_myplugin/config',  'moment','moment-fr'], function(unused,moment) {
moment.locale('fr');
window.moment = moment;
if (!window.moment) {
alert("moment ko");
}
//if no return, error:TypeError: moment is not a function
return moment;
}
);

amd/scr/datepicker.js

define(['tool_myplugin/config',  ,'datepicker','datepickerfr',], function(unused,datepicker) {
return datepicker;
}
);

amd/src/datepickerfr.js

define(['tool_myplugin/config', 'datepickerfr'], function(unused,datepickerfr) {
return datepickerfr;
}
);

 
Average of ratings: -
Picture of Justin Hunt
Re: Adding javasvript files
Particularly helpful MoodlersPlugin developers

Great work. You really stuck at it. Thanks for posting back, its really useful.

 
Average of ratings: -
Picture of David Scotson
Re: Adding javasvript files
Core developersDocumentation writersPlugin developers

You could just ignore those errors if you want. They won't stop your JS functioning.

Coincidentally I just closed a bug I filed complaining about this error, which explains what it is and how to fix it:

https://tracker.moodle.org/browse/MDL-62787

The reason you are seeing the error twice is that you are adding a JS library that Moodle already provides, so it loads twice and tries to fetch the map file twice. (The two slightly different error messages are because when it tries to dowload the two .map files, one returns a 404 and the other returns an unexpected file, but it's the same issue). Two libraries actually, as Moodle provides the popper library and Bootstrap 4 JS components (the latter live in theme/boost/amd/) .

To use the included popper try including 'core/popper' instead of 'tool_myplugin/popper' in that last file.



 
Average of ratings: -
Picture of Ludo M
Re: Adding javasvript files
 

Thank you for the answer.
But if I set another theme that has a different versin of bootstrap, will it work?
I thought requirejs was made also to not load lib twice.


 
Average of ratings: -