Skip to content

Commit

Permalink
Refactor embedding functions, create embed and embed-requirejs bundles.
Browse files Browse the repository at this point in the history
  • Loading branch information
jasongrout committed Aug 11, 2017
1 parent 8589114 commit 7eed820
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 143 deletions.
30 changes: 23 additions & 7 deletions docs/source/migration_guides.md
Expand Up @@ -117,23 +117,39 @@ JavaScript bundle when embedding widgets. The embed manager will look for the
bundle at `https://unpkg.com/<module-name>@<module-version>/dist/index.js`
when it finds a widget.

### Updating links
### Updating embedded widgets

If your code or your documentation references the link for the jupyter-widgets
script that loads widgets embedded outside the notebook, this has changed to
something similar to the following code:
There are now two options for embedding widgets in an HTML page outside of the notebook.

#### Embedding the standard widgets

If you are just embedding the standard widgets that come with ipywidgets, then you can simply include the following script tag:

```html
<script src="https://unpkg.com/@jupyter-widgets/html-manager@*/dist/embed.js"></script>
```

If you want to use a specific version of the embedder, you replace the `@*` with a semver range, such as `@^0.7.0`

#### Embedding with require.js and third-party widgets

In order to embed third-party widgets, you can use the require.js-based embedding. First, make sure that require.js is loaded on the page, for example:

```html
<!-- Load require.js. Delete this if your page already loads require.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js" integrity="sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA=" crossorigin="anonymous"></script>
```

Then require the embedder and run the `renderWidgets` function:
```html
<script>
window.require(['https://unpkg.com/@jupyter-widgets/html-manager/dist/embed'], function(embed) {
window.require(['https://unpkg.com/@jupyter-widgets/html-manager@*/dist/embed-requirejs'], function(embed) {
if (document.readyState === "complete") {
embed.renderInlineWidgets();
embed.renderWidgets();
} else {
window.addEventListener('load', embed.renderInlineWidgets);
window.addEventListener('load', function() {embed.renderWidgets();});
}
});
</script>
```
If you want to use a specific version of the embedder, you replace the `@*` with a semver range, such as `@^0.7.0`
2 changes: 2 additions & 0 deletions examples/web4/README.md
Expand Up @@ -13,6 +13,8 @@ replace

with

`window.require(['https://unpkg.com/@jupyter-widgets/html-manager/dist/embed'], `

`<script src="https://unpkg.com/@jupyter-widgets/html-manager/dist/embed-cdn.js"></script>`

If you need a specific version of the HTML widget manager, you can include a
Expand Down
6 changes: 3 additions & 3 deletions examples/web4/index.html
Expand Up @@ -14,11 +14,11 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js" integrity="sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA=" crossorigin="anonymous"></script>

