WebReflection / hyperHTML

A Fast & Light Virtual DOM Alternative
ISC License
3.07k stars 112 forks source link

conditional blocks #48

Closed moebiusmania closed 7 years ago

moebiusmania commented 7 years ago

trying to build a markup like this (which is basic tabs widget), all the code generated inside the ternary operator is rendered as text string and not as html:

this.html`<section>
      <h1>Title</h1>
      <ul>
        <li onclick="${this._switch.bind(this)}" data-index="0">Modules</li>
        <li onclick="${this._switch.bind(this)}" data-index="1">Network</li>
        <li onclick="${this._switch.bind(this)}" data-index="2">Follow us</li>
      </ul>
      ${state.active === 0 ? `<div>section 1</div>
        <ul>${
          state.links.applications.map(e => `<li><a href="">${e.name}</a></li>`)
        }</ul>` : null}
      ${state.active === 1 ? `<div>section 2</div>` : null}
      ${state.active === 2 ? `<div>section 3</div>` : null}
    </section>`; 

the elements rendered are exactly what I expect but not the output :-/ This is a beahavior related to the template strings functionality itself or more an HyperHTML issue (in this case I'm sure I'm doing something wrong 😃 ) thanks

WebReflection commented 7 years ago

few errors there

      </ul>
      ${state.active === 0 ? `<div>section 1</div>

This is already wrong. If you want any content different from text, you cannot have spaces around.

On top of that:

// this is an arrow function that returns a string
// there is nothing special in this
e => `<li><a href="">${e.name}</a></li>`

// while the following, is an arrow function
// that returns a wire, which is special
e => hyperHTML.wire(e)`
  <li><a href="">${e.name}</a></li>`

About your issue, there are at least two ways to solve your problem.

The CSS classes based solution

You need a class named like invisible with a display: none value.

// simplify through CSS classes
this.html`
  <section>
    <h1>Title</h1>
    <ul>
      <li onclick="${this._switch.bind(this)}" data-index="0">Modules</li>
      <li onclick="${this._switch.bind(this)}" data-index="1">Network</li>
      <li onclick="${this._switch.bind(this)}" data-index="2">Follow us</li>
    </ul>
    <div class="${state.active === 0 ? '' : 'invisible'}">
        section 1
    </div>
    <ul class="${state.active === 0 ? '' : 'invisible'}">${
      state.links.applications.map(
        e => hyperHTML.wire(e)`
        <li>
          <a href="${e.href}"> ${e.name} </a>
        </li>`
      )
    }</ul>
    <div class="${state.active === 1 ? '' : 'invisible'}">
        section 2
    </div>
    <div class="${state.active === 2 ? '' : 'invisible'}">
        section 3
    </div>
  </section>`;

The wired based solution

// or simplify via wires, my preferred way
this.wire = [

  hyperHTML.wire()`
  <div>section 1</div>
  <ul>${
    state.links.applications.map(
      e => hyperHTML.wire(e)`
      <li>
        <a href="${e.href}"> ${e.name} </a>
      </li>`
  )}</ul>`,

  hyperHTML.wire()`
  <div>section 2</div>`,

  hyperHTML.wire()`
  <div>section 3</div>`
];

this.html`
  <section>
    <h1>Title</h1>
    <ul>
      <li onclick="${this._switch.bind(this)}" data-index="0">Modules</li>
      <li onclick="${this._switch.bind(this)}" data-index="1">Network</li>
      <li onclick="${this._switch.bind(this)}" data-index="2">Follow us</li>
    </ul>${
      this.wire[state.active]
  }</section>`;
moebiusmania commented 7 years ago

thanks a lot man, I still need to better understand wire() 😄

joshgillies commented 7 years ago

@moebiusmania consider wires as super efficient partials in your template. 🎉

albertosantini commented 7 years ago

I followed the wired based suggestion with selectedTab as an element of a wires array:

...
const selectedTab = tabs[state.tabSelectedIndex];
...
render`... <div>${selectedTab}<div> ...`;

So far it works, but there is a detail.

The line above contains a typo: the tag is not closed correctly. If I close the tag, the tab doesn't switch, the element is not replaced in the dom. (Of course I noticed that not intentionally). :)

Any idea?

Live working example (with typo): https://plnkr.co/edit/rntzRrKMhZiBi9xCQwlp?p=preview To reproduce the issue, close the tag wrapping selectedTab.

WebReflection commented 7 years ago

That was a bug, fixed if you include https://unpkg.com/hyperhtml@latest which is v0.11.1

However, it sadden me it's so difficult to get these wires.

You are creating useless wraps every single time you invoke that update

        const tabs = [
            hyperHTML.wire()`<trades class="ma2 pa2">Trades placeholder</trades>`,
            hyperHTML.wire()`<orders class="ma2 pa2">Orders placeholder</orders>`,
            hyperHTML.wire()`<positions class="ma2 pa2">Positions placeholder</positions>`,
            hyperHTML.wire()`<exposure class="ma2 pa2">Exposure placeholder</exposure>`,
            hyperHTML.wire()`<activity class="ma2 pa2">Activity placeholder</activity>`,
            hyperHTML.wire()`<news class="ma2 pa2">News placeholder</news>`,
            hyperHTML.wire()`<plugins class="ma2 pa2">Plugins placeholder</plugins>`
        ];

Above thing is completely useless:

  1. you don't reference those wires so every single time you create a new fragment with fresh new content
  2. you don't use permutations, and since you create new content each time, you basically could simply drop the wire all together

Or you can use wires properly, either creating them once outside the update() method, or referencing them uniquely so that you won't create fresh new content each time but only when needed.

        const tabs = [
            hyperHTML.wire(render, ':trades')`
              <trades class="ma2 pa2">Trades placeholder</trades>`,
            hyperHTML.wire(render, ':orders')`
              <orders class="ma2 pa2">Orders placeholder</orders>`,
            hyperHTML.wire(render, ':positions')`
              <positions class="ma2 pa2">Positions placeholder</positions>`,
            hyperHTML.wire(render, ':exposures')`
              <exposure class="ma2 pa2">Exposure placeholder</exposure>`,
            hyperHTML.wire(render, ':activity')`
              <activity class="ma2 pa2">Activity placeholder</activity>`,
            hyperHTML.wire(render, ':news')`
              <news class="ma2 pa2">News placeholder</news>`,
            hyperHTML.wire(render, ':plugins')`
              <plugins class="ma2 pa2">Plugins placeholder</plugins>`
        ];

Now you have the possibility to create complex section with inputs and stuff without losing content when you change tabs.

I hope it's now clearer how to use a wire.

Thanks for the report, next time please file a new bug so I can track and properly link fixes to known issues 😉

albertosantini commented 7 years ago

However, it sadden me it's so difficult to get these wires.

Not your fault, I am guilty: I get the meaning, but I am not used to them. I have been porting my pet app, so I hope to improve the proficiency as soon as possible. :)

Thanks for the in-depth details.

Thanks for the report, next time please file a new bug so I can track and properly link fixes to known issues

Sure, but I was sure I was missing something on my side. :)