3

I am following the structure to implement tool tip from Sean Hunter Blog . Now i want provide tool-tip content as a dynamic html content i.e I want to show one html pattern inside content. How can I provide using Aurelia framework. In knockout JS using custom binding handler I am providing content as with id of division like below code.

Knockout Structure is

 <button data-bind="tooltip: { template: 'ElementId', trigger: 'click', placement: 'right', container: 'body' }">Click Me</button>

    <div id="ElementId" style="display: none;">
        <div>Dynamic content will go here</div>
    </div>

How to achieve same with Aurelia Structure:

<template>
<require from="templates/popover/tooltip"></require>
<button data-toggle="tooltip" tooltip="placement:bottom;trigger:click;html:true;template:'ElementId';title:tooltip Header">Click Me</button>

<div id="ElementId" style="display: none;">
            <div>Dynamic content will go here</div>
        </div>

</template>

Custom Attribute code

import {customAttribute, inject, bindable} from 'aurelia-framework';
import $ from 'bootstrap';

@customAttribute('tooltip')
@inject(Element)
export class Tooltip {
    element: HTMLElement;
    @bindable title: any;
    @bindable placement: any;
    @bindable content: any;
    @bindable template: any;

    constructor(element) {
        this.element = element;
    }

    bind() {
        if (this.content) {
            $(this.element).tooltip({ title: this.title, placement: this.placement, content: this.content });
        }
        else {
            $(this.element).tooltip({ title: this.title, placement: this.placement, template: $('#'+this.template).html() });
        }


    }

    // gets fired when the provided value changes, although not needed in this example since the json from reddit is static
    titleChanged(newValue) {
        $(this.element).data('bs.tooltip').options.title = newValue;
    }
    contentChanged(newValue) {
        if (this.content) {
            $(this.element).data('bs.tooltip').options.content = newValue;
        }
        else {
            $(this.element).data('bs.tooltip').options.template = newValue;
        }
    }
    placementChanged(newValue) {
        $(this.element).data('bs.tooltip').options.placement = newValue;
    }
}
Rayudu
  • 1,806
  • 1
  • 14
  • 31

2 Answers2

7

You would need to implement the rest of bootstrap's popover API in your custom attribute, and add some logic to turn a selector into a template.

Here's an example: https://gist.run?id=909c7aa984477a465510abe2fd25c8a1

Note: i've added the default values from bootstrap popovers for clarity

With a custom attribute:

src/app.html

<template>
  <h1>${message}</h1>

 <button class="btn btn-block btn-default" popover="title.bind: message; placement: top">Default popover</button>
 <button class="btn btn-block btn-default" popover="title.bind: message; template-selector: #popoverTemplate; placement: bottom; html: true">Custom popover</button>

 <div id="popoverTemplate">
   <div class="popover" role="tooltip">
     <div class="arrow"></div>
     <h3 class="popover-title"></h3>
     <div>Some custom html</div>
   </div>
 </div>
</template>

src/app.ts

export class App {
  message = "Hello world";
}

src/popover.ts

import {inject, customAttribute, bindable, DOM} from "aurelia-framework";

@customAttribute("popover")
@inject(DOM.Element)
export class Popover {
  public element: HTMLElement;

  constructor(element) {
    this.element = element;
  }

  @bindable({defaultValue: true})
  public animation: boolean;

  @bindable({defaultValue: false})
  public container: (string | false);

  @bindable({defaultValue: 0})
  public delay: (number | object);

  @bindable({defaultValue: false})
  public html: boolean;

  @bindable({defaultValue: "right"})
  public placement: (string | Function);

  @bindable({defaultValue: false})
  public selector: (string | false);

  @bindable({defaultValue: `<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>`})
  public template: string;

  @bindable({defaultValue: false})
  public templateSelector: (string | false);

  @bindable({defaultValue: ""})
  public title: (string | Function);

  @bindable({defaultValue: "click"})
  public trigger: string;

  @bindable({defaultValue: { selector: "body", padding: 0 }})
  public viewport: (string | Object | Function);

