Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout.js - how do I throttle custom bindings

I've got a custom binding to handle autocomplete, and when a user selects an item from the autocomplete I talk to the server and replace the text_field with a shortened name. The problem is that this triggers the 'update' function of my custom binding a second time.

Knockout.js code (edit: Note the following is CoffeeScript) :

ko.bindingHandlers.ko_autocomplete =
  init: (element, params) ->
    $(element).autocomplete(params())

  update: (element, valueAccessor, allBindingsAccessor, viewModel) ->
    unless task.name() == undefined
      $.ajax "/tasks/name",
        data: "name=" + task.name(),
        success: (data,textStatus, jqXHR) ->
          task.name(data.short_name)


  Task = ->
    @name = ko.observable()
    @name_select = (event, ui) ->
      task.name(ui.item.name)
      false  

  task = Task.new()

View

= f.text_field :name, "data-bind" => "value: name, ko_autocomplete: { source: '/autocomplete/tasks', select: name_select }"

Is there a way to apply a throttle to a custom binding?

I just want to stop the custom bindings 'update' function from triggering a second time when I set the task.name to the short_name sent back from the server.

like image 446
map7 Avatar asked May 02 '12 00:05

map7


2 Answers

In general, I've found a pattern like this to work for me

ko.bindingHandlers.gsExample = 
    update: (element, valueAccessor, allBindingsAccessor, viewModel) ->
        args = valueAccessor()

        # Process args here, turn them into local variables
        # eg.
        span = args['span'] || 10

        render = ko.computed ->
            # Put your code in here that you want to throttle
            # Get variables from things that change very rapidly here

            # Note: You can access variables such as span in here (yay: Closures)
            some_changing_value = some_observable()

            $(element).html(some_changing_value)

        # Now, throttle the computed section (I used 0.5 seconds here)
        render.extend throttle : 500

        # Cause an immediate execution of that section, also establish a dependancy so 
        #  this outer code is re-executed when render is computed.
        render()
like image 176
Tom Leys Avatar answered Oct 12 '22 09:10

Tom Leys


If you don't want to violate isolation, so you can use autocomplete's delay options and it's select event, throwing update function away.

Modify your init in this way:

   var options = $.extend(params(), { 
      select: function(ev, ui) {
        var name = ui.item ? ui.item.short_name : this.value
        task.name(name);
      }
    })
   $(element).autocomplete(options)

Your update is called a second time because (for simplification) update is some kind of computed itself. So it subscribes every observable accessed inside it. In this line unless task.name() == undefined you subscribe update to task.name(). Then when you update your observable with task.name(data.short_name) on success of ajax request, update get notified and recomputed.

like image 42
ILya Avatar answered Oct 12 '22 09:10

ILya