totoro88
totoro88

Reputation: 11

Using jQuery autocomplete or Twitter Typeahead with Aurelia

I’m trying to add an input filed with jQuery ui autocomplete or Twitter Typeahead. I can’t make either work. I get “$(...).typeahead is not a function” or “$(...).autocomplete is not a function” error.

I also tried aurelia-widget from https://github.com/drivesoftware/aurelia-widgets, but I also get “$(...).autocomplete is not a function” error.

I would appreciate if someone could tell me what I am doing wrong.

locate.js

import {customElement, bindable} from 'aurelia-framework';
import $ from 'jquery';
import { autocomplete }  from 'jquery-ui';

@customElement('locate')
export class Locate {
  @bindable data;  
  constructor(element) {
    this.element = element;   
  }
  activate() {}

  detached(){}

  attached(){
    $(this.element).autocomplete({
      source:['Japan', 'USA', 'Canada', 'Mexico']
    });
  }
}

locate.html

<template>
    <label for="locator-input"></label>
    <div id="locator-input-wrapper">
        <input id="locator-input" type="text" placeholder="Search">
    </div>
</template>

Upvotes: 1

Views: 1531

Answers (4)

alhpe
alhpe

Reputation: 1514

There are several step involved on this. Please let me note the key points

  1. First you must install the following packages (I am using nodeJS).

npm install -save jquery jquery-ui

(then and if you are coding on typescript the requested types…)

npm install -save @types/jquery @types/jqueryui

I am installing those packages only for coding with typescript and have intellisense working, but really I will not be using them on runtime.

  1. Where the jquery-ui package resides, on node_modules directory, go and create a ../node_modules/jquery-ui/dist directory.

Then download the built zip minimized version from https://jqueryui.com/ and decompress into that dist directory. Those are the files the we will be really using at runtime.

  1. Configure your AMD loader to point to that dist min file creating paths and shims for jquery and jquery-ui. In my case, the AMD loader is requireJS.
