Change model observable inside custom handler

I wrote the following custom bind handler which shows a jquery-ui autocomplete list. After selecting the element I want to change the given observable value which is given by options.value but there is no change when the select function enters.

ko.bindingHandlers.autocomplete = {
    init : function(element, valueAccessor, allBindings, viewModel, bindingContext) {

        var options = ko.unwrap(valueAccessor());               

        $(element).autocomplete(
                {
                    minLength : 2,
                    autoFocus : true,
                    source : function(request, response) {
                        $.ajax({
                            url : options.source,
                            data : {
                                term : request.term
                            },
                            dataType : "json",
                            type : "GET",
                            success : function(data) {
                                response(data);
                            }
                        });
                    },
                    select : function(event, ui) {
                        var selectedItem = ui.item;
                        options.value(selectedItem.name);
                    }
                });
    }
};

<input data-bind="autocomplete: { 
    value: myView().parent_name,
    source: '/data/autocomplete'
}" 
type="text" class="form-control">

Edit 1

I tried the binding from the comment but I still have an issue.

model.autocomplete = function(searchTerm, callback){
    $.ajax({
        type : "GET",
        url : "/data/autocomplete",
        data: {term: searchTerm}
    }).done(callback);
};

The input looks like this:

    <input data-bind="jqAuto: { 
        value: myView().parent_name,
        source: myView().autocomplete,
        labelProp: 'name',
        valueProp: 'parent_name'
    }" 
    type="text" class="form-control">

This doesn't change myView().parent_name

Edit 2

I think I know whats the problem now. I made a much simpler test file like this one:

 <!DOCTYPE html>
<html>
<body>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js"></script>
<script src="https://rawgit.com/rniemeyer/knockout-jqAutocomplete/master/build/knockout-jqAutocomplete.js"></script>

<h1>Testpage</h1>
<input data-bind="jqAuto : {
        source: sub().autocomplete,
        value: sub().id,
        labelProp: 'name',
        valueProp: 'id'
    }"></input>
<button data-bind="click: sub().output">ok</button>
<button data-bind="click: reset">reset</button>

<script>
function getData() {
    var data = [
        {
            name: "Test1",
            id: 1
        },
        {
            name: "Test2",
            id: 2
        },{
            name: "Test3",
            id: 3
        }       
    ];
    return data;
}

function Submodel() {
    var self = this;

    self.name = ko.observable("");
    self.id = ko.observable(null);

    self.autocomplete = function(searchTerm, callback) {
        callback(getData());
    };

    self.output = function(){
        alert(self.id());
    };
}

function PageModel() {
    var self = this;

    self.sub = ko.observable(new Submodel());

    self.reset = function(){
        self.sub(new Submodel());
    };
}

ko.applyBindings(new PageModel());
</script>

</body>
</html>

There is a submodel which is cleared at some point but this seems to invalidate the binding. Until the point I press the reset button, everything works fine. Is there any solution to update the binding after reset. I need this reset because I want to use a dialog with the model without using the dialog binding and without cleaning every variable by hand.

1 answer

  • answered 2017-01-11 14:23 user3297291

    Based on the (minimal) code you've provided and your answers to my question in the comments I might be able to help by showing a working example.

    Most important takeaways:

    • You'll have to read through the differences between valueProp, inputProp and labelProp: they are used to define what you want to store in your viewmodels versus what you want to show to the user.
    • Make sure the data your autocomplete method returns has the props you specify

    Notes to try the example:

    • I didn't include the css, so it's ugly
    • I've used the strings "one" to "ten" as example data. Type o or e or any other letter used in these words to get started.

    var Model = function() {
      return {
        parent_name: ko.observable(null),
        autocomplete: function(searchTerm, callback) {
          setTimeout(function() {
            callback(getData().filter(function(v) { 
              return v.name.includes(searchTerm.toLowerCase())
            }));
          }, 200);
        }
      };
    };
    
    var vm = {
      myView: ko.observable(new Model()),
      reset: () => vm.myView(new Model())
    };
    
    ko.applyBindings(vm);
    
    function getData() {
        return ["one", "two", "three", "four", 
                "five", "six", "seven", "eight", 
                "nine", "ten"]
          .map(str => ({ name: str }));
    };
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script
      src="http://code.jquery.com/ui/1.12.1/jquery-ui.min.js"
      integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU="
      crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <script src="https://rawgithub.com/rniemeyer/knockout-jqAutocomplete/master/build/knockout-jqAutocomplete.js"></script>
    
    <p>
      <code>parent_name</code>’s value is: 
      <strong>
        <code data-bind="text: JSON.stringify(myView().parent_name())"></code>  
      </strong>
    </p>
    
    
    <!-- ko with: myView -->
    <input data-bind="jqAuto: { 
            value: parent_name,
            source: autocomplete,
            labelProp: 'name',
            valueProp: 'name'
        }" type="text" class="form-control">
    <!-- /ko -->
    
    <button data-bind="click: reset">reset</button>