aupac / ember-aupac-typeahead

ember-cli ajax typeahead search component
MIT License
25 stars 38 forks source link

Allow different components to be used in templates #12

Open kgusarov opened 8 years ago

kgusarov commented 8 years ago

Hi!

In my opinion it would be nice to be able not to only specify template but also the component for typeahead templates. Recently I've had to create a footer with custom action and this is what I've came to:

import Ember from 'ember';
import AupacTypeahead from 'ember-aupac-typeahead/components/aupac-typeahead';

const {run, Component} = Ember;

const Key = {
  BACKSPACE : 8,
  DELETE : 46
};

export default AupacTypeahead.extend({
  suggestionComponent: Component,
  notFoundComponent: Component,
  pendingComponent: Component,
  headerComponent: Component,
  footerComponent: Component,

  initializeTypeahead() {
    const self = this;

    //Setup the typeahead
    //noinspection ChainedFunctionCallJS
    const t = this.$().typeahead({
      highlight: this.get('highlight'),
      hint: this.get('hint'),
      minLength: this.get('minLength'),
      classNames: this.get('typeaheadClassNames')
    }, {
      component: this,
      name: this.get('datasetName') || 'default',
      display: this.get('display'),
      async: this.get('async'),
      limit: this.get('limit'),
      source: this.get('source'),
      templates: {
        suggestion: function (model) {
          const cmp = self.get('suggestionComponent');

          //noinspection ChainedFunctionCallJS
          const item = cmp.create({
            model: model,
            layout: self.get('suggestionTemplate'),
            parentView: self
          }).createElement();
          return item.element;
        },
        notFound: function (query) {
          const cmp = self.get('notFoundComponent');

          //noinspection ChainedFunctionCallJS
          const item = cmp.create({
            query: query,
            layout: self.get('notFoundTemplate'),
            parentView: self
          }).createElement();
          return item.element;
        },
        pending: function (query) {
          const cmp = self.get('pendingComponent');

          //noinspection ChainedFunctionCallJS
          const item = cmp.create({
            query: query,
            layout: self.get('pendingTemplate'),
            parentView: self
          }).createElement();
          return item.element;
        },
        header: function (query, suggestions) {
          const cmp = self.get('headerComponent');

          //noinspection ChainedFunctionCallJS
          const item = cmp.create({
            query: query,
            suggestions: suggestions,
            layout: self.get('headerTemplate'),
            parentView: self
          }).createElement();
          return item.element;
        },
        footer: function (query, suggestions) {
          const cmp = self.get('footerComponent');

          //noinspection ChainedFunctionCallJS
          const item = cmp.create({
            query: query,
            suggestions: suggestions,
            layout: self.get('footerTemplate'),
            parentView: self
          }).createElement();
          return item.element;
        }
      }
    });
    this.set('_typeahead', t);

    // Set selected object
    //noinspection NestedFunctionCallJS
    t.on('typeahead:autocompleted', run.bind(this, (jqEvent, suggestionObject /*, nameOfDatasetSuggestionBelongsTo*/) => {
      this.set('selection', suggestionObject);
      this.sendAction('action', suggestionObject);
    }));

    //noinspection NestedFunctionCallJS
    t.on('typeahead:selected', run.bind(this, (jqEvent, suggestionObject /*, nameOfDatasetSuggestionBelongsTo*/) => {
      this.set('selection', suggestionObject);
      this.sendAction('action', suggestionObject);
    }));

    //noinspection NestedFunctionCallJS
    t.on('keyup', run.bind(this, (jqEvent) => {
      //Handle the case whereby the user presses the delete or backspace key, in either case
      //the selection is no longer valid.
      if (jqEvent.which === Key.BACKSPACE || jqEvent.which === Key.DELETE) {
        //noinspection ChainedFunctionCallJS
        const value = this.get('_typeahead').typeahead('val'); //cache value
        this.set('selection', null);
        this.sendAction('action', null);
        this.setValue(value); //restore the text, thus allowing the user to make corrections
      }
    }));

    //noinspection NestedFunctionCallJS
    t.on('focusout', run.bind(this, (/*jqEvent*/) => {
      //the user has now left the control, update display with current binding or reset to blank
      const model = this.get('selection');
      if (model) {
        this.setValue(model);
      } else {
        this.setValue(null);
      }
    }));
  }
});

Notice that I've had to copy almost all the code just to change small part of it :)

jackmatt2 commented 8 years ago

Yep this seems reasonable. I think using components instead of compiled templates might be the way to go.

kgusarov commented 8 years ago

Also I've "hacked" dynamically created components. Notice:

parentView: self
jackmatt2 commented 8 years ago

Is the reason for parentView: self because you wanted your actions to be handled by the controller and not swallowed by aupac-typeahead? We need a good and official way of handling this as there will be an unknown number of actions on the component. Ideas are welcome.

kgusarov commented 8 years ago

Actually I was trying to trigger action in aupac-typeahead. Maybe something has changed now since I am using Ember 1.13.10. However action wasn't triggered and I've received something like

component::ember472 has no handler for action XXX

Yes, this was sort of hack - in order to write {{action 'someAction' target=parentView}} in component template. However this doesn't seem a correct way to me. Main problem here is that dynamically create component isn't linked to its parent in correct way. I've currently haven't studied more correct way since this change had to be made ASAP but I am sure that it exists.

matthias-k commented 7 years ago

Hi,

we are trying to trigger actions from the template, too. This has to fail as the template is rendered in its own component and therefore does not have access to any other scope. Here is a demonstration:

https://ember-twiddle.com/b90d4e28f787686e219d2fa133727f7c?openFiles=templates.suggestion-template.hbs%2C

I would be happy to help with implementing this (maybe in combination with allowing the use of different components), but I am not sure what might be the best way to approach this.