1

I am developing a Joomla 3.x content plugin to display Openlayer maps in articles based on @Robbie Jacksons excellent tutorial at https://docs.joomla.org/J3.x:Developing_an_MVC_Component/Adding_a_Map and I managed to make it work. However, in Robbie's tutorial he includes the full Openlayers library which is discouraged for production sites:

    // everything's dependent upon JQuery
    JHtml::_('jquery.framework');

    // we need the Openlayers JS and CSS libraries
    $document->addScript("https://cdnjs.cloudflare.com/ajax/libs/openlayers/4.6.4/ol.js");
    $document->addStyleSheet("https://cdnjs.cloudflare.com/ajax/libs/openlayers/4.6.4/ol.css");

At the Openlayers website you are encouraged to "include" only the libraries required by your application and "compile" (the correct term is "transpile") a production version ("bundle"). Everything is built on node.js, see eg. https://openlayers.org/en/latest/doc/tutorials/bundle.html. In the production bundle you will find an index.html file referencing the transpiled Javascript code in another file, typically main.js The compiled/minified/obfuscated/uglified Javascript code is higly unreadable but there is a .js.map file to help you map that code into something readable. But even if you know that your function is in there, you cannot call it. Everything is named something else.

I ran into a number of problems when trying to put the transpiled bundle into my Joomla extension:

  1. In the examples at https://openlayers.org/en/latest/examples/, the Javascript code is invoked by a <script> tag just before the final </body> tag in an HTML file. But what you do in Joomla 3x is, that you put the Javascript declaration in the <head> section by manipulating the DOM-tree (see https://docs.joomla.org/J3.x:Adding_JavaScript_and_CSS_to_the_page). But it does not get triggered and it is very hard in Joomla plugins to put something just prior to the ending </body> tag. But you do not want it to run until the DOM-tree is ready, otherwise it will fail.
  2. I need to transfer options from my server-side PHP code to my client-side Openlayer Javascript. The Joomla 3x recommended method is described in https://docs.joomla.org/J3.x:Adding_JavaScript_and_CSS_to_the_page: At serverside you use $document->addScriptOptions() and then you retrive the options client side using e.g. var myOptions = Joomla.getOptions('mod_example'); But you cannot compile and test Javascript code containing Joomla specific code. Node.js does not know what Joomla.getOptions() means.

Please advise.

5
  • Have you created a bundle with the necessary OL files needed for your component? – Lodder Mar 26 at 8:00
  • By "bundle" you mean a Zip file with a manifest file and all of the PHP and JS files etc. that should go different places in the Joomla file system? Yes I did - initially. But the the way I do development is by working on the code AFTER it has been installed by Joomla. So I simply tried to save the Nodes generated code in the file system and replaced the reference to the full Openlayers library with a reference to the local, limited version. My plugin did not choke on that but also, it did not get triggered. – SoaringBird Mar 26 at 14:56
  • Sorry, when I say "bundle", I'm referring to the bundled Javascript. The tutorial uses imports/exports which are used by Javascript modules. You can either load a single JS file which in turn will import other JS files, or a bundle can be created. Which route are you going down? – Lodder Mar 26 at 16:47
  • Sorry @Lodder, I am not aware of alternative routes. I have tested my scripts in a Node environment. There I start out installing the full ol library using 'npm install ol'. Then, in my script, I import the sub libraries required, i.e. 'import Map from 'ol/Map';' 'npm start' runs the scripts in development mode. 'npm run build' produces ONE .js file only plus the map file. It is this .js file that I am trying to load into Joomla instead of the full online library. Does that clarify? Sorry, I am a noobie on on Nodes and Javascript development. – SoaringBird Mar 27 at 15:12
  • @Lodder, I got a little bit further and have revised my question accordingly. – SoaringBird Apr 2 at 13:24
1

I found a way to solve the problems myself and I will share them here:

