Extracted ExtensionPoint and extension point store out into a standalone NPM package

Blue Ocean core will be able to create and "export" this (js-modules export). Plugins will "import" that shared instance, allowing them to register/add extension points etc etc.

Will need to enhance the render function to do the async loading of modules when it encounters extension points that are not yet registered.
This commit is contained in:
tfennelly 2016-02-20 12:40:58 +00:00
parent a3a2430603
commit 5b7a7bb5f6
10 changed files with 115 additions and 117 deletions

View File

@ -8,6 +8,7 @@
"jenkins-js-test": "0.0.16"
},
"dependencies": {
"@jenkins-cd/extension-store": "0.0.1",
"bootstrap-detached": "^3.3.4-v6",
"handlebars": "^3.0.3",
"hbsfy": "^2.4.1",

1
js-extensions/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
ExtensionPoint.js

1
js-extensions/.npmignore Normal file
View File

@ -0,0 +1 @@
*.jsx

View File

@ -0,0 +1,70 @@
var React = require('react');
var ReactDOM = require('react-dom');
var store = require('./store.js');
var ExtensionPoint = React.createClass({
componentDidMount: function() {
this._renderAllExtensions();
},
componentDidUpdate: function() {
this._renderAllExtensions();
},
componentWillUnmount: function() {
this._unmountAllExtensions();
},
render: function() {
var extensionDivs = [];
var extensions = store.getExtensions(this.props.name);
for (var i = 0; i < extensions.length; i++) {
extensionDivs.push(<div key={i}/>);
}
return React.createElement(this.props.wrappingElement, null, extensionDivs);
},
_renderAllExtensions: function() {
// TODO: This needs to be a lot cleverer if the list of extensions for a specific point can change
const el = ReactDOM.findDOMNode(this).children;
const extensions = store.getExtensions(this.props.name);
for (var i = 0; i < extensions.length; i++) {
this._renderExtension(el[i], extensions[i]);
}
},
/** Actually render an individual extension */
_renderExtension: function(element, extension) {
var component = React.createElement(extension, this.props);
try {
ReactDOM.render(component, element);
} catch (e) {
console.log("error rendering", extension.name, e);
var errorDiv = <div className="error alien">Error rendering {extension.name}: {e.toString()}</div>;
ReactDOM.render(errorDiv, element);
}
},
/** Clean up child extensions */
_unmountAllExtensions: function() {
var children = ReactDOM.findDOMNode(this).children;
for (var i = 0; i < children.length; i++) {
ReactDOM.unmountComponentAtNode(children[i]); // TODO: Can this throw?
}
}
});
ExtensionPoint.defaultProps = {
wrappingElement: "div"
};
ExtensionPoint.propTypes = {
name: React.PropTypes.string.isRequired,
wrappingElement: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element])
};
module.exports = ExtensionPoint;

3
js-extensions/index.js Normal file
View File

@ -0,0 +1,3 @@
exports.store = require('./store.js');
exports.ExtensionPoint = require('./ExtensionPoint.js');

View File

@ -0,0 +1,20 @@
{
"name": "@jenkins-cd/js-extensions",
"version": "0.0.2",
"description": "Jenkins Extension Store",
"main": "index.js",
"scripts": {
"build": "jsx ExtensionPoint.jsx > ExtensionPoint.js",
"test": "echo \"Error: no test specified\" && exit 1",
"prepublish": "npm run build"
},
"author": "Tom Fennelly <tom.fennelly@gmail.com> (https://github.com/tfennelly)",
"license": "MIT",
"peerDependencies": {
"react": "^0.14.7",
"react-dom": "^0.14.7"
},
"devDependencies": {
"react-tools": "^0.13.3"
}
}

14
js-extensions/store.js Normal file
View File

@ -0,0 +1,14 @@
var points = {};
exports.addExtensionPoint = function(key) {
points[key] = points[key] || [];
};
exports.addExtension = function (key, extension) {
exports.addExtensionPoint(key);
points[key].push(extension);
};
exports.getExtensions = function(key) {
return points[key] || [];
};

View File

@ -17,6 +17,7 @@
"author": "jmcdonald@cloudbees.com",
"license": "MIT",
"dependencies": {
"@jenkins-cd/js-extensions": "0.0.2",
"babel-runtime": "^6.3.19",
"history": "^1.17.0",
"react": "^0.14.5",

View File

@ -1,114 +0,0 @@
import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
//--------------------------------------------------------------------------
//
// ExtensionPointStore
//
//--------------------------------------------------------------------------
// TODO: move this into a proper redux store + reducer
export class ExtensionPointStore {
constructor() {
this.points = {};
}
addExtensionPoint(key) {
//console.log("addExtensionPoint");
this.points[key] = this.points[key] || [];
}
addExtension(key, extension) {
//console.log("addExtension");
this.addExtensionPoint(key);
this.points[key].push(extension);
}
getExtensions(key) {
//console.log("getExtensions", key, this.points);
return this.points[key] || [];
}
registerListener(key, handler) {
}
unRegisterListener(key, handler) {
}
}
// Ugly global
export const extensionPointStoreSingleton = new ExtensionPointStore();
//--------------------------------------------------------------------------
//
// ExtensionPoint
//
//--------------------------------------------------------------------------
/**
* TODO: Docs
*/
export class ExtensionPoint extends Component {
componentDidMount() {
this._renderAllExtensions();
}
componentDidUpdate() {
this._renderAllExtensions();
}
componentWillUnmount() {
this._unmountAllExtensions();
}
render() {
var extensionDivs = [];
var extensions = extensionPointStoreSingleton.getExtensions(this.props.name);
for (var i = 0; i < extensions.length; i++) {
extensionDivs.push(<div key={i}/>);
}
return React.createElement(this.props.wrappingElement, null, extensionDivs);
}
_renderAllExtensions() {
// TODO: This needs to be a lot cleverer if the list of extensions for a specific point can change
const el = ReactDOM.findDOMNode(this).children;
const extensions = extensionPointStoreSingleton.getExtensions(this.props.name);
for (let i = 0; i < extensions.length; i++) {
this._renderExtension(el[i], extensions[i]);
}
}
/** Actually render an individual extension */
_renderExtension(element, extension) {
var component = React.createElement(extension, this.props);
try {
ReactDOM.render(component, element);
} catch (e) {
console.log("error rendering", extension.name, e);
var errorDiv = <div className="error alien">Error rendering {extension.name}: {e.toString()}</div>;
ReactDOM.render(errorDiv, element);
}
}
/** Clean up child extensions */
_unmountAllExtensions() {
for (let node of ReactDOM.findDOMNode(this).children) {
ReactDOM.unmountComponentAtNode(node); // TODO: Can this throw?
}
}
}
ExtensionPoint.defaultProps = {
wrappingElement: "div"
};
ExtensionPoint.propTypes = {
name: PropTypes.string.isRequired,
wrappingElement: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
};

View File

@ -2,8 +2,9 @@
* Re-export things for convenience.
*/
import {ExtensionPoint, extensionPointStoreSingleton} from './extension-point.jsx';
import extensions from '@jenkins-cd/js-extensions';
import {store, actions} from './stores';
export const extensionPointStore = extensionPointStoreSingleton; // TODO: remove ugly global
export {ExtensionPoint, store, actions};
export const extensionPointStore = extensions.store; // TODO: remove ugly global
export const ExtensionPoint = extensions.ExtensionPoint;
export {store, actions};