diff --git a/IPython/html/widgets/__init__.py b/IPython/html/widgets/__init__.py
index 0a5c7cc..293c54f 100644
--- a/IPython/html/widgets/__init__.py
+++ b/IPython/html/widgets/__init__.py
@@ -11,7 +11,7 @@ from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select
from .widget_selectioncontainer import Tab, Accordion
from .widget_string import HTML, Latex, Text, Textarea
from .interaction import interact, interactive, fixed, interact_manual
-from .widget_link import Link, link, DirectionalLink, dlink
+from .widget_link import Link, link, DirectionalLink, directional_link, dlink
# Deprecated classes
from .widget_bool import CheckboxWidget, ToggleButtonWidget
diff --git a/IPython/html/widgets/widget_link.py b/IPython/html/widgets/widget_link.py
index d38c116..9ee17fa 100644
--- a/IPython/html/widgets/widget_link.py
+++ b/IPython/html/widgets/widget_link.py
@@ -2,31 +2,47 @@
Propagate changes between widgets on the javascript side
"""
-#-----------------------------------------------------------------------------
-# Copyright (c) 2014, the IPython Development Team.
-#
+
+# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
from .widget import Widget
-from IPython.utils.traitlets import Unicode, Tuple, Any
+from IPython.testing.skipdoctest import skip_doctest
+from IPython.utils.traitlets import Unicode, Tuple, List,Instance, TraitError
-#-----------------------------------------------------------------------------
-# Classes
-#-----------------------------------------------------------------------------
+class WidgetTraitTuple(Tuple):
+ """Traitlet for validating a single (Widget, 'trait_name') pair"""
+
+ def __init__(self, **kwargs):
+ super(WidgetTraitTuple, self).__init__(Instance(Widget), Unicode, **kwargs)
+
+ def validate_elements(self, obj, value):
+ value = super(WidgetTraitTuple, self).validate_elements(obj, value)
+ widget, trait_name = value
+ trait = widget.traits().get(trait_name)
+ trait_repr = "%s.%s" % (widget.__class__.__name__, trait_name)
+ # Can't raise TraitError because the parent will swallow the message
+ # and throw it away in a new, less informative TraitError
+ if trait is None:
+ raise TypeError("No such trait: %s" % trait_repr)
+ elif not trait.get_metadata('sync'):
+ raise TypeError("%s cannot be synced" % trait_repr)
+
+ return value
class Link(Widget):
- """Link Widget"""
+ """Link Widget
+
+ one trait:
+ widgets, a list of (widget, 'trait_name') tuples which should be linked in the frontend.
+ """
_model_name = Unicode('LinkModel', sync=True)
- widgets = Tuple(sync=True, allow_none=False)
+ widgets = List(WidgetTraitTuple, sync=True)
- def __init__(self, widgets=(), **kwargs):
+ def __init__(self, widgets, **kwargs):
+ if len(widgets) < 2:
+ raise TypeError("Require at least two widgets to link")
kwargs['widgets'] = widgets
super(Link, self).__init__(**kwargs)
@@ -35,19 +51,39 @@ class Link(Widget):
self.close()
+@skip_doctest
def link(*args):
+ """Link traits from different widgets together on the frontend so they remain in sync.
+
+ Parameters
+ ----------
+ *args : two or more (Widget, 'trait_name') tuples that should be kept in sync.
+
+ Examples
+ --------
+
+ >>> c = link((widget1, 'value'), (widget2, 'value'), (widget3, 'value'))
+ """
return Link(widgets=args)
class DirectionalLink(Widget):
- """Directional Link Widget"""
+ """A directional link
+
+ source: a (Widget, 'trait_name') tuple for the source trait
+ targets: one or more (Widget, 'trait_name') tuples that should be updated
+ when the source trait changes.
+ """
_model_name = Unicode('DirectionalLinkModel', sync=True)
- targets = Any(sync=True)
- source = Tuple(sync=True)
+ targets = List(WidgetTraitTuple, sync=True)
+ source = WidgetTraitTuple(sync=True)
# Does not quite behave like other widgets but reproduces
# the behavior of IPython.utils.traitlets.directional_link
- def __init__(self, source, targets=(), **kwargs):
+ def __init__(self, source, targets, **kwargs):
+ if len(targets) < 1:
+ raise TypeError("Require at least two widgets to link")
+
kwargs['source'] = source
kwargs['targets'] = targets
super(DirectionalLink, self).__init__(**kwargs)
@@ -56,6 +92,21 @@ class DirectionalLink(Widget):
def unlink(self):
self.close()
+@skip_doctest
+def directional_link(source, *targets):
+ """Link the trait of a source widget with traits of target widgets in the frontend.
+
+ Parameters
+ ----------
+ source : a (Widget, 'trait_name') tuple for the source trait
+ *targets : one or more (Widget, 'trait_name') tuples that should be updated
+ when the source trait changes.
+
+ Examples
+ --------
+
+ >>> c = dlink((src_widget, 'value'), (tgt_widget1, 'value'), (tgt_widget2, 'value'))
+ """
+ return DirectionalLink(source=source, targets=targets)
-def dlink(source, *targets):
- return DirectionalLink(source, targets)
+dlink = directional_link