Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue/2824-react Added react template support #2963

Merged
merged 8 commits into from
Mar 11, 2021
Merged

Conversation

oliverfoster
Copy link
Member

@oliverfoster oliverfoster commented Nov 2, 2020

#2824 #2944

Added react support and handlebars-style react templates.

These plugins achieve react templating and integration in the same way we have been doing with handlebars:

Templates

Supports react 'templates' as near as I could get it to our current handlebars templates - separate, override-able etc. This change does not require a wholesale switch to React views but instead uses our current Backbone views and React as a templating engine. This means that some of the usual functionality of React views, such as the state object, aren't used to cause view re-render, instead the this.changed() function should be called to re-render a Backbone View with a React template.

Here are a few reasons for using React as a templating engine only:

  1. Our entire architecture uses Backbone events heavily - which React views don't support.
  2. Many of our bugs arise from jQuery DOM manipulation and using React to manage the DOM manipulation removes those bugs whilst introducing much more flexibility in the view <> state updates, this is as each manipulation is now automated and no longer needs to be coded by hand.
  3. Introducting jsx as a templating engine allows the upskilling requirement to be confined to templates only, which should be easier and less daunting for developers to confront.
  4. We will be able to convert to React at our leisure by not mandating a wholesale view layer rewrite.
  5. As React will be a part of the architecture we can better experiment with dropping Backbone as a large part of the architectural work is already complete.

Usage

A React version of the accordion lives here pr / branch

// This is an example template file in **/templates/**/name.jsx
// It will get registered in Adapt by filename as handlebars templates currently do
// Templates will override from theme, menu, extension, component as usual
// There is no partials distinction with the react templates, they are all just templates which can be called from one another

export default function (model, view) {
  return <div></div> // React component
};
// This is a template file in components/adapt-contrib-accordion/templates/accordion.jsx

// These functions help handlebars integration
import Adapt from 'core/js/adapt';
import { 
  compile, // Compile handlebars into html: compile(template, data);
  classes, // Helper for joining and filtering classes
  templates, // Lookup other react templates: templates[name](model, view);
  html, // Render html as react html:  html(`<div>new div</div>`); or html(compile('{{#if _prop}}<div>new div</div>{{/if}'));
  partial // Use a handlebars partial: partial(name, data);
} from 'core/js/reactHelpers';

export default function (model, view) {
  const data = model.toJSON();
  data._globals = Adapt.course.get('_globals');
  return <div className="component__inner accordion__inner">
    {templates.component(model, view)}
    <div className="component__widget accordion__widget">
      {data._items.map(({ _graphic, _classes, title, body, _index, _isVisited, _isActive }, index) => {
        return <div key={_index} className={classes([
              "accordion__item",
              _graphic.src && 'has-image',
              _classes && _classes
          ])} data-index={_index}>
          <button onClick={view.onClick.bind(view)} id={`${data._id}-${index}-accordion-button`} className={classes([
            'accordion__item-btn',
            'js-toggle-item',
            _isVisited && 'is-visited',
            _isActive ? 'is-open is-selected' : 'is-closed'
          ])} aria-expanded={_isActive ? 'true' : 'false'}>
            <div className="accordion__item-btn-inner">
              <div className="accordion__item-icon">
                <div className="icon"></div>
              </div>
              <div className="accordion__item-title">
                <div className="accordion__item-title-inner">
                  {html(title)}
                </div>
              </div>
            </div>
          </button>
          <div className="accordion__item-content" role="region" aria-labelledby={`${data._id}-${index}-accordion-button`}>
            <div className="accordion__item-content-inner">
              {body &&
              <div className="accordion__item-body">
                <div className="accordion__item-body-inner">
                  {html(compile(body))}
                </div>
              </div>
              }
              {_graphic.src &&
              <div className={classes([
                  'accordion__item-image-container',
                  _graphic.attribution && 'has-attribution'
              ])}>
                <img className="accordion__item-image" src={_graphic.src} aria-label={_graphic.alt} aria-hidden={_graphic.alt ? 'true' : 'false'} />
                {_graphic.attribution &&
                <div className="component__attribution accordion__attribution">
                  <div className="component__attribution-inner accordion__attribution-inner">
                    {html(_graphic.attribution)}
                  </div>
                </div>
                }
              </div>
              }
            </div>
          </div>
        </div>
      })}
    </div>
  </div>
}
// Example from accordion.
// adaptView is extended to support react templates, so all Adapt views can use react template as below.

import ComponentView from 'core/js/views/componentView';

class AccordionView extends ComponentView {

  preRender() {
    this.listenTo(this.model.getChildren(), {
      'all': this.changed // call changed to rerender
    });
  }

  /** missing code */

}

AccordionView.template = 'accordion.jsx'; // Just update the template name to use the react template

export default AccordionView;

Copy link
Contributor

@tomgreenfield tomgreenfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also missing changes to lockfile.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Contributor

@tomgreenfield tomgreenfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magnificent.

@moloko
Copy link
Contributor

moloko commented Jan 20, 2021

Can we hold off merging this until next week's FW meeting - just to give us a chance to talk about when & how to release this?

@tomgreenfield
Copy link
Contributor

tomgreenfield commented Feb 4, 2021

While this is on hold...

Feature request

Have the view redo its runtime properties/functions (i.e. attributes, className, id, tagName) when this.changed() is called.

Copy link
Contributor

@moloko moloko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, shall we get this merged in and released then?

@oliverfoster
Copy link
Member Author

I'd just like to test that it still works first. Give me an hour or so?

@oliverfoster oliverfoster merged commit a4a1023 into master Mar 11, 2021
@oliverfoster oliverfoster deleted the issue/2824-react branch November 24, 2021 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants