7

I'm trying to use Bootstrap Popover with EmberJS, so that the content of the popover will be a ember/handlebars template (with binding etc). How can this be done? (Ember 1.0.0-rc2)

madth3
  • 7,275
  • 12
  • 50
  • 74
Ori Shavit
  • 255
  • 2
  • 5
  • 8

5 Answers5

12

Here is a working example for an ember bootstrap popover (see http://jsfiddle.net/72fSd/):

App.Popover = Ember.View.extend({
        parentSelector: '',
        contentSelector: '',
        didInsertElement: function () {
            var self = this;
            $(self.parentSelector).popover({
                html: true,
                content: function() {
                    var $content = $(self.contentSelector);
                    return $content.html();
                }
            });
        }

Instantiate the view:

{{view App.Popover templateName="my-popover-content" parentSelector=".popoverButton" contentSelector="#popovercontent"}}

Here, parentSelector might e.g. select a button. Make sure you have a div container with id #popovercontent in your my-popover-content template in order for the contentSelector to work. Of course you need to load the template prior to initialization of the view.

Two-way binding should work with that solution.

Robert
  • 1,710
  • 2
  • 18
  • 35
  • can you put this in a jsfiddle? – Ben Apr 22 '13 at 16:26
  • 1
    perfect. winner winner chicken dinner. – Ben Apr 23 '13 at 23:54
  • 1
    Recently, I ran into issues with that solution: Returning $content.html() does not always work. Especially if your content gets more complex, you should consider to use http://api.jquery.com/remove/ instead, which would really take the DOM element and would move it into the popover. Unfortunately I am not able to adapt the fiddle accordingly. HTH – Robert Apr 29 '13 at 08:07
  • 1
    Not sure why you need `parentSelector` and `contentSelector`. The simplest solution is as described in http://cowbell-labs.com/2013-10-20-using-twitter-bootstrap-js-widgets-with-ember.html – morgoth Oct 20 '13 at 17:12
  • Grabbing the HTML content of an undisplayed tag doesn't look like best practice but it's simple and it works for me. The other options I have seen to have a templated popover content involved literally rewriting the whole Bootstrap code into an Ember component, which can be a pain. – cwarny Sep 01 '14 at 16:29
  • The JSFiddle in the answer "may" have an issue. Inspecting the code, I see that the input tag in the template has the same ID that ends up the input tag of the actual popover content. Having the same ID multiple times in a page is not valid. Is there a way to avoid this? – Terry Roe Oct 09 '14 at 14:28
7

I took Terry's answer a bit further and think I've come up with a simple, general solution to this problem.

I created a bootstrap-popover component like so:

App.BootstrapPopoverComponent = Ember.Component.extend({
  tagName: 'div',      //whatever default you want... div is default anyway here
  classNames: '',      //whatever default you want
  placement: 'bottom', //whatever default you want
  didInsertElement: function () {
    var component = this,
        contents = this.$('.popoverJs');
    component.$().popover({
      animation: false,
      placement: component.get('placement'),
      html: true,
      content: contents
    }).on('show.bs.popover', function () {
      contents.removeClass('hide');
    });
  },
  willDestroyElement: function () {
    this.$().popover('destroy');
  }
});

Here is the associated template:

<script type="text/x-handlebars" id="components/bootstrap-popover">
  {{title}}
  <div class="popoverJs hide">
    {{yield}}
  </div>
</script>

Note the use of the "hide" class to hide the yielded contents initially. This class is simply "display: none". Without this, things won't work quite how you'd hope.

Once you have that, you can do simply do something like this whenever you want a popover:

  {{#bootstrap-popover title="My Fancy Popover" tagName="button"}}
    <ul>
      <li>my</li>
      <li>awesome</li>
      <li>popover</li>
      <li>contents</li>
      <li>example</li>
    </ul>
  {{/bootstrap-popover}}

The contents should be able to be whatever you want -- any arbitrary HTML, rendering a component or partial, etc.. Naturally, you can specify other tagNames, classNames, title, placement, etc. as you see fit.

I hope this solution helps.

eeggers
  • 71
  • 1
  • 1
  • 1
    This is really slick. Thanks! While it is certainly more complex than the accepted answer, I think its reusability makes this the best. – xcskier56 Mar 12 '15 at 00:16
  • There's a problem with this approach, under certain not common circumstances: if the template rendering the popover contents has elements with `{{action ...}}` helpers applied, these are lost because the what the component is doing is cloning the popover html from the elements rendered by the template, into new elements that will be actually rendered. The action handlers are lost in that process. I'm still trying to figure out how to overcome this. However, nothing wrong with the answer per-se. I know my use case is very uncommon. – Ernesto Aug 29 '16 at 19:54
  • this works perfect ! one question ! how to close opened popovers expect clicked one? – Thilina Dinith Fonseka Jul 17 '19 at 07:09
2

ex if you want to popup a image do something like this in you view

imgTag: '<img src="smiley.gif" alt="Smiley face" height="42" width="42">',

didInsertElement: function () {
    var self = this;
    Ember.run.schedule('actions', this, function () {
        self.$().popover({
            title: 'Smile!!!',
            html: true,
            content: self.get('imgTag'),
            placement: 'bottom',
            trigger: 'hover'
        });
    });
},

willDestroyElement: function () {
    this.$().popover('destroy');
}
pjlammertyn
  • 989
  • 6
  • 10
  • This lets me use static HTML in the content, how can render a template in it? – Ori Shavit Apr 15 '13 at 14:11
  • content: Ember.Handlebars.compile("my handlebar {{handlebarProperty}}") – pjlammertyn Apr 16 '13 at 06:47
  • 1
    or content: '
    ' and then in code create your view: var popupContentView = APP.PopUpContentView.create({ controller: popupContentController }); and append it to your content div popupContentView.appendTo($('#myPopupContentId'));
    – pjlammertyn Apr 16 '13 at 06:57
2

I ran into this problem as well and had the same problem Robert mentioned above where the acceptable solution simply doesn't scale well to more complicated scenarios.

I ran into a very elegant fix, but I'm not sure how future-friendly it is. I'm taking advantage of the function renderToBuffer - see below:

//make your popover view to be created later
App.PopoverView = Ember.View.extend({
  templateName : 'name-of-your-template-with-content'
});

//then you make your link that will trigger the popover
App.PopoverLinkView = Ember.View.extend({

  tagName : 'a',

  didInsertElement : function(){

  var that = this;

  this.$().popover({
    'html' : true,
    'content' : function(el){
    var detailView = App.PopoverView.create();
    var html = detailView.renderToBuffer().buffer;
    return html;
    }
  });

  }

});

The advantage here is that you can pass in a model and make things dynamic. Haven't tested it thoroughly, but wanted to get this out there to potentially help others.

gcoladarci
  • 889
  • 9
  • 13
1

I took Robert's answer above a bit further. I created a Component and also just use the jQuery element for the content instead of calling .html(). (This alleviates the problem of having duplicated IDs in the page.)

App.CustomPopoverComponent = Ember.Component.extend({
  tagName: 'button',
  classNames: 'btn btn-default',
  type: 'button',
  popoverContentSelector: '',
  didInsertElement: function () {
    var component = this,
        contents = $(component.get('popoverContentSelector'));

    component.$().popover({
      placement: 'bottom',
      html: true,
      content: contents
    }).on('show.bs.popover', function () {
      contents.removeClass('hide');
    });
  },
  willDestroyElement: function () {
    this.$().popover('destroy');
  }
});

I used Bootstrap's 'hide' class to hide the contents initially. Then I removed the 'hide' class the first time to the popover is shown. From then on things work as expected.

This is how to use the component in your handlebars template:

  {{#custom-popover popoverContentSelector='.popoverContents'}}
    Popover Button
  {{/custom-popover}}
Terry Roe
  • 1,134
  • 10
  • 15