Knockout.js jQuery 移动版 1.4 select 的 3.3.0 选项绑定

Knockout.js 3.3.0 options binding for jQuery mobile 1.4 select

我正在使用 knockout.js 3.3.0 和 jQuery 移动版 1.4。 knockout.js 与 jQuery 移动设备一起使用的问题在于,由于 html 元素的 JQM 重构,以编程方式对底层视图模型的更改并不总是反映到图形用户界面,或者小部件。因此,例如,JQM select 菜单从用户界面同步到视图模型,但不是以其他方式。

我正在尝试将 3.3.0 KO 'options' 绑定与当前 JQM 实际版本的自定义绑定结合在一起。 对于 'refresh' 问题已经在 SO 上针对 2.x 版本的 KO 提出了两种可能的解决方案:jqmSelect 和 jqmValue,作为自定义绑定。 我尝试将此建议用于最近的 KO+JQM 组合,将在 SO 找到的关于该主题的所有 answers/comments 放在一起。

这是我用来测试的js:

$(document).ready(function () {
    var jsonResultData = [{
        "id": 6,
            "name": "First item",
            "pattern": "Cheetah"
    }, {
        "id": 2,
            "name": "Second item",
            "pattern": "Viper"
    }, {
        "id": 1,
            "name": "Third item",
            "pattern": "Scorpion"
    }];
    ko.applyBindings(new AdminViewModel(jsonResultData));
});

function Match(data) {
    this.id = ko.observable(data.id);
    this.pattern = ko.observable(data.pattern);
    this.name = ko.observable(data.name);
}

function AdminViewModel(allData) {
    var self = this;
    self.matches = ko.observableArray([]);
    self.matchesFromDb = $.map(allData, function (item) {
        return new Match(item);
    });
    self.matches = self.matchesFromDb;
    self.selectedMatchId = ko.observable(self.matches[0].id());
    self.selectedMatch = ko.observable(self.matches[0]);
    self.setSelectedMatchId = function (match) {
        if (match.id() != self.selectedMatchId()) {
            self.selectedMatchId(match.id());
            self.selectedMatch(match);
        }
    };
    self.patternValues = ko.observableArray(["Shark", "Viper", "Chameleon", "Cheetah", "Scorpion"]);
}

我做了一个 fiddle 来测试 jqmValue 自定义绑定,这是在 SO 发现的最新解决方案之一,但我无法让它工作:

ko.bindingHandlers.jqmValue = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        if (typeof ko.bindingHandlers.value.init !== 'undefined') {
            ko.bindingHandlers.value.init(element, valueAccessor, allBindingsAccessor, viewModel);
        }
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var instance;
        if (typeof ko.bindingHandlers.value.update !== 'undefined') {
            ko.bindingHandlers.value.update(element, valueAccessor, allBindingsAccessor, viewModel);
        }
        instance = $.data(element, 'mobile-selectmenu');
        if (instance) {
            $(element).selectmenu('refresh', true);
        }
    }
};

(AdminViewModel 的原始代码作为 KO 2.x 的参考,感谢 pablo 正在处理建议的更改以在标记中反转 value/options 感谢 JohnEarles 在 google 组)

这是带有实际 KO 和 JQM 版本的 fiddle,我试图在其中包含关于该主题的所有最佳建议:

http://jsfiddle.net/nHNzL/42/

但我仍在拉长头发,但没有成功。为什么在我的测试中 fiddle 对视图模型的更改没有反映到 JQM select 菜单中?我的错误是什么?

UPDATE:两页 fiddle 测试初始化​​:http://jsfiddle.net/nHNzL/50/

最终版本: 我做了 2 个小修复和 1 个更改: 1)每次都要检查isInstance 2) 删除了 if (currentValue == value) 3) 反转disabled属性 此外:我在 ko foreach 循环中测试了这个自定义绑定,每个 select 元素都需要是自己容器 div.

