3
votes

I have a button-icon component that, when hovered over, will send a hoverIn and hoverOut action (example code so please forgive any errors):

app/components/button-icon/component.js:

export default Ember.Component.extend({
  didInsertElement: function() {
    // hover in
    this.$().hover(function() {
      this.sendAction('hoverIn');
    ,
    // hover out
    function() {
      this.sendAction('hoverOut');
    }
  },

  // more code here to handle other stuff
});

app/components/button-icon/template.hbs:

<button>{{yield}}</button>

I also have a button-tooltip component that should be displayed when the button is hovered over:

app/components/button-tooltip/component.js:

export default Ember.Component.extend({
    // some code here to handle positioning
});

app/components/button-tooltip/template.hbs:

<div>{{yield}}</div>

In my template, I'd like to use both like so:

app/index/template.hbs:

{{#button-icon}}
  <div>Cat</div>
  {{#button-tooltip}}<img src="cat.png">{{/button-tooltip}}
{{/button-icon}}

{{#button-icon}}
  <div>Dog</div>
  {{#button-tooltip}}<img src="dog.png">{{/button-tooltip}}
{{/button-icon}}

{{#button-icon}}
  <div>Rat</div>
  {{#button-tooltip}}Nobody wants to see a picture of a rat!{{/button-tooltip}}
{{/button-icon}}

How do I get the button-icon to communicate with the button-tooltip so that the tooltip is shown when the icon is hovered over? If I only had one button, I can bind both the button and tooltip to a property on the controller, but I have a variable list of them to display. I can also wrap both components in another component (called button-with-tooltip or something), but this seems like it's starting to get into component-ception.

2

2 Answers

3
votes

It's pretty complicated to set such kind of interactions when you have that templates. Your components are fully independent, but they should have a parent-child connection, since they are actually the parent and a child.

A more suitable approach for me is below.

app/index/template.hbs

{{#buttons-list buttons=property_with_a_correct_list_from_the_index_controller}}

app/components/button-list/template.hbs

{{#each button in buttons}}
  {{#button-icon data=button}}
{{/each}}

app/components/button-icon/template.hbs

<div>{{data.title}}</div>
{{#button-tooltip data=data.tooltip}}

app/components/button-icon/component.js:

export default Ember.Component.extend({
  tagName: 'button',

  ...

});

app/components/button-tooltip/template.hbs:

{{#if isPicture}}
  <img src={{data.image}} />
{{else}}
  {{data.text}}
{{/if}}

app/components/button-tooltip/component.js:

export default Ember.Component.extend({
  tagName: 'div'
  isPicture: function() {
    this.get('data.type') == 'image'
  }.property('data.type')
});

And the property_with_a_correct_list_from_the_index_controller should look like this

[
  {title: 'Cat', tooltip: {isPicture: true, image: 'cat.png'}},
  {title: 'Dog', tooltip: {isPicture: true, image: 'dog.png'}},
  {title: 'Rat', tooltip: {isPicture: false, text: 'Nobody wants to see a picture of a rat!'}}
]

Let's review it:

  • I moved edged tags in the components to the controllers in a tagName property
  • A content of a tooltip now directed by the tooltip itself, not by the parent icon component
  • Now a tooltip and an icon have clear relationship, so the icon finally has any reasonable way to control it

For now let's determine your high-level requirement for the sample:

  • User should see a tooltip when hovers an icon.
  • User shouldn't see a tooltip when leaved an icon.

As you can see the icon component totally controls the tooltip visibility. In this case I'd consider an approach with conditional template. For example:

app/components/button-icon/component.js:

export default Ember.Component.extend({
  tagName: 'button',
  showTooltip: false,

  didInsertElement: function() {
    self = this // this will be redefined inside of hover function
    // hover in
    this.$().hover(function() {
      self.set('showTooltip', true);
    ,
    // hover out
    function() {
      self.set('showTooltip', false);
    }
  },

  // more code here to handle other stuff
});

app/components/button-icon/template.hbs

<div>{{data.title}}</div>
{{#if showTooltip}}
  {{#button-tooltip data=data.tooltip}}
{{/if}}

As you can see, your sample is rewritten in a more clear way, where you can see a approach to control tooltip visibility. I'd recommend to use such approach only in case when you need to control the tooltip visibility.

Back to

more code here to handle other stuff

For this I would recommend to not discard your approach with binding of a property. Just create a property with a required state in the button-icon component and make a corresponding computed property in the button-tooltip component, which will be listening for changes in the property.

Example:

app/components/button-icon/component.js:

export default Ember.Component.extend({
  tagName: 'button',
  tooltipProps: [],

  didInsertElement: function() {
    self = this // this will be redefined inside of hover function
    // hover in
    this.$().hover(function() { 
      self.get('tooltipProps').addObject('showIt');
    , function() { // hover out
      self.get('tooltipProps').removeObject('showIt');
    });
    this.$().on('click', function() {
      self.get('tooltipProps').addObject('makeitRed');
    });
  },

  // more code here to handle other stuff
});

app/components/button-icon/template.hbs

<div>{{data.title}}</div>
{{#button-tooltip data=data.tooltip props=tooltipProps}

app/components/button-tooltip/component.js:

export default Ember.Component.extend({
  tagName: 'div'
  isPicture: function() {
    this.get('data.type') == 'image'
  }.property('data.type'),

  onPropsChange: function() {
    if (this.get('props.makeItRed')) {
      // make it actually red directly by the own component code
    }
  }.observe('props') // an observer because you need to do actions here, if you want to change the internal state use a computed property
});

P.S. But the first approach I like more %)

0
votes

Once you are using a version of ember that supports yielding values in a component, I think this is 1.10.0, you can do the following, which is very intuitive.

{{#button-icon as |active|}}
  <div>Cat</div>

  {{#button-tooltip active=active}}
    <img src="cat.png">
  {{/button-tooltip}}
{{/button-icon}}

And in your button-icon component instead of sending an action up, you would set a value e.g. isActive, and send that down.

<button>{{yield isActive}}</button>

The idea is actions up, data down.