flexx icon indicating copy to clipboard operation
flexx copied to clipboard

Trying to build a SPA / Saas with Flexx

Open NolanG241 opened this issue 3 years ago • 1 comments

hi,

i'm relative new to flexx, but what i tried / seen so far is really nice 👍 .

for a little project of mine, i'm trying to build a single page application or a software as a service.

currently i'm researching/testing form<->model handling/validation. is there an how-to or a guide about two-way-data-binding?

what i have seen/tried so far is relative cumbersome.

class MyForm(flx.Widget):

    data_model = flx.DictProp(
        {'no': '1', 'name': 'Test', 'desc': 'Desc', 'dyn': 'Dyn'})

    def init(self, data=None):
        super().init()

        with flx.FormLayout():
            self.form_no = flx.LineEdit(title='No.:')
            self.form_name = flx.LineEdit(title='Name:')
            self.form_desc = flx.MultiLineEdit(title='Description:')
            with flx.HBox():
                self.save_btn = flx.Button(text='Save')
            flx.Widget(flex=1)  # Add a spacer

        self.update_model(data)

    @flx.reaction('form_no.user_done')
    def update_no(self, *events):
        data = {
            'no': events[-1]['new_value']
        }
        self.update_model(data)

    @flx.action
    def update_model(self, data):
        if data is None:
            return
        self._mutate_data_model(data, 'replace', -1)

    @flx.action
    def model_to_form(self):
        self.form_no.set_text(self.data_model['no'])
        self.form_name.set_text(self.data_model['name'])
        self.form_desc.set_text(self.data_model['desc'])

    @flx.reaction('save_btn.pointer_click')
    def save_form(self, *events):
        print(repr(self.data_model))

    @flx.reaction('data_model')
    def print_model(self, *events):
        print(repr(self.data_model))
        self.model_to_form()


class MyUI(flx.Widget):

    def init(self):
        super().init()

        with flx.HBox():
            self.form = MyForm(flex=1)


class MyApp(flx.PyComponent):

    def init(self):
        super().init()

        self.ui = MyUI()


if __name__ == '__main__':
    app = flx.App(MyApp)
    app.launch('browser')
    flx.start()

having to map each field of the form to the model and back... not very user friendly.

greetings nolan

NolanG241 avatar Nov 15 '22 18:11 NolanG241

hi,

i think somehow i found a solution for the form data binding. not pretty, but it works.

first i created a model js component which stores the data of the model:

` class DataModel(flx.JsComponent):

data = flx.DictProp({}, settable=True)

def init(self):
    super().init()

@flx.action
def update_data(self, data):
    self._mutate_data(data, 'replace', -1)

def dispose(self):
    self.data.clear()
    super().dispose()

`

next a single binding component to handle the changes in both ways:

` class DataBinding(flx.JsComponent):

model = flx.ComponentProp()
field = flx.ComponentProp()
key = flx.StringProp('', settable=True)
on_done = flx.BoolProp(False, settable=True)

def init(self, model, key, field, on_done=False):
    super().init()
    self._mutate_model(model)
    self._mutate_field(field)
    self._mutate_key(key)
    self._mutate_on_done(on_done)

@flx.reaction('model.data')
def on_model(self, *events):
    self.update_field()

@flx.action
def update_field(self):
    self.field.set_text(self.model.data[self.key])

@flx.reaction('field.user_done')
def on_field_done(self, *events):
    if self.on_done:
        self.update_model(events[-1]['new_value'])

@flx.reaction('field.user_text')
def on_field_text(self, *events):
    if not self.on_done:
        self.update_model(events[-1]['new_value'])

@flx.action
def update_model(self, value):
    data = {
        self.key: value,
    }
    self.model.update_data(data)

def dispose(self):
    self.model = None
    self.field = None
    super().dispose()

`

and at last the form with the bindings:

` class Form(flx.Widget):

model = flx.ComponentProp()
bindings = flx.ListProp([], settable=True)

def init(self):
    super().init()

    self._mutate_model(DataModel())

    with flx.FormLayout():
        self.form_no = flx.LineEdit(title='No.:')
        self.form_name = flx.LineEdit(title='Name:')
        self.form_desc = flx.MultiLineEdit(title='Description:')
        with flx.HBox():
            self.reset_btn = flx.Button(text='Reset')
            self.save_btn = flx.Button(text='Save')
        flx.Widget(flex=1)  # Add a spacer

    self.bind(self.model, 'no', self.form_no, True)
    self.bind(self.model, 'name', self.form_name, True)
    self.bind(self.model, 'desc', self.form_desc, True)

    data = {
        'no': '1',
        'name': 'Name',
        'desc': 'Desc',
    }

    self.model.update_data(data)

@flx.action
def bind(self, model, key, field, on_done=False):
    bd = DataBinding(model, key, field, on_done)
    self._mutate_bindings([bd], 'insert', len(self.bindings))

@flx.reaction('reset_btn.pointer_click')
def reset_form(self, *events):
    data = {
        'no': '1',
        'name': 'Name',
        'desc': 'Desc',
    }
    self.model.update_data(data)

@flx.reaction('save_btn.pointer_click')
def save_form(self, *events):
    print(repr(self.model.data))

def dispose(self):
    for entry in self.bindings:
        entry.dispose()
    self.bindings.clear()
    super().dispose()

class UI(flx.Widget):

def init(self):
    super().init()

    with flx.HBox():
        self.form = Form(flex=1)

class App(flx.PyComponent):

def init(self):
    super().init()

    self.ui = UI()

if name == 'main': app = flx.App(App) app.launch('browser') flx.start() `

what is not working having a manager to handle the bindings if i use the below manager instead of having the bind method on the form, i'm getting "RuntimeError: DataBinding15 needs a session!"

` class DataManager(flx.JsComponent):

bindings = flx.ListProp([], settable=True)

def init(self, model=None):
    super().init()
    # self._mutate_model(model)

@flx.action
def bind(self, model, key, field, on_done=False):
    bd = DataBinding(model, key, field, on_done)
    self._mutate_bindings([bd], 'insert', len(self.bindings))

@flx.action
def clear(self):
    self._mutate_bindings([])

def dispose(self):
    for entry in self.bindings:
        entry.dispose()
    self.bindings.clear()
    super().dispose()

`

greetings nolan

NolanG241 avatar Nov 16 '22 13:11 NolanG241