如何保持相互依赖的knockout.js可观察

时间:2013-12-07 07:04:47

标签: javascript knockout.js

我有3个相互关联的淘汰观察者。他们是

retail_price,selling_price,discount

当用户更改一个值时,将更新其他其他可观察对象。 例如,

  • 如果用户将售价输入1000,则零售价格设置为1000,折扣为0.
  • 按照上面的示例,现在如果用户将retail_price编辑为2000,则折扣会更新为50。

如果我没有afterkeydown事件,现在这一切都很好。因此,当用户输入任何值并移动到下一个框时,就可以了。

问题在于,当我继续追踪时,一旦我开始输入,基于第一个字符,就会计算其他值。现在,当我输入第二个整数时,这些值会混乱。

例如,

selling_price = 10,retail_price = 1,discount = -900

我无法找到解决方法。有什么建议吗?

问候,

更新

很抱歉不清楚。用户可以更新三个字段中的任何一个。我为此创造了一个小提琴。目前,我无法控制填充值的字段的顺序。它们可以按任何顺序填写。

  • 从零售开始:销售设置为零售和折扣= 0
  • 从销售开始:零售设置为销售和折扣= 0
  • 从折扣开始。可以添加零售或下一个销售。

用户可以按任何顺序输入值。

http://jsfiddle.net/EcD3d/5/

HTML:

<code>

<div>retail_price:
    <input type="text" data-bind="value: retail_price.formatted, hasfocus: retail_price.isFocused, valueUpdate: 'afterkeydown'" />
</div>
<div>selling_price:
    <input type="text" data-bind="value: selling_price.formatted, hasfocus: selling_price.isFocused, valueUpdate: 'afterkeydown'" />
</div>
<div>discount:
    <input type="text" data-bind="value: discount, hasfocus: discount.isFocused, valueUpdate: 'afterkeydown'" />
</div>

</code>

使用Javascript:

function hasOwnProperty(obj, prop) {
    var proto = obj.__proto__ || obj.constructor.prototype;
    return (prop in obj) && (!(prop in proto) || proto[prop] !== obj[prop]);
}

function roundNumber(value, precision, flt) {
    var precision = precision || 0,
        neg = value < 0,
        power = Math.pow(10, precision),
        value = Math.round(value * power),
        integral = String((neg ? Math.ceil : Math.floor)(value / power)),
        fraction = String((neg ? -value : value) % power),
        padding = new Array(Math.max(precision - fraction.length, 0) + 1).join('0');

    if (flt === true) {
        integral = parseFloat(integral);
    }
    return precision ? integral + '.' + padding + fraction : integral;
}

var util = {};
util.format = function (value, prefix) {
    var pr = prefix || '';
    toks = roundNumber(value, 2).replace('-', '').split('.');
    var display = pr + $.map(toks[0].split('').reverse(), function (elm, i) {
        return [(i % 3 === 0 && i > 0 ? ',' : ''), elm];
    }).reverse().join('') + '.' + toks[1];

    return value < 0 ? '-' + display : display;
};
ko.subscribable.fn.formatted = function (options) {
    var target = this;
    var _options = (options === undefined) ? {} : options;
    var _prefix = hasOwnProperty(_options, 'prefix') ? _options.prefix : '';
    var _precision = hasOwnProperty(_options, 'precision') ? _options.precision : 2;
    var _type = hasOwnProperty(_options, 'type') ? _options.type : 2;

    var format = function (value) {
        switch (_type) {
            case 1:
                return util.format(roundNumber(value, _precision), _prefix); //currency w/ symbol
            case 2:
                return roundNumber(value, _precision); //reg float
            default:
                throw new Error('illegal type');
        }
    };

    var focused = ko.observable(false);

    var writeTarget = function (value) {
        var stripped = value;
        if (isNaN(value)) {
            stripped = String(value).replace(/[^0-9.-]/g, '');
        }

        //target(parseFloat(stripped));
        value = parseFloat(stripped);
        focused() ? target(!isFinite(value) ? 0 : value) : target(!isFinite(value) ? 0 : roundNumber(value, _precision)); // Write to underlying storage
    };

    var result = ko.computed({
        read: function () {
            return target();
        },
        write: writeTarget
    });

    result.formatted = ko.computed({
        read: function () {
            if (focused()) {
                return (isNaN(target()) ? '' : target()); // Write to underlying storage
            }
            return format(target());
        },
        write: writeTarget
    });

    result.isNegative = ko.computed(function () {
        return target() < 0;
    });

    result.isFocused = ko.computed({
        read: function () {
            return focused();
        },
        write: function (value) {
            focused(value);
        }
    });


    return result;
};



var ViewModel = function () {
    var self = this;

    self.retail_price = ko.observable(0).extend({
        throttle: 100
    }).formatted({
        type: 1
    });
    self.selling_price = ko.observable(0).extend({
        throttle: 100
    }).formatted({
        type: 1
    });
    self.discount = ko.observable(0);


    // Whenever the retail price changes, change the selling price for jewelry
    self.retail_price.subscribe(function () {
        if (parseFloat(self.selling_price()) !== 0) {
            self.updateDiscount();
            return;
        }

        if (self.retail_price.isFocused()) {
            self.changeSellingPrice();
        }

    });

    // Whenever the discount changes, change the selling price
    self.discount.subscribe(function () {
        self.changeSellingPrice();
    });

    self.selling_price.subscribe(function (v) {
        if (self.selling_price.isFocused()) {
            if (parseFloat(self.retail_price()) !== 0) {
                self.updateDiscount();
                return;
            }

            var retPrice = (v * 100) / (100 - self.discount());
            self.retail_price(isNaN(retPrice) ? 0 : roundNumber(retPrice, 2));

        }
    });

    self.updateDiscount = function () {

        var retPr = parseFloat(self.retail_price());
        var askPr = parseFloat(self.selling_price());
        var discount = 100 * (retPr - askPr) / retPr;
        self.discount(!isFinite(discount) ? 0 : roundNumber(discount, 2));

    };

    self.changeSellingPrice = function () {
        var sellingPrice = self.retail_price() - (self.retail_price() * self.discount()) / 100;
        self.selling_price(isNaN(sellingPrice) ? 0 : roundNumber(sellingPrice, 2));
    };

};

ko.applyBindings(new ViewModel());