的子元素
ko.bindingHandlers.jqmSelectMenu = {
    init: function (element, valueAccessor, allBindings) {
        var options = ko.toJS(valueAccessor()),
            valueObservable = allBindings.get("value"), valueSubscription,
            optionsObservable = allBindings.get("options"), optionsSubscription;
        var refresh = function () { 
            var $el = $(element);
            var isInstance = !!$.data(element, 'mobile-selectmenu');
            if (isInstance) {
                $el.selectmenu('refresh', true); 
            } else {
                /* instantiate the widget unless jqm has already done so */
                $(element).selectmenu(options);
            }
        };
        refresh();

        /* hook up to the observables that make up the underlying <select> */
        if (ko.isSubscribable(valueObservable)) {
            valueSubscription = valueObservable.subscribe(refresh);
        }
        if (ko.isSubscribable(optionsObservable)) {
            optionsSubscription = optionsObservable.subscribe(refresh);
        }

        /* properly dispose of widgets & subscriptions when element is removed */
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $(element).selectmenu("destroy");
            if (valueSubscription) valueSubscription.dispose();
            if (optionsSubscription) optionsSubscription.dispose();
        });
    },
    update: function (element, valueAccessor, allBindings) {
        var options = ko.toJS(valueAccessor()),
            $el = $(element);

        /* update any widget options if necessary */
        ko.utils.objectForEach(options, function (key, value) {
            if (key === "enabled") {
                $el.selectmenu(value ? "enable" : "disable");
            } else {
                $el.selectmenu("option", key, value);
            }
        });
    }
};

事实上你是在混合两种不同的东西。

  1. 存在基础 <select> 元素。它包含可用选项以及 selected 选项。
  2. 然后是 jQuery 移动 SelectMenu 小部件。它与用户体验部分有关。看一下它提供的 the API options 就会发现它与底层 select 框确实没有太大关系,主要用于自初始化。

当然,小部件会将值更改传达给基础 select 框,但是当您以编程方式更改值时,您必须自己通过调用 refresh 将其传达给小部件。所以即使没有淘汰,这也不是双向沟通。

我们 有一组敲除内置绑定,可以很好地与 select 盒子一起工作,但我们不做 有一个绑定,可以将这些值的更改传达给小部件。我们还没有的是 initializing/updating 任何小部件的 API 选项的方法。

因此,我们不需要重新发明 value 绑定,而是需要一个处理小部件本身的绑定,否则只是对现有绑定进行补充:

ko.bindingHandlers.jqmSelectMenu = {
    init: function (element, valueAccessor, allBindings) {
        var options = ko.toJS(valueAccessor()),
            valueObservable = allBindings.get("value"), valueSubscription,
            optionsObservable = allBindings.get("options"), optionsSubscription,
            isInstance = !!$.data(element, 'mobile-selectmenu'),
            refresh = function () { $(element).selectmenu('refresh', true); };

        // instantiate the widget unless jqm has already done so
        if (!isInstance) $(element).selectmenu(options);
        refresh();

        // hook up to the observables that make up the underlying <select>
        if (ko.isSubscribable(valueObservable)) {
            valueSubscription = valueObservable.subscribe(refresh);
        }
        if (ko.isSubscribable(optionsObservable)) {
            optionsSubscription = optionsObservable.subscribe(refresh);
        }

        // properly dispose of widgets & subscriptions when element is removed
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $(element).selectmenu("destroy");
            if (valueSubscription) valueSubscription.dispose();
            if (optionsSubscription) optionsSubscription.dispose();
        });
    },
    update: function (element, valueAccessor, allBindings) {
        var options = ko.toJS(valueAccessor()),
            $elem = $(element);

        // update any widget options if necessary
        ko.utils.objectForEach(options, function (key, value) {
            var currentValue = $elem.selectmenu("option", key);
            if (currentValue !== value) {
                if (key === "disabled") {
                    $elem.selectmenu(value ? "disable" : "enable");
                } else {
                    $elem.selectmenu("option", key, value);
                }
            }
        });
    }
};

我本着knockout-jQueryUI的精神写下这篇文章的。我建议看看那个图书馆。

这里也更新了 fiddle。 http://jsfiddle.net/nHNzL/46/