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.";
        },
    },
});