BootstrapTagsInput-基础避坑指南

痴心易碎 提交于 2020-04-22 04:10:10

1.简介

BootstrapTagsInput是一个基于jQuery和Bootstrap.css的用于管理标签的插件。

官网在这:官网

这个官网呢,怎么说呢,比较简洁。示例聊胜于无。

最简单的用法就是在引入jquery,和Bootstrap的前提下,在input标签中添加 data-role="tagsinput",即可初始化。

<input type="text" value="" data-role="tagsinput" />

原因在于下方代码,代码最后一部分,写了是如何初始化的。

  /**
   * Initialize tagsinput behaviour on inputs and selects which have
   * data-role=tagsinput
   */
  $(function() {
    $("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
  });

还有一个通过 select标签初始化的方式。并在标签加上 multiple  属性,在通过$("select").tagsinput('items'),获取值时,返回的是一个数组,而不是逗号分隔的字符串了。

<select multiple data-role="tagsinput">
  <option value="Amsterdam">Amsterdam</option>
  <option value="Washington">Washington</option>
  <option value="Sydney">Sydney</option>
  <option value="Beijing">Beijing</option>
  <option value="Cairo">Cairo</option>
</select>

以上两个方法都会将现有value值或者option元素,自动设置为标签。

2.Typeahead&Typeahead

一般来说,如果你只使用输入,回车成为标签的功能,那你可能遇不到什么坑。

但是一旦你遇到了根据用户输入显示相关补全/提示/预输入的需求时,坑就来了。

2.1 Typeahead(预先输入)

在其文档中,有一个介绍,就是关于Typeahead的,他是这么说的:

Typeahead is not included in Bootstrap 3, so you'll have to include your own typeahead library. I'd recommed typeahead.js. An example of using this is shown below.

大意是:Bootstrap 3中已经不包含Typeahead啦,如果你想用,你必须自己引入的Typeahead库。

我们推荐了typeahead.js。下面是使用此示例的示例。巴拉巴拉。

<input type="text" value="Amsterdam,Washington" data-role="tagsinput" />
<script>
var citynames = new Bloodhound({
  datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
  queryTokenizer: Bloodhound.tokenizers.whitespace,
  prefetch: {
    url: 'assets/citynames.json',
    filter: function(list) {
      return $.map(list, function(cityname) {
        return { name: cityname }; });
    }
  }
});
citynames.initialize();

$('input').tagsinput({
  typeaheadjs: {
    name: 'citynames',
    displayKey: 'name',
    valueKey: 'name',
    source: citynames.ttAdapter()
  }
});
</script>

-------------------------------------------------------------------------------------------------------------------------------------------------------------

然后在其文档下方有个Options介绍表,里面有一项关于typeahead的介绍,其中关于source的介绍是:

An array (or function returning a promise or array), which will be used as source for a typeahead.

大意是:一个数组(或返回一个承诺或数组的函数),它将用typeahead的数据源。

2.2 眼睛瞪得像铜铃 射出闪电般的精明

说实话,tagsinput 这文档给我一种什么感觉呢?

就是=>饶你奸似鬼!照样喝老娘的洗脚水!

上面说了,他给了一个推荐的Typeahead示列,然后下方的option表中有其对应的描述。

如果你只是直接复制示例代码,直接运行,你会遇到别的坑,但是我要说的是,睁大眼睛,仔细观瞧!

在初始化tagsinput时有两个预输入选项,一个是typeaheadjs,另一个是typeahead

看出区别了吗?区别多了一个js!!!!!

2.2.1 typeaheadjs的坑

GitHub 地址

typeahead.js
Inspired by twitter.com's autocomplete search functionality, typeahead.js is a flexible JavaScript library that provides a strong foundation for building robust typeaheads.

The typeahead.js library consists of 2 components: the suggestion engine, Bloodhound, and the UI view, Typeahead. The suggestion engine is responsible for computing suggestions for a given query. The UI view is responsible for rendering suggestions and handling DOM interactions. Both components can be used separately, but when used together, they can provide a rich typeahead experience.

这里说明了,typeaheadjs是受twitter.com自动完成搜索功能的启发,独立的库。

typeahead.js库由2个组件组成:建议引擎, Bloodhound和UI视图,Typeahead。

然后按照tagsinput的示例代码去做。可以做出来。

但是例子是你显示什么!值就是什么!就是说你显示“大脑虎”,你选择输入“大脑虎”成为标签,你提交的值也是“大脑虎”这个字符串!

一般情况下,用于搜索功能没啥问题,因为搜的就是字符串,但是如果用于其他地方,实际值id和显示值name分离的怎么办??

例如城市选择:

[{id: 101, name: "北京"},{id: 102, name: "南京"}] 

给用户展示的是“北京”,“南京”的字符串,实际上传递给后台入库的其实是“id”的值。

示例中的初始化是这样的:

$('input').tagsinput({
  typeaheadjs: {
    name: 'citynames',
    displayKey: 'name',//
    valueKey: 'name',//你把这个name换成ID他也不正常
    source: citynames.ttAdapter()
  }
});

注意啊,这个displayKey和valueKey 设置的是typeaheadjs 的显示和实际值,和tagsinput没有关系,显示依然不正确!

typeaheadjs应用示列

需要引入的文件:

<link rel="stylesheet" href="./bootstrap-tagsinput.css" /> 
<script src="./jquery-3.2.1.js"></script>
<script src="./typeaheadjs.js"></script>
<script src="./bootstrap-tagsinput.js"></script>

提示栏的下拉样式: 

   <style>
      .label {
        background: #428bca;
      }
      .bootstrap-tagsinput .tag {
        color: #000;
      }

      .twitter-typeahead.tt-query,
      .twitter-typeahead .tt-hint {
        margin-bottom: 0;
      }

      .twitter-typeahead .tt-hint {
        display: none;
      }

      .tt-menu {
        position: absolute;
        top: 100%;
        left: 0;
        z-index: 1000;
        display: none;
        float: left;
        min-width: 160px;
        padding: 5px 0;
        margin: 2px 0 0;
        list-style: none;
        font-size: 14px;
        background-color: #ffffff;
        border: 1px solid #cccccc;
        border: 1px solid rgba(0, 0, 0, 0.15);
        border-radius: 4px;
        -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
        box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
        background-clip: padding-box;
        cursor: pointer;
      }

      .tt-suggestion {
        display: block;
        padding: 3px 20px;
        clear: both;
        font-weight: normal;
        line-height: 1.428571429;
        color: #333333;
        white-space: nowrap;
      }

      .tt-suggestion:hover,
      .tt-suggestion:focus {
        color: #ffffff;
        text-decoration: none;
        outline: 0;
        background-color: #428bca;
      }
    </style>

HTML:

 <body>
    <div class="example">
      <h2>基本示例</h2>
      <form action="#" method="get">
        <input data-ajax="test.php"  class="typeahead" type="text" name="test" placeholder="请输入省份" value="" />
        <button type="submit">提交</button>
      </form>
    </div>
</body>

PHP后端:

<?php 
    echo json_encode([["id"=>101,"name"=>"北京"],["id"=>102,"name"=>"南京"]]); 
?>

PHP注意事项:

1、新建一个名叫“test.php”的文件,把这个粘贴进去,放在与你script的同级目录下。

2、要在服务器端上运行(本地的),且安装了PHP。

js:

  function tagsinputAjax(inputSelector, valueName, showName) {
      var $input = $(inputSelector),
        ajaxUrl = $input.attr("data-ajax");
      //初始化bh
      var engine = new Bloodhound({
        queryTokenizer: Bloodhound.tokenizers.whitespace,
        datumTokenizer: Bloodhound.tokenizers.whitespace,
        remote: {
          url: ajaxUrl + "?q=%QUERY",
          wildcard: "%QUERY"
        }
      });

      engine.initialize();

      var typeaheadConfig = {
        name: valueName,
       // displayKey: valueName,
       // valueKey: showName,
        templates: {
          suggestion: function (data) { //格式化预输
            console.log(data)
            return '<div data-id="' + data[valueName] + '">' + data[showName] + '</div>';
          }
        },
        source: engine.ttAdapter()
      };

      //初始化
      $input.tagsinput({
        itemValue: valueName,
        itemText: showName,
        typeaheadjs: typeaheadConfig
      })
    }

 tagsinputAjax(".typeahead", "id", "name");

这段做了一个简单的封装,指定了class为“.typeahead”的input初始化为tags-input,显示的值为“name”,实际值为“id”。

注意事项:

1.此例子为后台处理查询值,前端get方式获取相似值。

2.数据为动态获取,非一次性获取全部数据,使用建议引擎去提示(tagsinput官网给的是全部的获取过来的)方式,而是每次输入的提示都从后台获取,我做的时候总数据有3w条,一次性加载过来显然是不合适的。

3.typeaheadjs会缓存数据的,所以在调试的时候要注意,如果未按照预期呈现结果,记得清缓存后再次调试。

4.注释掉的displayKey 和 valueKey不会有影响,是因为格式化了suggestion条目。

5.data-ajax="test.php"写在了input上,因为我实际做项目时,表单页面是后台动态生成的,根据不同表单设置不同的data-ajax可以获取不同预输入数据。

2.2.2 typeahead--Bootstrap 3 Typeahead

GitHub官网

介绍就不贴了,自己看吧。

Bootstrap 3 Typeahead 原来是集成在Bootstrap 2里的一个js插件,到3的时候给独立出来了。

直接翻到GitHub官网最下面,就是介绍Bootstrap Tags Input如何使用Bootstrap 3 Typeahead。

Bootstrap Tags Input is a jQuery plugin providing a Twitter Bootstrap user interface for managing tags. Bootstrap Tags Input has a typeahead option which allows you to set the source:

$("input").tagsinput({
  typeahead: {
    source: ["Amsterdam", "Washington", "Sydney", "Beijing", "Cairo"]
  }
});

or

$("input").tagsinput({
  typeahead: {
    source: function(query) {
      return $.get("http://someservice.com");
    }
  }
});

See also: https://github.com/bassjobsen/Bootstrap-3-Typeahead/issues/40

 本地的预输入没有问题,有问题的是第二个,你把$.get中的地址换成你自己之后,它请求成功了,但是!它没有提示!它没有显示预输入的下拉。

这是为啥呢?我也不知道。

查找问题

问题出现了!那就想办法解决吧。

首先,我把代码由:

       $("input").tagsinput({
                typeahead: {
                    source: function (query) {
                        return $.get("test.php?q=" + query);
                    }
                }
            });

改为:

      $("input").tagsinput({
                typeahead: {
                    source: function (query) {
                      //  return $.get("test.php?q=" + query);
                          return ["北京", "南京"];
                    }
                }
            });

结果发现可用,那说明什么?

说明:

1.请求时成功的,并且是返回数据的,那么请求的数据没有问题。

2.直接return城市数据组,依然可以使用,说明不是数据格式的问题。

3.问题出在了$.get()这了。

说明是返回数据的问题,我把数据返回一下,写成这样是不是就行了?

 $("input").tagsinput({
                typeahead: {
                    source: function (query) {
                        var result=null;
                        $.ajax({
                            url: "test.php?q=" + query,
                            type: "get",
                            dataType: "json",//对的,数据类型不能少
                            success: function (res) {
                                result = res.data;
                            }
                        });
                        return result;
                    }
                }
            });

然后报错了...

Uncaught TypeError: Cannot read property 'success' of null bootstrap-tagsinput.js:294

at Typeahead.source (bootstrap-tagsinput.js:294)

at Typeahead.<anonymous> (bootstrap3-typeahead.js:226)

查了一下 ,截取一下tagsinput.js

  ......
      source: function (query, process) {
            function processItems(items) {
              var texts = [];

              for (var i = 0; i < items.length; i++) {
                var text = self.options.itemText(items[i]);
                map[text] = items[i];
                texts.push(text);
              }
              process(texts);
            }

            this.map = {};
            var map = this.map,
                data = typeahead.source(query);// 这里的data就是返回的数据
            if ($.isFunction(data.success)) {//报错的是这里
              // support for Angular callbacks
              data.success(processItems);
            } else if ($.isFunction(data.then)) {
              // support for Angular promises
              data.then(processItems);
            } else {
              // support for functions and jquery promises
              $.when(data)
               .then(processItems);
            }
          },
......

打印了一下data为null,所以报错。为啥呢?为返回的数据是null。network里显示xhr请求时成功的啊,也返回了数据啊?

其实改一下代码就好了:

   $("input").tagsinput({
                typeahead: {
                    source: function (query) {
                        var result=null;
                        $.ajax({
                            url: "test.php?q=" + query,
                            type: "get",            
                            dataType: "json",//对的,数据类型不能少
                            async: false,//新增了一个async为false
                            success: function (res) {
                                result = res.data;
                            }
                        });
                        return result;
                    }
                }
            });

然后就OK了。为啥这就OK了呢?自己想一下,很简单的。

如果仍然想用显示和值分离,则如下:

 $("input").tagsinput({
                itemValue: '此处填写实际的要传递给后台的key',
                itemText: '此处填写显示给用户看的的key',
                typeahead: {
                    source: function (query) {
                        var result=null;
                        $.ajax({
                            url: "test.php?q=" + query,
                            type: "get",
                            async: false,
                            success: function (res) {
                                result = res.data;
                            }
                        });
                        return result;
                    }
                }
            });

php:

<?php 

    echo json_encode(["status" => "OK", "data" => [
         ["id"=>101,"name"=>"北京"],
         ["id"=>102,"name"=>"南京"]
    ]]) 
 
?>

注意:

3.总结

我这次做项目用到了tagsinput.js,这货的手册做的特别的不友好,示列也不准确。基本上初次用的人都会遇到各种各样的问题,我为了解决各种问题各种google。心累,不爱。

typeahead和typeaheadjs傻傻分不清楚。

tagsinput.js源码中有注释,看到没,它去判断设置中用的是Bootstrap 3 Typeahead的预输入还是用的typeaheadjs。

更操蛋的是,它重写了

Bootstrap tagsinput use the source parameter only. When using this typeahead option bootstrap tagsinput initiates a new typeahead class: self.$input.typeahead. Bootstrap tagsinput's typeahead classes overwrite the original source function and has a call to process already as shown below:

Bootstrap tagsinput仅使用source参数。使用此typeahead选项时,bootstrap tagsinput会启动一个新typeahead类:self.$input.typeahead。Bootstrap tagsinput的typeahead类覆盖原始源函数,并且已经调用process,如下所示:

 if (self.options.typeahead) {
        var typeahead = self.options.typeahead || {};

        makeOptionFunction(typeahead, 'source');

        self.$input.typeahead($.extend({}, typeahead, {
          source: function (query, process) {
            function processItems(items) {
              var texts = [];

              for (var i = 0; i < items.length; i++) {
                var text = self.options.itemText(items[i]);
                map[text] = items[i];
                texts.push(text);
              }
              process(texts);
            }

            this.map = {};
            var map = this.map,
                data = typeahead.source(query);
            if ($.isFunction(data.success)) {
              // support for Angular callbacks
              data.success(processItems);
            } else if ($.isFunction(data.then)) {
              // support for Angular promises
              data.then(processItems);
            } else {
              // support for functions and jquery promises
              $.when(data)
               .then(processItems);
            }
          },
          updater: function (text) {
            self.add(this.map[text]);
            return this.map[text];
          },
          matcher: function (text) {
            return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
          },
          sorter: function (texts) {
            return texts.sort();
          },
          highlighter: function (text) {
            var regex = new RegExp( '(' + this.query + ')', 'gi' );
            return text.replace( regex, "<strong>$1</strong>" );
          }
        }));
      }

生活啊 !坑爹啊!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!