def __init__( self, links, menuiconname="overflow-menu--vertical", menuname=None, direction="bottom", flip=False, item_attributes={}, **attributes, ): attributes["data-overflow-menu"] = True attributes["_class"] = attributes.get("_class", "") + " bx--overflow-menu" item_attributes["_class"] = (item_attributes.get("_class", "") + " bx--overflow-menu-options__option") menuid = hg.F(lambda c: OverflowMenu.MENUID_TEMPLATE % hg.html_id( c.get("row", self))) triggerid = hg.F(lambda c: (OverflowMenu.MENUID_TEMPLATE % hg.html_id( c.get("row", self))) + "-trigger") super().__init__( hg.BUTTON( Icon(menuiconname, size=16), _class="bx--overflow-menu__trigger" + (" bx--tooltip__trigger bx--tooltip--a11y bx--tooltip--right bx--tooltip--align-start" if menuname is not None else ""), aria_haspopup="true", aria_expanded="false", aria_controls=menuid, type="button", id=triggerid, ), hg.DIV( hg.UL( hg.Iterator( links, "link", hg.LI( hg.F(asoverflowbutton), **item_attributes, ), ), _class="bx--overflow-menu-options__content", ), _class="bx--overflow-menu-options" + (" bx--overflow-menu--flip" if flip else ""), tabindex="-1", role="menu", aria_labelledby=triggerid, data_floating_menu_direction=direction, id=menuid, ), **attributes, ) if menuname is not None: self[0].insert(0, hg.SPAN(menuname, _class="bx--assistive-text"))
def get_layout(self): self.checkboxcounterid = hg.html_id(self, "checkbox-counter") ret = super().get_layout() toolbar = list( ret.filter(lambda e, a: getattr(e, "attributes", {}).get( "_class", "") == "bx--toolbar-content"))[0] nfilters = self._checkbox_count() toolbar.insert( -2, hg.DIV( hg.SPAN(nfilters, id=self.checkboxcounterid), layout.icon.Icon( "close", focusable="false", size=15, role="img", onclick= f"document.location = '{self.request.path}?reset=1'", ), role="button", _class= "bx--list-box__selection bx--list-box__selection--multi bx--tag--filter", style="margin: auto 0.5rem;" + (" display: none;" if nfilters == 0 else ""), tabindex="0", title=("Reset"), ), ) return ret
def __init__(self, size="xl", widgetattributes=None, **kwargs): kwargs["_class"] = kwargs.get("_class", "") + f" bx--search bx--search--{size}" kwargs["data-search"] = True kwargs["role"] = "search" attributes = { "id": "search__" + hg.html_id(self), "_class": "bx--search-input", "type": "text", "placeholder": _("Search"), **(widgetattributes or {}), } super().__init__( hg.LABEL(_("Search"), _class="bx--label", _for=attributes["id"]), hg.INPUT(**attributes), Icon("search", size=16, _class="bx--search-magnifier", aria_hidden="true"), hg.BUTTON( Icon("close", size=20, _class="bx--search-clear"), _class="bx--search-close bx--search-close--hidden", title=_("Clear search input"), aria_label=_("Clear search input"), type="button", ), **kwargs, )
def __init__( self, size="xl", placeholder=None, widgetattributes=None, backend=None, resultcontainerid=None, show_result_container=True, resultcontainer_onload_js=None, disabled=False, **kwargs, ): """ :param SearchBackendConfig backend: Where and how to get search results """ kwargs["_class"] = kwargs.get("_class", "") + f" bx--search bx--search--{size}" kwargs["data_search"] = True kwargs["role"] = "search" width = kwargs.get("width", None) if width: kwargs["style"] = kwargs.get("style", "") + f"width:{width};" widgetattributes = { "id": "search__" + hg.html_id(self), "_class": "bx--search-input", "type": "text", "placeholder": placeholder or _("Search"), "autocomplete": "off", **(widgetattributes or {}), } if backend: if resultcontainerid is None: resultcontainerid = f"search-result-{hg.html_id((self, backend.url))}" widgetattributes["hx_get"] = backend.url widgetattributes["hx_trigger"] = "changed, click, keyup changed delay:500ms" widgetattributes["hx_target"] = hg.format("#{}", resultcontainerid) widgetattributes["hx_indicator"] = hg.format( "#{}-indicator", resultcontainerid ) widgetattributes["name"] = backend.query_parameter self.close_button = _close_button(resultcontainerid, widgetattributes) super().__init__( hg.DIV( hg.LABEL(_("Search"), _class="bx--label", _for=widgetattributes["id"]), hg.INPUT(**widgetattributes), _search_icon(), self.close_button, hg.If(backend is not None, _loading_indicator(resultcontainerid)), **kwargs, ), hg.If( backend is not None and show_result_container, _result_container(resultcontainerid, resultcontainer_onload_js, width), ), style=hg.If(disabled, hg.BaseElement("display: none")), )
def __init__( self, label: Any, description: Any, align: str = "center", position: str = "bottom", **attributes, ): """ Parameters ---------- label : Any Text to be displayed description : Any One line of string defining the label align : str, optional Specify where the arrow pointing the icon should align to the text. It can be either start, center, or end. The default value is 'center'. position : str Where should the tooltip appear besides the tooltip icon. It can be either top, left, right, or bottom. The default value is 'bottom'. """ tooltip_attributes = { "_class": "bx--tooltip--definition bx--tooltip--a11y ", "data_tooltip_definition": True, } tooltip_attributes = hg.merge_html_attrs(tooltip_attributes, attributes) asst_txt_id = hg.html_id(self, "bx--tooltip--definition-id") super().__init__( hg.DIV( hg.DIV( label, _class=( "bx--tooltip__trigger " "bx--tooltip--a11y " "bx--tooltip__trigger--definition " "bx--tooltip--%s " "bx--tooltip--align-%s" ) % (position, align), aria_describedby=asst_txt_id, ), hg.DIV( description, _class="bx--assistive-text", id=asst_txt_id, role="tooltip", ), **tooltip_attributes, ) )
def as_plain(*args, add_label=_("Add"), **kwargs): """Shortcut to render a complete formset with add-button""" formset = FormsetField(*args, **kwargs) id = hg.html_id(formset, prefix="formset-") return hg.BaseElement( hg.DIV(formset, id=id), formset.management_form, formset.add_button( buttontype="ghost", notext=False, label=add_label, container_css_selector=f"#{id}", ), )
def __init__( self, label, offlabel=_("Off"), onlabel=_("On"), help_text=None, errors=None, disabled=None, required=None, widgetattributes={}, **attributes, ): attributes["_class"] = attributes.get("_class", "") + " bx--form-item" widgetattributes["_class"] = (widgetattributes.get("_class", "") + " bx--toggle-input") widgetattributes["type"] = "checkbox" widgetattributes["id"] = widgetattributes.get("id", None) or hg.html_id(self) self.input = hg.INPUT(**widgetattributes) self.label = hg.LABEL( label, hg.If(required, REQUIRED_LABEL), hg.SPAN( hg.SPAN(offlabel, _class="bx--toggle__text--off", aria_hidden="true"), hg.SPAN(onlabel, _class="bx--toggle__text--on", aria_hidden="true"), _class="bx--toggle__switch", ), _class=hg.BaseElement( "bx--label bx--toggle-input__label", hg.If(disabled, " bx--label--disabled"), ), _for=widgetattributes["id"], ) super().__init__( self.input, self.label, HelpText(help_text), ErrorList(errors), **attributes, )
def __init__(self, label, status, optional=False, tooltip=None, disabled=False, **kwargs): assert (status in ProgressStep.STATUS ), f"{status} must be one of {ProgressStep.STATUS}" kwargs["class"] = (kwargs.get("class", "") + f" bx--progress-step bx--progress-step--{status}") if disabled: kwargs["aria_disabled"] = "true" kwargs["class"] += " bx--progress-step--disabled" elements = [ Icon(ProgressStep.STATUS[status], size=16), hg.P(label, tabindex=0, _class="bx--progress-label"), hg.SPAN(_class="bx--progress-line"), ] if optional: elements.insert( 2, hg.P(_("Optional"), _class="bx--progress-optional")) if tooltip is not None: tooltipid = hg.html_id(tooltip, "tooltip-label") elements[1]["aria-describedby"] = tooltipid elements.insert( 2, hg.DIV( hg.SPAN(_class="bx--tooltip__caret"), hg.P(tooltip, _class="bx--tooltip__text"), id=tooltipid, role="tooltip", data_floating_menu_direction="bottom", _class="bx--tooltip", data_avoid_focus_on_open=True, ), ) super().__init__(*elements, **kwargs)
def __init__( self, label=None, help_text=None, errors=None, inputelement_attrs=None, boundfield=None, # for django-form select elements use this choices=None, # for non-django-form select elements use this **attributes, # for non-django-form select elements use this ): inputelement_attrs = inputelement_attrs or {} optgroups = (_optgroups_from_choices( choices, name=inputelement_attrs.get("name"), value=inputelement_attrs.get("value"), ) if choices else _gen_optgroup(boundfield)) def countselected(context): options = [ o for og in hg.resolve_lazy(optgroups, context) for o in og[1] ] return len([o for o in options if o and o["selected"]]) searchfieldid = hg.html_id(self) super().__init__( label, hg.If( inputelement_attrs.get("disabled"), hg.DIV( hg.Iterator( optgroups, "optiongroup", hg.Iterator( hg.C("optiongroup.1"), "option", hg.If(hg.C("option.selected"), Tag(hg.C("option.label"))), ), )), hg.DIV( hg.DIV( hg.DIV( hg.F(countselected), Icon( "close", focusable="false", size=15, role="img", onclick= "clearMultiselect(this.parentElement.parentElement.parentElement)", ), role="button", _class= "bx--list-box__selection bx--list-box__selection--multi bx--tag--filter", tabindex="0", title="Clear all selected items", ), hg.INPUT( id=searchfieldid, _class="bx--text-input", placeholder="Filter...", onclick= "this.parentElement.nextElementSibling.style.display = 'block'", onkeyup= "filterOptions(this.parentElement.parentElement)", ), hg.DIV( Icon("chevron--down", size=16, role="img", focusable="false"), _class="bx--list-box__menu-icon", onclick= "this.parentElement.nextElementSibling.style.display = this.parentElement.nextElementSibling.style.display == 'none' ? 'block' : 'none';", ), role="button", _class="bx--list-box__field", tabindex="0", onload= "window.addEventListener('click', (e) => {this.nextElementSibling.style.display = 'none'})", ), hg.FIELDSET( hg.Iterator( optgroups, "optgroup", hg.Iterator( hg.C("optgroup.1"), "option", hg.DIV( hg.DIV( hg.DIV( hg.LABEL( hg.INPUT( type="checkbox", readonly=True, _class="bx--checkbox", value=hg.C("option.value"), lazy_attributes=hg.C( "option.attrs"), onchange= "updateMultiselect(this.closest('.bx--multi-select'))", checked=hg.C( "option.selected"), name=hg.C("option.name"), ), hg.SPAN( _class= "bx--checkbox-appearance"), hg.SPAN( hg.C("option.label"), _class= "bx--checkbox-label-text", ), title=hg.C("option.label"), _class="bx--checkbox-label", ), _class= "bx--form-item bx--checkbox-wrapper", ), _class= "bx--list-box__menu-item__option", ), _class="bx--list-box__menu-item", ), ), ), _class="bx--list-box__menu", role="listbox", style="display: none", ), _class=hg.BaseElement( "bx--multi-select bx--list-box bx--multi-select--selected bx--combo-box bx--multi-select--filterable", hg.If( inputelement_attrs.get("disabled"), " bx--list-box--disabled", ), ), data_invalid=hg.If(getattr(errors, "condition", None), True), ), ), help_text, errors, **hg.merge_html_attrs( attributes, { "onclick": "event.stopPropagation()", "_class": "bx--list-box__wrapper", }, ), )
def __init__( self, label: Any, body: Any, heading: Optional[Any] = None, link: Optional[Link] = None, button: Optional[Button] = None, icon: Union[Icon, str] = "information", menudirection: str = "bottom", **attributes, ): """ Parameters ---------- label : Any Label for a tooltip body : Any The content inside a tooltip heading : Any, optional A heading for a tooltip. link : Link, optional Bread's Link NamedTuple in case you want to bring users to a specific webpage. button : Button, optional Insert the Bread's Button onto the tooltip. icon : Icon, str, optional Specify an icon for the tooltip The default value is "information". menudirection : str Where should the tooltip appear besides the tooltip icon. It can be either top, left, right, or bottom. The default value is "bottom". """ base_class = "bx--tooltip" footer_elements: typing.List[hg.BaseElement] = [] if link: footer_elements.append(hg.A(link.label, href=link.href, _class="bx--link")) if button: footer_elements.append(button) base_id = hg.html_id(self, base_class + "-id") label_id = f"{base_id}-label" trigger_attributes = { "_class": base_class + "__trigger", "aria_controls": base_id, "aria_expanded": "false", "aria_haspopup": "true", "aria_labelledby": label_id, "data_tooltip_target": "#" + base_id, "data_tooltip_trigger": True, } tooltip_attributes = { "_class": base_class, "aria_hidden": "true", "data_floating_menu_direction": menudirection, "id": base_id, } tooltip_content_attributes = { "_class": base_class + "__content", "aria_describedby": base_id + "-body", "aria_labelledby": label_id, "role": "dialog", "tabindex": "-1", } tooltip_attributes = hg.merge_html_attrs(tooltip_attributes, attributes) icon = _get_icon(icon) super().__init__( # tooltip label hg.DIV( label, # cannot use bread's Button class because # only the class bx--tooltip__trigger can be used hg.DIV( icon, **trigger_attributes, ), id=label_id, _class=base_class + "__label", ), # the real tooltip goes here hg.DIV( hg.SPAN(_class=base_class + "__caret"), hg.DIV( hg.If( bool(heading), hg.H4( heading, id=base_id + "-heading", _class=base_class + "__heading", ), ), hg.P(body, id=base_id + "-body"), hg.If( len(footer_elements) > 0, hg.DIV( *footer_elements, _class=base_class + "__footer", ), ), **tooltip_content_attributes, ), hg.SPAN(tabindex="0"), **tooltip_attributes, ), )
def test(self): self.assertNotEqual(hg.html_id(object()), hg.html_id(object())) o = object() self.assertNotEqual(hg.html_id(o), hg.html_id(o))
def as_datatable( fieldname: str, fields: List, title: typing.Optional[str] = None, formname: str = "form", formsetfield_kwargs: dict = None, **kwargs, ) -> hg.BaseElement: from ..datatable import DataTable, DataTableColumn """ :param str fieldname: The fieldname which should be used for an formset, in general a one-to-many or many-to-many field :param list fields: A list of strings or objects. Strings are converted to DataTableColumn, objects are passed on as they are :param str title: Datatable title, automatically generated from form if None :param str formname: Name of the surounding django-form object in the context :param dict formsetfield_kwargs: Arguments to be passed to the FormSetField constructor :param kwargs: Arguments to be passed to the DataTable constructor :return: A datatable with inline-editing capabilities :rtype: hg.HTMLElement """ columns = [] for f in fields: if isinstance(f, str): f = FormField(f, no_wrapper=True, no_label=True, no_helptext=True) if isinstance(f, FormFieldMarker): f = DataTableColumn( hg.BaseElement( hg. C(f"{formname}.{fieldname}.formset.form.base_fields.{f.fieldname}.label" ), HelpText( hg. C(f"{formname}.{fieldname}.formset.form.base_fields." f"{f.fieldname}.help_text")), ), f, ) columns.append(f) columns.append( DataTableColumn( hg.If( hg.C(f"{formname}.{fieldname}.formset.can_order"), _("Order"), ), hg.If( hg.C(f"{formname}.{fieldname}.formset.can_order"), FormField( forms.formsets.ORDERING_FIELD_NAME, no_wrapper=True, no_label=True, ), ), )) columns.append( DataTableColumn( "", hg.If( hg.C(f"{formname}.{fieldname}.formset.can_delete"), InlineDeleteButton(parentcontainerselector="tr"), ), )) formset = FormsetField( fieldname, DataTable.row(columns), formname=formname, **(formsetfield_kwargs or {}), ) id = hg.html_id(formset, prefix="formset-") return hg.BaseElement( DataTable( row_iterator=formset, rowvariable="", columns=columns, id=id, **kwargs, ).with_toolbar( title=title or hg.C(f"{formname}.{fieldname}.label"), primary_button=formset.add_button( buttontype="primary", container_css_selector=f"#{id} tbody"), ), formset.management_form, )
def __init__( self, heading, *content, label="", size="sm", buttons=(), id=None, with_form=False, **attributes, ): """ heading: Modal title content: content elements of the modal, can be empty label: small informative label above heading size: one of ["xs", "sm", "md", "lg"] buttons: buttons displayed on bottom of modal, last button has default focus the attribute "data_modal_close" can be set on an button in order to make it a cancel button In order to open the modal just pass self.openerattributes as kwargs to another html element, e.g. a button modal = modal.Modal("My Modal", "Hello world") button.Button("Model", **modal.openerattributes) """ if size not in Modal.SIZES: raise ValueError( f"argument 'size' has value {size} but needs to be one of {Modal.SIZES}" ) if buttons and "data_modal_primary_focus" not in buttons[-1].attributes: buttons[-1].attributes["data_modal_primary_focus"] = True attributes["_class"] = attributes.get("_class", "") + " bx--modal" self.id = hg.html_id(self, prefix="modal-") if id is None else id self.openerattributes = { "data_modal_target": hg.format("#{}", self.id) } self.contentcontainer = hg.DIV( *content, _class="bx--modal-content" + (" bx--modal-content--with-form" if with_form else ""), tabindex=0, ) super().__init__( hg.DIV( hg.DIV( hg.P( label, _class="bx--modal-header__label", ), hg.P( heading, _class="bx--modal-header__heading", ), hg.BUTTON( Icon( "close", size=16, _class="bx--modal-close__icon", aria_hidden="true", ), _class="bx--modal-close", type="button", data_modal_close=True, ), _class="bx--modal-header", ), self.contentcontainer, hg.DIV(_class="bx--modal-content--overflow-indicator"), hg.DIV( *buttons, _class="bx--modal-footer", ) if buttons else "", _class=f"bx--modal-container bx--modal-container--{size}", ), data_modal=True, id=self.id, role="dialog", aria_modal="true", tabindex="-1", **attributes, )