forked from woocommerce/woocommerce-blocks
-
Notifications
You must be signed in to change notification settings - Fork 0
/
render-frontend.js
204 lines (194 loc) · 7.67 KB
/
render-frontend.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/**
* External dependencies
*/
import { render, Suspense } from '@wordpress/element';
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
// Some blocks take care of rendering their inner blocks automatically. For
// example, the empty cart. In those cases, we don't want to trigger the render
// function of inner components on load. Instead, the wrapper block can trigger
// the event `wc-blocks_render_blocks_frontend` to render its inner blocks.
const selectorsToSkipOnLoad = [ '.wp-block-woocommerce-cart' ];
// Given an element and a list of wrappers, check if the element is inside at
// least one of the wrappers.
const isElementInsideWrappers = ( el, wrappers ) => {
return Array.prototype.some.call(
wrappers,
( wrapper ) => wrapper.contains( el ) && ! wrapper.isSameNode( el )
);
};
/**
* Renders a block component in each `containers` node.
*
* @param {Object} props Render props.
* @param {Function} props.Block React component to use as a
* replacement.
* @param {NodeList} props.containers Containers to replace with
* the Block component.
* @param {Function} [props.getProps] Function to generate the
* props object for the block.
* @param {Function} [props.getErrorBoundaryProps] Function to generate the
* props object for the error
* boundary.
*/
const renderBlockInContainers = ( {
Block,
containers,
getProps = () => ( {} ),
getErrorBoundaryProps = () => ( {} ),
} ) => {
if ( containers.length === 0 ) {
return;
}
// Use Array.forEach for IE11 compatibility.
Array.prototype.forEach.call( containers, ( el, i ) => {
const props = getProps( el, i );
const errorBoundaryProps = getErrorBoundaryProps( el, i );
const attributes = {
...el.dataset,
...( props.attributes || {} ),
};
el.classList.remove( 'is-loading' );
renderBlock( {
Block,
container: el,
props,
attributes,
errorBoundaryProps,
} );
} );
};
/**
* Renders a block component in a single `container` node.
*
* @param {Object} props Render props.
* @param {Function} props.Block React component to use as a
* replacement.
* @param {Node} props.container Container to replace with
* the Block component.
* @param {Object} [props.attributes] Attributes object for the
* block.
* @param {Object} [props.props] Props object for the block.
* @param {Object} [props.errorBoundaryProps] Props object for the error
* boundary.
*/
export const renderBlock = ( {
Block,
container,
attributes = {},
props = {},
errorBoundaryProps = {},
} ) => {
render(
<BlockErrorBoundary { ...errorBoundaryProps }>
<Suspense fallback={ <div className="wc-block-placeholder" /> }>
<Block { ...props } attributes={ attributes } />
</Suspense>
</BlockErrorBoundary>,
container
);
};
/**
* Renders the block frontend in the elements matched by the selector which are
* outside the wrapper elements.
*
* @param {Object} props Render props.
* @param {Function} props.Block React component to use as a
* replacement.
* @param {string} props.selector CSS selector to match the
* elements to replace.
* @param {Function} [props.getProps] Function to generate the
* props object for the block.
* @param {Function} [props.getErrorBoundaryProps] Function to generate the
* props object for the error
* boundary.
* @param {NodeList} props.wrappers All elements matched by the
* selector which are inside
* the wrapper will be ignored.
*/
const renderBlockOutsideWrappers = ( {
Block,
getProps,
getErrorBoundaryProps,
selector,
wrappers,
} ) => {
const containers = document.body.querySelectorAll( selector );
// Filter out blocks inside the wrappers.
if ( wrappers.length > 0 ) {
Array.prototype.filter.call( containers, ( el ) => {
return ! isElementInsideWrappers( el, wrappers );
} );
}
renderBlockInContainers( {
Block,
containers,
getProps,
getErrorBoundaryProps,
} );
};
/**
* Renders the block frontend in the elements matched by the selector inside the
* wrapper element.
*
* @param {Object} props Render props.
* @param {Function} props.Block React component to use as a
* replacement.
* @param {string} props.selector CSS selector to match the
* elements to replace.
* @param {Function} [props.getProps] Function to generate the
* props object for the block.
* @param {Function} [props.getErrorBoundaryProps] Function to generate the
* props object for the error
* boundary.
* @param {Element} props.wrapper Wrapper element to query the
* selector inside.
*/
const renderBlockInsideWrapper = ( {
Block,
getProps,
getErrorBoundaryProps,
selector,
wrapper,
} ) => {
const containers = wrapper.querySelectorAll( selector );
renderBlockInContainers( {
Block,
containers,
getProps,
getErrorBoundaryProps,
} );
};
/**
* Renders the block frontend on page load. If the block is contained inside a
* wrapper element that should be excluded from initial load, it adds the
* appropriate event listeners to render the block when the
* `wc-blocks_render_blocks_frontend` event is triggered.
*
* @param {Object} props Render props.
* @param {Function} props.Block React component to use as a
* replacement.
* @param {string} props.selector CSS selector to match the
* elements to replace.
* @param {Function} [props.getProps] Function to generate the
* props object for the block.
* @param {Function} [props.getErrorBoundaryProps] Function to generate the
* props object for the error
* boundary.
*/
export const renderFrontend = ( props ) => {
const wrappersToSkipOnLoad = document.body.querySelectorAll(
selectorsToSkipOnLoad.join( ',' )
);
renderBlockOutsideWrappers( {
...props,
wrappers: wrappersToSkipOnLoad,
} );
// For each wrapper, add an event listener to render the inner blocks when
// `wc-blocks_render_blocks_frontend` event is triggered.
Array.prototype.forEach.call( wrappersToSkipOnLoad, ( wrapper ) => {
wrapper.addEventListener( 'wc-blocks_render_blocks_frontend', () => {
renderBlockInsideWrapper( { ...props, wrapper } );
} );
} );
};
export default renderFrontend;