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

Allow nodes to be preprocessed #90

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,120 @@ var reactHtml = ReactDOMServer.renderToStaticMarkup(
assert.equal(reactHtml, htmlExpected);
```

### Preprocessing element based on a criteria before rendering

There may be a situation where you want to preprocess an element before rendering
it via a custom component.
This is beneficial if you want to add attributes to all the matching elements
and then render them separately

#### Example

Below is a simple template in which you may want to replace ids of a div but also update the component.
Replacing ids can be shared.

##### Before
```
<div class="row">
<div id="first" data-process="shared">
<p>Sample For First</p>
</div>
<div id="second" data-process="shared">
<p>Sample For Second</p>
</div>
</div>
```

##### After

The ids are replaced during preprocessing and also the components are update via individual processors.

```
<div class="row">
<h1 id="preprocessed-first">First</h1>
<h2 id="preprocessed-second">Second</h2>
</div>
```

#### Setup

In your instructions object, you must specify `replaceChildren: true`.
manixate marked this conversation as resolved.
Show resolved Hide resolved

```javascript
var React = require('react');
var HtmlToReact = require('html-to-react');
var HtmlToReactParser = require('html-to-react').Parser;

var htmlToReactParser = new HtmlToReactParser();
var htmlInput = '<div class="row">' +
'<div id="first" data-process="shared">' +
'<p>Sample For First</p>' +
'</div>' +
'<div id="second" data-process="shared">' +
'<p>Sample For Second</p>' +
'</div>' +
'</div>';

var htmlExpected = '<div class="row">' +
'<h1 id="preprocessed-first">First</h1>' +
'<h2 id="preprocessed-second">Second</h2>' +
'</div>';

var isValidNode = function () {
return true;
};

var processNodeDefinitions = new HtmlToReact.ProcessNodeDefinitions(React);

// Order matters. Instructions are processed in
// the order they're defined
var preprocessingInstructions = [
{
// This will allow you to update ids in a shared instruction set
shouldPreprocessNode: function (node) {
return node.attribs && node.attribs['data-process'] === 'shared';
},
preprocessNode: function (node, children, index) {
node.attribs = {id: `preprocessed-${node.attribs.id}`,};
},
}
];
var processingInstructions = [
{
shouldProcessNode: function (node) {
return node.attribs && node.attribs.id === 'preprocessed-first';
},
processNode: function(node, children, index) {
return React.createElement('h1',
{key: index, id: node.attribs.id,}, 'First');
},
},
{
shouldProcessNode: function (node) {
return node.attribs && node.attribs.id === 'preprocessed-second';
},
processNode: function(node, children, index) {
return React.createElement('h2',
{key: index, id: node.attribs.id,}, 'Second');
},
},
{
shouldProcessNode: function (node) {
return true;
},
processNode: function(node, children, index) {
return processNodeDefinitions.processDefaultNode(node, children, index);
},
},
];

var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode,
processingInstructions, preprocessingInstructions);
var reactHtml = ReactDOMServer.renderToStaticMarkup(
reactComponent);
assert.equal(reactHtml, htmlExpected);
```

## Tests & Coverage

Test locally: `$ npm test`
Expand Down
32 changes: 28 additions & 4 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,31 @@ function Html2ReactParser(options) {
});
};

function traverseDom(node, isValidNode, processingInstructions, index) {
function traverseDom(
node,
isValidNode,
processingInstructions,
preprocessingInstructions,
index
) {

if (isValidNode(node)) {
if (preprocessingInstructions) {
preprocessingInstructions.map(function (instruction) {
if (instruction.shouldPreprocessNode(node)) {
instruction.preprocessNode(node, children, index);
manixate marked this conversation as resolved.
Show resolved Hide resolved
}
});
}

var processingInstruction = find(function (processingInstruction) {
return processingInstruction.shouldProcessNode(node);
}, processingInstructions);
if (processingInstruction != null) {
var children = reject(function (x) {return x == null || x === false;},
addIndex(map)(function (child, i) {
return traverseDom(child, isValidNode, processingInstructions, i);
return traverseDom(child, isValidNode, processingInstructions,
preprocessingInstructions, i);
}, node.children || []));

if (processingInstruction.replaceChildren) {
Expand All @@ -47,11 +63,19 @@ function Html2ReactParser(options) {
}
};

function parseWithInstructions(html, isValidNode, processingInstructions) {
function parseWithInstructions(
html,
isValidNode,
processingInstructions,
preprocessingInstructions
) {

var domTree = parseHtmlToTree(html);

var list = domTree.map(function (domTreeItem, index) {
return traverseDom(domTreeItem, isValidNode, processingInstructions, index);
return traverseDom(
domTreeItem, isValidNode, processingInstructions, preprocessingInstructions, index
);
});
return list.length <= 1 ? list[0] : list;
};
Expand Down
2 changes: 2 additions & 0 deletions lib/process-node-definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var voidElementTags = [
];

function ProcessNodeDefinitions() {
function preprocessDefaultNode(node, children, index) { }
manixate marked this conversation as resolved.
Show resolved Hide resolved
function processDefaultNode(node, children, index) {
if (node.type === 'text') {
return node.data;
Expand All @@ -27,6 +28,7 @@ function ProcessNodeDefinitions() {
}

return {
preprocessDefaultNode: preprocessDefaultNode,
processDefaultNode: processDefaultNode,
};
}
Expand Down
57 changes: 57 additions & 0 deletions test/html-to-react-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,63 @@ describe('Html2React', function () {

assert.equal(reactComponent.props.children.length, 5);
});

it('should preprocess a node and also process a node afterwards', function () {
var htmlInput = '<div class="row">' +
'<div id="first" data-process="shared">' +
'<p>Sample For First</p>' +
'</div>' +
'<div id="second" data-process="shared">' +
'<p>Sample For Second</p>' +
'</div>' +
'</div>';

var htmlExpected = '<div class="row">' +
'<h1 id="preprocessed-first">First</h1>' +
'<h2 id="preprocessed-second">Second</h2>' +
'</div>';

var isValidNode = function () {
return true;
};
var preprocessingInstructions = [{
shouldPreprocessNode: function (node) {
return node.attribs && node.attribs['data-process'] === 'shared';
},
preprocessNode: function (node, children, index) {
node.attribs = {id: `preprocessed-${node.attribs.id}`,};
},
},];
var processingInstructions = [{
shouldProcessNode: function (node) {
return node.attribs && node.attribs.id === 'preprocessed-first';
},
processNode: function(node, children, index) {
return React.createElement('h1',
{key: index, id: node.attribs.id,}, 'First');
},
}, {
shouldProcessNode: function (node) {
return node.attribs && node.attribs.id === 'preprocessed-second';
},
processNode: function(node, children, index) {
return React.createElement('h2',
{key: index, id: node.attribs.id,}, 'Second');
},
},{
shouldProcessNode: function (node) {
return true;
},
processNode: function(node, children, index) {
return processNodeDefinitions.processDefaultNode(node, children, index);
},
},];
var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode,
processingInstructions, preprocessingInstructions);

var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent);
assert.equal(reactHtml, htmlExpected);
});
});
});

Expand Down