Getting into Sitecore JSS, Part 3: NPM and JSS

Posted 12/20/2018 by William Hubbell

Welcome back to our introductory series on getting into Sitecore JSS (and also React and some other basic web development tools)! In Part 1 we went over basic React development, and in Part 2 we covered UI Frameworks that utilize React. That was the easy part. Now we’re going to import our Carousel into a Sitecore JSS project. However, first we need to make our JSS project.

There are a couple of additional technologies you need to be familiar with before we can get started with JSS: NPM and Node.js. NPM allows you to import javascript libraries into a project through a command prompt, and Node.js allows you to easily host a local test server for your JSS project. Go to the Node website and install the latest LTS release. Setup is easy and there’s not really any configuration you have to do. If you’re so inclined, Microsoft’s guide for Node.js with Visual Studio code is helpful.

Next, we’re going to use NPM to create a JSS project. Let’s get our file structure in order first, though. You’re going to have one folder that contains one “node_modules” folder, in addition to folders for individual JSS projects. We’ll call this parent folder the “master” folder. This division is important to remember because when we import React-Bootstrap later, we’re going to import it into the master folder and not into the project folder. However, the JSS setup has you create your project folder then change directories into it. Don’t get confused, or you may not be able to import React-Bootstrap correctly later on.

While inside your master folder, right-click anywhere and select “open with Code.” Visual Studio Code will open up your master directory and show you its contents in the explorer window to the left. There shouldn’t be anything in it yet, unless you want to move your TGHelloWorld.html file in there. Now open a new Terminal window, using Powershell. You’ll automatically be in the Master Folder directory.

Follow Sitecore’s quickstart instructions for a JSS react project. Verify that node runs and the demo site works correctly in the browser. You can poke around the project here, seeing how “disconnected mode” works. If you want to make a simple “hello world” component for JSS, feel free to follow the Your First Component guide. However, I’m about to go over the same stuff in the process of importing the carousel.

Importing the Carousel into JSS

If you successfully followed the quickstart instructions for the JSS project, in the terminal you changed directory to the project folder and started the demo site with the “jss start” command. You should be able to see the demo site at http://localhost:3000/. 

Open a new terminal window in VS Code since your last one is busy running the Node server. Instead of using a CDN call for the React-Bootstrap framework, we’re going to download it through NPM and import it into the project. In your new terminal window, make sure you’re in the master folder and not the project folder. Enter in the two NPM statements (demarcated by the ‘$’ sign, which you don’t enter into the terminal) from the React-Bootstrap getting started page. This should install React and React-Bootstrap into the “node_modules” folder in your master folder. Now any JSS project you create in this folder can easily reference this library. 

We need to change the JSS demo project’s version of Bootstrap from 4 to 3. This is fairly simple. In the explorer window, navigate to Layout.js under the ‘src’ folder ([master folder]/[project folder]/src/Layout.js). Comment out line 9, the one that imports bootstrap. Then, scroll down to line 57, the opening <Helmet> tag inside the ‘Layout’ component. This <Helmet> component renders the <head> tag, so we’ll paste our CDN call for the Bootstrap 3 styling in here:

<Helmet>
     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"/>
     <title>
       {(route.fields && route.fields.pageTitle && route.fields.pageTitle.value) || 'Page'}
     </title>
   </Helmet>

I have to warn you: this makes our pretty demo site not-so-pretty anymore. Fortunately, React-Bootstrap support for Bootstrap 4 is in development. 

Next, open up package.json in the project folder and remove the “bootstrap” line under “dependencies”.

Lastly, open [project folder]\src\assets\app.css, and add the following styling info:

.centered-text{
 width: 25%;
 text-align: center;
}

.carousel img{
 width: 100%;
}

Perfect, thanks. Now we can import the carousel we made earlier. We’re actually going to split it into two different components: a Slide component and a Carousel component. Doing so will allow Sitecore content authors to add slides to the Carousel using the Experience Editor.

