Color Chooser Illustrates ViewModel Hierarchy Questions


#1

In Communication between "sibling" ViewModels, I said:

Conceptually, I’m attempting to 1) build a series of similar HTML elements in a loop; and 2) listen and respond to changes and/or events occurring on each one separately.

I’ve since coded a CanJS color chooser, my first framework-based web project. It attempts to accomplish the above, but I have some questions about my design. In particular:

  1. Do I rely too heavily on global variables to share information between ViewModels?

  2. Can I create a <final-el> VM to manage color cell properties individually rather than relying on indexes in their element ID’s? (for details see todo: <final-el> in the project’s index.html file).

  3. I want to play a sound effect when the user hovers over a grid cell, but the sound lags so far behind the visual display as to degrade rather than improve UX (see todo: address lag in index.html). Is there a better way?

The full source code is posted on GitHub. Thanks In Advance for your constructive criticism!


#2
  1. Instead of using globals, can you have them passed down from parent component to child component?

  2. Yeah, that seems odd. Btw, you can link to line numbers in github. For example: https://github.com/CODE-REaD/CanJS-ColorChooser/blob/master/index.html#L332 . This will make it far easier to know which code you are talking about. Why are you relying on indexes? I’ve never had to do this in CanJS.

  3. You probably need to pre-load the sound source. I don’t know how, but I bet there’s a good way.


#3

