wp-plugin-leaflet-map icon indicating copy to clipboard operation
wp-plugin-leaflet-map copied to clipboard

Use HTML element with data-attributes instead of JavaScript for rendering shortcodes

Open scrobbleme opened this issue 4 years ago • 8 comments

Hello,

The current behavior, directly rendering JavaScript into the content is quite error prone with page builder and other tools. I.e. I'm using Toolset quite often and can't use the shortcodes within templates as the JavaScript is broken than. I've always workaround this with escaping the code and than executing on DOMContentLoaded (which also improves Caching compatibility btw)

I think it would be better to render a hidden div (or whatever fits) with data attributes and than load it via JavaScript instead. (beside that it needlessly much JavaScript code printed...)

scrobbleme avatar Mar 11 '21 08:03 scrobbleme

The main script does execute on load: https://github.com/bozdoz/wp-plugin-leaflet-map/blob/master/scripts/construct-leaflet-map.js#L372-L377

As for the "hidden div (or whatever fits)", are you suggesting that instead of rendering

<div class="wp-leaflet-map-plugin-node" data-instructions="{...some JSON data here}">

Or are you suggesting something else?

If that is what you're suggesting, I love it. Generally, I'd like to have PHP merely create some JSON-input for Javascript. It was an Issue I created awhile back, but closed because it was too vague: #93

I'm not sure if the above DOM example with data attributes would work or not, but I'd love to test it out.

bozdoz avatar Mar 11 '21 19:03 bozdoz

Exactly like this, the div itself would be hidden...

Just one encoded JSON is fine, it might make sence to extract some "key" attributes directly, to make it more obvious. The name might be data-leaflet to be more precise as well.

Maybe also, the content of the popup (if any) could be the content of the div, so this must not be encoded as JSON.

<div data-leftlet="map1" data-leaflet-lat="" data-leaflet-long="" ... data-leaflet-configuration="{...some JSON data here}">
      This is the marker "content"
</div>

Than whenever map was loaded it could load its markers:

document.querySelectorAll('[data-leaflet="+ mapId +"]').forEach(marker => {
    // Add marker to map
});

An alternative to a div might be a script of type text/plain:

<script type="text/plain" data-leftlet="map1" data-leaflet-lat="" data-leaflet-long="" ... data-leaflet-configuration="{...some JSON data here}">
      This is the marker "content"
</div>

scrobbleme avatar Mar 12 '21 09:03 scrobbleme

is this an issue belonging to mine? I render 1000 markers with the shortcodes and this is really slow... can I put them in one json`?

emojized avatar Mar 19 '21 07:03 emojized

@emojized What is your issue?

But generally yes, the way it works makes the page slower on load as all markers are evaluated immediately instead of deferred after page load (if i'm not wrong with this). Especially with so many markers, you possibly have a lot of superfluous JavaScript code within your page.

scrobbleme avatar Mar 19 '21 07:03 scrobbleme

so whart should i do then?

emojized avatar Mar 19 '21 07:03 emojized

We are using a custom marker shortcode to escape the leaflet shortcode and then load it later. You can try this as well.

Unfortunately I can't give you the full code, but the idea is as follows:

add_shortcode('custom-leaflet-marker', function($atts, $content) {
  $result =  Leaflet_Marker_Shortcode::shortcode($atts, $content);
  $result  str_replace(['<script>', '</script>'], ['', ''], $result);
  return '<script data-leaflet="yes" type="text/plain">' . $result . '</script>';
});

Then you need a JavaScript based on load hook, like this:

document.addEventListener('DOMContentLoaded', _ => {
  document.querySelectorAll('script[data-leaflet="yes"]').forEach(script => {
      const markerScript = script.textContent;
      eval(markerScript);
  });
})

scrobbleme avatar Mar 19 '21 07:03 scrobbleme

Thinking about this some more. I'm concerned about asynchronous sections of the page; the section loads with a map: currently it renders a script tag which directly calls the main script. If we rendered a div, or equivalent, instead, then I'm not sure how the main script will be made aware of it. Does this make sense to you? This makes me wary of making such a change. Does it solve your issue to wrap each script in DOMContentLoaded, like you said in your original post? I'm curious about your current solution involving escaping the code.

bozdoz avatar Jun 25 '21 02:06 bozdoz

@bozdoz Good point.

The main reason for me to use data attributes is not to avoid JavaScript at all, but to improve readability and esp. compatibility with i.e. caching plugins...

Ideas:

  • still use divs with data attributes → just to improve readability, caching compatibility + only a very tiny "inline" JavaScript call to invoke the loading
  • maybe have a look at MutationObserver. I also never used it so far, but I think this might work

scrobbleme avatar Jun 25 '21 06:06 scrobbleme