Using React and Redux inside Alfresco Share

Using React and Redux inside Alfresco Share

Sergey Palyukh
10. Jul ‘20

alfresco-react-redux.jpg

The article for Alfresco developers that describes how to inject React and Redux into custom Alfresco Share page. It is a step by step guide which covers requirements for using React in Alfresco Share, a description of how to compile jsx files and how to define React components via Dojo

1. Add surf-module to your Alfresco Share add-on

You can introduce with extension modules by the link https://docs.alfresco.com/6.0/concepts/dev-extensions-share-surf-extension-modules-introduction.html (as for example, path to the file with extension can be ${path_to_your_share_module}/src/main/resources/alfresco/web-extension/site-data/extensions/share-reactsample-customization.xml)

Code module snippet below:


	FlexSolution-ReactJS-lib
	1.0
	true
	
	    
	        
	            
	                
	                    
	                    
	                    
	                    
	                    
	                
	            
	        
	    
	

2. Download required React libraries

You must download the following libraries:

  • react.js
  • redux.js
  • react-redux.js
  • react-dom.js

You can find libs on this site https://cdnjs.com/libraries

Then put files into ${path_to_your_share_module}/src/main/resources/web/js/lib folder, also create an empty folder called react-components there (we will use it later for our custom .jsx files).

3. Implement your business logic in jsx files

By way of example, we have implemented pretty straightforward logic which allows to watch the list of portfolio items and search by name. Copy content of the files bellow into ${path_to_your_share_module}/src/main/resources/web/js/lib/react-components folder or implement you own business logic based on these examples.

  • Form.jsx:

const Form = (React,
              store,
              Articles,
              Search) => {
  return {

    action(type, value, selectedName) {
      return { type: type, [selectedName]: value};
    },

    onSearch(searchTerm) {
      store.dispatch(this.action("SEARCH_SET", searchTerm, 'searchTerm'));
    },

    filterArticle(searchTerm, articles) {
      let includes = articles.filter(article => article.title.toLowerCase().includes(searchTerm.toLowerCase()));

      if (searchTerm === '' && includes.length === 0 ) {
        includes = articles;
      }

      store.dispatch(this.action("UPDATE_ARTICLE_SET", includes, 'articles'));

      return includes;
    },


    render() {
      const Form = this.props;

      return (
      
this.onSearch(e)}>

Search

); } } }; define([ 'dojo/_base/declare', 'react', "redux", "react-redux", "react-components/Store", "react-components/article/Articles", "react-components/search/Search", ], function (declare, React, Redux, ReactRedux, Store, Articles, Search) { const mapStateToProps = state => ({ initArticles: state.articlesState.initArticles, searchTerm: state.searchState.searchTerm, }); let store = Store.prototype; let FormDeclared = declare( "Form", [React.Component], Form(React, store, Articles, Search)); return ReactRedux.connect(mapStateToProps)(FormDeclared) });
  • Reducer.jsx

define([
    'dojo/_base/declare',
  "redux",
    'react-components/article/ArticleReducer',
    'react-components/search/SearchReducer'
], function (declare,
             redux,
             ArticleReducer,
             SearchReducer) {

  return declare("Reducer", null, {
    rootReducer: function rootReducer() {
      let articleReducer = ArticleReducer.prototype.articleReducer;
      let searchReducer = SearchReducer.prototype.searchReducer;

      return redux.combineReducers({
        articlesState: articleReducer,
        searchState: searchReducer
      });
    }
  });
});

  • Runner.jsx

require([
  `react`,
  `react-dom`,
  "redux",
  "react-redux",
  'react-components/Form',
  'react-components/Store'
], function (React, ReactDOM, Redux, ReactRedux, Form, Store) {

  let store = Store.prototype;

  ReactDOM.render(
      
        
, document.getElementById('reactRender') ); });
  • Store.jsx

define([
  'dojo/_base/declare',
  'react',
  "redux",
  "react-redux",
  "react-components/Reducer"], function (declare,
                                    React,
                                    Redux,
                                    ReactRedux,
                                    Reducer) {

  const store = Redux.createStore(Reducer.prototype.rootReducer());

  return declare("Store", null, store);

});

  • article/Article.jsx

define(['dojo/_base/declare','react'], function (declare, React) {

  return declare("Article", [React.Component], {

    render() {
      return (
          
            {this.props.article.title}
          
      );
    }
  });
});

  • article/Articles.jsx

const Articles = (
              React,
              Article) => {
  return {

    render() {
      const articles = this.props;
      return (
          
    {articles.articles.map(article =>
  • )}
); } } }; define([ 'dojo/_base/declare', 'react', "redux", "react-redux", "react-components/article/Article" ], function (declare, React, Redux, ReactRedux, Article) { return declare( "Articles", [React.Component], Articles(React, Article)); });
  • article/ArticleReducer.jsx

