Spreadsheet-like functionality using Meteor

Let’s say I have many input fields that a user can fill out, and various math is done and results are shown in real time. E.g.:

[] x [] = (non editable value)
[] + [] = …

Since you can’t do math in Spacebars ({{someVal * anotherVal}}), I’m wondering what the best way to do this, because using helpers will make things messy pretty quickly, I think:

Template.page.helpers({
  totalSheepCost: function() {
    return Template.instance().sheepQty.get() * Template.instance().sheepPrice.get();
  });

  totalChickenCost: function() {
    return Template.instance().chickenQty.get() * Template.instance().chickenPrice.get();
  });
});

Or if not using ReactiveVar:

...
return $('#sheep-qty').val() * $('#sheep-price').val(); //might be a bad idea, or not even possible

There’s got to be a better way…

it’s not meteor specific, but I’d recommend handsOnTable. You can then reactively update specific cells as you like.

Well, I don’t literally mean a spreadsheet. Just a few input fields within a form, formatted with Bootstrap.

Another way of wording what I want to be able to do:

<input id="chicken-count" type="number"> x unit cost ${{chickenCost}} = ${{chickenTotal}}
Template.orderForm.helpers({
  chickenCost: function() {
    /* some default value stored in a collection or reactive var */
    return ....;
  },

  chickenTotal: function() {
    /* return the chickenCost * $('#chicken-count').val() */
  }
});

But imagine there were like 100 different farm animals. :slight_smile: I’m trying to think of an optimized way of doing this.

You can use parameters on your helpers, so you could make a general multiplication helper that accept params.

{{multiply sheepQty sheepCost}}

Have multiply return the product of the params. I’m assuming these values are present in your data context. Don’t forget to check for type.

Hmmmm. Ok, that helps quite a bit. But then where do sheepQty and sheepCost come from? I wish I could just directly reference an <input> element’s value and have that be reactive. I guess I can have a change event watch the inputs, and when they change, it collects the value and … does something.

This is where Angular’s ng-model would come in so handy.

<input ng-model="sheepQty" id="sheep-qty">

And then that sheepQty value is “live” and can be accessed directly.

I don’t know where they would come from, perhaps from a document in a collection or another helper. You could have another helper that accepts an id as a param and finds the value if that element of you don’t want to make a helper for hundreds of fields.

I think a client-side collection might be the way to go. Store all input values as they’re entered into the collection.

Agreed, feed all the logic into the local collection & then use your template to just spit out the values. In generic terms MVC terms, keep as much logic out of the view layer as possible.

1 Like

This is trivially easy with view models:

<body>
    Sheep Qty: <input data-bind="value: sheepQty"><br>
    Sheep Cost: <input data-bind="value: sheepCost"><br>
    Sheep Total: <label data-bind="text: sheepTotal"></label><br>
    <br>
    Chicken Qty: <input data-bind="value: chickenQty"><br>
    Chicken Cost: <input data-bind="value: chickenCost"><br>
    Chicken Total: <label data-bind="text: chickenTotal"></label><br>
    <br>
    Grand Total: <label data-bind="text: grandTotal"></label><br>
</body>
Template.body.viewmodel({
  sheepQty: 0,
  sheepCost: 5,
  sheepTotal: function() {
    return this.sheepQty() * this.sheepCost()
  },
  chickenQty: 0,
  chickenCost: 1,
  chickenTotal: function() {
    return this.chickenQty() * this.chickenCost();
  },
  grandTotal: function() {
    return this.sheepTotal() + this.chickenTotal();
  }
});

See http://viewmodel.meteor.com

4 Likes

Cool stuff! Too bad I already wrote my own solution. :slight_smile: Will definitely keep that package in mind for the future though.

I was also just about to recommend viewmodel.

There is also https://atmospherejs.com/deanius/worksheet definitely worth checking out.

1 Like

As a dinosaur, I’d say, “Don’t forget unobtrusive JS + naming conventions”