Stimulus: New Targets creation

Created on 5 Jan 2018  路  13Comments  路  Source: hotwired/stimulus

This time I will try to act smarter and ask before write code 馃槀

Sometimes I need to create a DOM elements, that Stimulus controller should controller after.
For example in this interface, when I create a new item, I need a way to add it to DOM.
vr9svsvjo7

Right now I need to write this

    // create tag element
    const item = document.createElement('div')
    item.setAttribute('data-title', 'aa')
    item.setAttribute('data-id', 123)
    item.setAttribute('data-target', 'categories.selected_item')
    item.setAttribute('data-action', 'click->categories#removeItem')
    item.setAttribute('class', 'categories--selected-item')
    item.innerHTML = 'aa'

    this.targets.find('selected_items').appendChild(item)

Can we present some sort of function to simply create new dom elements inside. Like:

// element can be a 'div' by default
const target = this.targets.create('selected_item', { 
  element: 'div', 
  data: {}, 
  htmlOptions: { class: 'categories--selected-item' }, 
  innerHTML: '' 
})
this.targets.find('selected_items').appendChild(target)

Probably we can just create a simple standard way to adding mix-ins to controllers.
This case we can keep things like that outside of Stimulus

Most helpful comment

You can access a controller's own identifier with this.identifier. So you can avoid hard coding and repeating it by doing:

item.setAttribute('data-target', `${this.identifier}.selected_item`)
item.setAttribute('data-action', `click->${this.identifier}#removeItem`)

Instead of:

 item.setAttribute('data-target', 'categories.selected_item')
 item.setAttribute('data-action', 'click->categories#removeItem')

All 13 comments

@mdesantis thank you! 馃帀

You can access a controller's own identifier with this.identifier. So you can avoid hard coding and repeating it by doing:

item.setAttribute('data-target', `${this.identifier}.selected_item`)
item.setAttribute('data-action', `click->${this.identifier}#removeItem`)

Instead of:

 item.setAttribute('data-target', 'categories.selected_item')
 item.setAttribute('data-action', 'click->categories#removeItem')

@javan what I actually did.
I created own Conrollerclass with wrapping original Stimulus one and added functions there.

Can we think of a simple way to do a mix-in? Like for example in Vue

@mdesantis one problem with templates, seems like they not supported in IE 馃挬
But a lot of our users still use last version of IE

Yes, but there are many working polyfills in the wild :-)

Anyway, I feel the needing of managing potentially existing HTML components, in a library like this one. Something that solves the requirement "How to display a new incoming message on my instant messaging app?"

Agree, this can be placed in examples and in the handbook. Seems like I closed this issue too early :)

@mdesantis idea with templates is really nice, this case you can keep HTML related things in Rails view, but we need to include polyfill in a framework (could increase library size a little).

Or we can create new elements and insert them like I showed, plus wrap it in nicer helper. But I see a downside here, that new element markup fully created in the .js file.

Or introduce data-template.
@javan what you think about all these?

Another alternative would be to provide facilities for cloning existing elements, so you can change any attribute you want before appending to the DOM. That way you don't have to deal with template strings (or rendering engine?!) which I'm not in favor of adding an unnecessary layer of complexity. I was thinking of data-bind-* attributes which allows you to expose an interface to your JS instead.

<ul class="tasks">
  <li data-controller="task_item" data-bind-text="task_item.name">Todo item</li>
</ul>

Or you could define this interface in your controller to avoid duplicated code if you're not using <template>:

interface: {
  name: '@text' // xpath-like expressions
}

Then somewhere in your JS you would do something like this:

this.clone().feed({ name: "New todo item" }).appendTo(this.element.parentNode)

Just my 2 cents.

+1 for <template> and pollyfills.

Remember:

Stimulus is a JavaScript framework with modest ambitions. It doesn't seek to take over your entire front-end鈥攊n fact, it's not concerned with rendering HTML at all.

We don't plan to add our own way to do rendering, and there are a number of great options for you to chose from. I'd start small with document.createElement() or element.cloneNode(), and then reach for a templating library if needed. I'm a fan of hyperHTML and good ol' EJS.

@savroff on top of using template, you can also combine the template functionality with lodash's template and create this:

javascript: https://github.com/games-directory/games.directory/blob/v2.0-beta1/app/javascript/packs/controllers/identity_verification_process_controller.js

view: https://github.com/games-directory/games.directory/blob/v2.0-beta1/app/views/users/form/verification/_search.erb

The only downside is, at least for me, that you have to use .erb.

TL;DR

You can create a simple HTML template enclosed by a script tag which gets ignored when viewing the page because of type='text/template'

<div id='responseBody' class='row align-top'></div>

<script type='text/template' id='responseBodyTemplate'>

  <div class='cell-sm-12 -margin-top_2'>

    {{ _.forEach(identities, (identity) => { }}

      <h3 class='-line-reset'>{{= identity.name }}</h3>
      <p class='-line-reset -margin-top_4'>
        About: 
        <span class='-weight-regular'>{{= identity.about }}</span>
      </p>

    {{ }) }}

  </div>  <!-- cell-sm-12 -->

</script>
// Find the Template
const container = document.getElementById('responseBodyTemplate');

// Tell lodash to grab the contents of the Template
const template  = _.template(container.innerHTML);

// Magic happens
// 'parse' the template and pass the required variables
document.getElementById('responseBody').innerHTML = template({
  identities: [{ name: 'Foo', about: 'Bar' }]
});

@pacMakaveli thanks!
I personally like keep markup outside from js files. 馃槏 We similar approach to this one and keep HTML in erb sounds really reasanoble in my case.

Just for posterity I have found two libraries that does exactly what I was looking for: bind.js and simulacra. One of the authors published an article explaining how it works: Tiny two way data binding. It would be very simple to integrate with a stimulus controller and template tags.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jenkijo picture jenkijo  路  3Comments

gerdriesselmann picture gerdriesselmann  路  3Comments

paulozoom picture paulozoom  路  4Comments

jaredcwhite picture jaredcwhite  路  3Comments

ngan picture ngan  路  3Comments