widget_link.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # Copyright (c) Jupyter Development Team.
  2. # Distributed under the terms of the Modified BSD License.
  3. """Link and DirectionalLink classes.
  4. Propagate changes between widgets on the javascript side.
  5. """
  6. from .widget import Widget, register, widget_serialization
  7. from .widget_core import CoreWidget
  8. from traitlets import Unicode, Tuple, Instance, TraitError
  9. class WidgetTraitTuple(Tuple):
  10. """Traitlet for validating a single (Widget, 'trait_name') pair"""
  11. info_text = "A (Widget, 'trait_name') pair"
  12. def __init__(self, **kwargs):
  13. super(WidgetTraitTuple, self).__init__(Instance(Widget), Unicode(), **kwargs)
  14. def validate_elements(self, obj, value):
  15. value = super(WidgetTraitTuple, self).validate_elements(obj, value)
  16. widget, trait_name = value
  17. trait = widget.traits().get(trait_name)
  18. trait_repr = "%s.%s" % (widget.__class__.__name__, trait_name)
  19. # Can't raise TraitError because the parent will swallow the message
  20. # and throw it away in a new, less informative TraitError
  21. if trait is None:
  22. raise TypeError("No such trait: %s" % trait_repr)
  23. elif not trait.metadata.get('sync'):
  24. raise TypeError("%s cannot be synced" % trait_repr)
  25. return value
  26. @register
  27. class Link(CoreWidget):
  28. """Link Widget
  29. source: a (Widget, 'trait_name') tuple for the source trait
  30. target: a (Widget, 'trait_name') tuple that should be updated
  31. """
  32. _model_name = Unicode('LinkModel').tag(sync=True)
  33. target = WidgetTraitTuple(help="The target (widget, 'trait_name') pair").tag(sync=True, **widget_serialization)
  34. source = WidgetTraitTuple(help="The source (widget, 'trait_name') pair").tag(sync=True, **widget_serialization)
  35. def __init__(self, source, target, **kwargs):
  36. kwargs['source'] = source
  37. kwargs['target'] = target
  38. super(Link, self).__init__(**kwargs)
  39. # for compatibility with traitlet links
  40. def unlink(self):
  41. self.close()
  42. def jslink(attr1, attr2):
  43. """Link two widget attributes on the frontend so they remain in sync.
  44. The link is created in the front-end and does not rely on a roundtrip
  45. to the backend.
  46. Parameters
  47. ----------
  48. source : a (Widget, 'trait_name') tuple for the first trait
  49. target : a (Widget, 'trait_name') tuple for the second trait
  50. Examples
  51. --------
  52. >>> c = link((widget1, 'value'), (widget2, 'value'))
  53. """
  54. return Link(attr1, attr2)
  55. @register
  56. class DirectionalLink(Link):
  57. """A directional link
  58. source: a (Widget, 'trait_name') tuple for the source trait
  59. target: a (Widget, 'trait_name') tuple that should be updated
  60. when the source trait changes.
  61. """
  62. _model_name = Unicode('DirectionalLinkModel').tag(sync=True)
  63. def jsdlink(source, target):
  64. """Link a source widget attribute with a target widget attribute.
  65. The link is created in the front-end and does not rely on a roundtrip
  66. to the backend.
  67. Parameters
  68. ----------
  69. source : a (Widget, 'trait_name') tuple for the source trait
  70. target : a (Widget, 'trait_name') tuple for the target trait
  71. Examples
  72. --------
  73. >>> c = dlink((src_widget, 'value'), (tgt_widget, 'value'))
  74. """
  75. return DirectionalLink(source, target)