How to work with jquery (in repeat.for)

I’m trying to use some global jquery-libraries e.g. bootstrap-datepicker and facing the problem, that especially in repeat.for blocks, the jquery gets not executed.
I guess this is because of the dynamic rendering of html blocks.

But is there a clever way to bypass that?

An example:

<div repeat.for="item of items">
    <input type="text" name="date" class="datepicker" />
</div>
export class testView{
    attached(){
        $('input[name="date"]').datepicker();
    }
}

Sometimes, the datepicker gets attached, and sometimes (after reloading the page) it gets not attached.

Wrap the input in a custom element, this wraps the attached / detached per instance of repeated item.

import { bindable, bindingMode, inlineView, containerless } from 'aurelia-framework';
import $ from 'jquery';

@inlineView(`
   <template>
      <input ref="element" type="text" name.bind="name" value.bind="value" />
   </template>
`)
@containerless()
export class DatePicker {

    @bindable() name = '';
    @bindable({ defaultBindingMode: bindingMode.twoWay }) value = '';

    attached() {
        $(this.element).datepicker();
    }

    detached() {
        $(this.element).datepicker('destroy');
    }

}

Edited because I forgot <template> in the inlineView

2 Likes

Then you can use it like

<div repeat.for="item of items">
    <date-picker name.bind="item.name" 
                 value.bind="item.value"></date-picker>
</div>

Thank you for your support. This solution looks really good.

Currently I solved that issue using a custom-attribute:

import { DOM, customAttribute, inject } from 'aurelia-framework';
import 'bootstrap-datepicker';
import * as $ from 'jquery';

@customAttribute('datepicker')
@inject(DOM.Element)
export class DatepickerCustomAttribute {
  private value: Date;

  constructor(private element: Element) {
  }

  public attached() {
    var options = {
      format: 'yyyy.mm',
      todayHighlight: true,
      autoclose: true,
      language: "de"
    };

    $(this.element)
      .datepicker(options)
      .on('changeDate', evt => {
        this.value = evt.date;
      });
  }

  public detached() {
    $(this.element).datepicker('destroy');
  }
}

But looking to your solution I’m not sure which is the better way. Do you have any experiences?

I’m a little more partial to custom elements for jquery custom wrappers because it just looks cleaner to me and the ease of access to bindables.

I’ve also had issues with @bindable() in attributes when using @dynamicoptions in the past, for me it didn’t trigger property changed or bindable changed if the value passed in was set inside the consuming component’s constructor, but the value was updated in the attribute.

That may or may not be fixed now but I don’t know.

As an example, it was something like this:

<template>
   <div some-custom-attribute="some.bind: myValue;"></div>
</template>
export class SomeViewModel {
   constructor() {
      this.myValue = 200;
   }
}

Could you please give me an example how to implement that solution with allowing the addition of further attributes like “click.delegate” or “change.delegate”?

Currently you only have “name.bind=“name” value.bind=“value”” in your template-html.

How to use the examples, keep in mind the called functions can be named whatever you want the change.call is the important part it could also be done with change.bind though you would get a single object param rather than your params spread out, with the element code below that.

Consume

<date-picker change.call="change(event, value)"
             keyup.call="keyup(event, value)"
             keydown.call="keydown(event, value)"
             value-change.call="valueChange(value)"
             select-change.call="selectChange(event, value)"></date-picker
export class SomePage  {

    change(event, value) { ... }

    keyup(event, value) { ... }

    keydown(event, value) { ... }

    valueChange(value) { ... }

    selectChange(event, value) { ... }

}

So here are 5 options of how you could pursue change checking, I would toss a console log in each and see if it gives you what you want and triggers when you would expect.

Component

import { bindable, bindingMode, inlineView, containerless } from 'aurelia-framework';
import $ from 'jquery';

@inlineView(`
   <template>
      <input ref="element" 
             type="text" 
             name.bind="name" 
             value.bind="value"
             change.delegate="proxy('change', $event)"
             keyup.delegate="proxy('keyup', $event)"
             keydown.delegate="proxy('keydown', $event)" />
   </template>
`)
@containerless()
export class DatePicker {

    @bindable() change = () => {}; // aurelia's built in for <input> value change
    @bindable() keyup = () => {}; // on keyup so value has been changed
    @bindable() keydown = () => {}; // on keydown so value has not been changed yet
    @bindable() valueChange = () => {}; // triggers when this.value is modified before this.value is set with new value
    @bindable() selectChange = () => {}; // jquery.datepicker onSelect event is triggered
    @bindable() name = '';
    @bindable({ defaultBindingMode: bindingMode.twoWay }) value = '';

    constructor() {
        var vm = this;
        this.options = {
            onSelect: function(event) {
                if(vm.selectChange)
                    vm.selectChange({ event: event, value: this.value });
            }
        };
    }

    proxy(eventName, event) {
        if(this[eventName])
            this[eventName]({ event: event, value: this.value });
    }

    valueChange(value) {
        if(this.valueChange)
            this.valueChange({ value: value });
    }

    attached() {
        $(this.element).datepicker(this.options);
    }

    detached() {
        $(this.element).datepicker('destroy');
    }

}

@SNO, your code will only work if items has been populated before the attached is called, but if you’re loading them in a Promise they may not exist before attach is triggered, so JQuery won’t have any elements yet to bind to.
@Malexion’s solution ensures the jquery attachment is made only when the element is added. You could use a custom attribute instead to avoid the new element, but the key thing is that the call to .datepicker() is on each inner loop element, meaning the DOM element must be in existence.