如何使用输入进行自动完成搜索?

时间:2018-07-14 18:05:24

标签: javascript jquery search input

我需要jquery或javascript用搜索类型的结果来完成自动完成下拉列表。我需要将结果作为链接“”,以便我可以在新标签中打开它,也可以通过单击直接打开它。 这是我的代码:

import 'dart:async';
import 'package:flutter/material.dart';

String appTitle = 'Overlay Example';

class _CustomDelegate extends SingleChildLayoutDelegate {
  final Offset target;
  final double verticalOffset;

  _CustomDelegate({
    @required this.target,
    @required this.verticalOffset,
  }) : assert(target != null),
       assert(verticalOffset != null);

  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints.loosen();

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    return positionDependentBox(
      size: size,
      childSize: childSize,
      target: target,
      verticalOffset: verticalOffset,
      preferBelow: true,
    );
  }

  @override
  bool shouldRelayout(_CustomDelegate oldDelegate) {
    return
      target != oldDelegate.target
      || verticalOffset != oldDelegate.verticalOffset;
  }
}

class _CustomOverlay extends StatelessWidget {
  final Widget child;
  final Offset target;

  const _CustomOverlay({
    Key key,
    this.child,
    this.target,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    double borderWidth = 2.0;
    Color borderColor = Theme.of(context).accentColor;

    return new Positioned.fill(
      child: new IgnorePointer(
        ignoring: false,
        child: new CustomSingleChildLayout(
          delegate: new _CustomDelegate(
            target: target,
            verticalOffset: -5.0,
          ),
          child: new Padding(
            padding: const EdgeInsets.symmetric(horizontal: 10.0),
            child: new ConstrainedBox(
              constraints: new BoxConstraints(
                maxHeight: 100.0,
              ),
              child: new Container(
                decoration: new BoxDecoration(
                  color: Colors.white,
                  border: new Border(
                    right: new BorderSide(color: borderColor, width: borderWidth),
                    bottom: new BorderSide(color: borderColor, width: borderWidth),
                    left: new BorderSide(color: borderColor, width: borderWidth),
                  ),
                ),
                child: child,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _CustomInputState extends State<_CustomInput> {
  TextEditingController _inputController = new TextEditingController();
  FocusNode _focus = new FocusNode();
  List<String> _listItems;
  OverlayState _overlay;
  OverlayEntry _entry;
  bool _entryIsVisible = false;
  StreamSubscription _sub;

  void _toggleEntry(show) {
    if(_overlay.mounted && _entry != null){
      if(show){
        _overlay.insert(_entry);
        _entryIsVisible = true;
      }
      else{
        _entry.remove();
        _entryIsVisible = false;
      }
    }
    else {
      _entryIsVisible = false;
    }
  }

  void _handleFocus(){
    if(_focus.hasFocus){
      _inputController.addListener(_handleInput);
      print('Added input handler');
      _handleInput();
    }
    else{
      _inputController.removeListener(_handleInput);
      print('Removed input handler');
    }
  }

  void _handleInput() {
    String newVal = _inputController.text;

    if(widget.parentStream != null && _sub == null){
      _sub = widget.parentStream.listen(_handleStream);
      print('Added stream listener');
    }

    if(_overlay == null){
      final RenderBox bounds = context.findRenderObject();
      final Offset target = bounds.localToGlobal(bounds.size.bottomCenter(Offset.zero));

      _entry = new OverlayEntry(builder: (BuildContext context){
        return new _CustomOverlay(
          target: target,
          child: new Material(
            child: new ListView.builder(
              padding: const EdgeInsets.all(0.0),
              itemBuilder: (BuildContext context, int ndx) {
                String label = _listItems[ndx];
                return new ListTile(
                  title: new Text(label),
                  onTap: () {
                    print('Chose: $label');
                    _handleSubmit(label);
                  },
                );
              },
              itemCount: _listItems.length,
            ),
          ),
        );
      });
      _overlay = Overlay.of(context, debugRequiredFor: widget);
    }

    setState(() {
      // This can be used if the listItems get updated, which won't happen in
      // this example, but I figured it was useful info.
      if(!_entryIsVisible && _listItems.length > 0){
        _toggleEntry(true);
      }else if(_entryIsVisible && _listItems.length == 0){
        _toggleEntry(false);
      }else{
        _entry.markNeedsBuild();
      }
    });
  }

  void _exitInput(){
    if(_sub != null){
      _sub.cancel();
      _sub = null;
      print('Removed stream listener');
    }
    // Blur the input
    FocusScope.of(context).requestFocus(new FocusNode());
    // hide the list
    _toggleEntry(false);

  }

  void _handleSubmit(newVal) {
    // Set to selected value
    _inputController.text = newVal;
    _exitInput();
  }

  void _handleStream(ev) {
    print('Input Stream : $ev');
    switch(ev){
      case 'TAP_UP':
        _exitInput();
        break;
    }
  }

  @override
  void initState() {
    super.initState();
    _focus.addListener(_handleFocus);
    _listItems = widget.listItems;
  }

  @override
  void dispose() {
    _inputController.removeListener(_handleInput);
    _inputController.dispose();

    if(mounted){
      if(_sub != null) _sub.cancel();
      if(_entryIsVisible){
        _entry.remove();
        _entryIsVisible = false;
      }
      if(_overlay != null && _overlay.mounted) _overlay.dispose();
    }

    super.dispose();
  }

  @override
  Widget build(BuildContext ctx) {
    return new Row(
      children: <Widget>[
        new Expanded(
          child: new TextField(
            autocorrect: true,
            focusNode: _focus,
            controller: _inputController,
            decoration: new InputDecoration(
              border: new OutlineInputBorder(
                borderRadius: const BorderRadius.all(
                  const Radius.circular(5.0),
                ),
                borderSide: new BorderSide(
                  color: Colors.black,
                  width: 1.0,
                ),
              ),
              contentPadding: new EdgeInsets.all(10.0),
              filled: true,
              fillColor: Colors.white,
            ),
            onSubmitted: _handleSubmit,
          ),
        ),
      ]
    );
  }
}

class _CustomInput extends StatefulWidget {
  final List<String> listItems;
  final Stream parentStream;

  _CustomInput({
    Key key,
    this.listItems,
    this.parentStream,
  }): super(key: key);

  @override
  State createState() => new _CustomInputState();
}

class HomeState extends State<Home> {
  List<String> _overlayItems = [
    'Item 01',
    'Item 02',
    'Item 03',
  ];
  StreamController _eventDispatcher = new StreamController.broadcast();

  Stream get _stream => _eventDispatcher.stream;

  _onTapUp(TapUpDetails details) {
    _eventDispatcher.add('TAP_UP');
  }

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose(){
    super.dispose();
    _eventDispatcher.close();
  }

  @override
  Widget build(BuildContext context){
    return new GestureDetector(
      onTapUp: _onTapUp,
      child: new Scaffold(
        appBar: new AppBar(
          title: new Row(
            children: <Widget>[
              new Padding(
                padding: new EdgeInsets.only(
                  right: 10.0,
                ),
                child: new Icon(Icons.layers),
              ),
              new Text(appTitle)
            ],
          ),
          bottom: new PreferredSize(
            preferredSize: const Size.fromHeight(30.0),
            child: new Padding(
              padding: new EdgeInsets.only(
                bottom: 10.0,
                left: 10.0,
                right: 10.0,
              ),
              child: new _CustomInput(
                key: new ObjectKey('$_overlayItems'),
                listItems: _overlayItems,
                parentStream: _stream,
              ),
            ),
          ),
        ),
        body: const Text('Body content'),
      ),
    );
  }
}

class Home extends StatefulWidget {
  @override
  State createState() => new HomeState();
}

void main() => runApp(new MaterialApp(
  title: appTitle,
  home: new Home(),
));

2 个答案:

答案 0 :(得分:1)

纯JavaScript可能的解决方案。

  • 再次创建与输入匹配的标签数组
  • 为输入字段设置input事件监听器
  • 在每个input事件上,清除列表元素的内容,并基于一些匹配函数,动态创建一个新事件,然后添加 每个匹配字符串的链接

// array of values to match against
const tags = ['bmw', 'toyota', 'opel', 'kia'];

// fetch input and list elements
const input = document.querySelector('input');
const list = document.querySelector('ul');

// simple match function, performs substing matching
const getMatch = (toMatch, tags) =>
  tags.filter((tag) => tag.includes(toMatch.toLowerCase()));

input.addEventListener('input', (event) => {
  // clear contents of list
  list.innerHTML = '';

  // start matching only when input field is non-empty
  if (input.value !== '') {
    // call matching function and create a link for each
    // item returned by it
    getMatch(input.value, tags).forEach((match) => {
      const item = document.createElement('li');
      list.append(item);

      const link = document.createElement('a');
      item.append(link);

      // add href attribute and text dynamically
      link.setAttribute('href', `www.example.com/${match}`);
      link.textContent = match;
    });
  }
});
Search: <input type="text">
<ul></ul>

答案 1 :(得分:0)

我在这里使用@MatusDubrava脚本和其他帮助找到了解决方案。

$(function () {
                            var menuPages = $('#main-menu li a:has(.childtitle)').map(function () {
                                return {
                                    label: $(this).text(),
                                    value: $(this).attr('href')
                                };
                            }).toArray();
                            $("#txt_MenuSearch").autocomplete({
                                source: menuPages,
                                select: function (event, target) {
                                    /* On select, show item's label in text input */
                                    event.preventDefault();
                                    $("#txt_MenuSearch").val(target.item.label);
                                    var link = target.item.value;
                                    window.open(link);
                                },
                            });
                            /* Highlight text */
                            $("#txt_MenuSearch").data("ui-autocomplete")._renderItem = function (ul, item) {
                                var newText = String(item.label).replace(
                                    new RegExp(this.term, "gi"),
                                    "<span style='color:#DDBA15'>$&</span>");

                                /* Wrapping the matching option within <a> tags */
                                newText = '<a href="' + item.value + '">' + newText + '</a>';

                                return $("<li>")
                                .attr("data-value", item.value)
                                .append(newText)
                                .appendTo(ul);
                            };
                        });
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  <link rel="stylesheet" href="/resources/demos/style.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

<input type="text" id="txt_MenuSearch" class="search-input" placeholder="Search here.." />

<ul id="main-menu">   
    <li class="has-sub"><a href="javascript:;"><span class="title">Menu Header1</span></a>
       <ul>
          <li><a href="example.com"><span class="childtitle">brand one</span></a></li>
          <li><a href="example.com"><span class="childtitle">brand two</span></a></li>
       </ul>    
    </li>

    <li class="has-sub"><a href="javascript:;"><span class="title">Menu Header2</span></a>
       <ul>
          <li><a href="example.com"><span class="childtitle">Car BMW</span></a></li>
          <li><a href="example.com"><span class="childtitle">Car Toyota</span></a></li>
          <li><a href="example.com"><span class="childtitle">Car Opel</span></a></li>
          <li><a href="example.com"><span class="childtitle">Car Kia</span></a></li>
       </ul>
    </li>
</ul>