Problem 1) You can either embed your Javascript code in a jQuery(document).ready(function) event handler or, you can simply use the DEFER option when declaring the Javascript code in the <head> section. That will cause it to be executed when the DOM is ready. And then you do not need jQuery at all. In your server side PHP code this would look something like this:

$document->addScript(JURI::base() . '<my javascript>' , array("async" => "async"), array("defer" => "defer"));

(<my javascript> is the transpiled code generated by node.js and parcel-bundler (in my case)).

Problem 2). You can split your Java-code into a Joomla-dependent script, local.js, that picks up the Joomla-options using Joomla.getOptions() and make that script invoke another non-Joomla dependent script, main.js and pass parameters as function parameters. I.e. rather than write your Openlayers code like this:

Main.js:

import 'ol/ol.css';
import Map from 'ol/Map';
import OSM from 'ol/source/OSM';
import TileLayer from 'ol/layer/Tile';
import View from 'ol/View';

var map = new Map({
  layers: [
    new TileLayer({
      source: new OSM(),
    }) ],
  target: 'map-container',
  view: new View({
    center: fromLonLat[east, north],
    zoom: 2,
  }),
});

You write it like this:

Main.js:

import 'ol/ol.css';
import Map from 'ol/Map';
import OSM from 'ol/source/OSM';
import TileLayer from 'ol/layer/Tile';
import View from 'ol/View';

window.main = function main(east,north) {
    var map = new Map({
      layers: [
        new TileLayer({
          source: new OSM(),
        }) ],
      target: 'map',
      view: new View({
        center: fromLonLat[east, north],
        zoom: 2,
      }),
    });
}

By declaring the function main() as belonging to window, you make it global and it becomes possible to invoke it from your joomla-dependent script. There are other ways to make functions available from outside the bundle as discussed in https://stackoverflow.com/questions/57602686/javascript-function-wont-trigger-when-called-in-html-file-during-parcel-build but this is an easy method.

In order to test and transpile it under node.js you will have to write another version of the first script, local.js, that does not use Joomla functions. Instead, it just provides a set of test parameters to main.js above, ie.

local.js for testing and transpilation:

let east = -4.088;
let north = 42.713;
main(east,north);

local.js for production:

let myOptions = Joomla.getOptions('mod_example');
let east = myOptions.east;
let north = myOptions.north;
main(east,north);

Then your serverside PHP code would look something like this:

...
// Calculate and transfer options to client-side
$mapParams = array(.....)
$document->addScriptOptions(''mod_example', $mapParams);

// Get CSS and Javascript in place
$document = Factory::getDocument();
$options = array("async" => "async");
$attributes = array("defer" => "defer");

// ... and our own JS and CSS. Except  local.js, code is generated by node.js and parcel.
$document->addStyleSheet(JURI::base() . 'media/<my-plugin>/css/<my-plugin>.css' , $options);
$document->addScript(JURI::base() . 'media/<my-plugin>/js/main-compiled.js' , $options);
$document->addScript(JURI::base() . 'media/<my-plugin>/js/local.js' , $options,$attributes);

<Text substitution code - this is where you substitute a text string with the HTML-code where the map should be targeted), i.e. <DIV id="map-container" class="map"></DIV> >

The CSS file and the main-compiled.js file are the ones transpiled by node.js (found in the dist folder). local.js would be your production version.

My plugin is used here: https://cnj.dk/blog/camino-de-santiago-2019-images. My implementation is not general enough to be relased at the Joomla extension site but go ahead and ask if you need my help to implement something similar.

1
  • In a response to this question: joomla.stackexchange.com/questions/10558/…, @Robbie Jackson provided an alternative to having two different scripts, one for transpiling and one for production: You can look up in /media/system/js/core-uncompressed.js how Joomla implements Joomla.getOptions() (using document.querySelectorAll() ) and simply put that code in your Openlayers code. That part is not Joomla dependent. Thanks, Robbie. – SoaringBird Apr 5 at 22:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.