Skip to content
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

Some help needed with Form styles #521

Open
barendburger opened this issue Nov 12, 2024 · 5 comments
Open

Some help needed with Form styles #521

barendburger opened this issue Nov 12, 2024 · 5 comments

Comments

@barendburger
Copy link

I'm at best an intermediate on the Python side. I'm busy doing an app using Bootstrap, and want to get my forms styled, but this section in the docs is just not giving me enough to make a start.

Customizing forms
Good applications also need good styles. This is why Emmett forms allows you to set a specific style with the formstyle attribute. But how should you edit the style of your form?

Well, in Emmett, the style of a form is decided by the FormStyle class._

Can anyone please provide some code samples on how to get my forms styled with Bootstrap css classes at the highest possible level in the app, ie, I would rather specifiy things once, than having to inject every time I call a form. But at this point I won't be picky, either approach will work for me.

I'll commit to contribute my learnings to that section in the doicuments, if I can just get started. I've spent way too many hours at this point trying to get this done by myself.

@gi0baro
Copy link
Member

gi0baro commented Nov 13, 2024

Hi @barendburger, the idea behind the FormStyle class is to customise form items using Emmett's html builtin helpers.

In emmett form elements are divided by field type, so you have individual widget functions to return the relevant HTML. A very rough example of a bootstrap style for Emmett forms might look like this:

from emmett.forms import FormStyle
from emmett.html import tag

class BSFormStyle(FormStyle):
    @staticmethod
    def widget_bool(attr, field, value, _id=None):
        return FormStyle.widget_bool(attr, field, value, _class="bool checkbox", _id=_id)

    def on_start(self):
        self.parent = tag.fieldset()

    def style_widget(self, widget):
        wtype = widget['_class'].split(' ')[0]
        if wtype not in ["bool", "upload_wrap", "input-group"]:
            widget['_class'] += " form-control"

    def create_label(self, label):
        wid = self.element.widget['_id']
        return tag.label(label, _for=wid, _class='col-sm-2 control-label')

    def create_comment(self, comment):
        return tag.p(comment, _class='help-block')

    def create_error(self, error):
        return tag.p(error, _class='text-danger')

    def add_widget(self, widget):
        _class = 'form-group'
        label = self.element.label
        wrapper = tag.div(widget, _class='col-sm-10')
        if self.element.error:
            wrapper.append(self.element.error)
            _class += ' has-error'
        if self.element.comment:
            wrapper.append(self.element.comment)
        self.parent.append(tag.div(label, wrapper, _class=_class))

    def add_buttons(self):
        submit = tag.input(_type='submit', _value=self.attr['submit'], _class='btn btn-primary')
        buttons = tag.div(submit, _class="col-sm-10 col-sm-offset-2")
        self.parent.append(tag.div(buttons, _class='form-group'))

    def render(self):
        self.attr['_class'] = self.attr.get('_class', 'form-horizontal')
        return super().render(self)

this should give you an idea on how it works. The only widget implement here is for boolean fields, but you can use the same approach for, let's say, add date pickers implementing the relevant widget_date static method.

There's also a very old extension to Emmett pre-2.0 here (https://github.com/gi0baro/weppy-bs3). It won't work with Emmett directly, but should give you more details on how to put everything together!

@barendburger
Copy link
Author

Thank you so much for your hepl @gi0baro , it makes sense, but let me wrap my head around it and see how far I get.

@barendburger
Copy link
Author

@gi0baro I need to admit defeat. I've tried to implement your code aboveby following the linked example for an extension, also tried passing the FormStyle in when instantiating the form, but both fail with the error below. I tried that in my app, and then also in your blog example to try simplify things.

TypeError: FormStyle.render() takes 1 positional argument but 2 were given

Seems to happen on
return super().render(self)

Here's the full trace:

ERROR in handlers [/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/emmett_core/protocols/rsgi/handlers.py:146]:
Application exception:
Traceback (most recent call last):
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/emmett_core/protocols/rsgi/handlers.py", line 136, in dynamic_handler
http = await self.router.dispatch(request, response)
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/emmett_core/routing/router.py", line 230, in dispatch
return await match.dispatch(reqargs, response)
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/emmett_core/routing/dispatchers.py", line 59, in dispatch
rv = self.response_builder(await self.f(**reqargs), response)
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/emmett_core/routing/response.py", line 63, in call
response.status, self.process(output, response), headers=response.headers, cookies=response.cookies
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/emmett/routing/response.py", line 65, in process
return self.route.app.templater.render(self.route.template, output)
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/renoir/apis.py", line 172, in render
return self._render(source, file_path, context)
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/renoir/apis.py", line 166, in _render
make_traceback(exc_info)
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/renoir/debug.py", line 114, in make_traceback
reraise(exc_type, exc_value, tb)
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/renoir/_internal.py", line 15, in reraise
raise value.with_traceback(tb)
File "/home/barend/Code/test_emmett/templates/new_post.html", line 4, in template
{{=form}}
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/renoir/writers.py", line 46, in escape
self.write(self._escape_data(data))
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/renoir/writers.py", line 42, in _escape_data
body = self._to_html(self._to_unicode(data))
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/renoir/writers.py", line 29, in _to_unicode
return to_unicode(data)
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/renoir/_shortcuts.py", line 33, in to_unicode
return str(obj)
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/emmett_core/html.py", line 89, in str
return self.html()
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/emmett/forms.py", line 231, in html
return self._render().html()
File "/home/barend/Code/test_emmett/pythonenv/lib/python3.10/site-packages/emmett/forms.py", line 192, in _render
return styler.render()
File "/home/barend/Code/test_emmett/style.py", line 45, in render
return super().render(self)
TypeError: FormStyle.render() takes 1 positional argument but 2 were given

If you can show me how to connect/inject the new FormStyle in a way that works, then I should be able to take it from there.

@gi0baro
Copy link
Member

gi0baro commented Nov 21, 2024

@barendburger sorry, typo from my side, just replace that line with super().render()

@barendburger
Copy link
Author

LOL, I can't believe the one thing I did't try when playing with arguments was to remove it completely. Thanks, that worked. Let me dig a bit deeper with my current project, and will then do a pull request for some extra documentation when I've worked with it a bit more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants