@mason-api/javascript-sdk
Version:
Mason component rendering library
581 lines (473 loc) • 20.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "integrations", {
enumerable: true,
get: function get() {
return _integrations.default;
}
});
exports.default = exports.versionNumber = void 0;
var _lodash = _interopRequireDefault(require("lodash"));
var _immutabilityHelper = _interopRequireDefault(require("immutability-helper"));
var _formSerialize = _interopRequireDefault(require("form-serialize"));
var _utils = require("@mason-api/utils");
var _integrations = _interopRequireWildcard(require("./integrations"));
var _transformations = _interopRequireWildcard(require("./transformations"));
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var versionNumber = '3.0.0';
exports.versionNumber = versionNumber;
var _Mason =
/*#__PURE__*/
function () {
function _Mason(_ref) {
var apiKey = _ref.apiKey,
bucket = _ref.bucket,
draft = _ref.draft,
host = _ref.host,
projectId = _ref.projectId,
transformations = _ref.transformations;
_classCallCheck(this, _Mason);
if (!apiKey) {
throw new Error('Please provide an apiKey in Mason() call');
}
Mason.instance = this;
this.callback = this.callback.bind(this);
this.findComponentInstance = this.findComponentInstance.bind(this);
this.handleAnchorClick = this.handleAnchorClick.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.getContext = this.getContext.bind(this);
this.getProps = this.getProps.bind(this);
this.receiveComponents = this.receiveComponents.bind(this);
this.receiveProjects = this.receiveProjects.bind(this);
this.render = this.render.bind(this);
this.api = _utils.API.urls({
apiKey: apiKey,
bucket: bucket,
draft: draft,
host: host
});
this.bucket = bucket || 'component-cache';
this.callbacks = {};
this.components = {};
this.componentInstances = {};
this.projects = {};
this.transformations = _transformations.applyTransformations.apply(void 0, _toConsumableArray(transformations || _transformations.default))(this.getContext);
if (projectId) {
this.fetchProjects(projectId);
this.fetchStylesheets(projectId);
}
this.observe.bind(this)();
}
_createClass(_Mason, [{
key: "addCallback",
value: function addCallback(name, f, componentId) {
if (componentId) {
var $operation = _lodash.default.has(this.callbacks, componentId) ? '$merge' : '$set';
this.callbacks = (0, _immutabilityHelper.default)(this.callbacks, _defineProperty({}, componentId, _defineProperty({}, $operation, _defineProperty({}, name, f))));
} else {
this.callbacks = (0, _immutabilityHelper.default)(this.callbacks, {
$merge: _defineProperty({}, name, f)
});
}
}
}, {
key: "callback",
value: function callback(name, fromComponentId, target) {
var callbacks = [_lodash.default.get(this.callbacks, "".concat(fromComponentId, ".").concat(name), _lodash.default.identity), _lodash.default.get(this.callbacks, name, _lodash.default.identity)];
if (target) {
callbacks.unshift(_lodash.default.get(this.componentInstances, "".concat(target.getAttribute('id') || fromComponentId, ".props.").concat(name), _lodash.default.identity));
} // callbacks all take the form of a payload as the first argument
// and some metadata as the other args
// the metadata needs to be passed through the chain unmutated
// while the data will be returned from each callback
// and flows through the chain
return function (first) {
for (var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
rest[_key - 1] = arguments[_key];
}
var funcs = _lodash.default.map(callbacks, function (callback) {
return _lodash.default.partialRight.apply(_lodash.default, [callback].concat(rest));
});
return _lodash.default.flow(funcs)(first);
};
}
}, {
key: "fetchProjects",
value: function fetchProjects(projectId, useApi) {
var _this = this;
var pIds = _utils.OBJECT.makeArray(projectId);
var url = this.api.components({
pIds: pIds,
extra: {
s3Miss: useApi
}
}, useApi);
_utils.HTTP.makeCall({
url: url,
verb: _utils.HTTP.GET
}).then(function (response) {
_this.receiveProjects(response.projects);
_this.receiveComponents(response.components);
}).catch(function () {
if (!useApi && _lodash.default.includes(url, 'amazonaws.com')) {
_this.fetchProjects(projectId, true);
}
});
}
}, {
key: "fetchStylesheets",
value: function fetchStylesheets(projectId) {
_utils.CSS.insertProjectStylesheet(projectId, this.api);
}
}, {
key: "findComponentInstance",
value: function findComponentInstance(target) {
var root = target.tagName === 'MASON-CANVAS' ? target : this.findTarget(target);
return _lodash.default.get(this.componentInstances, root.getAttribute('id') || root.getAttribute('data-id'));
}
}, {
key: "findTarget",
value: function findTarget(element) {
for (var parent = element; parent; parent = parent.parentElement) {
if (parent.tagName === 'MASON-CANVAS') {
return parent;
}
}
return null;
}
}, {
key: "getContext",
value: function getContext(target) {
var context = _lodash.default.pick(this, 'callback', 'components', 'findTarget', 'projects', 'transformations');
if (target) {
var root = this.findTarget(target);
var component = _lodash.default.get(this.components, root.getAttribute('data-id'));
var instance = _lodash.default.get(this.componentInstances, root.getAttribute('id') || component.id);
var project = _lodash.default.get(this.projects, component.projectId);
return _objectSpread({}, context, {
component: component,
instance: instance,
project: project,
render: Mason.render
});
}
return context;
}
}, {
key: "getProps",
value: function getProps(target) {
return _lodash.default.get(this.findComponentInstance(target), 'props', {});
}
}, {
key: "handleAnchorClick",
value: function handleAnchorClick(e) {
if (e.target.tagName === 'A') {
var href = e.target.getAttribute('href');
if (_lodash.default.startsWith(href, 'mason:')) {
e.preventDefault();
var root = this.findTarget(e.target);
root.setAttribute('data-config-subpath', _lodash.default.replace(href, 'mason:', ''));
Mason.render(root, this.getProps(root));
}
}
}
}, {
key: "handleFormSubmit",
value: function handleFormSubmit(e) {
var _this2 = this;
e.preventDefault();
var form = e.target;
var root = this.findTarget(form);
var fromComponentId = root.getAttribute('data-id');
var data = (0, _formSerialize.default)(form, {
hash: true
});
var _DOM$attributeValues = _utils.DOM.attributeValues(form, 'id', 'method', 'name'),
id = _DOM$attributeValues.id,
method = _DOM$attributeValues.method,
name = _DOM$attributeValues.name;
var _DOM$attributeValues2 = _utils.DOM.attributeValues(form, 'action'),
action = _DOM$attributeValues2.action; // add componentId as query param to form action
if (_lodash.default.toUpper(action) === _utils.HTTP.GET) {
data.componentId = fromComponentId;
} else {
if (!action.includes('?')) {
action += '?';
} else {
action += '&';
}
action += "componentId=".concat(fromComponentId);
}
var ownData = {
action: action,
data: data,
headers: {}
}; // Expose action and headers in case they want to modify them in willSendData
Promise.resolve(this.callback('willSendData', fromComponentId, root)(ownData, name, fromComponentId)).then(function (d) {
if (d === false) {
return;
}
form.dispatchEvent(new CustomEvent('willSendData', {
bubbles: true,
detail: {
componentId: fromComponentId,
data: d,
name: name
}
}));
_utils.HTTP.makeCall({
url: d.action,
data: d.data,
headers: d.headers,
verb: _lodash.default.toUpper(method)
}).then(function (response) {
return _this2.callback('didReceiveData', fromComponentId, root)(response, name, fromComponentId);
}).then(function (response) {
form.dispatchEvent(new CustomEvent('didReceiveData', {
bubbles: true,
detail: {
componentId: fromComponentId,
data: response,
name: name
}
}));
var errorNode = document.getElementById("".concat(id, "-error"));
if (errorNode) {
form.removeChild(errorNode);
}
var root = _this2.findTarget(form);
var component = _lodash.default.get(_this2.components, fromComponentId);
var path = _lodash.default.last(_lodash.default.split(id, '-'));
var node = _utils.TREE.findNodeAtPath(component.config.data.default.tree, path);
if (_lodash.default.has(node.p, '_events.success')) {
_lodash.default.forEach(node.p._events.success, function (successAction) {
if (successAction.type === 'form') {
if (successAction.action === 'reset') {
Mason.render(form, _this2.getProps(root));
}
} else if (successAction.type === 'page') {
if (_lodash.default.has(component.config.data, successAction.id)) {
root.setAttribute('data-config-subpath', successAction.id);
Mason.render(root, _this2.getProps(root));
}
}
});
}
}).catch(function (response) {
var errorMessage = 'Whoops! An unexpected error occurred.';
if (_lodash.default.isString(response)) {
errorMessage = response;
} else if (_lodash.default.has(response, 'error') && _lodash.default.isString(response.error)) {
errorMessage = response.error;
}
var errorNode = document.getElementById("".concat(id, "-error"));
if (!errorNode) {
errorNode = document.createElement('p');
errorNode.setAttribute('id', "".concat(id, "-error"));
form.prepend(errorNode);
}
errorNode.innerText = errorMessage;
});
});
}
}, {
key: "observe",
value: function observe() {
var root = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
// Observe for changes in standalone components
if (typeof MutationObserver !== 'undefined') {
this.observer = new MutationObserver(function (mutations) {
// Wait for document to load and async config to initialize
if (typeof masonAsyncInit !== 'undefined') {
return false;
}
_lodash.default.forEach(mutations, function (mutation) {
if (mutation.type === 'childList') {
_lodash.default.forEach(mutation.addedNodes, function (addedNode) {
return addedNode.tagName === 'MASON-CANVAS' && Mason.render(addedNode);
});
} else {
Mason.render(mutation.target);
}
});
return true;
});
this.observer.config = {
attributeFilter: ['data-id', 'data-config-subpath', 'data-render'],
attributes: true,
// listen to changes in specific attributes
childList: true,
// listen to node insertions/removals
subtree: true // listen to all nodes in document
};
this.observer.observe(root, this.observer.config);
}
}
}, {
key: "receiveComponents",
value: function receiveComponents(components) {
var _this3 = this;
this.components = (0, _immutabilityHelper.default)(this.components, {
$merge: _lodash.default.keyBy(components, 'id')
});
var withIntegrations = _lodash.default.map(components, function (component) {
var appliedIntegrations = _lodash.default.filter(_integrations.default, function (integration) {
return integration.has(component.config);
});
return _objectSpread({}, component, {
config: (0, _integrations.applyIntegrations)(_lodash.default.map(appliedIntegrations, 'init'))(_this3.getContext)(_lodash.default.identity)(component.config),
render: (0, _integrations.applyIntegrations)(_lodash.default.map(appliedIntegrations, 'render'))(_this3.getContext)(_this3.render)
});
});
this.components = (0, _immutabilityHelper.default)(this.components, {
$merge: _lodash.default.keyBy(withIntegrations, 'id')
});
_lodash.default.forEach(withIntegrations, function (component) {
var targets = document.querySelectorAll("mason-canvas[data-id=\"".concat(component.id, "\"]"));
_lodash.default.forEach(targets, function (target) {
return Mason.render(target, _this3.getProps(target));
});
});
window.dispatchEvent(new CustomEvent('mason.didReceiveComponents'));
}
}, {
key: "receiveProjects",
value: function receiveProjects(projects) {
this.projects = (0, _immutabilityHelper.default)(this.projects, {
$merge: _lodash.default.keyBy(projects, 'id')
});
}
}, {
key: "renderAllInstancesOfComponent",
value: function renderAllInstancesOfComponent(component) {
var _this4 = this;
var targets = document.querySelectorAll("mason-canvas[data-id=\"".concat(component.id, "\"]"));
_lodash.default.forEach(targets, function (target) {
return Mason.render(target, _this4.getProps(target));
});
}
}, {
key: "render",
value: function render(config, configSubpath, target, props) {
var subConfig = config.data[configSubpath];
var tree = _utils.TREE.render(subConfig.tree, _objectSpread({}, props, {
config: config,
configSubpath: configSubpath,
transformations: this.transformations
}));
if (target) {
var root = this.findTarget(target);
if (root !== target) {
var partial = tree.querySelector(target.getAttribute('id'));
document.replaceChild(partial, target);
target.dispatchEvent(new CustomEvent('render', {
bubbles: true
}));
return partial;
}
var id = target.getAttribute('id') || config.componentId;
this.componentInstances = (0, _immutabilityHelper.default)(this.componentInstances, {
$merge: _defineProperty({}, id, {
config: config,
configSubpath: configSubpath,
props: props,
target: target
})
});
target.addEventListener('click', this.handleAnchorClick);
target.addEventListener('submit', this.handleFormSubmit);
target.innerHTML = '';
_lodash.default.forEach(tree, function (node) {
return target.appendChild(node);
});
target.dispatchEvent(new CustomEvent('render', {
bubbles: true
}));
return target;
}
return tree;
}
}, {
key: "setOptions",
value: function setOptions(_ref2) {
var _this5 = this;
var projectId = _ref2.projectId;
var pIds = _utils.OBJECT.makeArray(projectId);
var newProjects = _lodash.default.reject(pIds, function (pId) {
return _lodash.default.has(_this5.projects, pId);
});
if (newProjects.length) {
this.fetchProjects(newProjects);
this.fetchStylesheets(newProjects);
}
}
}]);
return _Mason;
}();
function Mason() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (Mason.instance) {
Mason.instance.setOptions(options);
} else {
_utils.CSS.prependBoilerplateStylesheet();
return new _Mason(options);
}
}
Mason.instance = undefined;
Mason.initGuard = function () {
if (!Mason.instance) {
throw new Error('Please call Mason() with your api key before performing other actions');
}
};
Mason.render = function (target) {
var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var el = function () {
if (_lodash.default.isString(target)) {
return document.querySelector(target);
}
if (_lodash.default.isElement(target)) {
return target;
}
return null;
}();
var id = props.id || el && el.getAttribute('data-id');
var component = _lodash.default.get(Mason.instance.components, id);
if (component) {
var configSubpath = el.getAttribute('data-config-subpath') || 'default';
if (!_lodash.default.has(component.config.data, configSubpath)) {
configSubpath = 'default';
}
return component.render(component.config, configSubpath, el, props);
}
window.addEventListener('mason.didReceiveComponents', function () {
Mason.render(target, props);
});
return null;
};
Mason.callback = function (name, f, componentId) {
Mason.initGuard();
if (_lodash.default.isFunction(f) && _lodash.default.includes(['didFetchData', 'didReceiveData', 'willFetchData', 'willSendData'], name)) {
Mason.instance.addCallback(name, f, componentId);
return true;
}
return false;
};
if (typeof window !== 'undefined') {
window.Mason = Mason;
} else {
global.Mason = Mason;
}
var _default = Mason;
exports.default = _default;