Settings
new and improved
“A presentation is something you write to give people information. Presentations are for important things. A presentation can bring people together. A presentation can be a call to arms, a manifesto, a poem. A presentation can change the world. A presentation is something you write to give people information. Presentations are for important things. A presentation can bring people together. A presentation can be a call to arms, a manifesto, a poem. A presentation can change the world.”
Foreword from Sergey
Diagram of settings

const contentConfig = require('./../../config/apps/form_builder/content').default;
const ObservableStore = require('./../../config/apps/form_builder/observable_store').default;
self.observableStore = new ObservableStore();
self.observableStore.model = this.model;
self.observableStore.experiments = window.GLOBALS;
const content = ReactDOM.render(
<PowrSection componentsList={contentConfig} store={self.observableStore} />,
document.querySelector('#powr-form-builder-content')
);

This is how settings are built
PowrSection
- Is the main Mobx Observer.
- All the sections share the same store. Each section knows about the model which is used in entire settings. We are still using the backbone model.
- So the PowrSection has two main things in it. One is a random number and the second thing is all the children elements of the sections created from the Config.
- This is responsible for reading the Config for each section and then recursively rendering the contents within.
- It also exports a function which will help you recursively create child elements.
MOBX
React and MobX together are a powerful combination. React renders the application state by providing mechanisms to translate it into a tree of renderable components. MobX provides the mechanism to store and update the application state that React then uses.
https://mobx.js.org/getting-started.html

https://www.sitepoint.com/javascript-decorators-what-they-are/
import React, {Fragment} from 'react';
import {observer} from 'mobx-react';
@observer
class PowrComponent extends React.Component {
constructor() {
this.state = { internalThingOnly: false }
}
render() {
return (
<Fragment>
{ this.props.mobxStore.somethingGlobal? this.renderX() : this.renderY() }
{ this.state.internalThingOnly ? this.renderA() : this.renderB() }
</Fragment>
);
}
}
// observable store
import {decorate, observable, action} from 'mobx';
class ObservableStore {
somethingGlobal = 1;
updateSomethingGLobal(newValue) { this.somethingGlobal = newValue; }
}
decorate(ObservableStore, {
somethingGlobal: observable,
updateSomethingGLobal: action
});
some other component
this.store.updateSomethingGLobal('2')
<PowrComponent mobxStore=this.store/>The @observer decorator from the mobx-react package wraps React component render method in autorun, automatically keeping your components in sync with the state.
import React, {Fragment} from 'react';
import {observer} from 'mobx-react';
@observer
class PowrSection extends React.Component {
render() {
return (
<Fragment>
<div className="hid">{this.props.store.randomNumber}</div>
{recursivelyBuildComponents(this.props)}
</Fragment>
);
}
}[New Syntax Alert]
Section Config
- This is an array of components that go within a section
- This is the replacement for data passing through hidden inputs haml.




Before (HAML, helpers etc)
Now (JSON - explicit)
What Section Config is
An array of js objects, where each item has:
- powrComponent - actual React component
- passedProps - react component's props (they are hardcoded and don't change based on user or app)
- dynamicProps - these are based on the model (for example if you need to pass an ID or createdAt). They come from the store
- conditionals - when to show this component
- innerComponents - children of component
What Section Config is
An array of js objects, where each item has:
- powrComponent - actual React component
- passedProps - react component's props (they are hardcoded and don't change based on user or app)
- dynamicProps - these are based on the model (for example if you need to pass an ID or createdAt). They come from the store
- conditionals - when to show this component
- innerComponents
{
powrComponent: PowrPanel,
passedProps: {
namespace: 'importExisting',
},
dynamicProps: [
{
propName: 'panelTitle',
key: 'store.model.meta.app_common_name',
evaluate: currentValue => {
return `${sc('import_existing')} ${currentValue}`;
},
},
],
innerComponents: [
{
powrComponent: AppsList,
}
],
}
Dynamic Props
dynamicProps: [
{
propName: 'mailchimpList',
key: 'observableStore.model.attributes.mailchimpList',
},
{
propName: 'mailchimpListName',
key: 'observableStore.model.attributes.mailchimpListName',
},
{
propName: 'appId',
key: 'observableStore.model.meta.id',
},
{
propName: 'externalUserId',
key: 'observableStore.model.meta.external_user_id',
},
],

propName is a React component's prop
key is where the data comes from
<MailchimpButton
appId={134}
external_user_id={456}>
</MailchimpButton>
More about dynamicProps
{
powrComponent: PowrPanel,
passedProps: {
namespace: 'importExisting',
},
dynamicProps: [
{
propName: 'panelTitle',
key: 'store.model.meta.app_common_name',
evaluate: currentValue => {
return `${sc('import_existing')} ${currentValue}`;
},
},
],
innerComponents: [
{
powrComponent: AppsList,
}
],
}

Conditionals - when to show this component

{
powrComponent: PowrToggle,
passedProps: {
label: sc('require_payment'),
namespace: 'paymentRequired',
additionalClasses: 'form-element',
helpText: sc('require_payment_help'),
handleChangeComplete: (value, store) => {
store.updateValue('model.paymentRequired', value, true);
},
},
},