If you are building elements in a loop, can you use {{#each }}?

Component.extend({
  tag: "my-app",
  view: `{{#each colors}}<color-block color:from="this"/>{{/each}}`
  ViewModel: {
    colors: {
      default: (){
        return [{name: "black", value: "#000"}, ....]
      }
    }
  }
})

#4

this is a sweet example btw!


#5

@justinbmeyer, thank you for your comments! My remarks on them:

  1. When I try to configure the several VM’s under a common ‘parent’, I seem to lose the ability to derive the grid sizes from CSS as in https://github.com/CODE-REaD/CanJS-ColorChooser/blob/master/index.html#L242 and https://github.com/CODE-REaD/CanJS-ColorChooser/blob/master/index.html#L321 because I can no longer call window.getComputedStyle() since the target of that call is contained in the parent VM.

  2. What I mean by “indexes” is, I’m storing each cell’s RGB values in its element ID and then parsing that to find its RGB value (see https://github.com/CODE-REaD/CanJS-ColorChooser/blob/master/index.html#L292). Then to change things in a single cell I have to send its x,y location to the VM (because the VM is working at the element’s “parent” level). If I could manage properties with an individual cell’s ViewModel, I would be able to store and retrieve these values directly as VM properties.

    Basically I’m asking for the power of the parent VM (on <final-grid> in this case) but with the precision to handle properties with a child VM (on <final-el>). In my experience, the child VM handles element content but element properties both aren’t set and aren’t found by the child VM. That was the issue I was attempting to raise in Communication between “sibling” ViewModels.

I would love to be enlightened on any of this.:smile:


#6

@justinbmeyer, in response to your answer about using {{#each}}, I tried that and it works, but the ViewModel still operates on the parent tag level. E.g., the following sets the content of all elements at once to “Received a mouseover”. Is this what you had in mind?

Component.extend({
    tag: "my-app",
    view: `
{{#each colors}}
    <my-el style="font-family:{{fontname}}; background-color:{{name}}">{{fontname}},{{tagText}}:{{value}}<br></my-el>
{{/each}}`,
    ViewModel: {
        colors: {
            default: function () {
                return [
                    {name: "white", value: "#fff"},
                    {name: "red", value: "#f00"},
                    {name: "green", value: "#0f0"},
                    {name: "blue", value: "#00f"},
                ];
            }
        },
        tagText: {default: "Here is my default text."}
    },
    events: {
        mouseover: function () {
            this.viewModel.tagText = "Received a mouseover.";
        },
    },
});

#8

As an exercise in comparison, I’ve created the same Color Chooser web app as the one referred to above, this time using Vue (code at https://github.com/CODE-REaD/VueColorChooser/blob/master/index.html and working site at http://vue-colors.code-read.com/; code for CanJS version at https://github.com/CODE-REaD/CanJS-ColorChooser/blob/master/index.html and working site at http://colors.code-read.com)

I’m adding this post in hopes of gleaning your thoughts on this comparison.

I initially tried to port the code as intact as possible from CanJS to Vue. As you can see, I was not successful. I believe the two frameworks each require their own ideom. My initial observations:

  • The Vue Chooser’s code is about 10% shorter (excluding identical CSS sections). Should it be?;

  • The Vue Chooser embeds way more logic in <body> and in HTML generally than the CanJS one;

  • The CanJS Chooser relies much more heavily on custom elements than the Vue Chooser (should it?).

Questions:

  • Did I approach the CanJS version’s design properly / ideomatically?

  • Is CanJS overkill / Vue more appropriate for this type of limited app?

  • Do you notice ways I might have condensed the CanJS code (while keeping it legible)?

Thanks for your interest.


#9

I guess the biggest difference is that you broke CanJS into multiple components. I think if the 10% difference has you worried, I would just do everything in one component.

Also, I remember asking this, but I don’t remember why the code is generating stache like this:

    let finalItems = "";
    for (let i = 0; i < finalCols; i++) {
        for (let j = 0; j < finalCols; j++) {
            finalItems += `<final-el id="${i}.${j}" `
                + `class="{{isSelected(${i}.${j})}}" `
                + `style="color: {{cellFgColor(${i},${j})}}; background-color: rgb({{cellBgColor('Red',${i},${j})}},{{cellBgColor('Grn',${i},${j})}},{{cellBgColor('Blu',${i},${j})}})">`
                + `R:{{cellBgColor('Red',${i},${j})}}<br>G:{{cellBgColor('Grn',${i},${j})}}<br>B:{{cellBgColor('Blu',${i},${j})}}</final-el>`;
        }
    }

One final thing, why are you adding template literals like:

`<span style="text-align: right; padding: 6px"><b>Final<br>Color:</b></span>`
        + `<span style="color: {{textColor}}; padding: 12px; border: 3px solid gray; background-color: `
        + `rgb({{readoutRed}}, {{readoutGrn}}, {{readoutBlu}}); ">`
        + `R:{{readoutRed}}<br>G:{{readoutGrn}}<br>B:{{readoutBlu}}</span>`
        + `<span style="padding: 12px">{{#if(clipCopied)}}<b>{{clipCopied}}</b> copied to clipboard.{{/if}}</span>`
        + `<span></span>`
        + `<button on:click="copyToClip('{{readoutRGB}}')">Copy <b>{{readoutRGB}}</b> to clipboard</button>`
        + `<button on:click="copyToClip('{{readoutHex}}')">Copy <b>{{readoutHex}}</b> to clipboard</button>`,

Instead of just doing:

`<span style="text-align: right; padding: 6px"><b>Final<br>Color:</b></span>
 <span style="color: {{textColor}}; padding: 12px; border: 3px solid gray; background-color: 
 ... `

Template literals span multiple lines.


#10

If you could record a 5 min video of you showing off the functionality, I’ll create a demo.


#11

Oh

DAMN!!!

Carson … How dare you bring in that weak as VUE shit. Now you know it

is on!!!

Look what you made me do! I built a prototype of your color picker in 125 lines of code:

:grinning:

The prototype is fairly close in functionality to what you built. I didn’t make it perfect (no copy to clipboard). But hopefully you can see how it’s made.

Want to present this at a DoneJS Chicago?


#12

Thank you, @justinbmeyer. To answer your question, I would be honored to present this Color Picker at DoneJS Chicago.

As you can tell from prior messages, I’ve been struggling with CanJS view inheritance. My concern was to avoid iterating through the entire <ul> every time a <li> event occurred. I kept trying to manage click and hover events at the <li> level, but it seems a CanJS-coded event handler only operates at its view level.

Perhaps I have stumbled upon the purpose of the virtual DOM: my code need not manage DOM event listeners but rather should rely on CanJS to intelligently update them. Am I on the right track?


Another issue: each element in your <ul class=final-colors> appears in Chrome DevTools’ Elements panel as:

<li on:click="selectFinalColor(color)"...>

yet is sending your selectFinalColor(color) method color values specific to that <li>.

How this works is far from obvious. In pure JavaScript it simply fails and a function called via onclick generates an Uncaught ReferenceError: color is not defined. I’m assuming this is because parameters to onclick must be values, not references.

Adding a debugger statement to your selectFinalColor(color) method causes DevTools to reveal in its Sources panel that in your Color Picker, color is an object enclosing a property for each of the <li>'s three colors.

I didn’t expect that and am beginning to understand how much CanJS does “behind the scenes.” Apparently once we create an object (color here), it can be passed back and forth between views and methods, and even revised “declaratively” by changing one of its dependencies (which you’ve stacked up here as this.baseColor || this.suggestedBaseColor || this.lastBaseColor) and CanJS synchronizes all side effects for me.

If the above is correct, I’m impressed by the power of it, but also concerned: how do I predict what CanJS will and will not manage “behind the scenes?” How is this object-passing capability described formally?

Example taken. Pointers appreciated.


Addendum:
Dear @justinbmeyer, I cannot help but notice a performance problem with your example. When dragging my mouse back and forth over the top row of colors (<ul class ='base-colors'), updates to the final-colors grid are notably slow. Chrome DevTools attributes this jank to Scripting:
image
There are also complaints in Chrome’s console:
19:47:58.043 core.js:18161 [Violation] 'mouseenter' handler took 237ms
19:47:58.307 core.js:18161 [Violation] 'mouseenter' handler took 226ms
...

The same test with http://vue-colors.code-read.com/ shows no such jank or console “Violations” and its performance report looks like:
image

Even my CanJS example runs much more smoothly.

Frankly, I’m stumped as to what is causing this. I found using //unpkg.com/can/core.min.mjs increased performance of your code significantly but still not on par with the other examples above. This is not what I expected. Why does my amateur code (and even my evil Vue script) outperform your far more elegant example?


Why is my Color Chooser's frame rate with Vue so much higher than with CanJS?