<script>
window.require(['https://unpkg.com/@jupyter-widgets/html-manager/dist/embed'], function(embed) {
window.require(['https://unpkg.com/@jupyter-widgets/html-manager/dist/embed-requirejs'], function(embed) {
if (document.readyState === "complete") {
embed.renderInlineWidgets();
embed.renderWidgets();
} else {
window.addEventListener('load', embed.renderInlineWidgets);
window.addEventListener('load', function() {embed.renderWidgets();});
}
});
</script>
Expand Down
6 changes: 3 additions & 3 deletions ipywidgets/embed.py
Expand Up @@ -22,9 +22,9 @@
<script>
window.require(['{embed_url}'], function(embed) {
if (document.readyState === "complete") {
embed.renderInlineWidgets();
embed.renderWidgets();
} else {
window.addEventListener('load', embed.renderInlineWidgets);
window.addEventListener('load', function() {embed.renderWidgets();});
}
});
</script>
Expand Down Expand Up @@ -52,7 +52,7 @@
{view_spec}
</script>"""

DEFAULT_EMBED_SCRIPT_URL = u'https://unpkg.com/@jupyter-widgets/html-manager@%s/dist/embed.js'%__html_manager_version__
DEFAULT_EMBED_SCRIPT_URL = u'https://unpkg.com/@jupyter-widgets/html-manager@%s/embed-requirejs'%__html_manager_version__


def _find_widget_refs_by_state(widget, state):
Expand Down
2 changes: 1 addition & 1 deletion packages/base/src/manager-base.ts
Expand Up @@ -412,7 +412,7 @@ abstract class ManagerBase<T> {
* current manager state, and then attempts to redisplay the widgets in the
* state.
*/
set_state(state) {
set_state(state): Promise<WidgetModel[]> {
// Check to make sure that it's the same version we are parsing.
if (!(state.version_major && state.version_major <= 2)) {
throw "Unsupported widget state format";
Expand Down
55 changes: 55 additions & 0 deletions packages/html-manager/src/embed-requirejs.ts
@@ -0,0 +1,55 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import {
renderInlineWidgets
} from './embedlib';


// Populate the requirejs cache with local versions of @jupyter-widgets/base,
// @jupyter-widgets/controls, @jupyter-widgets/html-manager. These are externals
// when compiled with webpack.
require('./base');
require('./controls');
require('./index');

/**
* Load a package using requirejs and return a promise
*
* @param pkg Package name or names to load
*/
let requirePromise = function(pkg: string | string[]): Promise<any> {
return new Promise((resolve, reject) => {
let require = (window as any).require;
if (require === undefined) {
reject("Requirejs is needed, please ensure it is loaded on the page.");
} else {
require(pkg, resolve, reject);
}
});
}

function requireLoader(moduleName: string, moduleVersion: string) {
return requirePromise([`${moduleName}.js`]).catch((err) => {
let failedId = err.requireModules && err.requireModules[0];
if (failedId) {
console.log(`Falling back to unpkg.com for ${moduleName}@${moduleVersion}`);
return requirePromise([`https://unpkg.com/${moduleName}@${moduleVersion}/dist/index.js`]);
}
});
}

/**
* Render widgets in a given element.
*
* @param element (default document.documentElement) The element containing widget state and views.
*/
export
function renderWidgets(element = document.documentElement) {
requirePromise(['@jupyter-widgets/html-manager']).then((htmlmanager) => {
let managerFactory = () => {
return new htmlmanager.HTMLManager({loader: requireLoader});
}
renderInlineWidgets(managerFactory, element);
});
}
119 changes: 11 additions & 108 deletions packages/html-manager/src/embed.ts
@@ -1,115 +1,18 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import 'font-awesome/css/font-awesome.css';
import '@phosphor/widgets/style/index.css';
import '@jupyter-widgets/controls/css/widgets.built.css';

// WidgetModel is *just* used as a typing below
import {
WidgetModel
} from '@jupyter-widgets/base';

// Populate the requirejs cache with local versions of @jupyter-widgets/base,
// @jupyter-widgets/controls, @jupyter-widgets/html-manager
require('./base');
require('./controls');
require('./index');

/**
* Load a package using requirejs and return a promise
*
* @param pkg Package name or names to load
*/
let requirePromise = function(pkg: string | string[]) {
return new Promise((resolve, reject) => {
let require = (window as any).require;
if (require === undefined) {
reject("Requirejs is needed to run the Jupyter Widgets html manager");
} else {
require(pkg, resolve, reject);
}
});
}

// Element.prototype.matches polyfill for IE
// See https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.msMatchesSelector ||
Element.prototype.webkitMatchesSelector;
}
HTMLManager
} from './index';

// Load json schema validator
var Ajv = require('ajv');
var widget_state_schema = require('@jupyter-widgets/schema').v2.state;
var widget_view_schema = require('@jupyter-widgets/schema').v2.view;

let ajv = new Ajv()
let model_validate = ajv.compile(widget_state_schema);
let view_validate = ajv.compile(widget_view_schema);

/**
* Render the inline widgets inside a DOM element.
*
* @param element (default document.documentElement) The document element in which to process for widget state.
*/
export
function renderInlineWidgets(element: HTMLElement = document.documentElement) {
let tags = element.querySelectorAll('script[type="application/vnd.jupyter.widget-state+json"]');
for (let i=0; i!=tags.length; ++i) {
renderManager(element, JSON.parse(tags[i].innerHTML));
}
}

/**
* Create a widget manager for a given widget state.
*
* @param element The DOM element to search for widget view state script tags
* @param widgetState The widget manager state
*
* #### Notes
*
* Widget view state should be in script tags with type
* "application/vnd.jupyter.widget-view+json". Any such script tag containing a
* model id the manager knows about is replaced with a rendered view.
* Additionally, if the script tag has a prior img sibling with class
* 'jupyter-widget', then that img tag is deleted.
*/
function renderManager(element, widgetState) {
(window as any).require(['@jupyter-widgets/html-manager'], (htmlmanager) => {
let valid = model_validate(widgetState);
if (!valid) {
console.error('Model state has errors.', model_validate.errors);
}
let manager = new htmlmanager.HTMLManager();
manager.set_state(widgetState).then(function(models) {
let tags = element.querySelectorAll('script[type="application/vnd.jupyter.widget-view+json"]');
for (let i=0; i!=tags.length; ++i) {
let viewtag = tags[i];
let widgetViewObject = JSON.parse(viewtag.innerHTML);
let valid = view_validate(widgetViewObject);
if (!valid) {
console.error('View state has errors.', view_validate.errors);
}
let model_id = widgetViewObject.model_id;
// Find the model id in the models. We should use .find, but IE
// doesn't support .find
let model = models.filter( (item : WidgetModel) => {
return item.model_id == model_id;
})[0];
if (model !== undefined) {
if (viewtag.previousElementSibling &&
viewtag.previousElementSibling.matches('img.jupyter-widget')) {
viewtag.parentElement.removeChild(viewtag.previousElementSibling);
}
let widgetTag = document.createElement('div');
widgetTag.className = 'widget-subarea';
viewtag.parentElement.insertBefore(widgetTag, viewtag);
manager.display_model(undefined, model, { el : widgetTag });
}
}
});
import {
renderInlineWidgets
} from './embedlib';

// Render the widgets that we can
if (!(window as any)._jupyter_widget_embedder) {
(window as any)._jupyter_widget_embedder = true;
window.addEventListener('load', () => {
renderInlineWidgets(() => new HTMLManager())
});
}

83 changes: 83 additions & 0 deletions packages/html-manager/src/embedlib.ts
@@ -0,0 +1,83 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import 'font-awesome/css/font-awesome.css';
import '@phosphor/widgets/style/index.css';
import '@jupyter-widgets/controls/css/widgets.built.css';

/* Used just for the typing */
import {
HTMLManager
} from './index';

// Load json schema validator
var Ajv = require('ajv');
var widget_state_schema = require('@jupyter-widgets/schema').v2.state;
var widget_view_schema = require('@jupyter-widgets/schema').v2.view;

let ajv = new Ajv()
let model_validate = ajv.compile(widget_state_schema);
let view_validate = ajv.compile(widget_view_schema);

/**
* Render the inline widgets inside a DOM element.
*
* @param managerFactory A function that returns a new HTMLManager
* @param element (default document.documentElement) The document element in which to process for widget state.
*/
export
function renderInlineWidgets(managerFactory: () => HTMLManager, element: HTMLElement = document.documentElement) {
let tags = element.querySelectorAll('script[type="application/vnd.jupyter.widget-state+json"]');
for (let i=0; i!=tags.length; ++i) {
renderManager(element, JSON.parse(tags[i].innerHTML), managerFactory);
}
}

/**
* Create a widget manager for a given widget state.
*
* @param element The DOM element to search for widget view state script tags
* @param widgetState The widget manager state
*
* #### Notes
*
* Widget view state should be in script tags with type
* "application/vnd.jupyter.widget-view+json". Any such script tag containing a
* model id the manager knows about is replaced with a rendered view.
* Additionally, if the script tag has a prior img sibling with class
* 'jupyter-widget', then that img tag is deleted.
*/
function renderManager(element: HTMLElement, widgetState: any, managerFactory: () => HTMLManager) {
let valid = model_validate(widgetState);
if (!valid) {
console.error('Model state has errors.', model_validate.errors);
}
let manager = managerFactory();
manager.set_state(widgetState).then(function(models) {
let tags = element.querySelectorAll('script[type="application/vnd.jupyter.widget-view+json"]');
for (let i=0; i!=tags.length; ++i) {
let viewtag = tags[i];
let widgetViewObject = JSON.parse(viewtag.innerHTML);
let valid = view_validate(widgetViewObject);
if (!valid) {
console.error('View state has errors.', view_validate.errors);
}
let model_id: string = widgetViewObject.model_id;
// Find the model id in the models. We should use .find, but IE
// doesn't support .find
let model = models.filter( (item) => {
return item.model_id == model_id;
})[0];
if (model !== undefined) {
let prev = viewtag.previousElementSibling;
if (prev && prev.tagName === 'img' && prev.classList.contains('jupyter-widget')) {
viewtag.parentElement.removeChild(prev);
}
let widgetTag = document.createElement('div');
widgetTag.className = 'widget-subarea';
viewtag.parentElement.insertBefore(widgetTag, viewtag);
manager.display_model(undefined, model, { el : widgetTag });
}
}
});
}

0 comments on commit 7eed820

Please sign in to comment.