Template Files
Templates Directory
Custom Templates
Customize Stencil Checkout
Handlebars Syntax and Helpers
Handlebars.js
Handlebars Helpers
Stencil Object Model
Stencil Objects

Advanced NPM Example

Video of What We’re Building

Install Dependencies

Material-UI requires the react-tap-event-plugin module. Also, document-register-element is needed to polyfill document.registerElement. The babel presets and plugins are needed to support Object.assign, react, and Material-UI, respectively:

npm install -save-dev document-register-element material-ui react react-dom react-tap-event-plugin  
npm install -save-dev babel-plugin-transform-object-assign babel-preset-react babel-preset-stage-1

Import Dependencies

Next, import the new dependencies into <theme-name>/assets/js/app.js

Note the CouponDrawer import at the bottom. This file doesn’t yet exist, but we’ll shortly create a React component with this name:

app.js imports
app.js imports

Update webpack.conf.js

Update webpack.conf.js with the new presets and plug-ins, as shown here:

webpack.config.js: presets and plugins
webpack.config.js: presets and plugins

Update app.js

Add the following code to the bottom of <theme-name>/assets/js/app.js:

window.initReact = function(contextJSON = '{}') {
    injectTapEventPlugin();
    const context = JSON.parse(contextJSON);
    const proto = Object.create(HTMLElement.prototype, {
        attachedCallback: {
            value: function() {
                const mountPoint = document.createElement('span');
                const attrs = [].reduce.call(this.attributes, (memo, attr) => {
                    memo[attr.name] = attr.value;
                    return memo;
                }, {});
                const data = Object.assign({}, context, attrs);

                this.appendChild(mountPoint);
                ReactDOM.render(
                    <MuiThemeProvider>
                        <CouponDrawer data={data} />
                    </MuiThemeProvider>,
                    mountPoint
                );
            }
        }
    });
    document.registerElement('x-coupon-drawer', {prototype: proto});
}

This sets up a handler for attaching an <x-coupon-drawer> element to the page. We’re using React here to render the CouponDrawer component. This block of code was taken and modified from https://facebook.github.io/react/docs/web-components.html#using-react-in-your-web-components.

Add the <x-coupon-drawer> Element to the Page

Add this in <theme-name>/templates/layout/base.html. (See the image below.) We’re using this layout template for this example, although you would follow the same steps in any other template.

Call initReact from base.html

Add a call to window.initReact. We’re continuing to work with the base.html page for this example. Notice that we’re also passing in the jsContext here. The initReact method will merge this context with an object created from the attributes placed upon the <x-coupon-drawer>, and will pass the combined data along to the React component.

base.html setup
base.html setup

Create an assets/js/components Folder

We’ll store our React components here.

Create a CouponDrawer.js File

Create a <theme-name>/assets/js/components/CouponDrawer.js file. Populate this file with the following code block (which is adapted from http://www.material-ui.com/#/components/drawer):

import React from 'react';
import Drawer from 'material-ui/Drawer';
import AppBar from 'material-ui/AppBar';
import RaisedButton from 'material-ui/RaisedButton';
import VerticalLinearStepper from './VerticalLinearStepper';

export default class CouponDrawer extends React.Component {

  constructor(props) {
    super(props);
    this.state = {open: false};
  }

  handleToggle = () => this.setState({open: !this.state.open});

  render() {
    return (
      <div>
        <RaisedButton
          label="Click here to get a coupon!"
          onTouchTap={this.handleToggle}
        />
        <Drawer width={200} openSecondary={true} open={this.state.open} >
          <AppBar title="Coupon magic" titleStyle={{fontSize: 14}} />
          <VerticalLinearStepper />
        </Drawer>
      </div>
    );
  }
}

Create a VerticalLinearStepper.js File

Create a <theme-name>/assets/js/components/VerticalLinearStepper.js file. Populate this file with the following code block:

import React from 'react';
import {
  Step,
  Stepper,
  StepButton,
  StepContent,
} from 'material-ui/Stepper';
import RaisedButton from 'material-ui/RaisedButton';
import FlatButton from 'material-ui/FlatButton';
import TextField from 'material-ui/TextField';

/**
 * A basic vertical non-linear implementation
 */
class VerticalLinearStepper extends React.Component {

  constructor(props) {
      super(props);
  }

  state = {
    stepIndex: 0,
  };

  handleNext = () => {
    const {stepIndex} = this.state;
    if (stepIndex < 2) {
      this.setState({stepIndex: stepIndex + 1});
    }
  };

  handlePrev = () => {
    const {stepIndex} = this.state;
    if (stepIndex > 0) {
      this.setState({stepIndex: stepIndex - 1});
    }
  };

  renderStepActions(step) {
    return (
      <div style={{margin: '12px 0'}}>
        <RaisedButton
          label="Next"
          disableTouchRipple={true}
          disableFocusRipple={true}
          primary={true}
          onTouchTap={this.handleNext}
          style={{marginRight: 12}}
        />
        {step > 0 && (
          <FlatButton
            label="Back"
            disableTouchRipple={true}
            disableFocusRipple={true}
            onTouchTap={this.handlePrev}
          />
        )}
      </div>
    );
  }

  render() {
    const {stepIndex} = this.state;

    return (
      <div style={{maxWidth: 380, maxHeight: 400, margin: 'auto'}}>
        <Stepper
          activeStep={stepIndex}
          linear={false}
          orientation="vertical"
        >
          <Step>
            <StepButton onTouchTap={() => this.setState({stepIndex: 0})}>
              Do this
            </StepButton>
            <StepContent>
              <p>
                Navigate to <a href="http://example.com">here</a> and get your code.
              </p>
              {this.renderStepActions(0)}
            </StepContent>
          </Step>
          <Step>
            <StepButton onTouchTap={() => this.setState({stepIndex: 1})}>
              Then this
            </StepButton>
            <StepContent>
              <TextField hintText="Enter your code here" style={{height: 100, width: 100}}/>
              {this.renderStepActions(1)}
            </StepContent>
          </Step>
          <Step>
            <StepButton onTouchTap={() => this.setState({stepIndex: 2})}>
              Collect your coupon!
            </StepButton>
            <StepContent>
              <p>
                <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9b/Wikipedia_mobile_en.svg/296px-Wikipedia_mobile_en.svg.png"/>
              </p>
              {this.renderStepActions(2)}
            </StepContent>
          </Step>
        </Stepper>
      </div>
    );
  }
}

export default VerticalLinearStepper;

The above code block was adapted from http://www.material-ui.com/#/components/stepper.

Note on the VerticalLinearStepper.js Example

We must register a custom element to set up the “root” of every React component we create. However, within a React component, we can import other React components without having to register them. For example, here we register x-coupon-drawer as a custom element that renders the React CouponDrawer component. However, within CouponDrawer, we can simply import the VerticalLinearStepper component needing to set it up in the same way.