function hasOwnProperty(obj, prop) { var proto = obj.__proto__ || obj.constructor.prototype; return (prop in obj) && (!(prop in proto) || proto[prop] !== obj[prop]); } function roundNumber(value, precision, flt) { var precision = precision || 0, neg = value < 0, power = Math.pow(10, precision), value = Math.round(value * power), integral = String((neg ? Math.ceil : Math.floor)(value / power)), fraction = String((neg ? -value : value) % power), padding = new Array(Math.max(precision - fraction.length, 0) + 1).join('0'); if (flt === true) { integral = parseFloat(integral); } return precision ? integral + '.' + padding + fraction : integral; } var util = {}; util.format = function (value, prefix) { var pr = prefix || ''; toks = roundNumber(value, 2).replace('-', '').split('.'); var display = pr + $.map(toks[0].split('').reverse(), function (elm, i) { return [(i % 3 === 0 && i > 0 ? ',' : ''), elm]; }).reverse().join('') + '.' + toks[1]; return value < 0 ? '-' + display : display; }; ko.subscribable.fn.formatted = function (options) { var target = this; var _options = (options === undefined) ? {} : options; var _prefix = hasOwnProperty(_options, 'prefix') ? _options.prefix : ''; var _precision = hasOwnProperty(_options, 'precision') ? _options.precision : 2; var _type = hasOwnProperty(_options, 'type') ? _options.type : 2; var format = function (value) { switch (_type) { case 1: return util.format(roundNumber(value, _precision), _prefix); //currency w/ symbol case 2: return roundNumber(value, _precision); //reg float default: throw new Error('illegal type'); } }; var focused = ko.observable(false); var writeTarget = function (value) { var stripped = value; if (isNaN(value)) { stripped = String(value).replace(/[^0-9.-]/g, ''); } //target(parseFloat(stripped)); value = parseFloat(stripped); focused() ? target(!isFinite(value) ? 0 : value) : target(!isFinite(value) ? 0 : roundNumber(value, _precision)); // Write to underlying storage }; var result = ko.computed({ read: function () { return target(); }, write: writeTarget }); result.formatted = ko.computed({ read: function () { if (focused()) { return (isNaN(target()) ? '' : target()); // Write to underlying storage } return format(target()); }, write: writeTarget }); result.isNegative = ko.computed(function () { return target() < 0; }); result.isFocused = ko.computed({ read: function () { return focused(); }, write: function (value) { focused(value); } }); return result; }; var ViewModel = function () { var self = this; self.retail_price = ko.observable(0).extend({ throttle: 100 }).formatted({ type: 1 }); self.selling_price = ko.observable(0).extend({ throttle: 100 }).formatted({ type: 1 }); self.discount = ko.observable(0); // Whenever the retail price changes, change the selling price for jewelry self.retail_price.subscribe(function () { if (parseFloat(self.selling_price()) !== 0) { self.updateDiscount(); return; } if (self.retail_price.isFocused()) { self.changeSellingPrice(); } }); // Whenever the discount changes, change the selling price self.discount.subscribe(function () { self.changeSellingPrice(); }); self.selling_price.subscribe(function (v) { if (self.selling_price.isFocused()) { if (parseFloat(self.retail_price()) !== 0) { self.updateDiscount(); return; } var retPrice = (v * 100) / (100 - self.discount()); self.retail_price(isNaN(retPrice) ? 0 : roundNumber(retPrice, 2)); } }); self.updateDiscount = function () { var retPr = parseFloat(self.retail_price()); var askPr = parseFloat(self.selling_price()); var discount = 100 * (retPr - askPr) / retPr; self.discount(!isFinite(discount) ? 0 : roundNumber(discount, 2)); }; self.changeSellingPrice = function () { var sellingPrice = self.retail_price() - (self.retail_price() * self.discount()) / 100; self.selling_price(isNaN(sellingPrice) ? 0 : roundNumber(sellingPrice, 2)); }; }; ko.applyBindings(new ViewModel());

3 个答案:

答案 0 :(得分:0)

您可以对计算的可观察量进行折扣并包含一些健全性检查:

var discount = ko.computed(function() {
  var discount;

  // your calculation goes here
  // discount = retail_price() - selling_price() …

  return discount > 0 ? discount : 0;
});

答案 1 :(得分:0)

我添加了另一个observable来跟踪用户是否输入了特定值。例如。如果用户输入了零售价值,则可观察到。

不确定这是否正确,但似乎有效。

答案 2 :(得分:0)

这个值没有更新,因为你检查过该值应该大于0(零)然后才会更新该值。现在你正在使用afterkeydown绑定,所以在按下一个数字后,它将值更新为最初为0(零),然后它因为值为非零而停止工作。

我假设在这种情况下你应该删除valueupdate绑定并删除isfocused的检查。我做了一些改变。如果您仍有任何疑虑,请仔细阅读并告知我们:

http://jsfiddle.net/mLKEb/

HTML:

<div>retail_price:
<input type="text" data-bind="value: retail_price.formatted, hasfocus: retail_price.isFocused" />
</div>
<div>selling_price:
<input type="text" data-bind="value: selling_price.formatted, hasfocus: selling_price.isFocused" />
</div>
<div>discount:
<input type="text" data-bind="value: discount, hasfocus: discount.isFocused" />
</div>

使用Javascript:

function hasOwnProperty(obj, prop) {
var proto = obj.__proto__ || obj.constructor.prototype;
return (prop in obj) && (!(prop in proto) || proto[prop] !== obj[prop]);
}

function roundNumber(value, precision, flt) {
var precision = precision || 0,
    neg = value < 0,
    power = Math.pow(10, precision),
    value = Math.round(value * power),
    integral = String((neg ? Math.ceil : Math.floor)(value / power)),
    fraction = String((neg ? -value : value) % power),
    padding = new Array(Math.max(precision - fraction.length, 0) + 1).join('0');

if (flt === true) {
    integral = parseFloat(integral);
}
return precision ? integral + '.' + padding + fraction : integral;
}

var util = {};
util.format = function (value, prefix) {
var pr = prefix || '';
toks = roundNumber(value, 2).replace('-', '').split('.');
var display = pr + $.map(toks[0].split('').reverse(), function (elm, i) {
    return [(i % 3 === 0 && i > 0 ? ',' : ''), elm];
}).reverse().join('') + '.' + toks[1];

return value < 0 ? '-' + display : display;
};
ko.subscribable.fn.formatted = function (options) {
var target = this;
var _options = (options === undefined) ? {} : options;
var _prefix = hasOwnProperty(_options, 'prefix') ? _options.prefix : '';
var _precision = hasOwnProperty(_options, 'precision') ? _options.precision : 2;
var _type = hasOwnProperty(_options, 'type') ? _options.type : 2;

var format = function (value) {
    switch (_type) {
        case 1:
            return util.format(roundNumber(value, _precision), _prefix); //currency w/ symbol
        case 2:
            return roundNumber(value, _precision); //reg float
        default:
            throw new Error('illegal type');
    }
};

var focused = ko.observable(false);

var writeTarget = function (value) {
    var stripped = value;
    if (isNaN(value)) {
        stripped = String(value).replace(/[^0-9.-]/g, '');
    }

    //target(parseFloat(stripped));
    value = parseFloat(stripped);
    focused() ? target(!isFinite(value) ? 0 : value) : target(!isFinite(value) ? 0 : roundNumber(value, _precision)); // Write to underlying storage
};

var result = ko.computed({
    read: function () {
        return target();
    },
    write: writeTarget
});

result.formatted = ko.computed({
    read: function () {
        if (focused()) {
            return (isNaN(target()) ? '' : target()); // Write to underlying storage
        }
        return format(target());
    },
    write: writeTarget
});

result.isNegative = ko.computed(function () {
    return target() < 0;
});

result.isFocused = ko.computed({
    read: function () {
        return focused();
    },
    write: function (value) {
        focused(value);
    }
});


return result;
};



var ViewModel = function () {
var self = this;

self.retail_price = ko.observable(0).extend({
    throttle: 100
}).formatted({
    type: 1
});
self.selling_price = ko.observable(0).extend({
    throttle: 100
}).formatted({
    type: 1
});
self.discount = ko.observable(0);


// Whenever the retail price changes, change the selling price for jewelry
self.retail_price.subscribe(function () {
    if (parseFloat(self.selling_price()) !== 0) {
        self.updateDiscount();
        return;
    }

    if (self.retail_price.isFocused()) {
        self.changeSellingPrice();
    }

});

// Whenever the discount changes, change the selling price
self.discount.subscribe(function () {
    self.changeSellingPrice();
});

self.selling_price.subscribe(function (v) {
        var retPrice = (v * 100) / (100 - self.discount());
        self.retail_price(isNaN(retPrice) ? 0 : roundNumber(retPrice, 2));
        if (parseFloat(self.retail_price()) !== 0) {
            self.updateDiscount();
        }
});

self.updateDiscount = function () {

    var retPr = parseFloat(self.retail_price());
    var askPr = parseFloat(self.selling_price());
    var discount = 100 * (retPr - askPr) / retPr;
    self.discount(!isFinite(discount) ? 0 : roundNumber(discount, 2));

};

self.changeSellingPrice = function () {
    var sellingPrice = self.retail_price() - (self.retail_price() * self.discount()) / 100;
    self.selling_price(isNaN(sellingPrice) ? 0 : roundNumber(sellingPrice, 2));
};

};

ko.applyBindings(new ViewModel());