234 lines
6.7 KiB
JavaScript
234 lines
6.7 KiB
JavaScript
// ES5-compatible local override for react-rails UJS.
|
|
// This shadows the gem asset so legacy Qt5WebKit can parse the page.
|
|
(function(root) {
|
|
"use strict";
|
|
|
|
var React = root.React;
|
|
var ReactDOM = root.ReactDOM;
|
|
var ReactDOMServer = root.ReactDOMServer;
|
|
|
|
if (!React || !ReactDOM) {
|
|
return;
|
|
}
|
|
|
|
var ReactRailsUJS = {
|
|
CLASS_NAME_ATTR: "data-react-class",
|
|
PROPS_ATTR: "data-react-props",
|
|
RENDER_ATTR: "data-hydrate",
|
|
CACHE_ID_ATTR: "data-react-cache-id",
|
|
TURBOLINKS_PERMANENT_ATTR: "data-turbolinks-permanent",
|
|
jQuery: (typeof root.jQuery !== "undefined" && root.jQuery) ? root.jQuery : null,
|
|
components: {},
|
|
roots: [],
|
|
|
|
findDOMNodes: function(searchSelector) {
|
|
var selector;
|
|
var parent;
|
|
var attr = this.CLASS_NAME_ATTR;
|
|
|
|
switch (typeof searchSelector) {
|
|
case "undefined":
|
|
selector = "[" + attr + "]";
|
|
parent = document;
|
|
break;
|
|
case "object":
|
|
selector = "[" + attr + "]";
|
|
parent = searchSelector;
|
|
break;
|
|
case "string":
|
|
selector = searchSelector + "[" + attr + "], " + searchSelector + " [" + attr + "]";
|
|
parent = document;
|
|
break;
|
|
default:
|
|
selector = "[" + attr + "]";
|
|
parent = document;
|
|
}
|
|
|
|
if (this.jQuery) {
|
|
return this.jQuery(selector, parent);
|
|
}
|
|
return parent.querySelectorAll(selector);
|
|
},
|
|
|
|
constructorFromGlobal: function(className) {
|
|
var topLevel = (typeof window === "undefined") ? this : window;
|
|
var constructor = topLevel[className];
|
|
if (!constructor) {
|
|
constructor = eval(className); // legacy behavior from react-rails
|
|
}
|
|
if (constructor && constructor["default"]) {
|
|
constructor = constructor["default"];
|
|
}
|
|
return constructor;
|
|
},
|
|
|
|
getConstructor: function(className) {
|
|
return this.constructorFromGlobal(className);
|
|
},
|
|
|
|
useContext: function() {
|
|
return this;
|
|
},
|
|
|
|
useContexts: function() {
|
|
return this;
|
|
},
|
|
|
|
serverRender: function(renderFunction, className, props) {
|
|
if (!ReactDOMServer || typeof ReactDOMServer[renderFunction] !== "function") {
|
|
throw new Error("ReactDOMServer." + renderFunction + " is not available");
|
|
}
|
|
var Constructor = this.getConstructor(className);
|
|
var element = React.createElement(Constructor, props);
|
|
return ReactDOMServer[renderFunction](element);
|
|
},
|
|
|
|
findRoot: function(node) {
|
|
var i;
|
|
for (i = 0; i < this.roots.length; i += 1) {
|
|
if (this.roots[i].node === node) {
|
|
return this.roots[i].root;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
findOrCreateRoot: function(node) {
|
|
var existing = this.findRoot(node);
|
|
var rootRecord;
|
|
|
|
if (existing) {
|
|
return existing;
|
|
}
|
|
|
|
rootRecord = {
|
|
render: function(component) {
|
|
if (typeof ReactDOM.render === "function") {
|
|
return ReactDOM.render(component, node);
|
|
}
|
|
throw new Error("ReactDOM.render is not available");
|
|
},
|
|
unmount: function() {
|
|
if (typeof ReactDOM.unmountComponentAtNode === "function") {
|
|
return ReactDOM.unmountComponentAtNode(node);
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
this.roots.push({ node: node, root: rootRecord });
|
|
return rootRecord;
|
|
},
|
|
|
|
unmountRoot: function(node) {
|
|
var rootObj = this.findRoot(node);
|
|
var i;
|
|
if (rootObj && typeof rootObj.unmount === "function") {
|
|
rootObj.unmount();
|
|
}
|
|
|
|
for (i = this.roots.length - 1; i >= 0; i -= 1) {
|
|
if (this.roots[i].node === node) {
|
|
this.roots.splice(i, 1);
|
|
}
|
|
}
|
|
},
|
|
|
|
mountComponents: function(searchSelector) {
|
|
var nodes = this.findDOMNodes(searchSelector);
|
|
var i;
|
|
|
|
for (i = 0; i < nodes.length; i += 1) {
|
|
var node = nodes[i];
|
|
var className = node.getAttribute(this.CLASS_NAME_ATTR);
|
|
var Constructor = this.getConstructor(className);
|
|
var propsJson = node.getAttribute(this.PROPS_ATTR);
|
|
var props = propsJson && JSON.parse(propsJson);
|
|
var shouldHydrate = node.getAttribute(this.RENDER_ATTR);
|
|
var cacheId = node.getAttribute(this.CACHE_ID_ATTR);
|
|
var isPermanent = node.hasAttribute(this.TURBOLINKS_PERMANENT_ATTR);
|
|
var element;
|
|
|
|
if (!Constructor) {
|
|
throw new Error("Cannot find component: '" + className + "'");
|
|
}
|
|
|
|
element = this.components[cacheId];
|
|
if (typeof element === "undefined") {
|
|
element = React.createElement(Constructor, props);
|
|
if (isPermanent) {
|
|
this.components[cacheId] = element;
|
|
}
|
|
}
|
|
|
|
if (shouldHydrate && typeof ReactDOM.hydrate === "function") {
|
|
ReactDOM.hydrate(element, node);
|
|
} else {
|
|
this.findOrCreateRoot(node).render(element);
|
|
}
|
|
}
|
|
},
|
|
|
|
unmountComponents: function(searchSelector) {
|
|
var nodes = this.findDOMNodes(searchSelector);
|
|
var i;
|
|
for (i = 0; i < nodes.length; i += 1) {
|
|
this.unmountRoot(nodes[i]);
|
|
}
|
|
},
|
|
|
|
handleMount: function(e) {
|
|
var target;
|
|
if (e && e.target) {
|
|
target = e.target;
|
|
}
|
|
this.mountComponents(target);
|
|
},
|
|
|
|
handleUnmount: function(e) {
|
|
var target;
|
|
if (e && e.target) {
|
|
target = e.target;
|
|
}
|
|
this.unmountComponents(target);
|
|
},
|
|
|
|
_bind: function(eventName, handler) {
|
|
if (document.addEventListener) {
|
|
document.addEventListener(eventName, handler, false);
|
|
} else if (document.attachEvent) {
|
|
document.attachEvent("on" + eventName, handler);
|
|
}
|
|
},
|
|
|
|
detectEvents: function() {
|
|
var self = this;
|
|
var mountHandler = function(e) { self.handleMount(e); };
|
|
var unmountHandler = function(e) { self.handleUnmount(e); };
|
|
|
|
this._bind("DOMContentLoaded", mountHandler);
|
|
if (root.addEventListener) {
|
|
root.addEventListener("load", mountHandler, false);
|
|
} else if (root.attachEvent) {
|
|
root.attachEvent("onload", mountHandler);
|
|
}
|
|
|
|
// Legacy Turbolinks / pjax hooks (best-effort)
|
|
if (document.addEventListener) {
|
|
document.addEventListener("turbolinks:load", mountHandler, false);
|
|
document.addEventListener("page:change", mountHandler, false);
|
|
document.addEventListener("page:receive", unmountHandler, false);
|
|
document.addEventListener("pjax:end", mountHandler, false);
|
|
document.addEventListener("pjax:beforeReplace", unmountHandler, false);
|
|
}
|
|
|
|
if (this.jQuery) {
|
|
this.jQuery(mountHandler);
|
|
}
|
|
}
|
|
};
|
|
|
|
ReactRailsUJS.detectEvents();
|
|
root.ReactRailsUJS = ReactRailsUJS;
|
|
})(this);
|