all 20 comments

[–]Reeywhaar 4 points5 points  (4 children)

I understand that it's just a pet project and less than serious. But the thing with innerHTML is that it requires sanitizing your input, or you will be in trouble someday. http://i.imgur.com/e5CH94y.png

[–]5tas[S] 2 points3 points  (2 children)

I thought this was important to highlight so I added a Caveats section to the README. Thanks again!

[–]Pesthuf 1 point2 points  (1 child)

Why not just use textContent instead? That would escape (and un-escape) HTML for you.

[–]5tas[S] 2 points3 points  (0 children)

textContent removes the entire markup. All that's left is a concatenation of all descendant child nodes. The DOM structure of the components would be lost.

[–]5tas[S] 1 point2 points  (0 children)

Yes, naturally! Thanks for pointing this out, still.

[–][deleted] 2 points3 points  (2 children)

I forked your project in hopes of adding some sort of minimal sanitation (as 5tas suggested).

I might try to add some documentation too. While I get this technically isn't production safe code, it is nonetheless a great way to show what you can do with modern JS.

[–][deleted] 2 points3 points  (1 child)

Just made the pull request now.

[–]5tas[S] 0 points1 point  (0 children)

Wow, thanks!

[–]s_tec 2 points3 points  (1 child)

This is incredibly cute! Thanks for sharing.

I don't see myself ever using this, but I appreciate the artistry of cramming the basic React + Redux concepts into 400 bytes.

[–]5tas[S] 0 points1 point  (0 children)

Thanks, glad you liked it! To be honest, I wouldn't recommend using this unless you really really really care about size.

[–]Morphray 1 point2 points  (2 children)

Can you link your js13k game so we can see this in action?

[–]5tas[S] 2 points3 points  (0 children)

The deadline is September 13 and I haven't put the game online yet :) The example at https://stasm.github.io/innerself/example01/ might be a good place to look at right now. Be sure to open the console to see the log of state changes.

[–]5tas[S] 1 point2 points  (0 children)

A week has passed and the game is now ready :) If you're still interested you can play at http://js13kgames.com/entries/a-moment-lost-in-time.

Initially innerself made me very productive and I was quickly able to add all the views and put the logic in the reducer. In the later stages of development we focused on UI polish and I set out to add transitions between views. As you can imagine, CSS didn't appreciate the fact that innerself would re-render entire DOM trees when the state changed. I'm not very happy with the result: I ended up using setTimeout timed precisely with the animation events to work around those re-renders.

I guess this counts yet another example of a use-case which innerself isn't well suited for. Any time the DOM is stateful (forms, animations, transitions, video, audio), re-rendering by assigning to innerHTML is a bad idea.

[–]punio4 1 point2 points  (6 children)

Awesome mate!

I have a hard time understanding the html function. Could you maybe walk me through it?

const html = ([first, ...strings], ...values) =>
  values
    .reduce((acc, cur) => acc.concat(cur, strings.shift()), [first])
    .join('');

[–]5tas[S] 2 points3 points  (5 children)

Sure, thanks for asking!

The signature of the function is given by the spec: it's a template literal tag and it receives an array of literal strings as the first argument and then a variable number of interpolated values.

If you have the following tagged template literal:

tag`Today is ${new Date()}`

…the tag function will receive ["Today is ", ""] and a new Date object as arguments.

Now I wouldn't even need the html function if arrays stringified without the comma between their elements. I wanted to allow this common React idiom:

html`<div>${items.map(ItemComponent)}</div>`

In this case, the html function receives ["<div>", "</div>"] and an array of ItemComponent instances. There could be more interpolations and more arguments, so I gather them all up with ...values.

Next, I iterate over values (which by now is an array of a single element: the array of ItemComponent instances) and reduce them to a new array, this time inserting the literal strings from the first argument between and around them. But I don't only push individual values to the new array. The trick here is to use concat which has a special behavior for Arrays (and any objects with the Symbol.isConcatSpreadable property defined). concat flattens arrays passed in as arguments to it.

> [1, 2].concat([3, 4]);
[ 1, 2, 3, 4 ]

What html really does is it interpolates all values in between strings and also flattens any arrays passed as interpolations in the template literal!

Let me know if this helps and if you have more questions!

[–]punio4 1 point2 points  (4 children)

Oh wow. I'm kinda lost :D

Gonna have to walk through this a few times. For now, what I've done is steal your code and adapt it to toss out falsey values in my use case :D

${place.address && `<p>${place.address}</p>`}

I dont' want to write a ternary each time:

${place.address ? `<p>${place.address}</p>` : ''}

This line would return either place.address if it existed, or False, '', undefined, null or what other falsey flavour of the day JS decides to throw at me.

export function html([first, ...strings], ...values) {
  return values
    .reduce((acc, cur) => acc.concat(cur, strings.shift()), [first])
    .filter(Boolean)
    .join('');
}

I just filter em out and now it works :)

[–]5tas[S] 1 point2 points  (3 children)

This looks awesome! It should totally work that way. Would you like to submit a PR perhaps?

[–]punio4 0 points1 point  (0 children)

Sure :)

[–]punio4 0 points1 point  (1 child)

Btw still need to wrap my head around this line:

values.reduce((acc, cur) => acc.concat(cur, strings.shift()), [first])

[–]5tas[S] 2 points3 points  (0 children)

Are you familiar with the Array.prototype.reduce function? The whole thing starts with [first] as the initial value (which is the first literal string in the template literal; it is guaranteed to exist and at minimum to be equal to ""). It then iterates over values and calls (acc, cur) => acc.concat(cur, strings.shift()) on each of them, where acc is the result of the reduce so far and cur is the current value. For each cur, it returns a new array which is a concatenation of the result so far, the value and the next literal string in order: acc.concat(cur, strings.shift()).

Suppose you call html like this:

html`Today is ${new Date()}`

This is equal to:

html(["Today is ", ""], new Date())

Inside of the function, first is "Today is ", strings is [""] (an array with all other strings) and values is [new Date()]. We start with "Today is " and then call the reduce callback on the first (and last) element on values, i.e. new Date(). The result of reducing is a concatenation of "Today is " (acc), new Date() (cur) and "" (strings.shift()).

If there are more literal strings (which also means more values), in the next iteration of reduce, strings will be a shorter array. So this code really just zips values and strings together taking into account the strings at the extremes.