Building a Swag Shop: The Landing Page

Since it has been a couple of weeks since we started the Reaction Swag Shop project, I'd like to give an update on how it's going and what we have achieved so far. I've been working mostly on the Landing Page epic, which is now almost finished. Now is a good opportunity to recap a bit on this.

In this post, I'll cover how to get started, how to set up data fixtures, and how to include images using the Meteor Asset API. I’ll cover the rest of the epic next week.

For those who haven't been following, you can start with the first post and check out the live shop and the repository on GitHub.

Getting started: creating a plugin

Whenever starting a Reaction project, the first thing you should do after cloning the source from GitHub is to create a plugin.

Create a plugin by running: reaction plugins create --name <your_project>.

This will generate a bare-bones plugin in /imports/plugins/custom/<your_project>, which will serve as a starting point for further customizations.

alt

Remember to always restart reaction after you create a new plugin.

Now let's dive into the details and see what we've done so far:

Setting up data fixtures

When implementing a shop, you'll almost inevitably face the need to reset your database from time to time during development. Just adding a new translation key value requires a database reset. For our swag shop, it’s clear that the database needs to be restored to an initial state—that’s where data fixtures come in. Reaction ships with default data fixtures in /private/data.

In many cases, you will want to have fixtures that come with real products, categories, and shop settings, since you probably won’t want to recreate all your products whenever the database is reset. For the swag shop, we also want to include product images as a part of the import fixtures, which is why our own fixtures live in private/data and private/images within our plugin code base.

alt

To prevent Reaction from sourcing the default data fixtures, start the server with the SKIP_FIXTURES environmental variable and import the data in /imports/plugins/custom/reaction-swag-shop/server/init.js:

Hooks.Events.add("beforeCoreInit", () => {  
  methods.loadShops();
  Logger.info("Finished loading Shop data");
});

Hooks.Events.add("afterCoreInit", () => {  
  methods.initLayout();
  methods.loadProducts();
  methods.loadTags();
  methods.loadShipping();
  methods.enableShipping();
  methods.enablePayment();
  methods.importProductImages();
  Logger.info("Finished loading the rest of the Demo data");
});

As you can see, the shop data is imported into the beforeCoreInit hook, whereas all other fixtures are imported at a later time into the afterCoreInit hook. This is because an existing shop record is crucial for the internal bootstrap code that sets up the Packages collection in the database.

You can read more about setting environment variables in the Reaction Docs: Configuration.

Using Meteor Asset API for image data fixtures

All fixtures in /private/data are in JSON format, which means they can be required like any other form of JavaScript. The build process will ensure that the data fixtures are available in our bundled application. However, this isn’t possible with non-code resources, like product images or other files in binary format, because the require functionality can’t be used here (respective to the ES6 import statement).

You can see how the /private/data/Shops.json file is required in the snippet below:

/imports/plugins/custom/reaction-swag-shop/server/methods.js:

methods.loadShops = function () {  
  Logger.info("Starting load Shops");
  if (!checkForShops()) {
    const shops = require("../private/data/Shops.json");
    shops.forEach((shop) => {
      Shops.insert(shop);
      Logger.info(`Inserted shop: ${shop.name}`);
    });
    Logger.info("Shops loaded");
  }
};

But this doesn't include images. So how do we include product images, then?

The solution is to use the Meteor Assets API.

Because Meteor expects private assets to live in the application's /private folder, we’ll need to copy them from our plugin's directory into place, as described in our installation docs.

Here’s the code we’re using to load the product pictures through the Meteor Assets API. Then, insert them into the Media collection.

/imports/plugins/custom/reaction-swag-shop/server/methods.js:

methods.importProductImages = function () {  
  Logger.info("Started loading product images");
  if (!checkForMedia()) {
    const products = Products.find({ type: "simple" }).fetch();
    for (const product of products) {
      const productId = product._id;
      if (!Media.findOne({ "metadata.productId": productId })) {
        const shopId = product.shopId;
        const filepath = `plugins/reaction-swag-shop/images/${productId}.jpg`;
        const binary = Assets.getBinary(filepath);
        const fileObj = new FS.File();
        const fileName = `${productId}.jpg`;
        fileObj.attachData(binary, { type: "image/jpeg", name: fileName });
        const topVariant = getTopVariant(productId);
        fileObj.metadata = {
          productId: productId,
          variantId: topVariant._id,
          toGrid: 1,
          shopId: shopId,
          priority: 0,
          workflow: "published"
        };
        Media.insert(fileObj);
      }
    }
    Logger.info("loaded product images");
  } else {
    Logger.info("Skipped loading product images");
  }
};

Technically, it wouldn't be necessary to copy the subfolder /imports/plugins/custom/reaction-swag-shop/private/data with the JSON fixtures, because they can be required directly from their original location. But it couldn't hurt, either.

Wrapping it up

Now that we have our data and image fixtures set up, we're ready to move onto building new features on top of images. In our next post, we'll be focusing on extending schemas (in this case, the Product schema), adding fields in Admin by replacing React components, and customizing Publications and Subscriptions.

We hope you've learned some insights on how to customize Reaction to fit your specific needs. If you feel like something's missing, or you're interested in further details, don't hesitate to reach out to us at our Gitter channel. We're always glad to give you advice wherever we can.

More to come, stay tuned.

comments powered by Disqus