JQueryUI Spinner виджет с нокаутом

Как я могу использовать виджет jQuery UI Spinner в входе с нокаутом?

    <tbody data-bind="foreach: orders">
        <tr>
            <td data-bind="text: Name"></td>
            <td><input type="number" style="width: 100px;" data-bind="value: Price" /></td>
            <td><input type="number" style="width: 50px;" data-bind="value: VAT" /></td>
            <td><input type="number" style="width: 50px;" data-bind="value: Number" /></td>
            <td data-bind="text: Final()"></td>
            <td><a href="javascript:void(0);" data-bind="click: $root.removeOrder">Remove</a></td>
        </tr>    
    </tbody>

Ответ 1

Лучший способ - создать custom binding для привязки spinner к вводу:

ko.bindingHandlers.spinner = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        //initialize spinner with some optional options
        var options = allBindingsAccessor().spinnerOptions || {};
        $(element).spinner(options);

        //handle the field changing
        ko.utils.registerEventHandler(element, "spinchange", function () {
            var observable = valueAccessor();
            observable($(element).spinner("value"));
        });

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

    },
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            current = $(element).spinner("value");

        if (value !== current) {
            $(element).spinner("value", value);
        }
    }
};

А затем просто используйте его вместо привязки value:

<input
    type="number"
    style="width: 100px;"
    data-bind="spinner: Price, spinnerOptions: { min: 0 } " />

Здесь работает скрипка: http://jsfiddle.net/vyshniakov/SwKGb/

Ответ 2

Ответы на этой странице верны и полезны. Однако я обнаружил, что у меня плохое поведение, когда кто-то вводил значение в поле. Кажется, что каждое нажатие клавиши запускает событие "spinstop". Кроме того, нажатия клавиш были обход форматирования полей и options.step. К счастью, мы можем изучить входящее событие, чтобы увидеть, что на самом деле происходит. Могут быть лучшие способы, но я думал, что поделюсь с вами.

// Abstract to a function to allow for multiple binding types
function createSpinner(defaultOptions) {
    return {
        init: function (element, valueAccessor, allBindingsAccessor) {
            var options = $.extend(true, {}, allBindingsAccessor().spinnerOptions, defaultOptions);
            var widget = $(element);
            var observable = valueAccessor();

            widget.spinner(options);

            // handle field changes onblur [copies field -> model]
            ko.utils.registerEventHandler(element, "blur", function (event) {
                var inputValue = Number(widget.spinner("value"));
                var modelValue = observable();
                if (inputValue !== modelValue) {
                    // Set the widget (this forces formatting and rounding) - does not fire events
                    widget.spinner("value", inputValue);
                    // Read the value back out (saves us rounding)
                    var numberValue = Number(widget.spinner("value"));
                    // Set the observable
                    observable(numberValue);
                }
            });

            // handle other field changes
            ko.utils.registerEventHandler(element, "spinstop", function (event) {
                // jQuery.spinner spinstop is a bit overzealous with its spinstop event.
                if (event.keyCode !== undefined) {
                    // If it has a keyCode someone is typing... so don't interfere
                } else if (event.originalEvent && event.originalEvent.type === "mouseup") {
                    // This is an *actual* spinstop
                    var numberValue = Number(widget.spinner("value"));
                    observable(numberValue);
                }
            });

            // handle disposal
            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                widget.spinner("destroy");
            });
        },
        update: function (element, valueAccessor) {
            // [copies model -> field]
            var widget = $(element);
            var observable = valueAccessor();
            var inputValue = Number(widget.spinner("value"));
            var modelValue = observable();
            if (inputValue !== modelValue) {
                widget.spinner("value", modelValue);
            }
        }
    };
}
ko.bindingHandlers.moneyspin = createSpinner({ numberFormat: 'C0', culture: 'en-GB', min: 0, incremental: true });
ko.bindingHandlers.intspin = createSpinner({ numberFormat: 'n0', culture: 'en-GB' });

Ответ 3

@Артем Вышняков отвечает правильно. Однако, если вы ищете IE polyfill вместо замены ввода номера html5 в браузерах, которые его поддерживают, попробуйте следующее:

    ko.bindingHandlers.spinner = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        if (Modernizr.inputtypes.number) {
            ko.bindingHandlers.value.init.apply(null, arguments);
        } else {
            //initialize spinner with some optional options
            var options = allBindingsAccessor().spinnerOptions || {};
            $(element).spinner(options);

            //handle the field changing
            $(element).on("spinstop", function () {
                var observable = valueAccessor();
                observable($(element).spinner("value"));
            });

            //handle disposal
            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                $(element).off("spinstop");
                $(element).spinner("destroy");
            });
        }

    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        if (Modernizr.inputtypes.number) {
            ko.bindingHandlers.value.update.apply(null, arguments);
        } else {
            var value = ko.utils.unwrapObservable(valueAccessor());

            var disable = allBindingsAccessor().disable;

            if (typeof disable !== "undefined") {
                $(element).spinner((disable) ? "disable" : "enable");
            }

            var current = $(element).spinner("value");
            if (value !== current) {
                $(element).spinner("value", value);
            }
        }
    }
};

function Order(name, price, vat, number) {
    var self = this;

    self.Name = ko.observable(name);
    self.Price = ko.observable(price);
    self.VAT = ko.observable(vat);
    self.Number = ko.observable(number);

    self.Final = ko.computed(function() {
        return (self.Price() + self.VAT()) * self.Number();
    });
}

function ViewModel() {
    var self = this;

    self.orders = ko.observableArray();

    self.removeOrder = function(item) {
        self.orders.remove(item);
    };

    self.save = function() {
        alert(ko.toJSON(self));
    };
}

var viewModel = new ViewModel();
viewModel.orders.push(new Order("Sugar", 100, 15, 3));
viewModel.orders.push(new Order("Salt", 200, 25, 4));
viewModel.orders.push(new Order("Milk", 200, 35, 1));

ko.applyBindings(viewModel);

Завершить Fiddle здесь: http://jsfiddle.net/mberkom/pCJWc/