Conditionals - when to show this component
{
powrComponent: 'div',
passedProps: {
className: 'margin-top-l',
},
conditionals: [
{
key: 'store.model.attributes.paymentRequired',
value: true,
},
],
innerComponents: [
{
powrComponent: PowrTextInput,
passedProps: {
label: sc('Paypal Account'),
namespace: `${namespace}PaypalAccount`,
additionalClasses: 'form-element',
placeholder: sc('enter_paypal_email'),
type: 'email',
},
},
...

conditionals: [
{
key: `store.model.attributes.${namespace}PurchaseType`,
check: currentVal => {
return currentVal !== 'Recurring';
},
},
]There're 3 ways to handle handleChangeComplete in passedProps
handleChangeComplete: value => {
if (passedProps.handleChangeComplete) {
passedProps.handleChangeComplete(value, store);
} else {
if (passedProps.namespace) {
store.updateValue(`model.${passedProps.namespace}`, value);
}
}
}1) Default: don't pass handleChangeComplete if you don't want to modify saved data. It will be done automatically.
There're 3 ways to handle handleChangeComplete in passedProps
handleChangeComplete: value => {
if (passedProps.handleChangeComplete) {
passedProps.handleChangeComplete(value, store);
} else {
if (passedProps.namespace) {
store.updateValue(`model.${passedProps.namespace}`, value);
}
}
}1) Default: don't pass handleChangeComplete if you don't want to modify saved data. It will be done automatically.

There're 3 ways to handle handleChangeComplete in passedProps
handleChangeComplete: value => {
if (passedProps.handleChangeComplete) {
passedProps.handleChangeComplete(value, store);
} else {
if (passedProps.namespace) {
store.updateValue(`model.${passedProps.namespace}`, value);
}
}
}1) Default: don't pass handleChangeComplete if you don't want to modify saved data. It will be done automatically.
You don't have to do anything if namespace matches the model name.


{
powrComponent: PowrColorPicker,
passedProps: {
label: sc('border_color'),
namespace: 'backgroundBorderColor',
additionalClasses: 'form-element',
handleChangeComplete: (value, store) => {
store.updateValue(`model.${namespace}`, makeColorString(value));
},
alpha: true,
},
},
2) Needs to change value before saving to db
We are using a function makeColorString to modify value before saving it to database.
{
powrComponent: PowrToggle,
passedProps: {
label: sc('require_payment'),
namespace: 'paymentRequired',
additionalClasses: 'form-element',
helpText: sc('require_payment_help'),
handleChangeComplete: (value, store) => {
store.updateValue('model.paymentRequired', value, true);
},
},
},
3) When we need to re-render something in the settings after changing the value.
3) When we need to re-render something in the settings after changing the value.
Mobx is not great in working with backbone models so we force rerender using small hack.

Mobx





updateValue(key, value, refresh = false) {
const backboneModel = key.match(/model\.(.*)/);
if (backboneModel) {
const newVal = [];
newVal[backboneModel[1]] = value;
this.model.set(newVal);
// This is sort of a hack so we can continue using models and mobx together
// set does not really tell mobx observers that anything changed but it tells everyplace else what needs to be done.
// anytime we need to change the settings portion of things to follow something that changed on the model, pass refresh as true, otherwise we don't really need to do anything.
if (refresh) {
this.randomNumber = new Date().getTime();
}
} else {
this[key] = _.clone(value);
}
}Small hack to force rerender

updateValue(key, value, refresh = false) {
const backboneModel = key.match(/model\.(.*)/);
if (backboneModel) {
const newVal = [];
newVal[backboneModel[1]] = value;
this.model.set(newVal);
// This is sort of a hack so we can continue using models and mobx together
// set does not really tell mobx observers that anything changed but it tells everyplace else what needs to be done.
// anytime we need to change the settings portion of things to follow something that changed on the model, pass refresh as true, otherwise we don't really need to do anything.
if (refresh) {
this.randomNumber = new Date().getTime();
}
} else {
this[key] = _.clone(value);
}
}Small hack to force rerender

Inner Components

Inner Components


PowrDrilldown Component
Inner Components
Inner Components in Code:
const formbuilderDesign = [
{
powrComponent: PowrDrilldown,
passedProps: {
namespace: 'layout',
label: sc('layout'),
},
innerComponents: [
{
powrComponent: PowrMultitoggle,
passedProps: {
label: sc('select_label_style'),
namespace: 'labelType',
options: [[sc('inline_labels'), 'inlineLabels'], [sc('block_labels'), 'blockLabels']],
additionalClasses: 'form-element',
},
},
{
powrComponent: PowrMultitoggle,
passedProps: {
label: sc('title_alignment'),
namespace: 'titleAlign',
options: [[sc('left'), 'left'], [sc('center'), 'center'], [sc('right'), 'right']],
additionalClasses: 'form-element',
},
},
{
powrComponent: PowrSlider,
passedProps: {
label: sc('input_field_border_radius'),
namespace: 'inputBorderRadius',
min: 0,
max: 20,
measure: 'px',
step: 1,
renderStyleOnly: true,
additionalClasses: 'form-element',
},
},
],
},
To Be Continued ...
https://mobx.js.org/getting-started.html
https://www.sitepoint.com/javascript-decorators-what-they-are/
deck
By Praneeta Mhatre
deck
- 85