New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use with Enaml #359
Comments
Not sure, I don't have sufficient experience with Enaml to anwer this. But if you consider Flexx as a GUI backend, and if any GUI backend can be used with Enaml, than why not ... You might be interested to know that the author of Enaml (Chris Colbert) is also the author of PhosphorJS, which is the GUI library (and more) that Flexx uses for most of its layout.
Yes, this is a common problem. I think Flexx makes connecting things up really easy, but for larger applications / more complex interactions, we've felt that there might be room for improvement. Which is why I am interested to hear from you how Enaml keeps things more contained? Is it the declarative way to connect certain model attributed to views? |
Yes, I did see that. Perhaps he would know if this is feasible. I do know it's easy to add a toolkit for creating the widget tree and a different application/event loop to Enaml, but I'm unsure if it's possible to swap the data binding part (atom) of Enaml with an alternative.
Basically, yes. Instead of manually having to connect signals the Enaml parser does it for you, and it's all defined within the actual declarative widget tree, so handlers are not scattered all over. I've used Enaml for several projects and it's by far the cleanest way I've ever seen to do a model based UI. |
If I were to mock up a few examples rewritten using an "enaml flexx toolkit". This example http://flexx.readthedocs.io/en/latest/ui/lineedit.html from enaml.flexx.api import App, VBox, Label, LineEdit
enamldef Example(App):
VBox:
LineEdit: line:
placeholder_text = 'type here'
autocomp = ['foo','bar']
Label:
text << line.text # this is the @event.connect('line.text') and _change_label in one line Enaml uses Likewise Would be something like: from enaml.flexx.api import App, HBox, Button, ProgressBar
enamldef Example(App):
HBox:
Button:
text = "Less"
clicked :: prog.value -= 0.1 # @event.connect(b1.mouse_down) to prog.value
Button:
text = "More"
clicked :: prog.value += 0.1 # @event.connect(b2.mouse_down) to prog.value
ProgressBar: prog:
value = 0.1 # = sets Initial value Enaml uses The plotwidget example: from enaml.flexx.api import App, VBox, HBox, Label, Slider, PlotWidget
enamldef Example(App):
VBox:
HBox:
Label:
text = "Frequency"
Slider: s1:
min = 1
max = 10
value = 5
flex = 1
Label:
text = "Phase"
Slider: s2:
min = 0
max = 6
value = 0
flex = 1
PlotWidget:
flex = 1
xlabel = 'time'
ylabel = 'amplitude'
title = 'a sinusoid'
xdata = [i/100 for i in range(100)]
ydata << [s1.value * x * 2 * window.Math.PI + s2.value] # connects ydata to s1.value and s2.value
There's no examples I could find but you can also do two way data binding in enaml using the from enaml.flexx.api import App, LineEdit, VBox
enamldef Example(App):
VBox:
LineEdit: line1:
pass
LineEdit: line2:
text := line1.text # Would keep both in sync, or they could both be tied to a model property In all the examples they would need to be started in an enaml like way. if __name__=='__main__':
from enaml.flexx.app import FlexApplication
app = FlexApplication()
view = Example()
view.show()
app.start() # starts flexx io loop It's a bit more ugly/verbose when defining multiple properties but you can see it keeps all the handlers / signals within the widget using them which really keeps large UI's organized well. |
Thanks for these! I think we can distinguish between two approaches that make Enaml look so declarative. The first is the structure of the code, which is something that we can achieve pretty well in vanilla Flexx as it is, though its somewhat more verbose. For example the progressbar example: class Example(Widget):
def init(self):
with HBox():
Button(
text = "Less",
clicked = ??
)
Button(
text = "More",
clicked = ??
)
self.prog = ProgressBar(
value = 0.1 # = sets Initial value
) The other approach (the main point of this discussion, I think) is the way that Enaml allows declaring connectivity in-line at the place where a widget is defined. I see two variants of this: a property having a calculated value (from one or more other properties), and an in-line event handler. Enaml solves these by defining a Python/yaml -like format. One approach (as you suggested in the initial post) could be to make Flexx work with Enaml. Another path could be to keep writing Python and come up with something to put at the question marks above. Something that is both feasible and easy enough to get that "declarative" feel ... |
Thinking about the first case (a property that has a calculated value), let's look at the simplest example: In Enaml: LineEdit: line:
...
Label:
text << line.text Current Flexx inline approach (yes, this looks verbose in comparison!): label = Label()
line = LineEdit(...)
@line.connect('text')
def on_line_change(self, *events):
label.text =line.text First things that popped to mind: label = Label(text=lambda: line.text) # a prop with a function as a value is a calculated prop
label = Label(text='<<line.text') # some kind of prefix to indicate a calculated value The problem with the above lambda is that there is no way for the code to know what events it should connect to. Unless we create an AST for the code and see what variables the lambda uses, which could work in theory, but only in Python, because in JS we cannot parse Python code. The string is problematic because it refers to 'line.text', meaning that the variable 'line' should somehow be evaluated in the namespace that defines the string, which is possible with Perhaps something like this could work though: label = Label(text=follow(line, 'text')) The plot = PlotWidget(...., xdata = [i/100 for i in range(100)],
ydata=follow(s1, 'value', s2, 'value', 'xdata',
lambda s1_val, s2_val, xdata: [s1_val * x + 2 * PI + s2_val for x in xdata])) |
As for in-line event handlers, I think this would be more difficult, since lambdas are not really suited (cannot set an attribute in a lambda, for instance). I can imagine something like this: def on_click(*events):
prog.value += 1
button1 = Button(on_mouse_click=on_click) where keyword args starting with |
To me, it seems like the simplest syntax for the user is most desirable. label = Label(text=line.text)
|
|
Well, that's what I'm thinking; it should perhaps be the prop object instead. That's how libraries like vue.js and react.js accomplish it, I think. |
I am not sure if I follow. That would mean that you'd have to write But thinking in this direction, maybe something like |
On 21.02.2017 01:47, Almar Klein wrote:
As for in-line event handlers, I think this would be more difficult,
since lambdas are not really suited (cannot set an attribute in a
lambda, for instance). I can imagine something like this:
def on_click(*events):
prog.value += 1
button1 = Button(on_mouse_click=on_click)
I can set attribute in lambda:
b1 = Button(on_mouse_click=lambda val: setattr(prog, 'value', val))
HTH
Niki
|
You can make a prop object and its value behave like they are the same thing by implementing the Object Proxy pattern. |
At minute 10 in this video Enaml - A Framework for Building Declarative User Interfaces , he explains cases in which using something like
This is similar to how kivy does it: https://kivy.org/docs/guide/lang.html. While it does not really save many lines (unless using lambdas with setattr) it does somewhat help keep the code more organized, and is nicer than doing separate bindings. Not sure how scoping would work with js handlers, aren't they in separate classes? |
Regarding a potential enaml toolkit implementation... I found out that with enaml can you can override the operators and define code tracers that can watch any object that can be observed with callbacks (ie so it could be made to observe changes to flexx.app.Model). What is also interesting is that NEW custom operators can be defined (as is done here). So for instance you could define separate js handlers and py handlers by using different operators for each. Since enaml manipulates python bytecode I doubt it can run in js. I'm not sure how that would impact it's use with flexx as I don't quite understand what get's converted to js and pushed to the browser and what doesn't. On a side note (possibly should move to another discussion), is there a reason why flexx uses annotations for properties instead of a declarative property pattern (like traitlets, atom, kivy properties, django model fields, etc...)? If properties then could be class MyModel(Model):
# String, List, and Float would all be a subclass of some base Property object (ie an event.prop)
baz = String().tag(js=False) # Py only property
foo = Float(0) # defaults to both js and py
bar = List([1,2,3]).tag(py=False)
fee = Int(28).tag(writable=False) # Read only
sig = Emitter() # Emitter
@event.connect('foo')
def handle_changes_to_foo_in_python(self, *events):
... # knows it's a binding for both since foo is tagged for py and js by default
@event.connect('bar')
def handle_changes_to_foo_in_js(self, *events):
... # knows it's a js only binding because bar is tagged py=False Don't know, just wondering.. |
Thanks for that video link. Very interesting how Enaml achieves this feat of not having to setup any connections. Unfortunately, like you expected, this approach cannot be used in the JS part of our models, which makes it not well suited for Flexx. I did some experimenting yesterday, and I think that via another approach we can achieve this: # Simply track one property
label = Label(text=lambda: line.text)
# More complex case
plotwidget = PlotWidget(...,
ydata=lambda:[slider1.value * x**2 + slider2.value * x for x in plotwidget.xdata]
) This would set up a handler wrapping the lambda, and connect that handler automatically to any properties used in the handler. Similarly, this allows writing handlers without having to specify any connection strings. The method below would get automatically called when any of the properties it uses class MyModel(ui.Widget):
@trackthis
def some_method(self):
self.label.text = 'The names: ' + ', '.join([c.name.value for c in self.children]) I'd need to look into this some more. I am not sure yet how this will affect performance, for instance. But I do like the idea of this. Also, how do events that don't originate from a property fit into this (perhaps just connect these as is done now)? Edit, some background on how I think we can achive this: we can make the property getters call into a "tracker object" which is turned on right before calling the function. When the function is done, we thus have a description of the used properties, and can connect these up. It does mean some over head for each time that the handler is called, but perhaps that overhead can be made small for cases when there are no reconnections to be made. |
About the declarative property pattern: that is something that I've kept in mind, and I think this could still be implemented without having to change any of the existing behavior. I've been reluctant to do this though, because then we'd have 2 ways to do the same thing, and although the pattern is way more compact for simple cases, this advantage gets less when one adds proper documentation, or when you get complex enum structures, which are often solved much easier in a more imperative way. If you'd like to discuss this more, let's open a new issue. |
I'd love to dive into a few specific examples and see how declarative UI libraries measure up against imperative approaches. |
Some more thoughts ... which you may not like :) Although I was enthusiastic at first, I now think that I do not like the inline lambda pattern: Label(text=lambda: line.text) The reason is that such a pattern can only be applied Python side, because models are only instantiated from Python. Propagating such an approach would make people write apps that cannot be exported to standalone HTML. You could use a pattern of setting a property to a function in JS ... but I think that somewhat defeats the purpose. : class MyModel(app.Widget):
def init(self):
self.line = TextEdit()
self.label = Label()
class JS:
def init(self):
self.label.text = lambda: self.line,text What is seems to come down to, is that by its nature of classes that define Python and JS behavior, Flexx likes the handlers to be methods. Either on the main class (for Python-side behavior) or in the nested JS class (for JS-side behavior). What we could make work (as I mentioned earlier in a slightly different form): @event.react
def _react_line(self):
self.label.text = self.line.text Which would be a handler, that you can define either for Py or JS, and that gets automatically called whenever In the same line, it could allow computed properties: @event.computed
def full_name(self):
return self.first_name + ' ' + self.last_name This pattern would co-exists with the current |
To clarify my previous comment. I think that if we go with the |
Is there any way this could be implemented as a toolkit for use with Enaml?
Flexx does have a nice declarative way of defining the structure, however the data binding aspect is separated into different handlers, which (from experience) quickly gets tedious in larger applications. Enaml nicely keeps everything contained.
Just wondering... I think flexx is pretty awesome 👍
The text was updated successfully, but these errors were encountered: