Skip to content

Commit

Permalink
Add SilverStripeComponent base class
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmorrisonnz committed Oct 29, 2015
1 parent bd33696 commit 55085c2
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 10 deletions.
7 changes: 7 additions & 0 deletions docs/en/index.md
@@ -0,0 +1,7 @@
# ReactJS Common

ReactJS Common exposes the required libraries and classes for building React components in SilverStripe CMS.

## Contents

- [SilverStripeComponent](silverstripe-component.md): The base class for SilverStripe React components.
136 changes: 136 additions & 0 deletions docs/en/silverstripe-component.md
@@ -0,0 +1,136 @@
# SilverStripeComponent

The base class for SilverStripe React components. If you're building React components for the CMS, this is the class you want to extend. `SilverStripeComponent` extends `React.Component` and adds some handy CMS specific behaviour.

## Creating a component

__my-component.js__
```javascript
import SilverStripeComponent from 'silverstripe-component';

class MyComponent extends SilverStripeComponent {

}

export default MyComponent;
```

And that's how you create a SilverStripe React component!

## Interfacing with ye olde CMS JavaScript

One of the great things about ReactJS is that it works great with DOM based libraries like jQuery and Entwine. To allow legacy-land script to notify your component about changes add the following to your component.

__my-component.js__
```javascript
import SilverStripeComponent from 'silverstripe-component';

class MyComponent extends SilverStripeComponent {
componentDidMount() {
super.componentDidMount();
}

componentWillUnmount() {
super.componentWillUnmount();
}
}

export default MyComponent;
```

This is functionally no different from the first example. But it's a good idea to be explicit and add these `super` calls now. You will inevitably add `componentDidMount` and `componentWillUnmount` hooks to your component and it's easy to forget to call `super` then.

So what's going on when we call those? Glad you asked. If you've passed `cmsEvents` and `cmsEventsNamespace` into your component's `props` then wonderful things will happen.

Let's take a look at some examples.

### Getting data into a component

Sometimes you'll want to call component methods based on events happening outside of your component. For example when a CMS button is clicked you want to highlight your component.

__main.js__
```javascript
import $ from 'jquery';
import React from 'react';
import MyComponent from './my-component';

$('.my-component-wrapper').entwine({
onadd: function () {
var props = {
cmsEventsNamespace: 'my-component',
cmsEvents: {
highlightComponent: function () {
this.setState({ highlight: true });
}
}
};

React.render(
<MyComponent {...props} />,
this[0]
);
}
});
```

__legacy.js__
```javascript
(function ($) {
$.entwine('ss', function ($) {
$('.cms-button').entwine({
onclick: function () {
$(document).trigger('my-component.highlightComponent');
}
});
}
}(jQuery));
```
Each key in `props.cmsEvents` gets turned into an event listener in `SilverStripeComponent.componentDidMount`. When that namespaced event is triggered on `document` in legacy-land, the associated callback is invoked, in your component. Each callback has the component's context bound so you can set state easily.
All `SilverStripeComponent.componentWillUnmount` does is remove the event listeners.
### Getting data out of a component
There are time you'll want to update legacy-land based on changes in your component too.
`SilverStripeComponent` has a handly method to help with this.
__my-component.js__
```javascript
import SilverStripeComponent from 'silverstripe-component';

class MyComponent extends SilverStripeComponent {
componentDidMount() {
super.componentDidMount();
}

componentWillUnmount() {
super.componentWillUnmount();
}

componentDidUpdate() {
this._emitCmsEvent('updateCmsHeading', { heading: this.state.headingName });
}
}

export default MyComponent;
```
__legacy.js__
```javascript
(function ($) {
$.entwine('ss', function ($) {
$('.cms-button').entwine({
onmatch: function () {
$(document).on('my-component.updateCmsHeading', function (e, data) {
$('.cms-heading').text(data.heading);
});
},
onunmatch: function () {
$(document).off('my-component.updateCmsHeading');
}
});
}
}(jQuery));
```
3 changes: 3 additions & 0 deletions gulpfile.js
@@ -1,16 +1,19 @@
var gulp = require('gulp'),
browserify = require('browserify'),
babelify = require('babelify'),
source = require('vinyl-source-stream'),
buffer = require('vinyl-buffer'),
uglify = require('gulp-uglify');

gulp.task('build', function () {

browserify()
.transform(babelify)
.require('react/addons', { expose: 'react' })
.require('flux', { expose: 'flux' })
.require('./public/src/jquery', { expose: 'jquery' })
.require('./public/src/i18n', { expose: 'i18n' })
.require('./public/src/silverstripe-component', { expose: 'silverstripe-component' })
.bundle()
.pipe(source('bundle.js'))
.pipe(buffer())
Expand Down
12 changes: 6 additions & 6 deletions public/dist/bundle.js

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions public/src/silverstripe-component.js
@@ -0,0 +1,63 @@
/**
* @file Base component which all SilverStripe ReactJS components should extend from.
*/

import React from 'react';
import $ from 'jquery';

class SilverStripeComponent extends React.Component {

/**
* @func componentDidMount
* @desc Add event listeners which are triggered by the outside (non-react) world.
* This lets us update the component when something happens in legacy JavaScript land.
*/
componentDidMount() {
if (typeof this.props.cmsEvents === 'undefined' ||
typeof this.props.cmsEventsNamespace === 'undefined') {
return;
}

// Save some props for later. When we come to unbind these listeners
// there's no guarantee these props will be the same or even present.
this.cmsEvents = this.props.cmsEvents;
this.cmsEventsNamespace = this.props.cmsEventsNamespace;

for (let eventName in this.cmsEvents) {
$(document).on(this.cmsEventsNamespace + '.' + eventName, this.cmsEvents[eventName].bind(this));
}
}

/**
* @func componentWillUnmount
* @desc Unbind the event listeners we added in componentDidMount.
*/
componentWillUnmount() {
for (let eventName in this.cmsEvents) {
$(document).off(this.cmsEventsNamespace + '.' + eventName);
}
}

/**
* @func _emitCmsEvent
* @param string eventName
* @param object|string|array|number [data] - Some data you want to pass with the event.
* @desc Lets the outside world (legacy JavaScript) know something's happened in our component.
*/
_emitCmsEvent(eventName, data) {
if (typeof this.cmsEventsNamespace === 'undefined' ||
typeof eventName === 'undefined') {
return;
}

$(document).trigger(this.cmsEventsNamespace + '.' + eventName, data);
}

}

SilverStripeComponent.propTypes = {
'cmsEvents': React.PropTypes.object,
'cmsEventsNamespace': React.PropTypes.string
};

export default SilverStripeComponent;
6 changes: 2 additions & 4 deletions readme.md
@@ -1,8 +1,6 @@
# SilverStripe ReactJS Common

Exposes ReactJS and Flux for use in SilverStripe CMS React Components.

Instead of bundling React a bunch of times, in a bunch of components, include this module and import React from one place.
ReactJS Common exposes the required libraries and classes for building React components in SilverStripe CMS.

## Install

Expand All @@ -14,7 +12,7 @@ $ composer require silverstripe/reactjs-common

Once you have the module installed, React and Flux are available throughout the CMS, and can be accessed through Browserify.

__/yourHipsterComponent/gulpfile.js__
__./my-component/gulpfile.js__

```
Expand Down

0 comments on commit 55085c2

Please sign in to comment.