define(['dojo/_base/declare'], function (declare) {

  const INITIAL_STATE = {
    initArticles: [
      { id: '0', title: 'EXTRA DASHBOARD LAYOUTS ', url: 'https://flex-solution.com/page/alfresco-solution/extra-layouts' },
      { id: '1', title: 'DOCPROCESSOR INTEGRATION ADD-ON', url: 'https://flex-solution.com/page/alfresco-solution/docprocessor-integration-add-on' },
      { id: '2', title: 'ALFRESCO MULTIDASHBOARDS ADD-ON', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-multidashboards-add-on' },
      { id: '3', title: 'RESET PASSWORD ADD-ON', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-reset-password-add-on' },
      { id: '4', title: 'ALFRESCO SITES LOGO CHANGER', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-site-logo-changer' },
      { id: '5', title: 'ALFRESCO SOCIAL LOGIN ADD-ON', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-social-login-addon' },
      { id: '6', title: 'SELF REGISTRATION ADD-ON', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-sign-up-addon' },
      { id: '7', title: 'ALFRESCO SHARE THEME BUILDER', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-share-theme-builder' },
      { id: '8', title: 'VIEWCOVID', url: 'https://flex-solution.com/page/portfolio/viewcovid' },
      { id: '9', title: 'INTERCHANGE', url: 'https://flex-solution.com/page/portfolio/interchange-alfresco' },
      { id: '10', title: 'RUBBYHASUI', url: 'https://flex-solution.com/page/portfolio/rubbyhasui-vaadin14' },
      { id: '11', title: 'DOCPROCESSOR', url: 'https://flex-solution.com/page/portfolio/docprocessor' },
    ],
  };

  return declare("ArticleReducer", null, {

    articleReducer: function articleReducer(state = INITIAL_STATE, action) {
      switch (action.type) {
        case 'UPDATE_ARTICLE_SET':
          return {...state, articles: action.articles};
        case 'RESET' :
          return state = INITIAL_STATE
        default:
          return state;
      }
    }

  });
});

  • search/Search.jsx

define(['dojo/_base/declare','react'], function (declare, React) {

  return declare("Search", [React.Component], {

    render() {
      return (
          
{this.props.children} this.props.onSearch(event.target.value)} type="text" className={"flex-input"}/>
); } }); });
  • search/SearchReducer.jsx

define(['dojo/_base/declare'], function (declare) {

  const INITIAL_STATE = {
    searchTerm: '',
  };

  return declare("SearchReducer", null, {

    searchReducer: function searchReducer(state = INITIAL_STATE, action) {
      switch (action.type) {
        case 'SEARCH_SET':
          return {...state, searchTerm: action.searchTerm};
        default:
          return state;
      }
    }

  });
});

The declare function is defined in the dojo/_base/declare module. declare accepts three arguments: `className`, `superClass`, and `properties`.

The className as an example ('Form', 'Articles') argument represents the name of the class, including the namespace, to be created. Named classes are placed within the global scope. The className can also represent the inheritance chain via the namespace.

As an example class Form has an inheritance from React.Component. An array of classes signifies multiple inheritances. Properties and methods are inherited from left to right. The first class in the array serves as the base prototype, then the subsequent classes are mixins to that class.

If a property or method is specified in more than one inherited class, the property or method from the last inherited class is used.

4. Install Babel

The babel is required for compilation of jsx files. Execute the following commands from the root folder of your extension (pay attention that Node.js must be already installed on your computer):


npm init -y
npm install babel-cli@6 babel-preset-react-app@3

5. Compile .jsx files

Those files above must be compiled with Babel. Run the following command from ${path_to_your_share_module}/src/main/resources/web/js/lib/react-components:

npx babel ./ -d ./ --presets react-app/prod

6. Initialize Alfresco Share page

Next we need to configure a page where our React application will be rendered. To do we have to create 3 files: page descriptor, template instance, page template.

  • ${path_to_your_share_module}/src/main/resources/alfresco/web-extension/site-data/pages/react-sample.xml



    React Sample
    page.reactSample.title
    React Sample with Redux(store,connect)
    page.reactSample.description
    react-sample
    none

  • ${path_to_your_share_module}/src/main/resources/alfresco/web-extension/site-data/template-instances/react-sample.xml



    org/alfresco/react-sample
    

        
            react-sample
            /components/share-reactsample/react-sample
        

    

  • ${path_to_your_share_module}/src/main/resources/alfresco/web-extension/templates/org/alfresco/react-sample.ftl

<#include "include/alfresco-template.ftl" />

<@templateHeader>
    <@link rel="stylesheet" type="text/css" href="${url.context}/res/share-reactsample/react-sample/style.css" />
    <@script type="text/javascript" src="${url.context}/res/js/lib/react-components/Runner.js">



<@templateBody>

    <@markup id="alf-hd">
        
<@region scope="global" id="share-header" chromeless="true"/>
<@markup id="bd">
<@templateFooter> <@markup id="alf-ft">
<@region id="footer" scope="global" />

Pay attention to the div with id reactRender. It is the main div where our react app will be rendered

The whole project structure is shown on the image below:

That's all what you need to do. Then just compile amp and deploy it to your Alfresco Share. New page will be available by the following URL - http://localhost:8080share/page/react-sample. The final result is presented on the screenshot below:

Source Code

The source code of the module you can clone from our GitHub repo - https://github.com/FlexSolution/AlfrescoShareReactPageSample

Have any questions? Don't hesitate to contact us, we are here and ready to help!