With your Terminal pointed to your project directory, enter the command “jss scaffold CarouselSlide”. This will run a script that automatically creates the necessary files for a new component in disconnected mode. In the terminal you should now see some suggested next steps, all of which we’re about to do. 

First we’ll navigate to [project folder]/sitecore/definitions/components/CarouselSlide.sitecore.js. Here we see the first wrinkle in developing React for Sitecore: the component’s manifest. The manifest is analogous to a template item in Sitecore, and is used when importing a disconnected JSS project into sitecore to automatically create the proper template items. In the manifest file, you specify the fields that a component will render. These fields correspond to the field types available in Sitecore. 

We want our slides to output the data fields we had specified for the carousel earlier: an image, a label, and a caption. These will all be of the field type “Single Line Text.” Our manifest file for the Slide, CarouselSlide.sitecore.js, should look like this:

// eslint-disable-next-line no-unused-vars
import { CommonFieldTypes, SitecoreIcon, Manifest } from '@sitecore-jss/sitecore-jss-manifest';

/**
* Adds the CarouselSlide component to the disconnected manifest.
* This function is invoked by convention (*.sitecore.js) when 'jss manifest' is run.
* @param {Manifest} manifest Manifest instance to add components to
*/
export default function(manifest) {
 manifest.addComponent({
   name: 'CarouselSlide',
   icon: SitecoreIcon.DocumentTag,
   fields: [
     { name: 'imgSrc', type: CommonFieldTypes.SingleLineText },
     { name: 'label', type: CommonFieldTypes.SingleLineText },
     { name: 'caption', type: CommonFieldTypes.SingleLineText },
   ],
 });
}

Next, we’ll implement the React component. Navigate to src/components/CarouselSlide/index.js. (Yes, “index.js”). I’m going to just give you the code here and explain it.

import React from 'react';
import { withSitecoreContext } from '@sitecore-jss/sitecore-jss-react';
import { Carousel } from '../../../../node_modules/react-bootstrap';

const CarouselSlide = (props) => (
 <Carousel.Item active={props.active} animateIn={props.animateIn} animateOut={props.animateOut} direction={props.direction} index={props.index} onAnimateOutEnd={props.onAnimateOutEnd}>
   <img
     className="d-block w-100"
     src={props.fields.imgSrc.value}
     alt={props.fields.label.value}
   />
   <Carousel.Caption>
     <h3>{props.fields.label.value}</h3>
     <p>{props.fields.caption.value}</p>
     </Carousel.Caption>
 </Carousel.Item>
);

export default withSitecoreContext()(CarouselSlide);

The first thing you should notice is the import statement at the top, where we’re bringing in Carousel from the React-Bootstrap library. This is a different syntax from earlier, when we imported it from the CDN. Now we’re importing from the local file structure.

The next thing to note is all of the props we’re specifying for the component. You could technically do this by spreading the props, which would look like this:

const CarouselSlide = ({...props}) => (
 <Carousel.Item {...props}>
	//etc

However, this would send ALL of the props, including a bunch of Sitecore-specific stuff that doesn’t need to be passed down to the markup level. You’ll get warning messages in the console. So I’ve specified only the props necessary to make the carousel work (I used the react devtools extension to see which ones those were. Very handy for debugging).

The actual markup/JSX inside the component is the exact same markup we had used to render the “slides” array in the vanilla react example above. However, the significant difference here is that instead of rendering a hard-coded JSON object, we’re rendering field data, which is passed down through props.

Finally, our export statement actually returns our component wrapped inside a Sitecore Context component, which allows for experience editor functionality.

Now we’re ready to scaffold out our actual Carousel component. We do it the same way, with a terminal command: “jss scaffold CarouselWrapper”. Open the manifest file for this component (CarouselWrapper.sitecore.js). This will look a bit different than our Slide component manifest. Here it is:

// eslint-disable-next-line no-unused-vars
import { CommonFieldTypes, SitecoreIcon, Manifest } from '@sitecore-jss/sitecore-jss-manifest';

/**
* Adds the CarouselWrapper component to the disconnected manifest.
* This function is invoked by convention (*.sitecore.js) when 'jss manifest' is run.
* @param {Manifest} manifest Manifest instance to add components to
*/
export default function(manifest) {
 manifest.addComponent({
   name: 'CarouselWrapper',
   icon: SitecoreIcon.DocumentTag,
   /*
   If the component implementation uses <Placeholder> or withPlaceholder to expose a placeholder, register it here, or components added to that placeholder will not be returned by Sitecore:*/
   placeholders: ['jss-carousel-slides'],
 });
}

You’ll note that there are no fields specified here. We COULD specify some of them, like a title or something, but for our purposes all we’re going to do is add a placeholder. This is one of the differences between MVC and JSS (at least the way we're doing it here): placeholders are specified on the template level.

Next, navigate to CarouselWrapper/Index.js. We’re going to finish implementing the Carousel here. Here’s my code:

import React from 'react';
import { withPlaceholder, withSitecoreContext} from '@sitecore-jss/sitecore-jss-react';
import { Carousel } from '../../../../node_modules/react-bootstrap';

class CarouselWrapper extends React.Component {
 constructor(props, context) {
     super(props, context);

     this.handleSelect = this.handleSelect.bind(this);

     this.state = {
     index: 0,
     direction: null,
     };
 }

 handleSelect(selectedIndex, e) {
     this.setState({
     index: selectedIndex,
     direction: e.direction,
     });
 }

 render() {
     const { index, direction } = this.state;
     const { slidesPlaceholder} = this.props;

     return (
       //TODO: Add some logic for EE so that slides stack on top of each other
       <Carousel activeIndex={index} direction={direction} onSelect={this.handleSelect}>
           {(slidesPlaceholder || [])
             .filter((slide) => slide.props && slide.props.fields)
             .map((slide) =>
             {return slide;}
             )}
       </Carousel>
     );
 }
}

// This is a _higher order component_ that will wrap our component and inject placeholder
// data into it as a prop (in this case, props.tabsPlaceholder).
// this another option compared to using the <Placeholder> component;
// in this case, chosen because we primarily need access to the _data_
// of the placeholder.
//Other ways to use placeholders: https://jss.sitecore.net/docs/client-frameworks/react/react-placeholders#react-placeholder-techniques
const carouselComponentWithPlaceholderInjected = withPlaceholder({
 placeholder: 'jss-carousel-slides',
 prop: 'slidesPlaceholder',
})(CarouselWrapper);

// We need to know if experience editor is active, to disable slide behavior for editing. (TODO referenced above)
// Using the same technique as injecting the placeholder, we wrap the component again to inject the
// `sitecoreContext` prop.
const carouselWithPlaceholderAndSitecoreContext = withSitecoreContext()(
 carouselComponentWithPlaceholderInjected
);

export default carouselWithPlaceholderAndSitecoreContext;

The comments in this file actually come from a component packaged with the JSS demo site, called Styleguide-Layout-Tabs-Tab, which I used as a model for the carousel. I recommend you read through this code to better understand how nested components work.

The actual CarouselWrapper component isn’t hugely different than the vanilla react one we created previously. However, there are some crucially important differences here. The most important one involves placeholders.

Look at the render return statement above: as before, we’re rendering an array of slide markup, and we’re using the .map() function to do it. However, this time the slide markup is in a separate component (CarouselSlide), and that component is referenced through a placeholder. At the bottom of our code here, you can see how the placeholder works: it simply wraps around our component and sends whatever is supposed to go in the placeholder as props into the Carousel.

I realize this isn’t the most intuitive thing in the world. As MVC developers we’re used to slapping a placeholder in a layout or rendering and letting Sitecore do the rest. It feels weird to reference child components and their data in the layout as opposed to referencing items in the content tree in an MVC controller. This also upsets the hierarchical nature of react syntax a bit - instead of constantly passing data downwards to children as props, we’re actually accepting our children as data and then operating on them and rendering them as we see fit. However, this isn’t the only way to utilize placeholders in JSS, and I recommend you utilize Sitecore’s documentation on this subject. 

In the above code, what I ultimately end up doing is returning the slide components as they are, with a simple “return slide” statement. Let’s break this down:

(slidesPlaceholder || [])
             .filter((slide) => slide.props && slide.props.fields)
             .map((slide) =>
             {return slide;}
             )

Actually, let’s simplify it a bit to get to heart of things:

slidesPlaceholder.map((slide) =>
{ return slide;}
)

Now it makes more sense, right? This effectively says "for all of the components in this placeholder array, return them as they are."
"But Will,” you inquire. “How do you know the components inside the placeholder array/component are all slide components?” 

The answer is in the routing data

Remember our hard-coded JSON object from the vanilla react carousel? Now it’s time to implement the disconnected JSS version of that. Navigate to [project folder]/data/routes, and open en.yml. Here is the routing data, which specifies both the data to be rendered and the components to render them. This is somewhat analogous to layout details in the Sitecore backend, where components are put into the appropriate placeholders.

Since our components are already set up, all we need to do is set up the routing data and they’ll appear on our site. In en.yml, add the following code between “jss-main” and the first “-componentName” declaration. 

YAML:

placeholders:
 jss-main:
 - componentName: CarouselWrapper
   placeholders:
     jss-carousel-slides:
     - componentName: CarouselSlide
       fields:
         imgSrc: '/data/media/img/grecopersianwars.png'
         label: 'Greco-Persian Wars'
         caption: 'The Greek World During the Persian Wars'
     - componentName: CarouselSlide
       fields:
         imgSrc: '/data/media/img/PeloponnesianWar.png'
         label: 'The Peloponnesian War'
         caption: 'Athens fought Sparta a long time ago'
     - componentName: CarouselSlide
       fields:
         imgSrc: '/data/media/img/PhilipIIConquest.png'
         label: 'Alexanders Kingdom'
         caption: 'Philip II, his father, united all of Greece, save Sparta'

Compare and contrast this with the JSON data from earlier:

//Test Data
var carouselSlides = {
           "slideObjs" : [
               {
                   "imgSrc" : 'images/grecopersianwars.png',
                   "label" : 'Greco-Persian Wars',
                   "caption" : 'The Greek World During the Persian Wars'
               },
               {
                   "imgSrc" : 'images/PeloponnesianWar.png',
                   "label" : 'The Peloponnesian War',
                   "caption" : 'Athens fought Sparta a long time ago'
               },
               {
                   "imgSrc" : 'images/PhilipIIConquest.png',
                   "label" : 'Alexanders Kingdom',
                   "caption" : 'Philip II, his father, united all of Greece, save Sparta'
               }],
       };

The main difference is that Sitecore is including layout information - placeholders and the components that go in them - as well. 

Keep in mind that indentation is very important for yaml files. Look at the rest of the routing file as a guide if you’re having trouble. The other thing you’ll need to do in the yaml file is replace the image paths, putting whatever images you want to render in the ‘/data/media/img’ folder. 

All you need to do now is save all your changes, and if your node server is still running you should see them reflected on the site immediately. Congratulations, you made a Bootstrap Carousel with React and JSS!

To Be Continued...

Thanks for sticking with us so far, from Part 1 to Part 2 to here. Hopefully you understand React, UI Frameworks, NPM, and JSS a lot better now. In our fourth and final part, we're going to come full circle by importing our disconnected JSS project into Sitecore. For now, I encourage you to experiment with all of the techniques and technologies we covered here today. 

Happy holidays!

Add your comment