def test_column_clearfix(web_fixture): """If a logical row spans more than one visual row for a device size, bootstrap clearfixes are automatically inserted to ensure cells in resultant visual rows are neatly arranged. """ # Case: Adding a correct clearfix in the right place wrapping_layout = ColumnLayout(ColumnOptions('column_a', size=ResponsiveSize(md=8), offsets=ResponsiveSize(md=2)), ColumnOptions('column_b', size=ResponsiveSize(md=2), offsets=ResponsiveSize(md=2)) ) widget = Div(web_fixture.view).use_layout(wrapping_layout) [column_a, clearfix, column_b] = widget.children assert [column_a, column_b] == [i for i in wrapping_layout.columns.values()] assert 'clearfix' in clearfix.get_attribute('class') assert 'hidden-sm' in clearfix.get_attribute('class') # Case: When clearfix needs to take "implicit" sizes of smaller device classes into account wrapping_layout = ColumnLayout(ColumnOptions('column_a', size=ResponsiveSize(xs=8), offsets=ResponsiveSize(xs=2)), ColumnOptions('column_b', size=ResponsiveSize(lg=2), offsets=ResponsiveSize(lg=2)) ) widget = Div(web_fixture.view).use_layout(wrapping_layout) [column_a, clearfix, column_b] = widget.children assert [column_a, column_b] == [i for i in wrapping_layout.columns.values()] assert 'clearfix' in clearfix.get_attribute('class') assert 'hidden-md' in clearfix.get_attribute('class') # Case: When no clearfix must be added non_wrapping_layout = ColumnLayout(ColumnOptions('column_a', size=ResponsiveSize(xs=2), offsets=ResponsiveSize(xs=2)), ColumnOptions('column_b', size=ResponsiveSize(xs=2)) ) widget = Div(web_fixture.view).use_layout(non_wrapping_layout) [column_a, column_b] = widget.children assert [column_a, column_b] == [i for i in non_wrapping_layout.columns.values()]
def customise_widget(self): self.container = self.widget.add_child(Div(self.view)) self.container.use_layout(Container(fluid=False)) self.centre = self.container.add_child(Div(self.view)) column_layout = ColumnLayout(('left', ResponsiveSize(md=4)), ('right', ResponsiveSize(md=8))) self.centre.use_layout(column_layout)
def __init__(self, view): super(CommentForm, self).__init__(view, 'myform') comment = Comment() layout = ColumnLayout( ColumnOptions('left', size=ResponsiveSize(lg=6)), ColumnOptions('right', size=ResponsiveSize(lg=6))) # .add_child() returns the added child here: row = self.add_child(Div(view).use_layout(layout)) left_column = row.layout.columns['left'] # ... and .use_layout() returns the Widget it is called on section = Div(view).use_layout(FormLayout()) left_column.add_child(section) email_input = TextInput(self, comment.fields.email_address) section.layout.add_input(email_input) inline_section = Div(view).use_layout(InlineFormLayout()) left_column.add_child(inline_section) text_input = TextInput(self, comment.fields.text) inline_section.layout.add_input(text_input) right_column = row.layout.columns['right'] right_column.add_child( LiteralHTML.from_restructured_text( view, ''' This form has two columns. Inputs go into the left one and this text into the right one. Some inputs are stacked and others are inlined. Arbitrarily complicated layouts can be created like this.'''))
def column_clearfix(fixture): """If a logical row spans more than one visual row for a device size, bootstrap clearfixes are automatically inserted to ensure cells in resultant visual rows are neatly arranged. """ # Case: Adding a correct clearfix in the right place wrapping_layout = ColumnLayout(('column_a', ResponsiveSize(xs=8).offset(xs=2)), ('column_b', ResponsiveSize(xs=2).offset(xs=2)) ) widget = Div(fixture.view).use_layout(wrapping_layout) [column_a, clearfix, column_b] = widget.children vassert( [column_a, column_b] == [i for i in wrapping_layout.columns.values()] ) vassert( 'clearfix' in clearfix.get_attribute('class') ) vassert( 'visible-xs-block' in clearfix.get_attribute('class') ) # Case: When clearfix needs to take "implicit" sizes of smaller device classes into account wrapping_layout = ColumnLayout(('column_a', ResponsiveSize(xs=8).offset(xs=2)), ('column_b', ResponsiveSize(lg=2).offset(lg=2)) ) widget = Div(fixture.view).use_layout(wrapping_layout) [column_a, clearfix, column_b] = widget.children vassert( [column_a, column_b] == [i for i in wrapping_layout.columns.values()] ) vassert( 'clearfix' in clearfix.get_attribute('class') ) vassert( 'visible-lg-block' in clearfix.get_attribute('class') ) # Case: When no clearfix must be added non_wrapping_layout = ColumnLayout(('column_a', ResponsiveSize(xs=2).offset(xs=2)), ('column_b', ResponsiveSize(xs=2)) ) widget = Div(fixture.view).use_layout(non_wrapping_layout) [column_a, column_b] = widget.children vassert( [column_a, column_b] == [i for i in non_wrapping_layout.columns.values()] )
def add_upload_controls(self): controls_panel = self.upload_form.add_child(Div(self.view)).use_layout(FormLayout()) file_input = controls_panel.layout.add_input(FileInput(self.upload_form.form, self.fields.uploaded_file), hide_label=True) button_addon = file_input.html_representation.add_child(Div(self.view)) button_addon.append_class('input-group-append') button_addon.add_child(Button(self.upload_form.form, self.events.upload_file, style='secondary', outline=True)) return controls_panel
def test_column_gutters(web_fixture): """A ColumnLayout can be made that does not include gutters in the layout.""" fixture = web_fixture widget = Div(fixture.view).use_layout(ColumnLayout('column_name_a', 'column_name_b').without_gutters()) assert 'no-gutters' in widget.get_attribute('class').split()
def test_horizontal_alignment(web_fixture): """You can specify how columns are aligned horizontally.""" widget = Div( web_fixture.view).use_layout(ColumnLayout().with_justified_content( ContentJustification(md='around'))) assert 'justify-content-md-around' in widget.get_attribute('class').split()
def test_column_layout_default_options(web_fixture): """You can also pass only column names instead of ColumnOptions objects in which case the column will be configured with default ColumnOptions.""" widget = Div(web_fixture.view) widget.use_layout(ColumnLayout('column_a')) [column_a] = widget.children assert 'col' in column_a.get_attribute('class')
def test_vertical_alignment(web_fixture): """You can specify how columns are aligned vertically and override the alignment of a specific column.""" widget = Div(web_fixture.view).use_layout(ColumnLayout().with_vertical_alignment(Alignment(md='center'))) defaulted_column = widget.layout.add_column('default_column') specifically_overridden_column = widget.layout.add_column('overridden_column', vertical_align=Alignment(md='end')) assert 'align-items-md-center' in widget.get_attribute('class').split() assert 'align-self-md-end' in specifically_overridden_column.get_attribute('class').split() assert 'align' not in defaulted_column.get_attribute('class')
def test_column_layout_unspecified_size(web_fixture): """Specifying a size of True for a device class means that the size is automatically computed by dividing available space equally amongst all the columns so specified.""" layout = ColumnLayout(ColumnOptions('column_a', size=ResponsiveSize(lg=True)), ColumnOptions('column_b', size=ResponsiveSize(lg=True))) widget = Div(web_fixture.view) widget.use_layout(layout) column_a, column_b = widget.children assert 'col-lg' in column_a.get_attribute('class') assert 'col-lg' in column_b.get_attribute('class')
def test_containers(web_fixture): """There are two types of Bootstrap containers: a full width container, and a responsive (fluid) container.""" widget = Div(web_fixture.view).use_layout(Container()) tester = WidgetTester(widget) css_class = tester.xpath('//div')[0].attrib['class'] assert 'container' == css_class widget = Div(web_fixture.view).use_layout(Container(fluid=True)) tester = WidgetTester(widget) css_class = tester.xpath('//div')[0].attrib['class'] assert 'container-fluid' == css_class
def __init__(self, form, bound_field): file_input = FileInputButton(form, bound_field) super().__init__(file_input) self.input_group = self.add_child(Div(self.view)) self.input_group.append_class('input-group') self.input_group.append_class('reahl-bootstrapfileinput') self.set_html_representation(self.input_group) div = self.input_group.add_child(Div(form.view)) div.append_class('input-group-prepend') div.add_child(file_input) filename_input = self.input_group.add_child(Span(self.view, text=_('No files chosen'))) filename_input.append_class('form-control')
def add_twelve(self): div = Div(self.view).use_layout(ColumnLayout()) self.body.add_child(div) for i in range(1, 13): column = div.layout.add_column(str(i), size=ResponsiveSize(md=1)) column.add_child(P(self.view, text='1/12th on md and larger'))
def customise_widget(self): super().customise_widget() if not self.widget.css_id_is_set: raise ProgrammerError('%s has no css_id set. A %s can only be used with a Widget that has a css_id' % (self.widget, self.__class__)) collapsing_content = Div(self.view, css_id='%s_collapsable' % self.widget.css_id) collapsing_content.append_class('navbar-collapse') self.collapsing_content = collapsing_content self.contents_container.add_child(collapsing_content) self.contents_container = collapsing_content self.add_toggle(collapsing_content, text=self.text, left_aligned=self.align_toggle_left) toggle_size = self.collapse_below_device_class.one_smaller self.nav.append_class('navbar-expand-%s' % toggle_size.name)
def test_brand_may_be_collapsed_with_expandable_content( web_fixture, navbar_fixture, brand_collapse_fixture): """Brands may be collapse along with the other content.""" responsive_navbar = navbar_fixture.navbar responsive_navbar.set_id('my_navbar_id') responsive_navbar.use_layout( ResponsiveLayout( 'md', collapse_brand_with_content=brand_collapse_fixture.brand_collapse)) responsive_navbar.layout.add(navbar_fixture.nav) brand_widget = Div(web_fixture.view) responsive_navbar.layout.set_brand(brand_widget) if brand_collapse_fixture.brand_collapse: [toggle, collapse_div] = responsive_navbar.children[0].children [brand, navbar_nav] = collapse_div.children else: [brand, toggle, collapse_div] = responsive_navbar.children[0].children [navbar_nav] = collapse_div.children assert brand is brand_widget assert 'navbar-collapse' in collapse_div.get_attribute('class') assert 'navbar-nav' in navbar_nav.html_representation.get_attribute( 'class') assert 'navbar-toggler' in toggle.get_attribute('class')
def __init__(self, view, css_id, show_indicators=True, interval=5000, pause='hover', wrap=True, keyboard=True, min_height=None): super(Carousel, self).__init__(view) self.carousel_panel = self.add_child(Div(view, css_id=css_id)) self.carousel_panel.append_class('carousel') self.carousel_panel.append_class('slide') self.carousel_panel.set_attribute('data-ride', 'carousel') self.carousel_panel.set_attribute('data-interval', six.text_type(interval)) pause_option = HTMLAttributeValueOption(pause or 'false', True, constrain_value_to=['hover', 'false']) self.carousel_panel.set_attribute('data-pause', pause_option.as_html_snippet()) self.carousel_panel.set_attribute('data-wrap', 'true' if wrap else 'false') self.carousel_panel.set_attribute('data-keyboard', 'true' if keyboard else 'false') if min_height: style = self.carousel_panel.add_child(HTMLElement(self.view, 'style', children_allowed=True)) css_id = self.carousel_panel.css_id style.add_child(TextNode(self.view, '#%s .carousel-item { min-height: %sem; }' % (css_id, min_height))) self.show_indicators = show_indicators if self.show_indicators: self.indicator_list = self.carousel_panel.add_child(self.create_indicator_list()) self.inner = self.carousel_panel.add_child(self.create_inner()) self.slides = [] self.add_control(previous=True) self.add_control()
def test_adding_other_than_form_or_nav_is_not_allowed(web_fixture, navbar_fixture): """Only Navs, Forms and Text may be added to a NavbarLayout.""" navbar = navbar_fixture.navbar.use_layout(NavbarLayout()) not_a_form_or_nav = Div(web_fixture.view) with expected(IsInstance): navbar.layout.add(not_a_form_or_nav) # Case: Form navbar = navbar_fixture.new_navbar().use_layout(NavbarLayout()) navbar.layout.add(navbar_fixture.form) [added_widget] = navbar.children[0].children assert added_widget is navbar_fixture.form assert 'form-inline' in navbar_fixture.form.get_attribute('class') # Case: Nav navbar = navbar_fixture.new_navbar().use_layout(NavbarLayout()) assert 'navbar-nav' not in navbar_fixture.nav.html_representation.get_attribute( 'class').split(' ') navbar.layout.add(navbar_fixture.nav) [added_widget] = navbar.children[0].children assert added_widget is navbar_fixture.nav assert 'navbar-nav' in navbar_fixture.nav.html_representation.get_attribute( 'class').split(' ') # Case: Text navbar = navbar_fixture.new_navbar().use_layout(NavbarLayout()) navbar.layout.add(navbar_fixture.textnode) [added_widget] = navbar.children[0].children [textnode] = added_widget.children assert isinstance(added_widget, Span) assert textnode is navbar_fixture.textnode assert 'navbar-text' in added_widget.get_attribute('class').split(' ')
def test_checkbox_with_inline_layout(web_fixture, choices_fixture): """PrimitiveCheckboxInputs can be rendered inlined with each other by using the ChoicesLayout with the inline=True setting.""" fixture = choices_fixture inlined_container = Div(web_fixture.view).use_layout(ChoicesLayout(inline=True)) inlined_container.layout.add_choice(PrimitiveCheckboxInput(fixture.form, fixture.boolean_field)) assert 'custom-control-inline' in inlined_container.children[0].get_attribute('class').split(' ')
def add(self, widget, left=None, right=None): """Adds the given Form or Nav `widget` to the Navbar. :param widget: A :class:`~reahl.web.bootstrap.navs.Nav` or :class:`~reahl.web.bootstrap.ui.Form` to add. :keyword left: If True, `widget` is aligned to the left of the Navbar. :keyword right: If True, `widget` is aligned to the right of the Navbar. """ if isinstance(widget, reahl.web.bootstrap.navs.Nav): widget.append_class('navbar-nav') if left or right: child = Div(self.view).use_layout( ResponsiveFloat(left=left, right=right)) child.add_child(widget) else: child = widget self.contents_container.add_child(child) return widget
def make_allocation_input(self, allocation, field): div = Div(self.view).use_layout(FormLayout()) div.layout.add_input(TextInput(self, field, name_discriminator=allocation.fund_code, refresh_widget=self), hide_label=True) return div
def test_columns_classes(web_fixture): """The Div added for each column specified to ColumnLayout is given a CSS class derived from the column name.""" fixture = web_fixture widget = Div(fixture.view).use_layout(ColumnLayout('column_name_a')) column_a = widget.layout.columns['column_name_a'] assert 'column-column_name_a' in column_a.get_attribute('class')
def __init__(self, view, caption_text=None, summary=None, css_id=None): super().__init__(view) self.main_div = self.add_child(Div(view, css_id=css_id)) self.set_html_representation(self.main_div) self.table = self.main_div.add_child( reahl.web.ui.Table(view, caption_text=caption_text, summary=summary)) self.table.append_class('table')
def create_form_group(self, html_input): if isinstance(html_input, RadioButtonSelectInput): form_group = self.widget.add_child(FieldSet(self.view)) else: form_group = self.widget.add_child(Div(self.view)) form_group.append_class('form-group') html_input.add_attribute_source(reahl.web.ui.ValidationStateAttributes(html_input, error_class='is-invalid', success_class='is-valid')) return form_group
def create_contents(self): div = self.add_child(Div(self.view)) self.set_html_representation(div) div.add_child(P(self.view, text=self.task.title)) form = div.add_child(Form(self.view, 'task_form')) form.use_layout(FormLayout()) defer_btn = form.add_child(Button(form, self.user_interface.workflow_interface.events.defer_task)) defer_btn.use_layout(ButtonLayout(style='primary')) release_btn = form.add_child(Button(form, self.user_interface.workflow_interface.events.release_task.with_arguments(task=self.task))) release_btn.use_layout(ButtonLayout(style='primary'))
def test_navbar_toggle_customised(web_fixture, navbar_fixture): """The text on a toggle that hides an element is customisable""" element_to_collapse = Div(web_fixture.view, css_id='my_id') toggle = navbar_fixture.navbar_with_layout.layout.add_toggle( element_to_collapse, text='≎') [toggle_text_node] = toggle.children assert ('≎' == toggle_text_node.value)
def test_column_slots(web_fixture): """A ColumnLayout can be made that adds a Slot to each added column, named after the column it is added to.""" fixture = web_fixture widget = Div(fixture.view).use_layout(ColumnLayout('column_name_a', 'column_name_b').with_slots()) column_a, column_b = widget.layout.columns.values() assert 'column_name_a' in column_a.available_slots assert 'column_name_b' in column_b.available_slots
def __init__(self, html_input, cue_widget): super(CueInput, self).__init__(html_input) div = self.add_child(Div(self.view)) self.set_html_representation(div) div.append_class('reahl-bootstrapcueinput') cue_widget.append_class('reahl-bootstrapcue') div.add_child(html_input) self.cue_widget = div.add_child(cue_widget)
def __init__(self, prepend, input_widget, append): super(InputGroup, self).__init__(input_widget) self.div = self.add_child(Div(self.view)) self.div.append_class('input-group') if prepend: self.add_as_addon(prepend, 'prepend') self.input_widget = self.div.add_child(input_widget) if append: self.add_as_addon(append, 'append') self.set_html_representation(self.div)
def test_adding_columns_later_specifying_the_widget_to_use(web_fixture): """Specify the widget to use as a wrapper for the column. By default it is a Div""" widget = Div(web_fixture.view).use_layout(ColumnLayout()) column_widget_to_use = P(web_fixture.view) widget.layout.add_column('a_column', column_widget=column_widget_to_use) [added_column] = widget.children assert added_column is column_widget_to_use
def adding_columns(fixture): """You can add additional columns after construction.""" widget = Div(fixture.view).use_layout(ColumnLayout()) vassert( not widget.children ) widget.layout.add_column(ResponsiveSize(lg=4)) [added_column] = widget.children vassert( added_column.get_attribute('class') == 'col-lg-4' )