Knockout:自动完成组合框绑定而不清除可观察对象

时间:2017-09-29 13:06:06

标签: javascript knockout.js jquery-ui-autocomplete

我有一个使用knockout和jquery ui库的自动完成组合框。

当您输入的内容不在自动完成列表中时,组合框变为空白,这正是我想要它做的,但它也不会更新我的selectedOption值(我想设置当组合框留空时它为null)

我尝试向subscribe添加selectedOption事件,但在这种情况下,淘汰赛不会触发它。我也尝试使用valueAllowUnset属性,但它也没有用。

非常感谢你的帮助!

这是我的代码段(它看起来有点难看,因为我没有添加CSS,但它显示了问题):

function viewModel() {
  var self = this;

  self.myOptions = ko.observableArray([{
      Name: "First option",
      Id: 1
    },
    {
      Name: "Second option",
      Id: 2
    },
    {
      Name: "Third option",
      Id: 3
    }
  ]);

  self.selectedOption = ko.observable(3);
}

var myViewModel = new viewModel();
ko.applyBindings(myViewModel);
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>

<html>

<body>
  <p> Combobox: </p>
  <select id="myCombo" data-bind="options: myOptions, optionsText: 'Name', optionsValue: 'Id', value: selectedOption, valueAllowUnset: true, combo: selectedOption"></select>

  <p> Option selected Id: </p>
  <texarea data-bind="text: selectedOption()"> </texarea>
</body>

</html>

<script>
  // ko-combo.js
  (function() {

    //combobox 
    ko.bindingHandlers.combo = {
      init: function(element, valueAccessor, allBindingsAccessor) {
        //initialize combobox with some optional options
        var options = {};
        $(element).combobox({
          select: function() {
            var observable = valueAccessor();
            observable($(element).combobox('value'));
          }
        });

        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function() {
          var observable = valueAccessor();
          observable($(element).val());
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
          $(element).combobox("destroy");
        });

      },
      //update the control when the view model changes
      update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        $(element).combobox('value', value);
      }
    };

  })();
</script>

<script>
// jquery.ui.combobox.js
/*!
 * Copyright Ben Olson (https://github.com/bseth99/jquery-ui-extensions)
 * jQuery UI ComboBox @VERSION
 *
 *  Adapted from Jörn Zaefferer original implementation at
 *  http://www.learningjquery.com/2010/06/a-jquery-ui-combobox-under-the-hood
 *
 *  And the demo at
 *  http://jqueryui.com/autocomplete/#combobox
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 */

