Creating dynamic content with JS tools: any recommendations?

Hi everyone,
I would like ti create a site with “dynamic” reasoning that reacts to some basic user inputs. Something similar to the part with green and blue highlights (the blue parts adapting to the green inputs) in http://worrydream.com/ClimateChange/#media-debate

I’m therefore willing to integrate some JS library to do so in Grav: Tangle, KnockoutJS or any other…

Before starting from scratch, I just want to check if any of you does already have some experience with such an approach, and any recommandation ?

Thanks,
Xavier

Grav just assembles the final HTML from various pieces. You can include whatever JS you want in your templates, and your content doesn’t have to be MarkDown. You can manually code HTML if you need to. You don’t need a plugin or anything, though some exist if you want examples of how to do it.

Thank you @Perlkonig, very clear example.
Since I’m not a very proficient html developer, I was also wondering if anyone had concrete experience with one such framework, but otherwise based on your plugin I’ll try to develop myself and then provide back to the community.

@Perlkonig and any other skilled Grav developer :slight_smile:

After reading through (most of) Grav documentation and the plugin mechanism, I think the best would definitely be for me to create a plugin so that everything is cleanly organized, and others get a chance to use knockoutjs in the grav sites as well.

Basically, the plugin:

  • has to load the js file provided by knockout (and put in the plugin directory)
  • has to load another js file that would be in the user directory, where each user can declare his own viewmodel for knockout
  • and that would be it, since after that everything else will be in html in the pages…

But I’m hesitating about the best way to include the js from the plugin:

  • would you recommend doing it via the plugin php file, as a php function?
  • one that is called by the onPluginsInitialized event hook? Or is another hook best suited for that purpose (does not sound to me, but since I’m new…)
  • using a PHP call such as:
    if(file_exists('plugin://knockout/assets/' . 'plugins.knockout.knockout_version'))
    {
        //we know it will exists within the HTTP Context
        return sprintf("<script type=\"text/javascript\" src=\"%s\"></script>",'plugin://knockout/assets/' . 'plugins.knockout.knockout_version');
    }
    return "<!-- Unable to load " . 'plugins.knockout.knockout_version' . "-->";

?

  • and finally, I’m unclear about how to address the other file, that would be the knockout_viewmodel.js that each user should rather have in its custom part and not in the plugin generic assets. Shall I just reference to it as it it was in the assets, because if the user replicates such a file in his own user/assets/ directory , it will be overridden (and if not, then there is something I’ve definitely misunderstood) ?

Thanks for any help,
Xavier

You can load Knockout early in onAssetsInitialized (Datatables plugin). You can also add it later if you need to tweak it somehow (Tablesorter plugin).

I don’t know much about Knockout. Will every post need it’s own JS file? If so, just store them in the folder with the .md file and load it from there. Otherwise you can store it in a theme folder or other site-wide folder (see docs on PHP streams).

It needs only to be loaded once and for all, and definitely to be shared amongst all the pages/posts. So, I guess the ‘onAssetsInitialized’ will do. That’s the reason why I want the plugin to load it, and not to have to worry at page level.

Thanks for the help !

Is there a doc explaining with more details the “override” on the user side for my second file ?

Xavier

You would load it just as you would the general Knockout file (onAssetsInitialized), but you would reference it using the locator and checking the theme:// folder or wherever you ended up storing it.

Thank you, @Perlkonig. Just managed to have the plugin inject the includes.

I face however another issue which I do not know how to solve, nor if it can be solved with a plugin:

  • the injection of the Knockout js file need to happen after the html where I use some script elements
  • I tried many different even hooks, but it systematically injects everything before the

Can this be addressed with a plugin? Or does it mean I shall rather include this not via a plugin but via a template ?
Xavier

I assume by “before” you mean “at the top of the HTML file”? That’s fixable. See also the Google Analytics plugin for a live example.

Thanks for such a precise answer, @Perlkonig.
I’m not sure to understand the syntax of the options though.
If I write $this->grav['assets']->addJs('plugin://knockout/assets/knockout-3.5.0rc2.js', 'group':'bottom');
I receive the following error:
( ! ) Parse error: syntax error, unexpected ':' in /Users/xaviermarichal/Documents/Websites/energie.humoeursvertes.net/htdocs/user/plugins/knockout/knockout.php on line *34*

