antonmedv/monkberry

Ideas

paleo opened this issue · 25 comments

paleo commented

Hi Anton,

I will describe here what I need as a Monkberry user, and what I think of where Monkberry's strengths are.

Provide a way to select DOM elements

Here is a suggestion with :sel but it is not a directive:

<section>
  <div>
    <label>
      <span :sel="descriptionLabel">Enter the label:</span>
      <input type="text" :sel="descriptionInput">
    </label>
  </div>
</section>
let view = render(template)
let selectors = view.selectors // { "descriptionLabel": -the-span-element-, "descriptionInput": -the-input-element- }

Why? I need to access to the selected elements on the current template only. A call to view.querySelector will query all the DOM elements, including in nested templates of nested components, which is not compatible with an approach by components. And I'd rather not sprinkle .js-something CSS classes all over the DOM elements.

Simplify the way to includes nested things during the rendering

Here is a suggestion with :placeholder (maybe it is possible to implement it as a directive):

<section>
  <div :placeholder="makeDescriptionField"></div>
  <ul :placeholder="makeList"></ul>
</section>
let view = render(template, {
  placeholders: {
    makeDescriptionField: el => { /* returns a DOM element or an array of DOM elements */ },
    makeList: el => { /* returns a DOM element or an array of DOM elements */ }
  }
})
  • The callback parameter el is the DOM element of the placeholder;
  • The callback can return a DOM element or an array of DOM elements;
  • The returned DOM element(s) is/are then appended by the template engine to the placeholder element;
  • The callbacks are called synchronously, during the rendering.

Render without a parameter el

let view = render(template) // No unused parameter 'el'
let rootElement = view.rootEl // equivalent to 'view.nodes[0]'

What I don't need

  • The Monkberry component mechanism with inheritance from imported templates. I have my own framework that provides a way to do components, and I need to use the template engine as a library. In addition, the creation code of a subcomponent (with its parameters etc.) must remain in the JavaScript code, it should not be part of the HTML template (separation of concerns).
  • An optimized update for big keyed lists.
  • I rarely use the method update. I often prefer to access the DOM element to update it myself.

Monkberry helps to manually create DOM elements as the developper can, but with HTML syntax. It is the true Monkberry advantage. And Monkberry should help the developper manipulate the real DOM by exposing it, not hide it.

Because Monkberry doesn't do any magic, it allows us to manipulate DOM elements provided by Monkberry with pure DOM libraries like Sortable. And I personnaly think that none of the existing solutions for big keyed lists (React, Angular, Vue) are elegant neither perennial. If there is a specific need about big keyed lists, then maybe a specific library could do the job, but this additional complexity should'nt be added in the template engine.

Hi,

Wow, this is a big issue. Actually I'm working on new version of monkberry here: https://github.com/antonmedv/monkberry/tree/next
It will be much simple and will solve a couple of bugs. Some of your needs will be achieved, some not.

paleo commented

Maybe I can help, I see your code is in TypeScript without semicolons. I like that! But I don't know how to build monkberry-compiler. I installed the package preprocess (on Ubuntu), then I tried:

$ npm run build

> monkberry-compiler@5.0.0 build /path/to/monkberry/packages/monkberry-compiler
> make build

cd ./src/parser && \
preprocess grammar.jison . > ~grammar.jison && \
jison ~grammar.jison -o index.js && \
rm ~grammar.jison
preprocess: error: incorrect number of arguments: argv=['/usr/bin/preprocess', 'grammar.jison', '.']
Makefile:2: recipe for target 'build' failed
make: *** [build] Error 1
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! monkberry-compiler@5.0.0 build: `make build`
npm ERR! Exit status 2

Notice: The code of monkberry-compiler is gibberish to me.
Notice 2: Could we add the markup <img> to the empty markups?

Hmm, you can compile by hands:

cd ./src/parser && \
preprocess grammar.jison . > ~grammar.jison && \
jison ~grammar.jison -o index.js && \
rm ~grammar.jison

Yes, we can add

Also you need to figure out a lot's of stuff to help developing monkberry. :)

paleo commented
$ preprocess grammar.jison . > ~grammar.jison
preprocess: error: incorrect number of arguments: argv=['/usr/bin/preprocess', 'grammar.jison', '.']
$ preprocess --version
preprocess 1.1.0

NB: I just installed preprocess with a sudo apt install preprocess on Ubuntu 16.04. Have I to install something else?

=)) yes, it's npm package. Take a look at package.json.

paleo commented

So I think you use a special config in your PATH. I modify your Makefile:

	../../../../node_modules/.bin/preprocess grammar.jison . > ~grammar.jison && \
	../../../../node_modules/.bin/jison ~grammar.jison -o index.js && \
paleo commented

In monkberry-cli/package.json I see:

