Let's pass event object directly to the template. Um, how?


#1

Greetings! I am building an application for customizing the product grid order of an e-commerce website. Being able to present product order in responsive and flexible ways, would really make it easier for project managers to create a better experience for their customers.

Part of this grid order application is based on an article by Ryan Glover (to whom I owe many thanks), called Importing CSV’s https://themeteorchef.com/tutorials/importing-csvs, using PapaParse.

I am a UX Developer, and still being relatively new to Meteor, I am developing the MVP (minimally viable product), in a very vanilla, or stripped down way - first, and then refactoring it.

Here’s my pretty stripped down question: How do I get the event object into the template? I get the event object fine from the Papa.parse() function, and could console.log(it). But, how do I pass the event object into the template.

import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import './main.html';

// NOTE: trying to figure out how to pass event object to template
Template.upload.events({
  'change [name="uploadCSV"]' ( event, template ) {
  // Handles the conversion and upload
    var fileInput = document.querySelector('[name="uploadCSV"]');

    // Parse local CSV file
    Papa.parse(fileInput.files[0], {
      header: true,
    	complete: function(results) {
    		// console.log(results); // includes data, error, and misc

// NOTE: This is the data object to send to the template
        let itemData = results.data;
        console.log(itemData) // here is the data object

        // This test correctly iterates over the object, but should be done in the template
        itemData.forEach(function(item) {
          console.log(item)
          // console.log(item['itemcode'])
        });

// HELP! How do I send the object itemData to the template?

    	} // END complete
    }); // END parse

  } // END change
}) // END events

Here is a link to the repo. https://github.com/dylannirvana/gridorderapp/tree/master/meteor/vanilla

This has got to be embarrassingly easy. Many thanks in advance!


#2

The answer is to use the ReactiveVar you are already importing.

In your template’s onCreated define a new reactive var to hold the result.

this.something = new ReactiveVar();

Then, in your event, set the variable using the template parameter

template.something.set(itemData);

You can then use the data within a reactive context, such as a template helper. Note that the reactive var is referenced with

Template.instance().something.get();

in a template helper.

Apologies for the formatting - I’m on my phone at the moment.


#3

Thank you @robfallows. Looks right; I am going to give this a swirl right now…


#4

Very close. I keep hitting a type error. Updating repo now.

Uncaught TypeError: Cannot read property 'set' of undefined
    at Object.complete (main.js:46)

#5

Can you post your onCreated function block?


#6
import { ReactiveVar } from 'meteor/reactive-var';
import './main.html';

Template.grid.onCreated( () => {
  Template.instance().grid = new ReactiveVar();
})

Template.upload.helpers({

})

Template.upload.events({
  'change [name="uploadCSV"]' ( event, template ) {
    var fileInput = document.querySelector('[name="uploadCSV"]');

    Papa.parse(fileInput.files[0], {
      header: true,
    	complete: function(results) {

        let itemData = results.data;
        console.log(itemData)

        // return template.grid.set(itemData)
        Template.instance().grid.set(itemData)

    	} // END complete
    }); // END parse

  } // END change
}) // END events

#7

Check my post again. In the onCreated you need to use

this.grid = new ReactiveVar();

#8

Still getting the TypeError for the setter.

          Template.instance().grid.set(itemData)
        }```

_Uncaught TypeError: Cannot read property 'set' of undefined_

The `console.log(itemData)` is fine. Just no yet rendered data to the template.

I cleaned up main.js though, and updated the GitHub repo: https://github.com/dylannirvana/gridorderapp/tree/master/meteor/vanilla

#9

I’m finally able to read this on a computer and I see the problem: you have different templates for the ReactiveVar instantiation (grid) and use (upload). So, the template contexts are not the same (this in grid is not Template.instance() in upload).

There are ways to resolve this cleanly (for example, passing the context down the templates, much like props in React), but the easiest (not clean) way is to use a global ReactiveVar. So, instead of using this.grid and Template.instance().grid, use window.grid everywhere.

Hopefully, that will get you started.


#10

Thank you @robfallows. I cleaned things up in the repo. Put everything in the same template context. Tried ReactiveVar with a setter (TypeError). Made a collection. Subbed out the window object. Close but still no cigar.

Template.grid.onCreated( () => {
  Template.instance().grid = new ReactiveVar();
})

Template.grid.helpers({  // All the same template now
  grid() {
    // return Template.instance().grid.get(); 
    // return Grid.find({})
  },
})

Template.grid.events({
  'change [name="uploadCSV"]' ( event, template ) {
    const inventory = event.target.files[0];

    Papa.parse(inventory, {
      header: true,
    	complete: function(results) {

        let itemData = results.data;
        console.log(itemData); // works

        Template.instance().grid.set(itemData) // Why doesn't this work? 
        // console: grid.js:28 Uncaught TypeError: Cannot read property 'grid' of null
    at Object.complete (grid.js:28) // ugh!

        // // With collection
        // Grid.insert({
        //   itemData,
        // })

    	} // END complete
    }); // END parse

  } // END change
}) // END events

#11

The problem in the event handler is that you’re calling Template.instance in the callback of an async method, at which time, you’re no longer in the template’s scope and Template.instance won’t work.
Either save it to a variable before making the async call, or better, use the one passed into the handler as the second argument:

Template.grid.events({
  'change [name="uploadCSV"]' ( event, template ) {
    const inventory = event.target.files[0];

    Papa.parse(inventory, {
      header: true,
    	complete: function(results) { // Because this function is called later, it doesn't run in this scope

        let itemData = results.data;
        console.log(itemData); // works

        template.grid.set(itemData) // use the instance passed as second paramater
    	} // END complete
    }); // END parse

  } // END change
}) // END events

I wonder if part of this also stems from the use of an arrow funciton in onCreated, since the this binding is actually important in lifecycle hooks. I assumed that you could still use Template.instance inside it just fine but maybe not?
Try using a regular function instead, where this is the template instance:

Template.grid.onCreated(function () {
    this.grid = new ReactiveVar();
})

Or if you prefer being explicit, keep the Template.instance(), but still use a regular function.