Skip to content

Commit 97ae83f

Browse files
author
Robert Mosolgo
committed
Merge pull request #117 from robrobbins/ujs-api
Expose the React mounting and un-mounting mechanisms
2 parents 8c0e0b4 + 07dcbcb commit 97ae83f

File tree

2 files changed

+90
-47
lines changed

2 files changed

+90
-47
lines changed
Lines changed: 67 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,89 @@
1-
// Unobtrusive scripting adapter for React
2-
(function(document, window, React) {
3-
var CLASS_NAME_ATTR = 'data-react-class';
4-
var PROPS_ATTR = 'data-react-props';
1+
/*globals React, Turbolinks*/
52

3+
// Unobtrusive scripting adapter for React
4+
(function(document, window) {
65
// jQuery is optional. Use it to support legacy browsers.
7-
var $ = (typeof jQuery !== 'undefined') && jQuery;
8-
9-
var findReactDOMNodes = function() {
10-
var SELECTOR = '[' + CLASS_NAME_ATTR + ']';
11-
if ($) {
12-
return $(SELECTOR);
13-
} else {
14-
return document.querySelectorAll(SELECTOR);
15-
}
16-
};
17-
18-
var mountReactComponents = function() {
19-
var nodes = findReactDOMNodes();
20-
for (var i = 0; i < nodes.length; ++i) {
21-
var node = nodes[i];
22-
var className = node.getAttribute(CLASS_NAME_ATTR);
23-
// Assume className is simple and can be found at top-level (window).
24-
// Fallback to eval to handle cases like 'My.React.ComponentName'.
25-
var constructor = window[className] || eval.call(window, className);
26-
var propsJson = node.getAttribute(PROPS_ATTR);
27-
var props = propsJson && JSON.parse(propsJson);
28-
React.render(React.createElement(constructor, props), node);
29-
}
30-
};
31-
32-
var unmountReactComponents = function() {
33-
var nodes = findReactDOMNodes();
34-
for (var i = 0; i < nodes.length; ++i) {
35-
React.unmountComponentAtNode(nodes[i]);
6+
var $ = (typeof window.jQuery !== 'undefined') && window.jQuery;
7+
8+
// create the namespace
9+
window.ReactRailsUJS = {
10+
CLASS_NAME_ATTR: 'data-react-class',
11+
PROPS_ATTR: 'data-react-props',
12+
// helper method for the mount and unmount methods to find the
13+
// `data-react-class` DOM elements
14+
findDOMNodes: function() {
15+
// we will use fully qualified paths as we do not bind the callbacks
16+
var selector = '[' + window.ReactRailsUJS.CLASS_NAME_ATTR + ']';
17+
18+
if ($) {
19+
return $(selector);
20+
} else {
21+
return document.querySelectorAll(selector);
22+
}
23+
},
24+
25+
mountComponents: function() {
26+
var nodes = window.ReactRailsUJS.findDOMNodes();
27+
28+
for (var i = 0; i < nodes.length; ++i) {
29+
var node = nodes[i];
30+
var className = node.getAttribute(window.ReactRailsUJS.CLASS_NAME_ATTR);
31+
32+
// Assume className is simple and can be found at top-level (window).
33+
// Fallback to eval to handle cases like 'My.React.ComponentName'.
34+
var constructor = window[className] || eval.call(window, className);
35+
var propsJson = node.getAttribute(window.ReactRailsUJS.PROPS_ATTR);
36+
var props = propsJson && JSON.parse(propsJson);
37+
38+
React.render(React.createElement(constructor, props), node);
39+
}
40+
},
41+
42+
unmountComponents: function() {
43+
var nodes = window.ReactRailsUJS.findDOMNodes();
44+
45+
for (var i = 0; i < nodes.length; ++i) {
46+
var node = nodes[i];
47+
48+
React.unmountComponentAtNode(node);
49+
// now remove the `data-react-class` wrapper as well
50+
node.parentElement && node.parentElement.removeChild(node);
51+
}
3652
}
3753
};
3854

39-
var handleTurbolinksEvents = function() {
55+
// functions not exposed publicly
56+
function handleTurbolinksEvents () {
4057
var handleEvent;
58+
4159
if ($) {
4260
handleEvent = function(eventName, callback) {
4361
$(document).on(eventName, callback);
44-
}
62+
};
63+
4564
} else {
4665
handleEvent = function(eventName, callback) {
4766
document.addEventListener(eventName, callback);
48-
}
67+
};
4968
}
50-
handleEvent('page:change', mountReactComponents);
51-
handleEvent('page:receive', unmountReactComponents);
52-
};
69+
handleEvent('page:change', window.ReactRailsUJS.mountComponents);
70+
handleEvent('page:receive', window.ReactRailsUJS.unmountComponents);
71+
}
5372

54-
var handleNativeEvents = function() {
55-
if ($) {
56-
$(mountReactComponents);
57-
$(window).unload(unmountReactComponents);
73+
function handleNativeEvents() {
74+
if ($) {
75+
$(window.ReactRailsUJS.mountComponents);
76+
$(window).unload(window.ReactRailsUJS.unmountComponents);
77+
5878
} else {
59-
document.addEventListener('DOMContentLoaded', mountReactComponents);
60-
window.addEventListener('unload', unmountReactComponents);
79+
document.addEventListener('DOMContentLoaded', window.ReactRailsUJS.mountComponents);
80+
window.addEventListener('unload', window.ReactRailsUJS.unmountComponents);
6181
}
62-
};
82+
}
6383

6484
if (typeof Turbolinks !== 'undefined' && Turbolinks.supported) {
6585
handleTurbolinksEvents();
6686
} else {
6787
handleNativeEvents();
6888
}
69-
})(document, window, React);
89+
})(document, window);

test/view_helper_test.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,29 @@ class ViewHelperTest < ActionDispatch::IntegrationTest
4848
assert html.include?('class="test"')
4949
assert html.include?('data-foo="1"')
5050
end
51+
52+
test 'ujs object present on the global React object and has our methods' do
53+
visit '/pages/1'
54+
assert page.has_content?('Hello Bob')
55+
56+
# the exposed ujs object is present
57+
ujs_present = page.evaluate_script('typeof ReactRailsUJS === "object";')
58+
assert_equal(ujs_present, true)
59+
60+
# it contains the constants
61+
class_name_present = page.evaluate_script('ReactRailsUJS.CLASS_NAME_ATTR === "data-react-class";')
62+
assert_equal(class_name_present, true)
63+
props_present = page.evaluate_script('ReactRailsUJS.PROPS_ATTR === "data-react-props";')
64+
assert_equal(props_present, true)
65+
66+
#it contains the methods
67+
find_dom_nodes_present = page.evaluate_script('typeof ReactRailsUJS.findDOMNodes === "function";')
68+
assert_equal(find_dom_nodes_present, true)
69+
mount_components_present = page.evaluate_script('typeof ReactRailsUJS.mountComponents === "function";')
70+
assert_equal(mount_components_present, true)
71+
unmount_components_present = page.evaluate_script('typeof ReactRailsUJS.unmountComponents === "function";')
72+
assert_equal(unmount_components_present, true)
73+
end
5174

5275
test 'react_ujs works with rendered HTML' do
5376
visit '/pages/1'

0 commit comments

Comments
 (0)