Knockout + Select2 -- setting default values?
Asked Answered
P

2

7

TL;DR: I need to set a default value for display on a select2 field, bound via knockout, but the select2 binding keeps overriding my viewmodel value to "" instead of accepting the value.

The Ingredients

I am utilizing the following:

  • KnockoutJS
  • Select2 on input fields
  • Custom knockout binding to select2
  • An ajax call to load an object (an invoice) as part of a Start() method on my viewmodel.

The Goal

  • Load my values as part of the initial viewmodel's Start() function
  • Bind the select2 default values to the values of my VM at the time it loads the invoice.
  • Allow users to select other options as they choose
    • the default values wold be included in the select2 options as well due to the way we're bringing down the select2 values, so no need to

The Problem

  • Select2 would be working entirely fine if I was starting from a blank form. It loads my values from an Ajax call upon dropdown, etc.
  • However, when I load the invoice for display, the viewmodel values aren't set on the select2 controls.
    • It appears the select2 control actually loads the data and overwrites my viewmodel's value with "" when it loads, as a value hasn't been selected yet -- rather than letting me show a default item based on my bound value..

Thoughts so far on Trying to Solve it

I'll be investigating all of these:

  • I might not be properly using the knockout binding to allow for a default element choice that isn't a part of its values.
  • If there is a way I could verify that the select2 boxes are loaded and then trigger an element update, that would be fine, too.

The Code

Document Load

$(document).ready(function () {
    'use strict';

    console.log("creating viewmodel");
    vm = new invoiceDetailsPage.ViewModel();
    vm.Start();

    console.log("applying bindings");
    ko.applyBindings(vm);
});

The invoiceDetailsPage NameSpace(some irrelevant parts removed)

var invoiceDetailsPage = invoiceDetailsPage || {

    InvoiceDetailItem: function () {
        'use strict';
        var self = this;


        self.DatePayable = new Date(); 
        self.Fees = 0.00;
        self.Costs = 0.00;
        self.Adjustments = ko.observable();
        self.AdjustmentNote = ko.observable();
        self.Total = ko.computed(function () {

        });

        self.hasAdjustments = ko.computed(function () {

        });

    },


    Invoice: function (invoiceID, documentTypeID, firmID, invoiceNumber, invoicePeriod, datePayable, privateComment, statusID, vendorFirmID) {
        'use strict';
        var self = this;

        self.TypeID = ko.observable(documentTypeID);

        self.PrivateComment = ko.observable(privateComment);
        self.Status = ko.observable(statusID);
        self.FirmID = ko.observable(firmID);
        self.VendorFirmID = ko.observable(vendorFirmID);
        self.InvoicePeriod = ko.observable(invoicePeriod);
        self.DatePayable = ko.observable(datePayable);
        self.InvoiceNumbers = ko.observable(invoiceNumber);

        self.DetailItems = ko.observableArray([]);

        self.isFinalized = ko.computed(function () {
            //finalized if it has the appropriate status (anything except)
        });


        self.hasPrivateComments = ko.computed(function () {
            // if self.privatecomment isn't null or empty, true
        });

        self.TotalFees = ko.computed(function () {
            //foreach item in detailitems, sum of fees.
        });

        self.TotalCosts = ko.computed(function () {
            //foreach item in detailitems, sum of Costs.

        });

        self.TotalAdjustments = ko.computed(function () {
            //foreach item in detailitems, sum of adjustments.

        });

        self.GrandTotal = ko.computed(function () {
            //foreach item in detailitems, sum of totals.

        });

    },

    LoadInvoice: function (clientSiteID, invoiceID, callbackFunction, errorFunction) {
        'use strict';
        var self = this;

        self.clientSiteID = clientSiteID;
        self.invoiceID = invoiceID;


        $.ajax({
            url: '/api/DefenseInvoice/GetDefenseInvoice?ClientSiteID=' + self.clientSiteID + "&InvoiceID=" + invoiceID,
            type: 'GET',
            processData: false,
            contentType: 'application/json; charset=utf-8',
            dataType: "json",
            data: null,
            success: function (data) {
                console.log(data);
                callbackFunction(data);
            },
            error: function (jqXHR, textStatus, errorThrown) {
                errorFunction(jqXHR, textStatus, errorThrown);

            }
        });
    },

    ViewModel: function () {
        'use strict';
        var self = this;

        self.InvoiceLoaded = ko.observable();

        self.Invoice = ko.observable(new invoiceDetailsPage.Invoice()); // load blank invoice first

        self.clientSiteID = -1;
        self.invoiceID = -1;

        self.SaveInvoiceDetails = function () {
            // can only save the details prior to approval / rejection
            // should update only general invoice fields, not private comments or adjustments
        };

        self.LoadInvoice = function() {
            self.InvoiceLoaded(false);
            invoiceDetailsPage.LoadInvoice(self.clientSiteID, self.invoiceID, function(result) {
                //success
                vm.Invoice(new invoiceDetailsPage.Invoice(
                    result.InvoiceInfo.DefenseInvoiceID,
                    result.InvoiceDocumentTypeID,
                    result.InvoiceInfo.FirmID,
                    result.InvoiceInfo.InvoiceNumber,
                    result.InvoiceInfo.InvoicePeriod,
                    result.InvoiceInfo.DatePayable,
                    result.InvoiceInfo.PrivateComment,
                    result.InvoiceInfo.StatusID,
                    result.InvoiceInfo.VendorFirmID
                ));

                self.InvoiceLoaded(true);
            }, function() {
                //error
                toastr.error("We're sorry, but an error occurred while trying to load the invoice. Please contact support or refresh the page to try again.", "Invoice Approval");
                console.log("LoadInvoice -- ERROR");
                console.log("     error: " + errorThrown);
                toastr.clear(notifier);

            });
        };

        self.Start = function () {

            self.LoadInvoice();
        };
    },

    utils: {

        GetSelect2Options: function (placeholder, url) {
            'use strict';
            var options = {
                allowClear: false,
                placeholder: placeholder,
                query: function (query) {
                    var dto = {
                        query: query.term,
                        filters: {
                            ClientSiteID: Number(vm.clientSiteID)
                        }
                    };
                    $.ajax({
                        type: "POST",
                        url: url,
                        data: JSON.stringify(dto),
                        contentType: "application/json; charset=utf-8",
                        dataType: "json",
                        success: function (msg) {
                            query.callback(msg);
                        }
                    });
                }
            };
            return options;
        }
    }
};

