Created by @barglm in Q&As
@barglm
@barglm

Hi Dan, I have more questions, hopefully it will help others. So I am using the Nittro build from our training, with BootstrapErrorRenderer you wrote. It works fine, Nittro works fine. But I bumped into an issue and I am not sure if it is feature or bug.

Can Nittro handle Nette form's addCondition + toggle? I am not able to make it work. https://pla.nette.org/cs/forms-toggle

@jahudka
@jahudka

Hi Martin! Glad to hear it's working out so far. Toggling won't work "out of the box", but you can easily re-enable the default behaviour of netteForms.js by hand - just add a snippet setup callback next to your form and call Nette.toggleForm(document.getElementById({form.id myForm})) :-)

@barglm
@barglm

Works fine, only thing I see is the difference in approach. You are writing most of the code directly in latte files. I am trying to use external JS file for common features, so I can do this:

handleNetteToggle(snippet) {
  let forms = null;

  snippet
    .setup(() => {
      forms = document.querySelectorAll('form');
      forms.forEach((form) => {
        Nette.toggleForm(form); // eslint-disable-line
      });
    })
    .teardown(() => {
      forms = null;
    });
}

But I have 1 extra question, there is a common code I would like to use after any snippet is changed. Is there a way how to use setup/teardown for option like getSnippet('*')? It is a bit issue to rewrite every single form.

window._stack.push(() => {
  _context.invoke(di => {
    di.getService('snippetManager')
      .on('before-update', evt => {
        init(di);
      });
  });
});

function init(di) {
  let contentSnippet = page.getSnippet('snippet--content');
  let createFormSnippet = page.getSnippet('snippet-createForm-form');
  let formElements = [
    contentSnippet, createFormSnippet,...
  ];

  formElements.forEach((snippet) => {
    FormComponent.handleSelectPicker(snippet);
    FormComponent.handleTooltip(snippet);
    FormComponent.handleFlatpicker(snippet);
    FormComponent.handleNetteToggle(snippet);
  });

  ...
}
@jahudka
@jahudka

Hi, yeah, well, when I run across code that I repeat often I usually put it in an external file as well, but more often than not components I find myself writing need pretty specific widget init code which doesn't make sense outside of these components.. that's why I usually put widget initialisation inline within the component's template. But there's nothing wrong with extracting it out and putting it in the build, either ;-)

Just a note about your first code snippet: you're storing a reference to a collection of form elements outside of the setup callback and clearing that reference in teardown - it's great that you're taking care of cleanup so diligently, but in this specific case (unless there's more to your code than the excerpt you posted) you could just omit the external let forms = ... declaration as well as the teardown callback and just make it let forms = document.querySelectorAll() - the collection itself doesn't need any custom cleanup (like calling a .destroy() method) and the way your code is written the reference won't leak outside the setup callback's scope, so as soon as the setup() callback finishes running, the nearest GC should get rid of it automatically.

Before I get to your question, an even shorter note regarding part of your second code snippet: you're calling _context.invoke() from within a _stack.push()-ed callback to get a reference to the DI container - you can simplify that since callbacks pushed to the _stack are in fact called using _context.invoke(), so you can do _stack.push((di) => { /* di.getService('page')... */ }).

And now, at last, to answer your actual question :-) The page.getSnippet() method doesn't support any kind of magic like passing *; instead you can utilise the before-update and after-update events of the snippetManager service to implement pretty much any logic you need. Both of these events get passed the full changeset of snippets that are being modified by the currently running update, so you don't need to reinitialise everything on every request if you're careful enough.

di.getService('snippetManager').on('after-update', (evt) => {
    console.log(
        // each of these is a plain object with snippet ids as keys;
        // values are objects with the keys `element` and `content`
        // holding references to the existing (old) snippet's element
        // and its new content, respectively
        evt.data.add, // dynamic snippets being inserted into a container
        evt.data.update, // snippets with updated content
        evt.data.remove // dynamic snippets being removed and also descendants
                        // of updated snippets
    );
});

One noteworthy occurrence of the after-update event is at the initial page load where the changeset is empty (that is, evt.data.{add|update|remove} are undefined). Also you can call page.getSnippet('some-id').setup(...).teardown(...) from within an after-update handler and the callbacks will be processed as part of the current snippet lifecycle.

Does this satisfy your needs?

@barglm
@barglm

Thank you, this really helps, maybe 1 little question,... when I use after-update I can find list of snippets in evt.data.update, is there same option for initial page load?

@jahudka
@jahudka

No, there isn't - upon the initial page load no snippets are updated by Nittro, so it wouldn't make sense to populate the changeset. In theory you'd think you could find all "snippets" on a page by finding all elements with an ID starting with snippet-, but that might not necessarily work depending on how you work with IDs - for example when I need to use IDs in a component's template I always use the {snippet.id someId} macro to make sure the ID cannot collide with another instance of the same component, even if it looks like I'll only ever have one instance of the component at the time I'm writing it, because that can easily change.

@jahudka
@jahudka

But in terms of widget initialisation you don't really need to worry about it - upon the initial page load you want to initialise all the widgets in a page anyway. The way I usually implement this is that I'd look for the existence of evt.data.update and if it's present I'd build an initialisation context out of the IDs present in the changeset (considering both evt.data.update and evt.data.add) and if it's not present I'd build a context containing document.body - the rest of the initialisation code doesn't care how the context got populated, it just cares about receiving an array of elements to work with, which [document.body] satisfies.

@barglm
@barglm

Great, thank you

Sign in to post a reply