TErenberger
TErenberger

Reputation: 33

Accessing objects within viewmodel Knockout

I'm using knockout for a single page app that does some basic calculations based on several inputs to then populate the value of some html . In an attempt to keep my html concise I've used an array of objects in my viewModel to store my form. I achieved the basic functionality of the page however I wish to add a 'display' value to show on html that has formatted decimal points and perhaps a converted value in the future.

I'm not sure of a 'best practices' way of accessing the other values of the object that I'm currently 'in'. For example: If I want my display field to be a computed value that consists of the value field rounded to two decimal places.

display: ko.computed(function()
{
  return Math.round(100 * myObj.value())/100;
}

I've been reading through the documentation for knockout and it would appear that managing this is a common problem with those new to the library. I believe I could make it work by adding the computed function outside of the viewmodel prototype and access the object by

viewModel.input[1].value()

However I would imagine there is a cleaner way to achieve this.

I've included a small snippet of the viewModel for reference. In total the input array contains 15 elements. The HTML is included below that.

var ViewModel = function()
{
  var self = this;
  this.unitType = ko.observable('imperial');
  this.input = 
  [
    {
      name: "Test Stand Configuration",
      isInput: false
    },
    {
      name: "Charge Pump Displacement",
      disabled: false,
      isInput: true,
      unitImperial: "cubic inches/rev",
      unitMetric: "cm^3/rev",
      convert: function(incomingSystem)
      {
        var newValue = this.value();
        if(incomingSystem == 'metric')
        {
          //switch to metric
          newValue = convert.cubicinchesToCubiccentimeters(newValue);   
        }
        else
        {
          //switch to imperial
          newValue = convert.cubiccentimetersToCubicinches(newValue);
        }
        this.value(newValue);
      },
      value: ko.observable(1.4),
      display: ko.computed(function()
      {
        console.log(self);
      }, self)
    }
  ]
};

__

<!-- ko foreach: input -->
    <!-- ko if: $data.isInput == true -->
    <div class="form-group">
            <div class="col-sm-6">
                    <label class="control-label" data-bind="text: $data.name"></label>
            </div>
            <div class="col-sm-6">
                <div class="input-group">
                <!-- ko if: $data.disabled == true -->
                    <input data-bind="value: $data.value" type="text" class="form-control" disabled>
                <!-- /ko -->
                <!-- ko if: $data.disabled == false -->
            <input data-bind="value: $data.value" type="text" class="form-control">
                <!-- /ko -->
                <!-- ko if: viewModel.unitType() == 'imperial'-->
            <span data-bind="text: $data.unitImperial" class="input-group-addon"></span>
                <!-- /ko -->
                <!-- ko if: viewModel.unitType() == 'metric' -->
            <span data-bind="text: $data.unitMetric" class="input-group-addon"></span>
                <!-- /ko -->
                </div>
            </div>
    </div>
<!-- /ko -->
<!-- ko if: $data.isInput == false -->
<div class="form-group">
    <div class="col-sm-6">
            <h3 data-bind="text: $data.name"></h3>
    </div>
</div>
<!-- /ko -->

Upvotes: 2

Views: 2701

Answers (2)

webketje
webketje

Reputation: 11006

If you want to read/ write to & from the same output, @Aaron Siciliano's answer is the way to go. Else, ...

I'm not sure of a 'best practices' way of accessing the other values of the object that > I'm currently 'in'. For example: If I want my display field to be a computed value that consists of the value field rounded to two decimal places.

I think there's a misconception here about what KnockoutJS is. KnockoutJS allows you to handle all your logic in Javascript. Accessing the values of the object you are in is simple thanks to Knockout's context variables: $data (the current context, and the same as JS's this), $parent (the parent context), $root(the root viewmodel context) and more at Binding Context. You can use this variables both in your templates and in your Javascript. Btw, $index returns the observable index of an array item (which means it changes automatically when you do someth. wth it). In your example it'd be as simple as:

<span data-bind="$data.display"></span>

Or suppose you want to get an observable w/e from your root, or even parent. (Scenario: A cost indicator that increases for every item purchased, which are stored separately in an array).

<span data-bind="$root.totalValue"></span>

Correct me if I'm wrong, but given that you have defined self only in your viewmodel, the display function should output the whole root viewmodel to the console. If you redefine a self variable inside your object in the array, self will output that object in the array. That depends on the scope of your variable. You can't use object literals for that, you need a constructor function (like the one for your view model). So you'd get:

function viewModel() {
  var self = this;
  self.inputs = ko.observableArray([
    // this builds a new instance of the 'input' prototype
    new Input({initial: 0, name: 'someinput', display: someFunction}); 
  ])
}
// a constructor for your 15 inputs, which takes an object as parameter
function Input(obj) { 
  var self = this; // now self refers to a single instance of the 'input' prototype
  self.initial = ko.observable(obj.initial); //blank
  self.name = obj.name;
  self.display = ko.computed(obj.fn, this); // your function
}

As you mentioned, you can also handle events afterwards, see: unobtrusive event handling. Add your event listeners by using the ko.dataFor & ko.contextFor methods.

Upvotes: 2

user2620028
user2620028

Reputation:

It appears as though KnockoutJS has an example set up on its website for this exact scenario.

http://knockoutjs.com/documentation/extenders.html

From reading that page it looks as though you can create an extender to intercept an observable before it updates and apply a function to it (to format it for currency or round or perform whatever changes need to be made to it before it updates the ui).

This would probably be the closest thing to what you are looking for. However to be completely honest with you i like your simple approach to the problem.

Upvotes: 0

Related Questions