Same if I put braces around it as in in the doc: {'group':'bottom'}

I tried with just 'bottom', no error but it doesn’t catch the {{ assets.js('bottom') }} I put in my twig.

What am I doing wrong?
Xavier

You need to resolve the locator syntax to an actual path. Try the following:

  $path = $this->grav['locator']->findResource('plugin://knockout/assets/[FILENAME]', false);
  $this->grav['assets']->addJs($path, 9 /*priority*/ , true /*pipeline*/, 'async' /*or 'defer'*/, 'bottom');

(edited after the fact because I think it needs to be false)

You can try the options syntax provided in the docs. That should work too, but I can’t test it at the moment:

$this->grav['assets']->addJs($path, {'group': 'bottom'});

I’m a bit lost here… The path is fine.

With
$this->grav['assets']->addJs('plugin://knockout/assets/knockout-3.5.0rc2.js');
I see the <script> injected, but in the header of the html page

With
$this->grav['assets']->addJs('plugin://knockout/assets/knockout-3.5.0rc2.js','group':'bottom');
or
$this->grav['assets']->addJs('plugin://knockout/assets/knockout-3.5.0rc2.js',{'group':'bottom'});
I receive an error that states that ‘:’ is not expected

With
$this->grav['assets']->addJs('plugin://knockout/assets/knockout-3.5.0rc2.js', 9, true, 'async', 'bottom');
or with
$this->grav['assets']->addJs('plugin://knockout/assets/knockout-3.5.0rc2.js', 'bottom');
nothing happens, the <script> line is not injected in the generated html.

And I did inject the coded needed in my template twig:

{% block content %}
    {% include 'partials/page.html.twig' %}
{{ assets.js('bottom') }}
{% endblock %}

Or is what you mean @Perlkonig that I must have a resolution involved for the mechanism to work ?

What exactly is being injected into the <script> tag when it appears at the top? If it’s plugin://blah, then it’s not going to work. You need to resolve it to a path reachable by the browser. I don’t have the capacity to test code where I’m at, so maybe the addJs function does this for you, but I didn’t think it did.

As to why nothing seems to be appearing, I don’t know. I know the Google Analytics plugin works, so I’d inspect that closely and compare it to your code. There might be something very minor that’s awry. I’m sorry I can’t be more certain.

Thanks for the further hints, @Perlkonig, but I confirm: with

$this->grav['assets']->addJs('plugin://knockout/assets/knockout-3.5.0rc2.js');

I correctly get

<script src="/energie.humoeursvertes.net/htdocs/user/plugins/knockout/assets/knockout-3.5.0rc2.js" ></script>

injected in the hmtl header

So, I think the path resolution is clearly ok. It is really the bottom injection that fails. But I’ll look more intensely into your Google Analytics plugin. Thanks.

Xavier

Have you tried to enclose the parameters in brackets instead of curly braces?

According the API docs on Assets:

addJs( mixed $asset , int $priority=null , bool $pipeline=true , string $loading=null , string $group=null ) : \Grav\Common$this
Add a JavaScript asset. It checks for duplicates. You may add more than one asset passing an array as argument. The second argument may alternatively contain an array of options which take precedence over positional arguments.

I use the following code and all goes well:

$this->grav['assets']->addJs($manager, ['priority' => 1 , 'pipeline' => false, 'loading' => 'async', 'group' => 'bottom']);
$this->grav['assets']->addInlineJs($script, ['loading' => 'inline', 'group' => 'bottom']);

Got it !
The full line was good, but not the ‘async’ option.

Hence, with

$this->grav['assets']->addJs('plugin://knockout/assets/knockout-3.5.0rc2.js', 10, true, '', 'bottom');

I get it all up and working. (I still do not undertsand the documentation for these options, because the system still refuses the { } and : as show in the Adding Assets options.

Thanks a lot for your help, @Perlkonig. Without it, I’d not be there. Worth one of the best beers if you turn out to be once around Brussels, Belgium !

Indeed, it works as well like that, thanks:

$this->grav['assets']->addJs('plugin://knockout/assets/knockout-3.5.0rc2.js', ['group' => 'bottom']);

Félicitations! It feels good to get things working. Glad I could help a little.