The Knockout Binding we're using

ko.bindingHandlers.select2 = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        var obj = valueAccessor(),
            allBindings = allBindingsAccessor(),
            lookupKey = allBindings.lookupKey;
        $(element).select2(obj);
        if (lookupKey) {
            var value = ko.utils.unwrapObservable(allBindings.value);
            $(element).select2('data', ko.utils.arrayFirst(obj.data.results, function (item) {
                return item[lookupKey] === value;
            }));
        }

        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $(element).select2('destroy');
        });
    },
    update: function (element) {
        $(element).trigger('change');
    }
};

The HTML Element and its bindings

<input type="text" id="ddlInvoiceType" placeholder="Invoice Type" class="select2-container" data-bind="select2: invoiceDetailsPage.utils.GetSelect2Options('Invoice Type', '/api/DefenseInvoiceType/Post'), value: Invoice().TypeID"/>
Priapic answered 12/11, 2013 at 15:34 Comment(0)
T
1

Not sure I understood the question properly, at first I see the possible issue in the update part of the custom binding:

update: function (element, valueAccessor) {
  //added next row to update value
  $(element).val(ko.utils.unwrapObservable(valueAccessor()));

  $(element).trigger("change");
}
Tellus answered 12/11, 2013 at 15:52 Comment(3)
Hi Artem, thanks for jumping in. I'll try to add some screenshots. I'm thinking that the issue is that when the select2 box is wired up via my select2 knockout binding, it overwrites the viewmodel value to "" instead of loading its value from the ViewModel's Invoice().TypeID property.Priapic
Yes -- the issue is that then, it appears to be returning "[Object object]. In looking at it, I think part of the problem is that the valueAccessor appears to be returning a select2 object, and not the actual knockout binding, though I'm not sure what's going on. In chrome's debugger, I see properties like "allowClear: false" and "placeholder: "Invoice Type" " which are part of the select2 controlPriapic
For the init part we should also use: var val = ko.utils.unwrapObservable(valueAccessor()); $(element).val(val); Because var obj = valueAccessor() gives a function.Tellus
G
1

I got it working and I think the difference is in the init with an select like

<input type="hidden" class=""
    data-bind="value: observedValue, select2: --your options--">

Here is mine:

init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
        $(element).select2('destroy');
      });

      select2 = ko.utils.unwrapObservable(allBindings().select2);

  $(element).select2(select2);
},
Gatefold answered 23/6, 2015 at 20:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.