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