Skip to content

Commit

Permalink
Make the widget manager totally responsible for loading a class.
Browse files Browse the repository at this point in the history
  • Loading branch information
jasongrout committed Apr 26, 2017
1 parent 760156a commit 70918d3
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 139 deletions.
12 changes: 12 additions & 0 deletions jupyter-js-widgets/examples/web3/src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ class WidgetManager extends widgets.ManagerBase<HTMLElement> {
});
}

protected loadClass(className: string, moduleName: string, moduleVersion: string): Promise<any> {
if (moduleName === 'jupyter-js-widgets') {
if (widgets[className]) {
return Promise.resolve(widgets[className]);
} else {
return Promise.reject(`Cannot find class ${className}`)
}
} else {
return Promise.reject(`Cannot find module ${moduleName}`);
}
}

_create_comm(targetName, id, metadata) {
return this.commManager.new_comm(targetName, metadata, id);
}
Expand Down
12 changes: 12 additions & 0 deletions jupyter-js-widgets/examples/web5/src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ class WidgetManager extends widgets.ManagerBase<HTMLElement> {
});
}

protected loadClass(className: string, moduleName: string, moduleVersion: string): Promise<any> {
if (moduleName === 'jupyter-js-widgets') {
if (widgets[className]) {
return Promise.resolve(widgets[className]);
} else {
return Promise.reject(`Cannot find class ${className}`)
}
} else {
return Promise.reject(`Cannot find module ${moduleName}`);
}
}

