Skip to content
This repository has been archived by the owner on Jun 21, 2023. It is now read-only.

Conditional rendering example #61

Closed
OEvgeny opened this issue Aug 30, 2017 · 12 comments
Closed

Conditional rendering example #61

OEvgeny opened this issue Aug 30, 2017 · 12 comments

Comments

@OEvgeny
Copy link

OEvgeny commented Aug 30, 2017

Imagine we have different markup for empty list and for list which contains elements.
E.g. for empty one we could end up with something like:

<div class="items state-empty">There are no items yet</div>

And for list with items, markup could be:

<div class="items">
  <div class="item">
    <h5 class="name"></h5>
    <p class="description"></p>
  </div>
</div>

How could we use simulacra to deal with such cases? How do you solve conditional rendering problem with it?

@gr0uch
Copy link
Owner

gr0uch commented Aug 30, 2017

The template would contain both the list and the empty view. In your case, the template might look like:

<div class="items">
  <span class="empty-message"></span>
  <div class="item">
    <h5 class="name"></h5>
    <p class="description"></p>
  </div>
</div>

Then the state might look like:

{
  list: [ ... ],
  isEmpty: null
}

Here's an example of empty/error message in a template: https://github.com/fortunejs/fortune-http/blob/5ee54675e82cebf08b05029b706fa19da2627361/lib/template.html#L116-L117

@OEvgeny
Copy link
Author

OEvgeny commented Aug 30, 2017

@daliwali Looks interesting. As I understood it means that we have to duplicate some UI related state fields and keep them in sync with existing data.
So, to update list in this particular case we need to do something like:

state.list.push(...items)
state.isEmpty = state.list.length === 0

Could we use sort of computed property to handle isEmpty state? E.g.

const state = {
  list: [],
  get isEmpty () { return this.list.length }
}

I tried this approach but it won't work (see this fiddle). Looks like es6 getters are ignored.
I tried to use Object.defineProperty instead, but this doesn't work for me too.

As I understand the problem is what we have to mutate values directly instead of compute them using getters. It should notify renderer about update. For Array it means that to react on update, we need a way to subscribe on its changes (or modify mutation methods) to be able to perform update of isEmpty state.

@gr0uch
Copy link
Owner

gr0uch commented Aug 30, 2017

I understand what you mean. The simplest and easiest way to do this is to set it via the change function:

function (node, value, previous, path) {
  var target = path.target // reference to current object
  target.isEmpty = !target.list.length
}

This way you don't have to remember to set isEmpty manually. It can still be set manually from outside of the change function, though.

@OEvgeny
Copy link
Author

OEvgeny commented Aug 30, 2017

Also there are some cases when keeping both branches of conditional could be problematic. For example when we have two different views of some data and need to switch between them. E.g. list view and cards view. This views can be different in terms of semantics and accessibility.

Is there any way to remove falsy conditional branches from current DOM?

@OEvgeny
Copy link
Author

OEvgeny commented Aug 30, 2017

@daliwali

via the change function

Wow. I thought about something like that. But wasn't sure what it could be so simple :) Thanks!

@gr0uch
Copy link
Owner

gr0uch commented Aug 30, 2017

E.g. list view and cards view. This views can be different in terms of semantics and accessibility.

In this case, you might need to set isEmpty for both views, this can be extracted into a helper function to be reusable.

Is there any way to remove falsy conditional branches from current DOM?

No, there is not. Even with a template language like Mustache, falsy branches are still in the template.

@OEvgeny
Copy link
Author

OEvgeny commented Aug 30, 2017

E.g. list view and cards view.

I was thinking about something like:

const state = {
  currentView: 'list',
  items: [...]
}

In state. And template which looks like:

<div class="button-toggler">
   <!-- toggle views between card and list markup goes here -->
</div>
<div class="list-view">
   <div class="item">
     <!-- list item markup goes here -->
   </div>
</div>
<div class="card-view">
   <div class="item">
     <!-- card item markup goes here -->
   </div>
</div>

Sorry that it wasn't so clear from the previous message.

Now I understood what this will not work because we couldn't use one array for binding to multiple DOM nodes. To toggle between views we could use something like this in state:

const state = {
  currentView: 'list',
  cards: null,
  list: [...] // list will be shown
}

To toggle views we simply need to assign items to state.cards instead of state.list and flush the other one.

I wonder, is there a better way to handle such cases?

@gr0uch
Copy link
Owner

gr0uch commented Aug 30, 2017

Why not just include all of the data in your list, and conditionally hide and show things in CSS? I'm not sure why its necessary to do this in JS at all.

@OEvgeny
Copy link
Author

OEvgeny commented Aug 31, 2017

@daliwali as mentioned before markup could be different especially when semantics and accessibility involved. So, we couldn't just hide or show parts of markup because markup for each data representation (list item and card in this case) could be definitely different.

@OEvgeny
Copy link
Author

OEvgeny commented Aug 31, 2017

Ah, I see another solution for this. We can simplify markup to something like:

<div class="button-toggler">
   <!-- toggle views between card and list markup goes here -->
</div>
<div class="view">
   <div class="item">
     <div class="list-view">
       <!-- list item markup goes here -->
     </div>
     <div class="card-view">
       <!-- card item markup goes here -->
     </div>
  </div>
</div>

And add classes to .view according to currentView state to indicate which data representation should be shown. But if we need to map same item fields to both representations it won't work.

@gr0uch
Copy link
Owner

gr0uch commented Aug 31, 2017

In your case, I guess having two different lists is unavoidable.

You could do something very similar to the example I gave before and modify the second list via the first list's change function:

function (node, value, previous, path) {
  var target = path.target
  target.cards = target.list.slice()
}

@OEvgeny
Copy link
Author

OEvgeny commented Aug 31, 2017

@daliwali Got it. Thank you.

And thanks for sharing simulacra! Keep doing good stuff :)

@OEvgeny OEvgeny closed this as completed Aug 31, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants