2011-09-24 53 views
55

任何一個人知道,以創建淘汰賽JS模板自動完成組合框的最佳方式?如何創建自動完成組合框?

我有以下模板:

<script type="text/html" id="row-template"> 
<tr> 
... 
    <td>   
     <select class="list" data-bind="options: SomeViewModelArray, 
             value: SelectedItem"> 
     </select> 
    </td> 
...   
<tr> 
</script> 

有時這種名單很長,我想有與淘汰賽或許jQuery的自動完成或一些直JavaScript代碼發揮很好,但都收效甚微。

此外,jQuery.Autocomplete需要一個輸入域。有任何想法嗎?

回答

119

這裏是一個jQuery UI自動完成綁定,我寫的。它的目的是反映optionsoptionsTextoptionsValue,與幾個新增的選擇要素使用value結合模式(可以查詢通過AJAX選項,你可以區分什麼是顯示在輸入框中輸入與所顯示的。彈出選擇框

你並不需要提供所有的選項時,它會選擇默認設置爲您

這裏是沒有AJAX功能的示例:http://jsfiddle.net/rniemeyer/YNCTY/

這裏是相同的帶有按鈕的示例,使其更像一個組合框:http://jsfiddle.net/rniemeyer/PPsRC/

這裏是通過AJAX檢索選項的示例:http://jsfiddle.net/rniemeyer/MJQ6g/

//jqAuto -- main binding (should contain additional options to pass to autocomplete) 
//jqAutoSource -- the array to populate with choices (needs to be an observableArray) 
//jqAutoQuery -- function to return choices (if you need to return via AJAX) 
//jqAutoValue -- where to write the selected value 
//jqAutoSourceLabel -- the property that should be displayed in the possible choices 
//jqAutoSourceInputValue -- the property that should be displayed in the input box 
//jqAutoSourceValue -- the property to use for the value 
ko.bindingHandlers.jqAuto = { 
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) { 
     var options = valueAccessor() || {}, 
      allBindings = allBindingsAccessor(), 
      unwrap = ko.utils.unwrapObservable, 
      modelValue = allBindings.jqAutoValue, 
      source = allBindings.jqAutoSource, 
      query = allBindings.jqAutoQuery, 
      valueProp = allBindings.jqAutoSourceValue, 
      inputValueProp = allBindings.jqAutoSourceInputValue || valueProp, 
      labelProp = allBindings.jqAutoSourceLabel || inputValueProp; 

     //function that is shared by both select and change event handlers 
     function writeValueToModel(valueToWrite) { 
      if (ko.isWriteableObservable(modelValue)) { 
       modelValue(valueToWrite); 
      } else { //write to non-observable 
       if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['jqAutoValue']) 
         allBindings['_ko_property_writers']['jqAutoValue'](valueToWrite);  
      } 
     } 

     //on a selection write the proper value to the model 
     options.select = function(event, ui) { 
      writeValueToModel(ui.item ? ui.item.actualValue : null); 
     }; 

     //on a change, make sure that it is a valid value or clear out the model value 
     options.change = function(event, ui) { 
      var currentValue = $(element).val(); 
      var matchingItem = ko.utils.arrayFirst(unwrap(source), function(item) { 
       return unwrap(item[inputValueProp]) === currentValue; 
      }); 

      if (!matchingItem) { 
       writeValueToModel(null); 
      }  
     } 

     //hold the autocomplete current response 
     var currentResponse = null; 

     //handle the choices being updated in a DO, to decouple value updates from source (options) updates 
     var mappedSource = ko.dependentObservable({ 
      read: function() { 
        mapped = ko.utils.arrayMap(unwrap(source), function(item) { 
         var result = {}; 
         result.label = labelProp ? unwrap(item[labelProp]) : unwrap(item).toString(); //show in pop-up choices 
         result.value = inputValueProp ? unwrap(item[inputValueProp]) : unwrap(item).toString(); //show in input box 
         result.actualValue = valueProp ? unwrap(item[valueProp]) : item; //store in model 
         return result; 
       }); 
       return mapped;     
      }, 
      write: function(newValue) { 
       source(newValue); //update the source observableArray, so our mapped value (above) is correct 
       if (currentResponse) { 
        currentResponse(mappedSource()); 
       } 
      } 
     }); 

     if (query) { 
      options.source = function(request, response) { 
       currentResponse = response; 
       query.call(this, request.term, mappedSource); 
      } 
     } else { 
      //whenever the items that make up the source are updated, make sure that autocomplete knows it 
      mappedSource.subscribe(function(newValue) { 
       $(element).autocomplete("option", "source", newValue); 
      }); 

      options.source = mappedSource(); 
     } 

     ko.utils.domNodeDisposal.addDisposeCallback(element, function() { 
      $(element).autocomplete("destroy"); 
     }); 


     //initialize autocomplete 
     $(element).autocomplete(options); 
    }, 
    update: function(element, valueAccessor, allBindingsAccessor, viewModel) { 
     //update value based on a model change 
     var allBindings = allBindingsAccessor(), 
      unwrap = ko.utils.unwrapObservable, 
      modelValue = unwrap(allBindings.jqAutoValue) || '', 
      valueProp = allBindings.jqAutoSourceValue, 
      inputValueProp = allBindings.jqAutoSourceInputValue || valueProp; 

     //if we are writing a different property to the input than we are writing to the model, then locate the object 
     if (valueProp && inputValueProp !== valueProp) { 
      var source = unwrap(allBindings.jqAutoSource) || []; 
      var modelValue = ko.utils.arrayFirst(source, function(item) { 
       return unwrap(item[valueProp]) === modelValue; 
      }) || {};    
     } 

     //update the element with the value that should be shown in the input 
     $(element).val(modelValue && inputValueProp !== valueProp ? unwrap(modelValue[inputValueProp]) : modelValue.toString());  
    } 
}; 