_create_comm(targetName, id, metadata) {
return this.commManager.new_comm(targetName, metadata, id);
}
Expand Down
29 changes: 19 additions & 10 deletions jupyter-js-widgets/src-embed/embed-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {

import * as PhosphorWidget from '@phosphor/widgets';

import * as widgets from '../../jupyter-js-widgets/lib/index';

export
class EmbedManager extends ManagerBase<HTMLElement> {

Expand Down Expand Up @@ -43,18 +45,25 @@ class EmbedManager extends ManagerBase<HTMLElement> {
};

/**
* Takes a requirejs success handler and returns a requirejs error handler
* that attempts loading the module from unpkg.
* Load a class and return a promise to the loaded object.
*/
require_error(success_callback, failure_callback, version : string) {
return function(err) : any {
var failedId = err.requireModules && err.requireModules[0];
if (failedId) {
// TODO: Get typing to work for requirejs
(window as any).require(['https://unpkg.com/' + failedId + '@' + version + '/dist/index.js'], success_callback);
protected loadClass(className: string, moduleName: string, moduleVersion: string) {
return new Promise(function(resolve, reject) {
if (moduleName === 'jupyter-js-widgets') {
// Shortcut resolving the standard widgets so we don't load two
// copies on the page. If we ever separate the embed manager
// from the main widget package, we should get rid of this special
// case.
resolve(widgets);
} else {
(window as any).require([`https://unpkg.com/${moduleName}@${moduleVersion}/dist/index.js`], resolve, reject);
}
}).then(function(module) {
if (module[className]) {
return module[className];
} else {
failure_callback(err);
return Promise.reject(`Class ${className} not found in module ${moduleName}@${moduleVersion}`);
}
};
});
}
};
55 changes: 26 additions & 29 deletions jupyter-js-widgets/src-embed/embed-webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

// Element.prototype.matches polyfill
if (Element && !Element.prototype.matches) {
var proto = Element.prototype as any;
let proto = Element.prototype as any;
proto.matches = proto.matchesSelector ||
proto.mozMatchesSelector || proto.msMatchesSelector ||
proto.oMatchesSelector || proto.webkitMatchesSelector;
Expand All @@ -31,22 +31,19 @@ import * as _ from 'underscore';
// All it does is inserting a <script> tag for requirejs in the case it is not
// available and call `renderInlineWidgets`
function loadInlineWidgets(event) {
// If requirejs is not on the page on page load, load it from cdn.
if (!(window as any).requirejs) {
var scriptjs = require('scriptjs') as any;
scriptjs('https://unpkg.com/requirejs/require.js', function() {
// Define jupyter-js-widget requirejs module
//
// (This is needed for custom widget model to be able to AMD require jupyter-js-widgets.)
(window as any).define('jupyter-js-widgets', function() {
return widgets;
let loadRequire = new Promise(function(resolve, reject) {
if ((window as any).requirejs) {
resolve();
} else {
// If requirejs is not on the page on page load, load it from cdn.
let scriptjs = require('scriptjs') as any;
scriptjs('https://unpkg.com/requirejs/require.js', function() {
resolve();
});
// Render inline widgets
renderInlineWidgets(event);
});
} else {
}
});
loadRequire.then(function() {
// Define jupyter-js-widget requirejs module
//
// (This is needed for custom widget model to be able to AMD require jupyter-js-widgets.)
if (!(window as any).requirejs.defined('jupyter-js-widgets')) {
(window as any).define('jupyter-js-widgets', function () {
Expand All @@ -55,17 +52,17 @@ function loadInlineWidgets(event) {
}
// Render inline widgets
renderInlineWidgets(event);
}
});
}

// `renderInlineWidget` will call `renderManager(element, tag)` on each <script>
// tag of mime type
// "application/vnd.jupyter.widget-state+json"
// contained in `event.element`.
export function renderInlineWidgets(event) {
var element = event.target || document;
var tags = element.querySelectorAll('script[type="application/vnd.jupyter.widget-state+json"]');
for (var i=0; i!=tags.length; ++i) {
let element = event.target || document;
let tags = element.querySelectorAll('script[type="application/vnd.jupyter.widget-state+json"]');
for (let i=0; i!=tags.length; ++i) {
renderManager(element, tags[i]);
}
}
Expand All @@ -76,28 +73,28 @@ export function renderInlineWidgets(event) {
// Then it performs a look up of all script tags under the specified DOM
// element with the mime type
// "application/vnd.jupyter.widget-view+json".
// For each oone of these <script> tag, if the contained json specifies a model id
// For each one of these <script> tags, if the contained json specifies a model id
// known to the aforementioned manager, it is replaced with a view of the model.
//
// Besides, if the view script tag has an <img> sibling DOM node with class `jupyter-widget`,
// the <img> tag is deleted.
function renderManager(element, tag) {
var widgetStateObject = JSON.parse(tag.innerHTML);
var ajv = new Ajv(); // options can be passed, e.g. {allErrors: true}
var model_validate = ajv.compile(widget_state_schema);
var valid = model_validate(widgetStateObject);
let widgetStateObject = JSON.parse(tag.innerHTML);
let ajv = new Ajv(); // options can be passed, e.g. {allErrors: true}
let model_validate = ajv.compile(widget_state_schema);
let valid = model_validate(widgetStateObject);
if (!valid) {
console.log(model_validate.errors);
}
var manager = new embed.EmbedManager();
let manager = new embed.EmbedManager();
manager.set_state(widgetStateObject, {}).then(function(models) {
var tags = element.querySelectorAll('script[type="application/vnd.jupyter.widget-view+json"]');
for (var i=0; i!=tags.length; ++i) {
let tags = element.querySelectorAll('script[type="application/vnd.jupyter.widget-view+json"]');
for (let i=0; i!=tags.length; ++i) {
// TODO: validate view schema
let viewtag = tags[i];
let widgetViewObject = JSON.parse(viewtag.innerHTML);
var view_validate = ajv.compile(widget_view_schema);
var valid = view_validate(widgetViewObject);
let view_validate = ajv.compile(widget_view_schema);
let valid = view_validate(widgetViewObject);
if (!valid) {
console.log(view_validate.errors);
}
Expand Down
53 changes: 22 additions & 31 deletions jupyter-js-widgets/src/manager-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,9 @@ abstract class ManagerBase<T> {
create_view(model, options) {
model.state_change = model.state_change.then(() => {

return this.loadClass(
model.get('_view_name'),
return this.loadClass(model.get('_view_name'),
model.get('_view_module'),
model.get('_view_module_version'),
this.require_error
model.get('_view_module_version')
).then((ViewType) => {
var view = new ViewType({
model: model,
Expand Down Expand Up @@ -206,7 +204,7 @@ abstract class ManagerBase<T> {

/**
* Create a comm and new widget model.
* @param options - same options as new_model but, comm is not
* @param options - same options as new_model but comm is not
* required and additional options are available.
* @param serialized_state - serialized model attributes.
*/
Expand Down Expand Up @@ -327,11 +325,11 @@ abstract class ManagerBase<T> {
return widget_model;
};

var model_promise = this.loadClass(options.model_name,
options.model_module,
options.model_module_version,
that.require_error)
.then(function(ModelType) {
let model_promise = this.loadClass(
options.model_name,
options.model_module,
options.model_module_version
).then(function(ModelType) {
try {
return ModelType._deserialize_state(serialized_state || {}, that)
.then(function(attributes) {
Expand Down Expand Up @@ -450,22 +448,19 @@ abstract class ManagerBase<T> {
});
}

let modelCreate: ModelOptions = {
model_id: model_id,
model_name: model.model_name,
model_module: model.model_module,
model_module_version: model.model_module_version
};
if (live_comms.hasOwnProperty(model_id)) { // live comm
return this._create_comm(this.comm_target_name, model_id).then(new_comm => {
return this.new_model({
comm: new_comm,
model_name: models[model_id].model_name,
model_module: models[model_id].model_module,
model_module_version: models[model_id].model_module_version
});
return this._create_comm(this.comm_target_name, model_id).then(comm => {
modelCreate.comm = comm;
return this.new_model(modelCreate);
});
} else { // dead comm
return this.new_model({
model_id: model_id,
model_name: models[model_id].model_name,
model_module: models[model_id].model_module,
model_module_version: models[model_id].model_module_version
}, modelState);
} else {
return this.new_model(modelCreate, modelState);
}
}));
});
Expand All @@ -476,13 +471,9 @@ abstract class ManagerBase<T> {
/**
* Load a class and return a promise to the loaded object.
*/
protected loadClass(className, moduleName, moduleVersion, error) {
return utils.loadClass(className, moduleName, moduleVersion, null, error);
}

abstract _create_comm(comm_target_name, model_id, data?): Promise<any>;

abstract _get_comm_info();
protected abstract loadClass(className: string, moduleName: string, moduleVersion: string): Promise<any>;
protected abstract _create_comm(comm_target_name, model_id, data?): Promise<any>;
protected abstract _get_comm_info();

/**
* Dictionary of model ids and model instance promises
Expand Down
60 changes: 0 additions & 60 deletions jupyter-js-widgets/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,66 +45,6 @@ class WrappedError extends Error {
error_stack: any[];
}

/**
* Tries to load a class
*
* Tries to load a class from a module using require.js, if a module
* is specified, otherwise tries to load a class from the global
* registry, if the global registry is provided.
*
* The optional require_error argument is a function that takes the success
* handler and returns a requirejs error handler, which may call the success
* handler with a fallback module.
*
*/

export
function loadClass(class_name, module_name, module_version, registry, require_error): Promise<any> {
return new Promise(function(resolve, reject) {

// Try loading the view module using require.js
if (module_name) {
// If the module is jupyter-js-widgets, we can just self import.
var modulePromise;
var requirejsDefined = typeof window !== 'undefined' && (window as any).requirejs;
if (requirejsDefined) {
if (module_name !== 'jupyter-js-widgets' || (window as any).requirejs.defined('jupyter-js-widgets')) {
modulePromise = new Promise(function(innerResolve, innerReject) {
var success_callback = function(module) {
innerResolve(module);
};
var failure_callback = require_error ? require_error(success_callback, reject, module_version) : reject;
(window as any).require([module_name], success_callback, failure_callback);
});
} else if (module_name === 'jupyter-js-widgets') {
modulePromise = Promise.resolve(require('../'));
}
} else if (module_name === 'jupyter-js-widgets') {
modulePromise = Promise.resolve(require('../'));
} else {
// FUTURE: Investigate dynamic loading methods other than require.js.
throw new Error(['In order to use third party widgets, you ',
'must have require.js loaded on the page.'].join(''));
}

modulePromise.then(function(module) {
if (module[class_name] === undefined) {
reject(new Error('Class ' + class_name + ' not found in module ' + module_name));
} else {
resolve(module[class_name]);
}
});
} else {
if (registry && registry[class_name]) {
resolve(registry[class_name]);
} else {
reject(new Error('Class ' + class_name + ' not found in registry '));
}
}
});
}


/**
* Resolve a promiseful dictionary.
* Returns a single Promise.
Expand Down
20 changes: 15 additions & 5 deletions jupyter-js-widgets/test/src/dummy-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

import expect = require('expect.js');

import {
ManagerBase
} from '../../lib';
import * as widgets from '../../lib';
import * as services from '@jupyterlab/services';
import * as Backbone from 'backbone';

Expand All @@ -17,7 +15,7 @@ class MockComm {
}

export
class DummyManager extends ManagerBase<HTMLElement> {
class DummyManager extends widgets.ManagerBase<HTMLElement> {
constructor() {
super();
this.el = window.document.createElement('div');
Expand All @@ -30,7 +28,19 @@ class DummyManager extends ManagerBase<HTMLElement> {
return view.el;
});
}


protected loadClass(className: string, moduleName: string, moduleVersion: string): Promise<any> {
if (moduleName === 'jupyter-js-widgets') {
if (widgets[className]) {
return Promise.resolve(widgets[className]);
} else {
return Promise.reject(`Cannot find class ${className}`)
}
} else {
return Promise.reject(`Cannot find module ${moduleName}`);
}
}

_get_comm_info() {
return Promise.resolve({});
}
Expand Down
4 changes: 2 additions & 2 deletions jupyterlab_widgets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,10 @@ class WidgetManager extends ManagerBase<Widget> implements IDisposable {
/**
* Load a class and return a promise to the loaded object.
*/
protected loadClass(className: string, moduleName: string, moduleVersion: string, error: any): any {
protected loadClass(className: string, moduleName: string, moduleVersion: string): any {
let mod: any = this._registry.get(moduleName, moduleVersion);
if (!mod) {
return Promise.reject(`Module ${moduleName}, semver range ${moduleVersion} is not registered as a widget module`);
return Promise.reject(`Module ${moduleName}, semver range ${moduleVersion} is not registered as a widget module`);
}
let cls: any = mod[className];
if (!cls) {
Expand Down
Loading

0 comments on commit 70918d3

Please sign in to comment.