(function( $, undefined ) {

   $.widget( "ui.combobox", {

       options: {
           editable: false
       },
      version: "@VERSION",
      widgetEventPrefix: "combobox",
      uiCombo: null,
      uiInput: null,
      _wasOpen: false,

      _create: function() {

         var self = this,
             select = this.element.hide(),
             input, wrapper;

         input = this.uiInput =
                  $( "<input />" )
                      .insertAfter(select)
                      .addClass("ui-widget ui-widget-content ui-corner-left ui-combobox-input")
                      .val( select.children(':selected').text() );

         wrapper = this.uiCombo =
            input.wrap( '<span>' )
               .parent()
               .addClass( 'ui-combobox' )
               .insertAfter( select );

         input
          .autocomplete({

             delay: 0,
             minLength: 0,

             appendTo: wrapper,
             source: $.proxy( this, "_linkSelectList" )

          });

         $( "<button>" )
            .attr( "tabIndex", -1 )
            .attr( "type", "button" )
            .insertAfter( input )
            .button({
               icons: {
                  primary: "ui-icon-triangle-1-s"
               },
               text: false
            })
            .removeClass( "ui-corner-all" )
            .addClass( "ui-corner-right ui-button-icon ui-combobox-button" );


         // Our items have HTML tags.  The default rendering uses text()
         // to set the content of the <a> tag.  We need html().
         input.data( "ui-autocomplete" )._renderItem = function( ul, item ) {

               return $( "<li>" )
                           .append( $( "<a>" ).html( item.label ) )
                           .appendTo( ul );

            };

         this._on( this._events );

      },


      _linkSelectList: function( request, response ) {

         var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), 'i' );
         response( this.element.children('option').map(function() {

                  var text = $( this ).text();
                  
                  if ( this.value && ( !request.term || matcher.test(text) ) ) {
                      
                     return {
                           label: text.replace(
                              new RegExp(
                                  "(?![^&;]+;)(?!<[^<>]*)(" +
                                  $.ui.autocomplete.escapeRegex(request.term) +
                                  ")(?![^<>]*>)(?![^&;]+;)", "gi"),
                                  "<strong>$1</strong>"),
                           value: text,
                           option: this
                        };
                  }
              })
           );
      },
     
      _events: {

         "autocompletechange input" : function(event, ui) {
           
            var $el = $(event.currentTarget);

            if ( !ui.item ) {

               var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $el.val() ) + "$", "i" ),
               valid = false;

               this.element.children( "option" ).each(function() {
                     if ( $( this ).text().match( matcher ) ) {
                        this.selected = valid = true;
                        return false;
                     }
                  });

               if (!this.options.editable) {
                   if (!valid) {

                       // remove invalid value, as it didn't match anything
                       $el.val("");
                       this.element.prop('selectedIndex', -1);
                       //return false;

                   }
               }
            }

            this._trigger( "change", event, {
                  item: ui.item ? ui.item.option : null
                });

         },

         "autocompleteselect input": function( event, ui ) {
          
            ui.item.option.selected = true;
            this._trigger( "select", event, {
                  item: ui.item.option
            });


         },

         "autocompleteopen input": function ( event, ui ) {

            this.uiCombo.children('.ui-autocomplete')
               .outerWidth(this.uiCombo.outerWidth(true));
         },

         "mousedown .ui-combobox-button" : function ( event ) {
            this._wasOpen = this.uiInput.autocomplete("widget").is(":visible");
         },

         "click .ui-combobox-button" : function( event ) {

            this.uiInput.focus();

            // close if already visible
            if (this._wasOpen)
               return;

            // pass empty string as value to search for, displaying all results
            this.uiInput.autocomplete("search", "");

         }

      },

      value: function ( newVal ) {
         var select = this.element,
             valid = false,
             selected;

         if (!arguments.length) {
             selected = select.children(":selected");
             return selected.length > 0 ? selected.val() : null;
         } 

         select.prop('selectedIndex', -1);
         select.children('option').each(function() {
               if ( this.value == newVal ) {
                  this.selected = valid = true;
                  return false;
               }
            });

         if ( valid ) {
            this.uiInput.val(select.children(':selected').text());
         } else {
            this.uiInput.val( "" );
            this.element.prop('selectedIndex', -1);
         }

      },

      _destroy: function () {
         this.element.show();
         this.uiCombo.replaceWith( this.element );
      },

      widget: function () {
         return this.uiCombo;
      },

      _getCreateEventData: function() {

         return {
            select: this.element,
            combo: this.uiCombo,
            input: this.uiInput
         };
      }

    });


}(jQuery));

</script>

1 个答案:

答案 0 :(得分:1)

在绑定处理程序中通过change事件更改select事件:

function viewModel() {
  var self = this;

  self.myOptions = ko.observableArray([{
      Name: "First option",
      Id: 1
    },
    {
      Name: "Second option",
      Id: 2
    },
    {
      Name: "Third option",
      Id: 3
    }
  ]);

  self.selectedOption = ko.observable(3);
}

var myViewModel = new viewModel();
ko.applyBindings(myViewModel);
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>

<html>

<body>
  <p> Combobox: </p>
  <select id="myCombo" data-bind="options: myOptions, optionsText: 'Name', optionsValue: 'Id', value: selectedOption, valueAllowUnset: true, combo: selectedOption"></select>

  <p> Option selected Id: </p>
  <texarea data-bind="text: selectedOption()"> </texarea>
</body>

</html>

<script>
  // ko-combo.js
  (function() {

    //combobox 
    ko.bindingHandlers.combo = {
      init: function(element, valueAccessor, allBindingsAccessor) {
        //initialize combobox with some optional options
        var options = {};
        $(element).combobox({
          change: function() {
            var observable = valueAccessor();
            observable($(element).combobox('value'));
          }
        });

        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function() {
          var observable = valueAccessor();
          observable($(element).val());
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
          $(element).combobox("destroy");
        });

      },
      //update the control when the view model changes
      update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        $(element).combobox('value', value);
      }
    };

  })();
</script>

<script>
// jquery.ui.combobox.js
/*!
 * Copyright Ben Olson (https://github.com/bseth99/jquery-ui-extensions)
 * jQuery UI ComboBox @VERSION
 *
 *  Adapted from Jörn Zaefferer original implementation at
 *  http://www.learningjquery.com/2010/06/a-jquery-ui-combobox-under-the-hood
 *
 *  And the demo at
 *  http://jqueryui.com/autocomplete/#combobox
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 */

