UNPKG

@mason-api/javascript-sdk

Version:

Mason component rendering library

581 lines (473 loc) 20.6 kB
"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;