react_ujs/index.js
var React = require("react")
var ReactDOM = require("react-dom")
var ReactDOMServer = require("react-dom/server")
var detectEvents = require("./src/events/detect")
var constructorFromGlobal = require("./src/getConstructor/fromGlobal")
var constructorFromRequireContext = require("./src/getConstructor/fromRequireContext")
var constructorFromRequireContextWithGlobalFallback = require("./src/getConstructor/fromRequireContextWithGlobalFallback")
var constructorFromRequireContextsWithGlobalFallback = require("./src/getConstructor/fromRequireContextsWithGlobalFallback")
var { supportsHydration, reactHydrate, createReactRootLike } = require("./src/renderHelpers")
var supportsRootApi = require("./src/supportsRootApi")
var ReactRailsUJS = {
// This attribute holds the name of component which should be mounted
// example: `data-react-class="MyApp.Items.EditForm"`
CLASS_NAME_ATTR: 'data-react-class',
// This attribute holds JSON stringified props for initializing the component
// example: `data-react-props="{\"item\": { \"id\": 1, \"name\": \"My Item\"} }"`
PROPS_ATTR: 'data-react-props',
// This attribute holds which method to use between: ReactDOM.hydrate, ReactDOM.render
RENDER_ATTR: 'data-hydrate',
// A unique identifier to identify a node
CACHE_ID_ATTR: "data-react-cache-id",
TURBOLINKS_PERMANENT_ATTR: "data-turbolinks-permanent",
// If jQuery is detected, save a reference to it for event handlers
jQuery: (typeof window !== 'undefined') && (typeof window.jQuery !== 'undefined') && window.jQuery,
components: {},
roots: [],
// helper method for the mount and unmount methods to find the
// `data-react-class` DOM elements
findDOMNodes: function(searchSelector) {
var classNameAttr = ReactRailsUJS.CLASS_NAME_ATTR
// we will use fully qualified paths as we do not bind the callbacks
var selector, parent;
switch (typeof searchSelector) {
case 'undefined':
selector = '[' + classNameAttr + ']';
parent = document;
break;
case 'object':
selector = '[' + classNameAttr + ']';
parent = searchSelector;
break;
case 'string':
selector = searchSelector + '[' + classNameAttr + '], ' +
searchSelector + ' [' + classNameAttr + ']';
parent = document;
break
default:
break;
}
if (ReactRailsUJS.jQuery) {
return ReactRailsUJS.jQuery(selector, parent);
} else {
return parent.querySelectorAll(selector);
}
},
// Get the constructor for a className (returns a React class)
// Override this function to lookup classes in a custom way,
// the default is ReactRailsUJS.ComponentGlobal
getConstructor: constructorFromGlobal,
// Available for customizing `getConstructor`
constructorFromGlobal: constructorFromGlobal,
constructorFromRequireContext: constructorFromRequireContext,
constructorFromRequireContextWithGlobalFallback: constructorFromRequireContextWithGlobalFallback,
// Given a Webpack `require.context`,
// try finding components with `require`,
// then falling back to global lookup.
useContext: function(requireContext) {
this.getConstructor = constructorFromRequireContextWithGlobalFallback(requireContext)
},
// Given an array of Webpack `require.context`,
// try finding components with `require`,
// then falling back to global lookup.
useContexts: function(requireContexts) {
this.getConstructor = constructorFromRequireContextsWithGlobalFallback(requireContexts)
},
// Render `componentName` with `props` to a string,
// using the specified `renderFunction` from `react-dom/server`.
serverRender: function(renderFunction, componentName, props) {
var componentClass = this.getConstructor(componentName)
var element = React.createElement(componentClass, props)
return ReactDOMServer[renderFunction](element)
},
// Within `searchSelector`, find nodes which should have React components
// inside them, and mount them with their props.
mountComponents: function(searchSelector) {
var ujs = ReactRailsUJS
var nodes = ujs.findDOMNodes(searchSelector);
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
var className = node.getAttribute(ujs.CLASS_NAME_ATTR);
var constructor = ujs.getConstructor(className);
var propsJson = node.getAttribute(ujs.PROPS_ATTR);
var props = propsJson && JSON.parse(propsJson);
var hydrate = node.getAttribute(ujs.RENDER_ATTR);
var cacheId = node.getAttribute(ujs.CACHE_ID_ATTR);
var turbolinksPermanent = node.hasAttribute(ujs.TURBOLINKS_PERMANENT_ATTR);
if (!constructor) {
var message = "Cannot find component: '" + className + "'"
if (console && console.log) {
console.log("%c[react-rails] %c" + message + " for element", "font-weight: bold", "", node)
}
throw new Error(message + ". Make sure your component is available to render.")
} else {
var component = this.components[cacheId];
if(component === undefined) {
component = React.createElement(constructor, props);
if(turbolinksPermanent) {
this.components[cacheId] = component;
}
}
if (hydrate && supportsHydration()) {
component = reactHydrate(node, component);
} else {
const root = this.findOrCreateRoot(node);
component = root.render(component);
}
}
}
},
// Within `searchSelector`, find nodes which have React components
// inside them, and unmount those components.
unmountComponents: function(searchSelector) {
var nodes = ReactRailsUJS.findDOMNodes(searchSelector);
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
if(supportsRootApi) {
this.unmountRoot(node)
} else {
ReactDOM.unmountComponentAtNode(node);
}
}
},
// Check the global context for installed libraries
// and figure out which library to hook up to (pjax, Turbolinks, jQuery)
// This is called on load, but you can call it again if needed
// (It will unmount itself)
detectEvents: function() {
detectEvents(this)
},
findOrCreateRoot: function(node) {
var root = this.findRoot(node);
if (!root) {
root = createReactRootLike(node);
if(supportsRootApi) {
this.roots.push({"node": node, "root": root})
}
}
return root;
},
findRoot: function(node) {
if (!supportsRootApi) {
return;
}
var rootElement = this.roots.find(
function(rootElement) {
return rootElement["node"] && (rootElement["node"] === node)
}
);
if (rootElement) {
return rootElement["root"];
}
},
unmountRoot: function(node) {
var targetRoot = this.findRoot(node);
if (!targetRoot) {
return;
}
targetRoot.unmount();
this.roots = this.roots.filter(
function(rootElement) {
return rootElement["node"] !== node
}
);
}
}
// These stable references are so that handlers can be added and removed:
ReactRailsUJS.handleMount = function(e) {
var target = undefined;
if (e && e.target) {
target = e.target;
}
ReactRailsUJS.mountComponents(target);
}
ReactRailsUJS.handleUnmount = function(e) {
var target = undefined;
if (e && e.target) {
target = e.target;
}
ReactRailsUJS.unmountComponents(target);
}
if (typeof window !== "undefined") {
// Only setup events for browser (not server-rendering)
ReactRailsUJS.detectEvents()
}
// It's a bit of a no-no to populate the global namespace,
// but we really need it!
// We need access to this object for server rendering, and
// we can't do a dynamic `require`, so we'll grab it from here:
self.ReactRailsUJS = ReactRailsUJS
module.exports = ReactRailsUJS