Dojo的全功能自动完成小部件

时间:2014-03-02 11:59:25

标签: grails autocomplete dojo

截至目前(Dojo 1.9.2)我无法找到满足以下所有(典型)要求的Dojo自动完成小部件:

  • 仅在输入预定义数量的字符时才对服务器执行查询(不使用此选项,不应查询大数据集)
  • 在服务器上是否需要完整REST服务,只能使用搜索词进行参数化的URL,只返回包含ID和标签的JSON对象显示(因此对数据库的数据查询可以仅限于所需的数据字段,而不是加载完整的数据实体,之后只使用一个字段)
  • 在密钥版本和服务器查询开始之间有一个可配置的延时(不会对服务器触发过多的查询)
  • 能够识别不需要 新服务器查询(因为先前执行的查询比当前执行的查询更通用)。
  • Dropdown-stlye (有GUI元素表明这是一个选择器字段)

我已经创建了一个草案解决方案(见下文),请告知您是否有更简单,更好的解决方案,以满足上述Dojo要求> 1.9。

2 个答案:

答案 0 :(得分:0)

AutoComplete小部件作为Dojo AMD模块(根据AMD规则放入/gefc/dijit/AutoComplete.js):

//
// AutoComplete style widget which works together with an ItemFileReadStore
//
// It will re-query the server whenever necessary.
//
define([
  "dojo/_base/declare", 
  "dijit/form/FilteringSelect"
], 
function(declare, _FilteringSelect) {
  return declare(
    [_FilteringSelect], {

      // minimum number of input characters to trigger search
      minKeyCount: 2,

      // the term for which we have queried the server for the last time
      lastServerQueryTerm: null,

      // The query URL which will be set on the store when a server query
      // is needed
      queryURL: null,
      //------------------------------------------------------------------------
      postCreate: function() {
        this.inherited(arguments);
        // Setting defaults
        if (this.searchDelay == null)
          this.searchDelay = 500;
        if (this.searchAttr == null)
          this.searchAttr = "label";
        if (this.autoComplete == null)
          this.autoComplete = true;
        if (this.minKeyCount == null)
          this.minKeyCount = 2;
      },    
      escapeRegExp: function (str) {
        return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
      },
      replaceAll: function (find, replace, str) {
        return str.replace(new RegExp(this.escapeRegExp(find), 'g'), replace);
      },
      startsWith: function (longStr, shortStr) {
        return (longStr.match("^" + shortStr) == shortStr)
      },
      // override search method, count the input length
      _startSearch: function (/*String*/ key) {

        // If there is not enough text entered, we won't start querying
        if (!key || key.length < this.minKeyCount) {
          this.closeDropDown();
          return;
        }

        // Deciding if the server needs to be queried
        var serverQueryNeeded = false;

        if (this.lastServerQueryTerm == null)
          serverQueryNeeded = true;
        else if (!this.startsWith(key, this.lastServerQueryTerm)) {
          // the key does not start with the server queryterm
          serverQueryNeeded = true;
        }

        if (serverQueryNeeded) {
          // Creating a query url templated with the autocomplete term
          var url = this.replaceAll('${autoCompleteTerm}', key, this.queryURL);
          this.store.url = url
          // We need to close the store in order to allow the FilteringSelect 
          // to re-open it with the new query term
          this.store.close();
          this.lastServerQueryTerm = key;
        }

        // Calling the super start search
        this.inherited(arguments);
      }
    }
  );
});

备注

  • 我包含了一些字符串函数以使其独立,这些函数应该放在JS库中的适当位置。

嵌入到使用teh AutoComplete小部件的页面中的JavaScript:

  require([
    "dojo/ready", 
    "dojo/data/ItemFileReadStore", 
    "gefc/dijit/AutoComplete", 
    "dojo/parser"
  ], 
  function(ready, ItemFileReadStore, AutoComplete) {

    ready(function() {

      // The initially displayed data (current value, possibly null)
      // This makes it possible that the widget does not fire a query against
      // the server immediately after initialization for getting a label for
      // its current value
      var dt = null;
      <g:if test="${tenantInstance.technicalContact != null}">
        dt = {identifier:"id", items:[
          {id: "${tenantInstance.technicalContact.id}",
           label:"${tenantInstance.technicalContact.name}"
          }
        ]};
      </g:if>

      // If there is no current value, this will have no data
      var partnerStore = new ItemFileReadStore(
        { data: dt, 
          urlPreventCache: true, 
          clearOnClose: true
        }
      );

      var partnerSelect = new AutoComplete({
        id: "technicalContactAC",
        name: "technicalContact.id",
        value: "${tenantInstance?.technicalContact?.id}",
        displayValue: "${tenantInstance?.technicalContact?.name}",
        queryURL: '<g:createLink controller="partner" 
          action="listForAutoComplete" 
          absolute="true"/>?term=\$\{autoCompleteTerm\}',
        store: partnerStore,
        searchAttr: "label",
        autoComplete: true
      }, 
      "technicalContactAC"
      );

    })
  })

备注

  • 这不是独立的JavaScript,而是在服务器端使用Grails生成,因此您可以在代码中看到<g:if...和其他服务器端标记。用您自己的标记替换这些部分。
  • 服务器端页面生成后,
  • <g:createLink会产生类似的结果:/Limes/partner/listForAutoComplete?term=${autoCompleteTerm}

答案 1 :(得分:0)

从dojo 1.9开始,我首先建议你用dojo / store包中的商店替换你的ItemFileReadStore。

然后,我认为dijit / form / FilteringSelect已经具备了您需要的功能。

鉴于您要求在初始页面启动时避免服务器往返,我会设置2个不同的商店:

然后,为避免在每次击键时查询服务器,请将FilteringSelect的intermediateChanges属性设置为false,并在onChange扩展点中实现逻辑。

对于延迟后触发服务器调用的要求,也要在onChange中实现。在下面的例子中,我做了一个简单的setTimeout,但你应该考虑编写一个更好的debounce方法。请参阅此blog postutility functions of dgrid

我会在您的GSP页面中执行此操作:

require(["dojo/store/Memory", "dojo/store/JsonRest", "dijit/form/FilteringSelect", "dojo/_base/lang"],
function(Memory, JsonRest, FilteringSelect, lang) {
    var initialPartnerStore = undefined;

    <g:if test="${tenantInstance.technicalContact != null}">
        dt = {identifier:"id", items:[
          {id: "${tenantInstance.technicalContact.id}",
           label:"${tenantInstance.technicalContact.name}"
          }
        ]};
        initialPartnerStore = new Memory({
            data : dt
        });
    </g:if>

    var partnerStore = new JsonRest({
        target : '<g:createLink controller="partner" action="listForAutoComplete" absolute="true"/>',
    });

    var queryDelay = 500;

    var select = new FilteringSelect({
        id: "technicalContactAC",
        name: "technicalContact.id",
        value: "${tenantInstance?.technicalContact?.id}",
        displayValue: "${tenantInstance?.technicalContact?.name}",
        store: initialPartnerStore ? initialPartnerStore : partnerStore,
        query : { term : ${autoCompleteTerm} },
        searchAttr: "label",
        autoComplete: true,
        intermediateChanges : false,
        onChange : function(newValue) {
            // Change to the JsonRest store to query the server
            if (this.store !== partnerStore) {
                this.set("store", partnerStore);
            }

            // Only query after your desired delay
            setTimeout(lang.hitch(this, function(){
                this.set('query', { term : newValue }
            }), queryDelay);

        }
    }).startup();

});      

此代码未经测试,但您明白了......