reusable html object with viewof?

I’ve read a bit about views and viewof, but I am not entirely getting how to create a reusable view that doesn’t get dragged across the DOM when called.

In the notebook linked below, I try to define some color swatches as ‘viewof’ elements with the intention of creating a visual/color score. Yet when I try to call further views of that swatch and return them based on a numerical value, I can’t help that little swatch from getting moved around the DOM when another score is later updated to call a swatch in the same range [see what happens when both sliders are set to the same value]:

I don’t really know enough to go about this sort of a scoring tool in a more elegant way, but was hoping this hacky attempt would at least get me there.

Any guidance for how to create an element that can be multiplied?

The idea with viewof is that there’s both a view and a value. Your swatches here aren’t really views — they’re just values (that happen to be DOM elements). The typical use case of views is for values that are controlled interactively by the user, such as text inputs and sliders, where the view is an input element and the value is a string or a number. As a rule of thumb, the view is what the user sees whereas the value is what the code sees. If those things are the same (i.e., they’re both DOM elements), then you don’t need viewof.

So, the first change I’d make is to remove the unneeded viewofs from the swatch declarations. I.e., change this:

viewof score_1 = swatch("rgb(0, 0, 255)")

to this

score_1 = swatch("rgb(0, 0, 255)")

The second problem is that a DOM element can’t exist in more than one place in the DOM at a time. You’re currently referencing the swatch elements in four places: when they are declared (score1), in the value1 cell, in the value2 cells, and lastly in the htmlview cell (because the latter references value1 and value2, which are already shown in the DOM).

Probably the cleanest way to fix this would be to reference a value that’s not a DOM element. I.e., change this:

score_1 = swatch("rgb(0, 0, 255)")

to this

score_1 = "rgb(0, 0, 255)"

And then correspondingly update the htmlview definition to create the swatches inline:

htmlview = html`
Result 1: ${swatch(value1)} <br />
Result 2: ${swatch(value2)}
`

Side note, if you want your slider to only have a fixed number of options, you could also consider defining your scores as an array:

scores = [
  "rgb(0, 0, 255)",
  "rgb(137, 0, 225)",
  "rgb(224, 0, 149)",
  "rgb(246, 111, 187)",
  "rgb(255, 188, 203)"
]

And then changing your slider definition:

viewof value1_calc = slider({
  min: 0, 
  max: scores.length - 1, 
  step: 1, 
  value: 0, 
})

And then simplifying your value definition:

value1 = scores[value1_calc]

In fact, you can combine this into your slider definition using the getValue option:

viewof value1 = slider({
  min: 0, 
  max: scores.length - 1, 
  step: 1, 
  value: 0, 
  getValue: i => scores[i.valueAsNumber]
})
3 Likes

Wow Mike, thank you! This is a very clear and useful explanation–particularly with respect to the different ways of conceptualizing view vs. value.

For my purposes, I’ll need to retain the if/else comparison function (after having corrected it from the draft you saw) so that I can route results within a range to a specific swatch. Yet it’s really helpful that you took this explanation the extra mile! The way that getValue and valueAsNumber combine to interpret and re-present the score RGB values (inputs as a string rgb(0, 0, 255)) is still a bit bewildering to me and I am having fun working through it all. Thank you for your time and for this amazing platform! While I am still just beginning my journey already I’ve learned volumes thanks to you and this community. <3