change event on select with knockout binding, how

2019-03-08 05:02发布

I am building a permissions UI, I have a list of permissions with a select list next to each permission. The permissions are represented by an observable array of objects which are bound to a select list:

<div data-bind="foreach: permissions">
     <div class="permission_row">
          <span data-bind="text: name"></span>
          <select data-bind="value: level, event:{ change: $parent.permissionChanged}">
                   <option value="0"></option>
                   <option value="1">R</option>
                   <option value="2">RW</option>
           </select>
      </div>
 </div>

Now the problem is this: the change event gets raised when the UI is just populating for the first time. I call my ajax function, get the permissions list and then the event get raised for each of the permission items. This is really not the behavior I want. I want it to be raised only when a user really picks out a new value for the permission in the select list, how can I do that?

标签: knockout.js
8条回答
Viruses.
2楼-- · 2019-03-08 05:13

Quick and dirty, utilizing a simple flag:

var bindingsApplied = false;

var ViewModel = function() {
    // ...

    this.permissionChanged = function() {
        // ignore, if flag not set
        if (!flag) return;

        // ...
    };
};

ko.applyBindings(new ViewModel());
bindingsApplied = true; // done with the initial population, set flag to true

If this doesn't work, try wrapping the last line in a setTimeout() - events are async, so maybe the last one is still pending when applyBindings() already returned.

查看更多
Ridiculous、
3楼-- · 2019-03-08 05:22

I use this custom binding (based on this fiddle by RP Niemeyer, see his answer to this question), which makes sure the numeric value is properly converted from string to number (as suggested by the solution of Michael Best):

Javascript:

ko.bindingHandlers.valueAsNumber = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        var observable = valueAccessor(),
            interceptor = ko.computed({
                read: function () {
                    var val = ko.utils.unwrapObservable(observable);
                    return (observable() ? observable().toString() : observable());
                },
                write: function (newValue) {
                    observable(newValue ? parseInt(newValue, 10) : newValue);
                },
                owner: this
            });
        ko.applyBindingsToNode(element, { value: interceptor });
    }
};

Example HTML:

<select data-bind="valueAsNumber: level, event:{ change: $parent.permissionChanged }">
    <option value="0"></option>
    <option value="1">R</option>
    <option value="2">RW</option>
</select>
查看更多
相关推荐>>
4楼-- · 2019-03-08 05:25

I had a similar problem and I just modified the event handler to check the type of the variable. The type is only set after the user selects a value, not when the page is first loaded.

self.permissionChanged = function (l) {
    if (typeof l != 'undefined') {
        ...
    }
}

This seems to work for me.

查看更多
该账号已被封号
5楼-- · 2019-03-08 05:26

This is just a guess, but I think it's happening because level is a number. In that case, the value binding will trigger a change event to update level with the string value. You can fix this, therefore, by making sure level is a string to start with.

Additionally, the more "Knockout" way of doing this is to not use event handlers, but to use observables and subscriptions. Make level an observable and then add a subscription to it, which will get run whenever level changes.

查看更多
手持菜刀,她持情操
6楼-- · 2019-03-08 05:27

Here is a solution that may help with this strange behaviour. I couldn't find a better solution than place a button to manually trigger the change event.

EDIT: Maybe a custom binding like this could help:

ko.bindingHandlers.changeSelectValue = {

   init: function(element,valueAccessor){

        $(element).change(function(){

            var value = $(element).val();

            if($(element).is(":focus")){

                  //Do whatever you want with the new value
            }

        });

    }
  };

And in your select data-bind attribute add:

changeSelectValue: yourSelectValue
查看更多
Lonely孤独者°
7楼-- · 2019-03-08 05:28

Actually you want to find whether the event is triggered by user or program , and its obvious that event will trigger while initialization.

The knockout approach of adding subscription won't help in all cases, why because in most of the model will be implemented like this

  1. init the model with undefined data , just structure (actual KO initilization)
  2. update the model with initial data (logical init like load JSON , get data etc)
  3. User interaction and updates

The actual step that we want to capture is changes in 3, but in second step subscription will get call , So a better way is to add to event change like

 <select data-bind="value: level, event:{ change: $parent.permissionChanged}">

and detected the event in permissionChanged function

this.permissionChanged = function (obj, event) {

  if (event.originalEvent) { //user changed

  } else { // program changed

  }

}
查看更多
登录 后发表回答