require.config(
  {
    "paths": {
      "jquery": '../node_modules/jquery/dist/jquery.min',
      "jquery-ui": '../node_modules/jquery-ui/dist/jquery-ui.min'

(code removed for brevity…)

"shim": {
  "jquery": {
    "exports": '$'
  },
  "jquery-ui": {
    "exports": '$.autocomplete',
    "deps": ['jquery' ]
  },

(notice that the line "exports": '$.autocomplete' is not required. Since autocomplete, datepicker, etc. widgets, will be loading onto the $ jQuery global variable, I only used this line only as signaler to my AMD loader the it has really loaded something)

  1. Since my AMD loader don’t “parse” css files, I had to add jquery-ui.min.css style sheet manually to my index.html the
<!DOCTYPE html>
<html>
<head lang="en">

(code removed for brevity…)
    <link href="./node_modules/jquery-ui/dist/jquery-ui.min.css" rel="stylesheet" />
  1. Create a custom attribute or a custom element (in my opinion for this case the best choice is a custom attribute

i.e. create a class file called: auto-complete.ts (I am coding on typescript, remove types for vainilla javascript).

import { DOM, inject, bindable, bindingMode } from 'aurelia-framework';

import { fireEvent } 'library';

import * as $ from 'jquery';
import 'jquery-ui';

@inject(DOM.Element)
export class AutoCompleteCustomAttribute {

  @bindable source: any;
  @bindable options = {};
  @bindable({ defaultBindingMode: bindingMode.twoWay }) value: JQueryUI.AutocompleteUIParams;

  private readonly element: Element;

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

  attached() {
    $(this.element).autocomplete({
      change: (event, ui) => {
        if (ui.item == null) {
          $(this.element).val('');
          $(this.element).focus();
        }
      },
      select: (label, value) => this.value = value,
      source: this.source
    }).on('change', e => fireEvent(<any>e.target, 'input'));
  }

  detached() {
    $(this.element).autocomplete('destroy');
  }
}
  1. Create a shared module where to code shared functionality (or code directly on custom attribute itself, I am going to stick with the shared module option)

i.e. create a class file called: library.ts

export function fireEvent(element: Element, name: string) {
  var event = createEvent(name);
  element.dispatchEvent(event);
}

export function createEvent(name: string) {
  var event = document.createEvent('Event');
  event.initEvent(name, true, true);
  return event;
}
  1. The usage of this custom attribute on your code is just to attach it to a input text tag as follows:

    <input auto-complete="source.bind:countries; value.two-way: country">

where countries (string array) and country (string) are properties on your view model.

  1. Don’t forget to register your custom attribute as a global resource at your Aurelia project's ./src/resources/index.ts or manually adding it on you main.js configure() function as follows:

aurelia.globalResources(["auto-complete"]);

I hope this answer be usefull

Hi again, I am adding an updated code for the custom attribute here below

import { DOM, inject, bindable, bindingMode } from 'aurelia-framework';

import * as $ from 'jquery';
import 'jquery-ui';

import { fireEvent, AutoCompleteSource } from 'libs/library';

@inject(DOM.Element)
export class AutoCompleteCustomAttribute {

  @bindable options = {
    applyLabel: true,
    forceMatch: true
  };

  @bindable source: AutoCompleteSource[];
  @bindable({ defaultBindingMode: bindingMode.twoWay }) value: number;
  @bindable({ defaultBindingMode: bindingMode.twoWay }) label: string;

  private readonly element: JQuery<HTMLElement>;

  constructor(element: Element) {
    this.element = $(element);
  }

  attached() {
    this.element
      .autocomplete({
        source: this.source,
        change: (event, ui) => {
          if (ui.item == null && this.options.forceMatch) {
            this.element.val('');
          }
        },
        select: (event, ui) => {
          if (this.options.applyLabel) {
            event.preventDefault();
            this.element.val(ui.item.label);
          }
          this.label = ui.item.label;
          this.value = ui.item.value;
        },
        focus: (event, ui) => {
          if (this.options.applyLabel) {
            event.preventDefault();
            this.element.val(ui.item.label);
          }
          this.label = ui.item.label;
          this.value = ui.item.value;
        }
      }).on('change', e => fireEvent(<any>e.target, 'input'));
  }

  detached() {
    this.element
      .autocomplete('destroy');
  }
}

This version funcionality allows us to get the label and the value of the source array when dealing with scenarios where label is the text to search and value is a foreing key.

Added functionality to force the typed text to match one of the existing values. Added funcionality to apply the label instead of value on the input text display.

Custom attribute should be used as follows:

<input type="text" value="${color}" auto-complete="source.bind:colors;value.bind:colorId;label.bind:color">

where colors (array of { "label": string, "value": number }), colorId (number) and color (string) are properties on your view model.

notice also this new type definition added to the library (just simple typescript stuff)

export type AutoCompleteSource = { "label": string, "value": number };

Upvotes: 0

DimitarKostov
DimitarKostov

Reputation: 531

I had the same problem with jQuery UI datepicker. So i used jquery-ui-dist instead of jquery-ui when doing NPM install.

import "jquery-ui-dist/jquery-ui";
import "jquery-ui-dist/jquery-ui.min.css";
import "jquery-ui-dist/jquery-ui.theme.min.css";

And then:

$(this.element).datepicker()

Upvotes: 0

proggrock
proggrock

Reputation: 3289

I had the same error but when I retrieved jquery-ui using npm it worked. So instead of "jspm install jquery-ui" (which gave me the error) try:

    jspm install npm:jquery-ui 

package.json

    "jquery-ui": "npm:jquery-ui@^1.10.5",

Upvotes: 0

Fabio
Fabio

Reputation: 11990

First, you have to be sure about what 'jquery-ui' exports. Does it export something? I believe it exports nothing, instead, it just add some functions to jquery objects. So, you could try this:

import {customElement, bindable} from 'aurelia-framework';
import $ from 'jquery';
import 'jquery-ui';

@customElement('locate')
export class Locate {
  @bindable data;  
  constructor(element) {
    this.element = element;   
  }
  activate() {}

  detached(){}

  attached(){
    $(this.element).autocomplete({
      source:['Japan', 'USA', 'Canada', 'Mexico']
    });
  }
}

Upvotes: 1

Related Questions