copy-edit viget posts

This commit is contained in:
David Eisinger
2023-10-24 20:48:09 -04:00
parent 0438a6d828
commit f86f391e82
77 changed files with 1663 additions and 1380 deletions

View File

@@ -2,38 +2,37 @@
title: "Using Microcosm Presenters to Manage Complex Features"
date: 2017-06-14T00:00:00+00:00
draft: false
needs_review: true
canonical_url: https://www.viget.com/articles/using-microcosm-presenters-to-manage-complex-features/
---
We made [Microcosm](http://code.viget.com/microcosm/) to help us manage
state and data flow in our JavaScript applications. We think it\'s
state and data flow in our JavaScript applications. We think it's
pretty great. We recently used it to help our friends at
[iContact](https://www.icontact.com/) launch a [brand new email
editor](https://www.icontact.com/big-news). Today, I\'d like to show you
editor](https://www.icontact.com/big-news). Today, I'd like to show you
how I used one of my favorite features of Microcosm to ship a
particularly gnarly feature.
In addition to adding text, photos, and buttons to their emails, users
can add *code blocks* which let them manually enter HTML to be inserted
into the email. The feature in question was to add server-side code
santization, to make sure user-submitted HTML isn\'t invalid or
santization, to make sure user-submitted HTML isn't invalid or
potentially malicious. The logic is roughly defined as follows:
- User modifies the HTML & hits \"preview\";
- User modifies the HTML & hits "preview";
- HTML is sent up to the server and sanitized;
- The resulting HTML is displayed in the canvas;
- If the code is unmodified, user can \"apply\" the code or continue
- If the code is unmodified, user can "apply" the code or continue
editing;
- If the code is modified, user can \"apply\" the modified code or
\"reject\" the changes and continue editing;
- If the code is modified, user can "apply" the modified code or
"reject" the changes and continue editing;
- If at any time the user unfocuses the block, the code should return
to the last applied state.
Here\'s a flowchart that might make things clearer (did for me, in any
Here's a flowchart that might make things clearer (did for me, in any
event):
![](http://i.imgur.com/URfAcl9.png)
![](URfAcl9.png)
This feature is too complex to handle with React component state, but
too localized to store in application state (the main Microcosm
@@ -49,18 +48,18 @@ First, we define some
[Actions](http://code.viget.com/microcosm/api/actions.html) that only
pertain to this Presenter:
``` {.code-block .line-numbers}
```javascript
const changeInputHtml = html => html
const acceptChanges = () => {}
const rejectChanges = () => {}
```
We don\'t export these functions, so they only exist in the context of
We don't export these functions, so they only exist in the context of
this file.
Next, we\'ll define the Presenter itself:
Next, we'll define the Presenter itself:
``` {.code-block .line-numbers}
```javascript
class CodeEditor extends Presenter {
setup(repo, props) {
repo.addDomain('html', {
@@ -81,10 +80,10 @@ invoke the
function to add a new domain to the forked repo. The main repo will
never know about this new bit of state.
Now, let\'s instruct our new domain to listen for some actions:
Now, let's instruct our new domain to listen for some actions:
``` {.code-block .line-numbers}
register() {
```javascript
register() {
return {
[scrubHtml]: this.scrubSuccess,
[changeInputHtml]: this.inputHtmlChanged,
@@ -100,10 +99,10 @@ method defines the mapping of Actions to handler functions. You should
recognize those actions from the top of the file, minus `scrubHtml`,
which is defined in a separate API module.
Now, still inside the domain object, let\'s define some handlers:
Now, still inside the domain object, let's define some handlers:
``` {.code-block .line-numbers}
inputHtmlChanged(state, inputHtml) {
```javascript
inputHtmlChanged(state, inputHtml) {
let status = inputHtml === state.originalHtml ? 'start' : 'changed'
return { ...state, inputHtml, status }
@@ -124,11 +123,11 @@ inputHtmlChanged(state, inputHtml) {
```
Handlers always take `state` as their first object and must return a new
state object. Now, let\'s add some more methods to our main `CodeEditor`
state object. Now, let's add some more methods to our main `CodeEditor`
class.
``` {.code-block .line-numbers}
renderPreview = ({ html }) => {
```javascript
renderPreview = ({ html }) => {
this.send(updateBlock, this.props.block.id, {
attributes: { htmlCode: html }
})
@@ -148,10 +147,10 @@ the canvas with the given HTML. And `componentWillUnmount` is noteworthy
in that it demonstrates that Presenters are just React components under
the hood.
Next, let\'s add some buttons to let the user trigger these actions.
Next, let's add some buttons to let the user trigger these actions.
``` {.code-block .line-numbers}
buttons(status, html) {
```javascript
buttons(status, html) {
switch (status) {
case 'changed':
return (
@@ -183,10 +182,10 @@ that triggers an action when pressed. Its callback functionality (e.g.
`onOpen`, `onDone`) lets you update the button as the action moves
through its lifecycle.
Finally, let\'s bring it all home and create our model and view:
Finally, let's bring it all home and create our model and view:
``` {.code-block .line-numbers}
getModel() {
```javascript
getModel() {
return {
status: state => state.html.status,
inputHtml: state => state.html.inputHtml
@@ -232,11 +231,11 @@ demonstrates how you interact with the model.
The big takeaways here:
**Presenters can have their own repos.** These can be defined inline (as
I\'ve done) or in a separate file/object. I like seeing everything in
I've done) or in a separate file/object. I like seeing everything in
one place, but you can trot your own trot.
**Presenters can manage their own state.** Presenters receive a fork of
the main app state when they\'re instantiated, and changes to that state
the main app state when they're instantiated, and changes to that state
(e.g. via an associated domain) are not automatically synced back to the
main repo.
@@ -246,15 +245,15 @@ in `renderPreview` above) to push changes up the chain.
**Presenters can have their own actions.** The three actions defined at
the top of the file only exist in the context of this file, which is
exactly what we want, since that\'s the only place they make any sense.
exactly what we want, since that's the only place they make any sense.
**Presenters are just React components.** Despite all this cool stuff
we\'re able to do in a Presenter, under the covers, they\'re nothing but
we're able to do in a Presenter, under the covers, they're nothing but
React components. This way you can still take advantage of lifecycle
methods like `componentWillUnmount` (and `render`, natch).
------------------------------------------------------------------------
So those are Microcosm Presenters. We think they\'re pretty cool, and
So those are Microcosm Presenters. We think they're pretty cool, and
hope you do, too. If you have any questions, hit us up on
[GitHub](https://github.com/vigetlabs/microcosm) or right down there.