"dependencies": {
  "monkberry-compiler": "^5.0.0"

And I obtain:

$ npm install
npm ERR! code E404
npm ERR! 404 Not Found: monkberry-compiler@^5.0.0

Is there a way to say to TypeScript to use the directory src/monkberry-compiler instead of node_modules/monkberry-compiler for this package?

Yes! I use lerna for it. https://github.com/lerna/lerna

paleo commented

The build works!

./node_modules/.bin/lerna bootstrap
npm run build

Then, I run:

./node_modules/.bin/testem

But there are plenty of errors :

ReferenceError: SimpleDom is not defined
ReferenceError: TextNode is not defined
ReferenceError: AttributeVariable is not defined
ReferenceError: TextNodeAround is not defined
etc.

Is it normal?

Try npm test

paleo commented

Maybe the file packages/monkberry-compiler/src/parser/index.js should be in the .gitignore?
And also, I suggest these three minor changes.

paleo commented

In rendering.ts, the roots array is a memory leak. We could render multiple times the same template with always a different parent node (for example if we render a template for an <article> that is included in a <li> generated on the fly).

Maybe you could use a WeakMap<Element | Comment, VNode> instead. But it's not compatible with legacy browsers.

I persist to think the parentDom parameter should be optional.

In patching.ts I don't understand the following code:

    if (process.env.NODE_ENV !== 'production') {
      assert(!lastVNode.view, `patching last vNode without view`)
    }

This code should be executed in the browser, shouldn't it? But there is no process API in the browser.

This code will be deleted on build

paleo commented

The most essential code works with Jison. I'm trying to understand it. In packages/monkberry-compiler/src/compiler/attribute.js, line 35:

var variables = collectVariables(figure.getScope(), expr);

Where does the collectVariables function come from?

See imports

Owww, this is old code. Not refactored yet.

paleo commented

I'm a bit lost in the Jison grammar. Currently I can't learn by trial and error, because I have runtime errors. Notice we could still work together. For example, I could port the remaining JS code to TypeScript, including defining the interfaces etc.

To return to the original topic posted above: I really need the :sel pseudo-directive in order to simplify some code in my project. Is this a feature you could accept?

Can you describe:sel a little?

paleo commented

Sure.

The problem: In the example in the project Website, you do: view.querySelector('input'). In a real situation, I can rarely do that because I add nested components during the rendering (using directives) and maybe a nested component contains another <input> element.

The solution: Monkberry could provide a way to tag some DOM elements in order to do without querySelector.

Maybe :handle would be a better name than :sel. I try here with :handle. In the template, the Monkberry syntax provides a new syntax for tagging DOM elements with "handles". Several DOM elements can be tagged with the same handle (or an element in a loop can be tagged with a handle):

<input type="text" :handle="inputField">
<input type="number" :handle="inputField">
<button type="button" :handle="btn">

Then, the object returned by render(), contains a new member handles. In this object handles, there is a key for each handle name. For each key, the value is an array of the DOM elements, ordered in order of appearance in the template or DOM.

const view = Monkberry.render(Template, document.body)
console.log(view.handles) // { inputField: [-dom-input-1-, -dom-input-2-], btn: [-dom-button-1-] }

Notice: the object handles is regenerated or updated if necessary after a view.update(...).

Additionally, I suggest to provide a helper handle. Here are the definitions:

interface MonkberryView { // The object returned by 'render()'
  // …

  handles: Handles

  /**
   * @returns The single element tagged with the handle. Never returns `null` neither `undefined`.
   * @throws An Error if there is no element or several with this handle.
   */
  handle(handle: string): Element
}

interface Handles {
  [handle: string]: Element[]
}

The example in the project website could be rewritten with handles:

<!-- … -->
<form :handle="f">
  <input type="text" :handle="inputField">
  <button type="submit">Add #{{ todos.length + 1 }}</button>
</form>
// …
const view = Monkberry.render(Template, document.body);
view.handle('f').addEventListener("submit", ev => {
  // …
  let input = view.handle('inputField');
  // …
})

Yes! Actually I was going to implement same idea but with different name: ref (same as React's ref)

paleo commented

That's right! It is the same idea as React's ref. With React it is advisable to use ref as little as possible (React encourages the use of the virtual DOM only), whereas with Monkberry this feature could become a cornerstone of the library.

paleo commented

Hi,
I implemented all my needs with the current Monkberry 4, here.
I plan to publish my work as an npm package with the name lt-monkberry ("lesser than Monkberry"). Do you agree with this name or have I to rename without the words "monkberry" at all?

Cool work! Name is up to you. You can also use @scope: @paleo/monkberry

paleo commented

Good idea.
I put it in the scope of my team: https://www.npmjs.com/package/@fabtom/lt-monkberry
The repo has moved here: https://github.com/fabtom/lt-monkberry