你會用它喜歡:

<input data-bind="jqAuto: { autoFocus: true }, jqAutoSource: myPeople, jqAutoValue: mySelectedGuid, jqAutoSourceLabel: 'displayName', jqAutoSourceInputValue: 'name', jqAutoSourceValue: 'guid'" /> 

UPDATE:我保持一個版本的這個位置結合:https://github.com/rniemeyer/knockout-jqAutocomplete

+0

使用更像組合框的示例更新了答案:http://jsfiddle.net/rniemeyer/PPsRC/。使用快速自定義綁定來簡化將按鈕綁定添加到按鈕。一個樣本更好的樣本在這裏:http://jqueryui.com/demos/autocomplete/#combobox –

+0

謝謝RP ..必須停止這個功能,直到後來的衝刺。當我接觸到它時,會讓你知道我該怎麼走... –

+0

謝謝瑞恩。好看的解決方案。我週末一直在和其他人一起玩,但沒有提出任何'怪癖'的東西。好東西。 –

44

這是我的解決方案:

ko.bindingHandlers.ko_autocomplete = { 
    init: function (element, params) { 
     $(element).autocomplete(params()); 
    }, 
    update: function (element, params) { 
     $(element).autocomplete("option", "source", params().source); 
    } 
}; 

用法:

<input type="text" id="name-search" data-bind="value: langName, 
ko_autocomplete: { source: getLangs(), select: addLang }"/> 

http://jsfiddle.net/7bRVH/214/ 相比RP的是很基本的,但也許填滿您的需求。

+5

我喜歡這個。它更簡單,並完成工作。 – Jonathan

+1

確實,這對我有效。下面是一個小提琴,每個按鍵都向模擬服務器生成一個ajax請求:http://jsfiddle.net/wmaurer/WgPpq/ –

+0

喜歡這個,解耦自定義聯編程序和自動完成,並實現模型中的所有內容,使代碼維護。謝謝。 – anIBMer

13

處置需要....

這些解決方案都是偉大的(與尼邁耶的是更細粒度的),但他們都忘了處理處理!

他們應該用這種破壞jQuery的自動完成功能(防止內存泄漏)處理處置:

init: function (element, valueAccessor, allBindingsAccessor) { 
.... 
    //handle disposal (if KO removes by the template binding) 
    ko.utils.domNodeDisposal.addDisposeCallback(element, function() { 
     $(element).autocomplete("destroy"); 
    }); 
} 
+0

雖然有用,但這不是一個答案,而是一條評論 – StormeHawke

+1

實際上它補充了答案,所以我認爲它是'部分'的答案。特別是當它看起來很有用時。 –

4

小的改進,

