Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout + Select2 -- setting default values?

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"/>
like image 534
SeanKilleen Avatar asked Nov 12 '13 15:11

SeanKilleen


2 Answers

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");
}
like image 104
Artem Avatar answered Nov 15 '22 00:11

Artem


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);
},
like image 24
Assertnotnull Avatar answered Nov 14 '22 23:11

Assertnotnull