0

I'm making a system that allows reorder of products. Each display area in a shop is a unit, which has several groups which (in turn) house several products. I am currently using knockout to load a unit via a select element, then cycling through the groups providing quantity fields for products.

This all works, but I need a select all button which sets all products in a group to have a quantity of 1. I have given the group object a function (allForOne()) which should cycle through each product in the group's observable array and set this to one, however, no matter how I bind this to the #allInOne button, it always applies this to the last group in the unit's observable group array.

For example, when the code first loads, I want to select all in the first group and make their quantity one, but it only changes the quantity in the last group.

I use the same selector in the same binding to "alert" the correct unit name (based on the unit's groupCounter property) before I call the function, but it returns two different values.

Why is it not affecting the current group? I can't work this out for the life of me and Google hasn't been much help.

Here is a JSFiddle, or you can see the code below:

Here is the view:

<div class="select-container">
<label for="unit" class="label">Unit</select>
<select name="unit" class="select-unit drop-shadow" data-bind='options: units, optionsText: "skuName", optionsCaption: "Select...", value: unit'></select>
<div>
</div>
<div class="unit-image" data-bind="with: unit">
<img data-bind="attr{ src: $parent.unit().image, alt: $parent.unit().name }">
</div>
<div data-bind="with: unit" class="clearfix products-container">
<div class="clearfix" data-bind="with: $parent.groups()[$parent.groupCounter()]">
    <!--<div data-bind="style: { width: (((parseInt(limit) / parseInt($root.totalLimit))  * 100) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}" style="display: block; position: relative; float: left;">-->
        <h2 data-bind="text: name" style="margin-bottom: 12px;"></h2>
        <div data-bind="foreach: {data: products/*, beforeRemove: hideProduct, afterRender: showProduct*/}">
            <div class="prod-page-product drop-shadow" data-bind="style: { width: ((100 / $parent.limit) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">
                <p><span data-bind='text: sku'></span></p>
                <div>
                    <div class="add-container"><a href='#' data-bind="click: incrementQuantity" class="add-product">+</a></div>
                    <div><input type="number" class="is-numeric" min="0" data-bind='value: quantity, valueUpdate: "afterkeydown"' /></div>
                    <div class="remove-container"><a href='#' data-bind="click: decrementQuantity" class="remove-product">-</a></div>
                </div>
            </div>
        </div>
    <!--</div>-->
    <div class="clearfix">
        <button id="nextProdGroup" class="products-button float-left" data-bind="enable:$root.firstGroupBool, click: $root.prevGroup, style: { width: productWidth, maxWidth: ((100/4) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">Prev Group</button>
        <button id="prevProdGroup"  class="products-button float-left" data-bind="enable:$root.nextGroupBool, click: $root.nextGroup, style: { width: productWidth, maxWidth :((100/4) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">Next Group</button>

        <!--This is the offending button binding-->
        <button id="allForOne" class="products-button float-left" data-bind="click: function() { alert('Expected Group: '+$root.groups()[$root.groupCounter()].name());  $root.groups()[$root.groupCounter()].allForOne()}, style: { width: productWidth, maxWidth :((100/4) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">This is what messes up</button>


    </div>
</div>
</div>
<div id="shadow-overlay" class="ui-overlay" data-bind="visible: loaderBool">
<div class="ui-widget-overlay"></div>
<div id="top-overlay" class="ui-overlay" style="width: 50%; height: 80%; position: absolute; left: 25%; top: 10%;"></div>
<div id="ajax-loading-container">
    <p class="ajax-loader-text">Loading...</p>
</div>

Here is the viewModel:

var Product = function(id, sku) {
    var self = this;
    //Properties
    self.id = ko.observable(id);
    self.sku = ko.observable(sku);
    self.quantity = ko.observable(0);

    //Methods
    self.incrementQuantity = function(product) {
    var previousQuantity = parseInt(self.quantity());
        self.quantity(previousQuantity+1);
    };
    self.decrementQuantity = function(product) {
    var previousQuantity = parseInt(self.quantity());
        if(self.quantity() > 0)
        {
            self.quantity(previousQuantity-1);
        }
    };
};


//The object with the called function
var Group = function(name, limit, productList)
{
    self = this;
    //Properties
    self.name = ko.observable(name);
    self.nametwo = name;
    self.limit = limit;
    self.products = ko.observableArray();
    self.productWidth = ko.pureComputed(function(a, b)
    {
        return ((100 / limit) - 1)+'%';
    });

    //Methods

    //---------The offending function
    self.allForOne = function() {
        alert("Returned Group: "+self.name());
        ko.utils.arrayForEach(self.products(), function(product) {
            product.quantity(1);
        });
    };


    //Initial population
    $.each(productList, function(key, product) {
        self.products.push(new Product(product.id, product.sku));
    });
}


var Unit = function() {
    var self = this;
    //Properties
    self.unit = ko.observable();
    self.groups = ko.observableArray();
    self.groupCounter = ko.observable(0);
    self.lastGroup = ko.observable(true);
    self.totalLimit = 0;
    self.saved = true;
    self.loaderBool = ko.observable(false);
    self.firstGroupBool = ko.pureComputed(function()
    {
        return (self.groupCounter() < 1 ? false : true);
    });
    self.nextGroupBool = ko.pureComputed(function()
    {
        return (self.lastGroup() == true ? false : true);
    });

    self.unit.subscribe(function() {
        self.populateGroup();
    });

    //Methods
    self.onLoadCheck = (function() {
        if(units.length == 1)
        {
            self.unit(units[0]);
        }
    });

    self.populateGroup = function() {
        self.loaderBool(true);
        self.groups([]);
        //setTimeout(function() {
            self.TotalLimit = 0;
            $.each(self.unit().group, function(groupKey, groupVal) {
                //setTimeout(function() {
                    self.groups.push(new Group(groupVal.name, groupVal.limit, groupVal.product));
                //}, 0);
                self.totalLimit += parseInt(groupVal.limit);
            });
            self.groupCounter(0);
            self.lastGroup(false);
            self.loaderBool(false);
        //}, 0);
        console.log(self.groups());
        console.log(self.groups()[self.groupCounter()]);
    };

    self.prevGroup = function(a, b) {
        self.save(a, b);
        var groupCounter = parseInt(self.groupCounter());
        self.groupCounter(groupCounter-1);
        if(self.groupCounter() != self.groups().length-1)
        {
            self.lastGroup(false);
        }
    };
    self.nextGroup = function(a, b) {
        self.save(a, b);
        var groupCounter = parseInt(self.groupCounter());
        self.groupCounter(groupCounter+1);
        if(self.groupCounter() == self.groups().length-1)
        {
            self.lastGroup(true);
        }
    };

    self.save = function(a, b) {
        var productsToSave = self.groups()[self.groupCounter()].products();
        var dataToSave = $.map(productsToSave, function(line) {
            return {
                id: line.id(),
                quantity: line.quantity()
            }
        });
        if(productsToSave.length == 0)
        {
            dialog("Error", "You cannot submit before you add products.");
            return false;
        }
        else
        {
            var caller = $(b.toElement);
            //sendOrder("baskets/update", {products: dataToSave}, caller);

            if(caller.attr("id") == "submitProdGroup")
            {
                window.location = baseUrl+"basket";
            }
        }
    };

};

1 Answer 1

2

Take a look at this: http://jsfiddle.net/rks5te7v/5/ I rewrote your code using a viewmodel like this (look at the url above):

function ViewModel() {
    var self = this;
    self.units = ko.observableArray();

    self.chosenUnit = ko.observable();
    self.chosenGroup = ko.observable(0);

    self.goToNextGroup = function () {
        self.chosenGroup(parseInt(self.chosenGroup()) + 1);
    };

     self.goToPrevGroup = function () {
        self.chosenGroup(parseInt(self.chosenGroup()) - 1);
    };

    self.setGroupAs1 = function () {
        var products = self.chosenUnit().groups()[self.chosenGroup()].products();
        ko.utils.arrayForEach(products, function (product) {
            product.quantity(1);            
        });
    };

    //loading data
    var product1 = new Product('1', 'P1');
    var product2 = new Product('2', 'P2');
    var product3 = new Product('3', 'P3');
    var product4 = new Product('4', 'P4');

    var group1 = new Group('G1', [product1, product2]);
    var group2 = new Group('G2', [product3, product4]);

    self.units.push(new Unit('Unit 1', [group1, group2]));
};

Does it solve your problem? :)

Sign up to request clarification or add additional context in comments.

1 Comment

You, sir, are a hero! Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.