首先,所有這些都是一些非常有用的技巧,謝謝大家共享。顯然這可以通過處理來完成 - 或向下按上時

  1. 顯示標籤(而不是值):

    我使用張貼Epstone有以下改進版本焦點事件

  2. 使用可觀察到的陣列作爲數據源(而不是數組)

  3. 添加一次性處理程序通過喬治的建議

http://jsfiddle.net/PpSfR/

... 
conf.focus = function (event, ui) { 
    $(element).val(ui.item.label); 
    return false; 
} 
... 

順便說一句,指定的minLength爲0允許通過只移動箭頭鍵,而無需輸入任何文本顯示的替代品。

0

尼邁耶的解決方案非常棒,但是當我嘗試在模態中使用自動完成時遇到了一個問題。自動完成被破壞模態關閉事件(未捕獲的錯誤:不能調用在初始化之前自動完成的方法,試圖調用方法「選項」)我加入兩行綁定的訂閱方法固定它:

mappedSource.subscribe(function (newValue) { 
    if (!$(element).hasClass('ui-autocomplete-input')) 
     $(element).autocomplete(options); 
    $(element).autocomplete("option", "source", newValue); 
}); 
2

我試着Niemeyer's solution與JQuery UI 1.10.x,但自動完成框根本沒有顯示,經過一些搜索,我發現一個簡單的解決方法here。添加下面的規則到您的jQuery的ui.css文件的末尾解決了這個問題:

ul.ui-autocomplete.ui-menu { 
    z-index: 1000; 
} 

我也用淘汰賽-3.1.0,所以我不得不用KO更換ko.dependentObservable(...) (...)

此外,如果您的KO View模型包含一些數值,請確保您更改比較運算符:從===到==和!==到!=,以便類型轉換被執行。

我希望這可以幫助其他人

2

修復RP解決方案中負載輸入問題的清除問題。雖然它是一種間接的解決方案,我改變了這種在函數的末尾:

$(element).val(modelValue && inputValueProp !== valueProp ? 
unwrap(modelValue[inputValueProp]) : modelValue.toString()); 

這樣:

var savedValue = $(element).val(); 
$(element).val(modelValue && inputValueProp !== valueProp ? unwrap(modelValue[inputValueProp]) : modelValue.toString()); 
if ($(element).val() == '') { 
    $(element).val(savedValue); 
} 
+0

它的工作原理,謝謝! –

0

我知道這個問題是舊的,但我也一直在尋找一個真的很簡單,我們的團隊使用這種形式的解決方案,並發現jQuery autocomplete raises an 'autocompleteselect' event

這給了我這個想法。

<input data-bind="value: text, valueUpdate:['blur','autocompleteselect'], jqAutocomplete: autocompleteUrl" /> 

隨着處理程序僅是:

ko.bindingHandlers.jqAutocomplete = { 
    update: function(element, valueAccessor) { 
     var value = valueAccessor(); 

     $(element).autocomplete({ 
     source: value, 
     }); 
    }  
} 

我喜歡這種做法,因爲它使處理簡單,它不重視jQuery的事件到我的視圖模型。 這是一個數組而不是網址作爲源的小提琴。如果您單擊該文本框並且如果按下回車鍵,則這將起作用。

https://jsfiddle.net/fbt1772L/3/

0

Epstone原始解決方案的另一個變種。

我試圖使用它,但也發現只有手動輸入值時才更新視圖模型。選擇一個自動完成條目將視圖模型留下舊值,這有點令人擔憂,因爲驗證仍然通過 - 只有當您查看數據庫時纔會看到問題!

我使用的方法是在淘汰賽的綁定init中鉤住jquery UI組件的選擇處理程序,當選擇一個值時它會簡單地更新淘汰賽模型。這段代碼還包含了喬治上面有用答案中的處理管道。

init: function (element, valueAccessor, allBindingsAccessor) { 
 

 
     valueAccessor.select = function(event, ui) { 
 
      var va = allBindingsAccessor(); 
 
      va.value(ui.item.value); 
 
     } 
 

 
     $(element).autocomplete(valueAccessor); 
 

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

 
    } 
 
...
    <input class="form-control" type="text" data-bind="value: ModelProperty, ko_autocomplete: { source: $root.getAutocompleteValues() }" />

現在這是相當不錯的了。它旨在針對頁面上預加載的數組值而不是查詢api。