(function( $, undefined ) {

   $.widget( "ui.combobox", {

       options: {
           editable: false
       },
      version: "@VERSION",
      widgetEventPrefix: "combobox",
      uiCombo: null,
      uiInput: null,
      _wasOpen: false,

      _create: function() {

         var self = this,
             select = this.element.hide(),
             input, wrapper;

         input = this.uiInput =
                  $( "<input />" )
                      .insertAfter(select)
                      .addClass("ui-widget ui-widget-content ui-corner-left ui-combobox-input")
                      .val( select.children(':selected').text() );

         wrapper = this.uiCombo =
            input.wrap( '<span>' )
               .parent()
               .addClass( 'ui-combobox' )
               .insertAfter( select );

         input
          .autocomplete({

             delay: 0,
             minLength: 0,

             appendTo: wrapper,
             source: $.proxy( this, "_linkSelectList" )

          });

         $( "<button>" )
            .attr( "tabIndex", -1 )
            .attr( "type", "button" )
            .insertAfter( input )
            .button({
               icons: {
                  primary: "ui-icon-triangle-1-s"
               },
               text: false
            })
            .removeClass( "ui-corner-all" )
            .addClass( "ui-corner-right ui-button-icon ui-combobox-button" );


         // Our items have HTML tags.  The default rendering uses text()
         // to set the content of the <a> tag.  We need html().
         input.data( "ui-autocomplete" )._renderItem = function( ul, item ) {

               return $( "<li>" )
                           .append( $( "<a>" ).html( item.label ) )
                           .appendTo( ul );

            };

         this._on( this._events );

      },


      _linkSelectList: function( request, response ) {

         var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), 'i' );
         response( this.element.children('option').map(function() {

                  var text = $( this ).text();
                  
                  if ( this.value && ( !request.term || matcher.test(text) ) ) {
                      
                     return {
                           label: text.replace(
                              new RegExp(
                                  "(?![^&;]+;)(?!<[^<>]*)(" +
                                  $.ui.autocomplete.escapeRegex(request.term) +
                                  ")(?![^<>]*>)(?![^&;]+;)", "gi"),
                                  "<strong>$1</strong>"),
                           value: text,
                           option: this
                        };
                  }
              })
           );
      },
     
      _events: {

         "autocompletechange input" : function(event, ui) {
           
            var $el = $(event.currentTarget);

            if ( !ui.item ) {

               var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $el.val() ) + "$", "i" ),
               valid = false;

               this.element.children( "option" ).each(function() {
                     if ( $( this ).text().match( matcher ) ) {
                        this.selected = valid = true;
                        return false;
                     }
                  });

               if (!this.options.editable) {
                   if (!valid) {

                       // remove invalid value, as it didn't match anything
                       $el.val("");
                       this.element.prop('selectedIndex', -1);
                       //return false;

                   }
               }
            }

            this._trigger( "change", event, {
                  item: ui.item ? ui.item.option : null
                });

         },

         "autocompleteselect input": function( event, ui ) {
          
            ui.item.option.selected = true;
            this._trigger( "select", event, {
                  item: ui.item.option
            });


         },

         "autocompleteopen input": function ( event, ui ) {

            this.uiCombo.children('.ui-autocomplete')
               .outerWidth(this.uiCombo.outerWidth(true));
         },

         "mousedown .ui-combobox-button" : function ( event ) {
            this._wasOpen = this.uiInput.autocomplete("widget").is(":visible");
         },

         "click .ui-combobox-button" : function( event ) {

            this.uiInput.focus();

            // close if already visible
            if (this._wasOpen)
               return;

            // pass empty string as value to search for, displaying all results
            this.uiInput.autocomplete("search", "");

         }

      },

      value: function ( newVal ) {
         var select = this.element,
             valid = false,
             selected;

         if (!arguments.length) {
             selected = select.children(":selected");
             return selected.length > 0 ? selected.val() : null;
         } 

         select.prop('selectedIndex', -1);
         select.children('option').each(function() {
               if ( this.value == newVal ) {
                  this.selected = valid = true;
                  return false;
               }
            });

         if ( valid ) {
            this.uiInput.val(select.children(':selected').text());
         } else {
            this.uiInput.val( "" );
            this.element.prop('selectedIndex', -1);
         }

      },

      _destroy: function () {
         this.element.show();
         this.uiCombo.replaceWith( this.element );
      },

      widget: function () {
         return this.uiCombo;
      },

      _getCreateEventData: function() {

         return {
            select: this.element,
            combo: this.uiCombo,
            input: this.uiInput
         };
      }

    });


}(jQuery));

</script>