  public attached(): void {
    let template;

    if (this.templateSelector) {
      const templateElement = document.querySelector(this.templateSelector);
      template = templateElement.innerHTML;
    } else {
      template = this.template;
    }

    $(this.element).popover({
      animation: this.animation,
      container: this.container,
      delay: this.delay,
      html: this.html,
      placement: this.placement,
      selector: this.selector,
      template: template,
      title: this.title,
      trigger: this.trigger,
      viewport: this.viewport
    });
  }
}

With a custom element:

This is in response to @Ashley Grant's comment. It could improve clarity if you used a custom element for this. I'm not sure of the implementation he had in mind, but this would be one way to make it work without really losing flexibility.

src/app.html

<template>
  <h1>${message}</h1>

 <popover-element title.bind="message" placement="bottom">
 </popover-element>
 <popover-element title.bind="message" placement="bottom">
   <button slot="popoverTarget" class="btn btn-block btn-default">
     Custom popover (custom element)
   </button>
   <div slot="popoverTemplate" class="popover" role="tooltip">
     <div class="arrow"></div>
     <h3 class="popover-title"></h3>
     <div>Some custom html</div>
     <div>Message: ${message}</div>
   </div>
 </popover-element>

</template>

src/app.ts

export class App {
  message = "Hello world";
}

src/popover-element.html

<template>
  <div ref="target">
    <slot name="popoverTarget">
      <button class="btn btn-block btn-default">Default popover (custom element)</button>
    </slot>
  </div>
  <div ref="template">
    <slot name="popoverTemplate">
      <div class="popover" role="tooltip">
        <div class="arrow"></div>
        <h3 class="popover-title"></h3>
        <div class="popover-content"></div>
      </div>
    </slot>
  </div>
</template>

src/popover-element.ts

import {customElement, bindable} from "aurelia-framework";

@customElement("popover-element")
export class PopoverElement {
  public template: HTMLElement;
  public target: HTMLElement;

  @bindable({defaultValue: true})
  public animation: boolean;

  @bindable({defaultValue: false})
  public container: (string | false);

  @bindable({defaultValue: 0})
  public delay: (number | object);

  @bindable({defaultValue: false})
  public html: boolean;

  @bindable({defaultValue: "right"})
  public placement: (string | Function);

  @bindable({defaultValue: false})
  public selector: (string | false);

  @bindable({defaultValue: ""})
  public title: (string | Function);

  @bindable({defaultValue: "click"})
  public trigger: string;

  @bindable({defaultValue: { selector: "body", padding: 0 }})
  public viewport: (string | Object | Function);

  public attached(): void {

    $(this.target.firstElementChild).popover({
      animation: this.animation,
      container: this.container,
      delay: this.delay,
      html: this.html,
      placement: this.placement,
      selector: this.selector,
      template: this.template.firstElementChild.outerHTML,
      title: this.title,
      trigger: this.trigger,
      viewport: this.viewport
    });
  }
}
Fred Kleuver
  • 7,797
  • 2
  • 27
  • 38
  • Great answer, but honestly, this would probably work better as a custom element rather than an attribute, don't you think? – Ashley Grant Oct 10 '16 at 19:11
  • You may have a point, but define "better". I think it depends on the bigger picture of a project. From a semantic point of view a custom element might be easier to read/use. It also provides more encapsulation, which - if it's the **right** encapsulation - improves maintainability. I usually prefer integrating 3rd party components through custom attributes because I feel it's less constraining in terms of markup, and makes it easier to stack multiple behaviors on top of each other. – Fred Kleuver Oct 10 '16 at 19:32
  • @AshleyGrant see my latest edit, is this the kind of thing you had in mind for a custom element? – Fred Kleuver Oct 10 '16 at 20:01
  • @FredKleuver: Great answer awesome. Thank u. – Rayudu Oct 11 '16 at 06:53
  • @FredKleuver Hi Any idea on this question http://stackoverflow.com/questions/40037373/aurelia-popover-checkbox-checked-bind-not-reflecting-on-the-view-model. Because am pacing same problem – Rayudu Oct 14 '16 at 09:29
  • @Rayudu I answered it – Fred Kleuver Oct 14 '16 at 17:54
0

You can remove the '.outerHTML' from this line template: this.template.firstElementChild.outerHTML, as template: this.template.firstElementChild, in order to get the model binding.