def test_can_walk(self): elem = div([h1('title'), p('title')]) items = list(elem.walk()) assert len(items) == 5 elem = div([h1('title'), 'title']) assert len(list(elem.walk())) == 4
def render_collapsible(lst, item_role='collapsible-item', title=None, expanded=False, **kwargs): """ Renders a queryset or list of objects Args: lst: List or queryset of objects inside the collapsible list. item_role: Role assigned to each element in the list. Defaults to 'collapsible-item'. title (bool): Title in which the list of object is displayed. expanded (str): If true, start list in the "expanded" state. """ if title is None: if isinstance(lst, QuerySet): title = title.model._meta.verbose_name_plural else: raise TypeError('must provide an explicit title!') data = [html(x, item_role, **kwargs) for x in lst] return div(class_='CollapsibleList', is_component=True)[ h2([title, span(f'({len(data)})' ), fa_icon('angle-up')]), div(class_='CollapsibleList-data')[html_list(data), ]]
def popup(title, content, action=None, **kwargs): """ Return a popup screen. It does not include positioning and the overlay element. Args: title: Title of the popup. content: HTML or text content of the popup. action: Action button. Can be an anchor or other element. """ return div( [ icon("times-circle", class_="popup__close", is_component="popup:close"), div( [ h1([title], class_="title"), p(content), action and div(action) ], class_="popup__contents", ), ], **kwargs, ).add_class("popup")
def command_bar(*actions, **kwargs): """ Element that includes configuration links bellow the header bar. """ if len(actions) == 1: return div(actions, **kwargs).add_class("CommandBar") elif len(actions) == 2: return div(actions, **kwargs).add_class("CommandBar") else: n = len(actions) raise ValueError(f"cannot include more than 2 actions, got: {n}")
def toast(icon, title, description=None, **kwargs): """ Toast component: display some title with a highlighted icon. """ body = [h1(title)] if description: body.append(p(description)) return div([ _icon(icon, class_="toast__icon"), div(body, class_="toast__content") ], **kwargs).add_class("toast")
def render(self, name, value, attrs=None, renderer=None): widget = self.get_context(name, value, attrs)["widget"] w_name = widget.get("name", "") w_type = widget.get("type", "") w_attrs = widget.get("attrs", {}) return div(class_="FileInput")[div(class_="PickFileButton")[ input_(style="opacity: 0", type_=w_type, name=w_name, **w_attrs), _("Choose a file")], div(class_="FileStatus" )[_("No file chosen")], ].render()
def collapsible(data, title=None, collapsed=False): """ Renders a collapsible content. """ angle = fa_icon("angle-up", class_="collapsible__handle") return div( class_="collapsible", is_component=True, is_collapsed=collapsed, children=[ h2([title, angle], class_="collapsible__title"), div(data, class_="collapsible__data"), ], )
def conversation_create_comment(conversation, request=None, **kwargs): """ Render "create comment" button for one conversation. """ conversation.set_request(request) n_comments = conversation.n_user_total_comments n_moderation = conversation.n_pending_comments fn = rules.get_value("ej.max_comments_per_conversation") user = getattr(request, "user", None) max_comments = fn(conversation, user) moderation_msg = _("{n} awaiting moderation").format(n=n_moderation) comments_count = _("{n} of {m} comments").format(n=n_comments, m=max_comments) # FIXME: Reactivate when full UI for the comment form is implemented # return extra_content( # _("Create comment"), # Blob(f"{comments_count}" f'<div class="text-7 strong">{moderation_msg}</div>'), # icon="plus", # id="create-comment", # ) return div( Blob(f"{comments_count}" f'<div class="text-7 strong">{moderation_msg}</div>'), id="create-comment", class_="extra-content", )
def html(self, classes=()): if self.format == Format.HTML: data = sanitize_html(self.content) elif self.format == Format.MARKDOWN: data = markdown(self.content) text = Text(data, escape=False) return div(text, class_=classes)
def popup_content(title, text, action, **kwargs): """ Content of a pop-up window. """ return div(**kwargs)[h1(title), p(text), action].add_class( "PopupWindow", first=True )
def render_collapsible(lst, item_role='collapsible-item', title=None, expanded=False, **kwargs): """ Renders a queryset or list of objects Args: lst: List or queryset of objects inside the collapsible list. item_role: Role assigned to each element in the list. Defaults to 'collapsible-item'. title (bool): Title in which the list of object is displayed. expanded (str): If true, start list in the "expanded" state. """ if title is None: if isinstance(lst, QuerySet): title = title.model._meta.verbose_name_plural else: raise TypeError('must provide an explicit title!') data = [render(x, item_role, **kwargs) for x in lst] random_id = str(uuid.uuid4()) display = 'block' if expanded else 'none' return div( class_='CollapsibleList' )[h2(onclick=f"$('#{random_id}').toggle()", children=[title, span(f'({len(data)})'), fa_icon('angle-down')]), html_list(data, style=f'display: {display}', id=random_id), ]
def row(*children, padding=True, wrap=False, align=None, **kwargs): """ A row that contains several columns as children. Args: align ({'top', 'bottom', 'center', 'stretch', 'baseline'}): Defines the vertical alignment of elements in the row. Each of those options can also be passed as a boolean argument as in ``row(..., top=True)``. padding (bool): If set to False, eliminate the padding for cells in the given row. wrap (bool): If True, allow children to wrap over the next line when overflow. ``row`` also accepts additional HTML attributes as keyword arguments. """ options = {"row-no-padding": not padding, "row-wrap": wrap} options.update((k, kwargs[k]) for k in ROW_ALIGNMENTS.intersection(kwargs)) classes = ["row"] classes.extend(cls for cls, on in options.items()) if align is not None: if align not in ROW_ALIGNMENTS: raise ValueError(f"invalid alignment: {align!r}") classes.append(f"row-{align}") return hp.div(class_=classes, children=children, **kwargs)
def test_pretty(self): tag = div(class_='foo')[p('hello'), p('world')] html = ('<div class="foo">\n' ' <p>hello</p>\n' ' <p>world</p>\n' '</div>\n') assert tag.pretty() == html
def conversation_create_comment(conversation, request=None, **kwargs): """ Render "create comment" button for one conversation. """ conversation.set_request(request) n_comments = conversation.n_user_comments n_moderation = conversation.n_pending_comments max_comments = max_comments_per_conversation() moderation_msg = _("{n} awaiting moderation").format(n=n_moderation) comments_count = _("{ratio} comments").format( ratio=f"<strong>{n_comments}</strong> / {max_comments}") # FIXME: Reactivate when full UI for the comment form is implemented # return extra_content( # _("Create comment"), # Blob(f"{comments_count}" f'<div class="text-7 strong">{moderation_msg}</div>'), # icon="plus", # id="create-comment", # ) return div( Blob(f"{comments_count}" f'<div class="text-7 strong">{moderation_msg}</div>'), id="create-comment", class_="extra-content", )
def paragraph(title, description=None, **kwargs): """ Display a centered title with a small description paragraph. This content is wrapped into a div that centers the content into the main page layout. """ children = [h1(title, class_='Paragraph-title')] if description: children.append(p(description, class_='Paragraph-text')) return div(children, **kwargs).add_class('Paragraph', first=True)
def stats_table(conversation, stats=None, data="votes", request=None, **kwargs): if stats is None: stats = conversation.statistics() get = COLUMN_NAMES.get return div([html_map({get(k, k): v}) for k, v in stats[data].items()], **kwargs).add_class("stat-slab", first=True)
def role_model(model): links = [] cls = get_class(model) for role in get_roles(cls): href = reverse("role-model-list", kwargs={"model": model, "role": role}) links.append(a(role, href=href)) if not links: raise Http404 else: return {"data": div([h1(_("List of roles")), html_list(links)])}
def intro(title, description=None, **kwargs): """ Display a centered title with a small description paragraph. This content is wrapped into a div that centers the content into the main page layout. """ children = [h1(title)] if description: children.append(p(description)) return div(children, **kwargs).add_class("intro-paragraph", first=True)
def role_model(model): links = [] cls = get_class(model) for role in get_roles(cls): href = reverse('role-model-list', kwargs={'model': model, 'role': role}) links.append(a(role, href=href)) if not links: raise Http404 else: return {'data': div([h1(_('List of roles')), html_list(links)])}
def progress_bar(*args): """ Display a progress bar. progress_bar(pc) --> tell a percentage progress_bar(n, total) --> pass the number of items and total """ # Compute fractions if len(args) == 1: pc = args[0] n = total = None else: e = 1e-50 n, total = args pc = round(100 * (n + e) / (total + e)) # Build children children = [ div(strong(f"{pc}%")), div( class_="progress-bar__progress", children=[ div(" ", class_="color-brand-lighter", style=f"flex-grow: {pc + 3};"), div(" ", style=f"flex-grow: {100 - pc};"), ], ), ] if total is not None: children.append(div([strong(n), "/", total])) # Return return div(children, class_="progress-bar")
def progress_bar(*args, **kwargs): """ Display a progress bar. progress_bar(pc) --> tell a percentage progress_bar(n, total) --> pass the number of items and total """ # Compute fractions if len(args) == 1: pc = args[0] n = total = None aria_msg = _("Your progress: {pc} percent").format(pc=pc) else: e = 1e-50 n, total = args pc = round(100 * (n + e) / (total + e)) aria_msg = _("Your progress: {n} of {total}").format(n=n, total=total) # Build children children = [ strong(f"{pc}%", class_="block margin-r2", aria_hidden="true"), div( class_="progress-bar__progress", children=[ div(" ", class_="color-brand-lighter", style=f"flex-grow: {pc + 3};"), div(" ", style=f"flex-grow: {100 - pc};"), ], ), ] if total is not None: children.append(div([strong(n), "/", total], aria_hidden="true")) # Return return div(children, aria_label=aria_msg, role="img", **kwargs).add_class("progress-bar", first=True)
def column( *children, size=None, offset=None, top=False, bottom=False, center=None, align=None, **kwargs, ): """ A single column inside a flexible row. Args: size ({ 10, 20, 25, 33, 34, 40, 50, 60, 66, 67, 75, 80, 90, 100 }): Column size (in %). Only a few pre-determined sizes are accepted. offset (int): Column offset. It accepts the save values as size. align {'top', 'bottom', 'center'}: Defines the horizontal alignment of elements in a cell. Each of those options can also be passed as a boolean argument as in ``column(..., top=True)``. ``column`` also accepts additional HTML attributes as keyword arguments. """ classes = ["column"] # Size if size is not None: if size not in VALID_COLUMN_SIZES: raise ValueError("Invalid size: %s" % size) classes.append(f"column-{size:d}") if offset: if offset not in VALID_COLUMN_SIZES: raise ValueError("Invalid offset: %s" % size) classes.append(f"column-offset-{offset:d}") # Alignment if align is not None: if align not in ("top", "bottom", "center"): raise ValueError(f"invalid alignment: {align!r}") classes.append(f"column-{align}") elif top: classes.append("column-top") elif bottom: classes.append("column-bottom") elif center: classes.append("column-center") return hp.div(children, **kwargs).add_class(classes)
def tabs(items, select=0, js=True, **kwargs): """ Return a tabbed interface. """ items = items.items() if isinstance(items, Mapping) else items children = [] if js: kwargs["is-component"] = True for idx, (k, v) in enumerate(items): args = {"href": v} if isinstance(v, str) else v anchor = a(args, k, is_selected=select == idx) children.append(anchor) return div(children, **kwargs).add_class("tabs", first=True)
def column(*children, size=None, **kwargs): """ A single column inside a 12-columns based flexible row. Args: size (optional, 1 to 12): Number of columns this cell spans. Each row has 12 columns, do the math. If not given, automatically fill the remaining space distributing equally between each column. ``column`` also accepts additional HTML attributes as keyword arguments. """ if size is None: class_ = "col" else: class_ = f"col-{size}" return hp.div(children, **kwargs).add_class(class_)
def extra_content(title, text, icon=None, **kwargs): """ Simple element with a title and a small paragraph with information. Used, for instance, in the detail page of conversations to link the comment form or cluster information. Args: title: Title of the content text: Paragraph that follows content. icon: Optional icon to be included with title element. Returns: """ title = h1([_icon(icon), title]) if icon else h1(title) return div([title, text], **kwargs).add_class("extra-content")
def column(*children, size=12, offset=None, **kwargs): """ A single column inside a 12-columns based flexible row. Args: size (1 to 12): Number of columns this cell spans. Each row has 12 columns, do the math. offset (int): Column offset in the 12-column system. ``column`` also accepts additional HTML attributes as keyword arguments. """ classes = [COLUMN_MAP[size - 1], "columns"] if offset is not None: name = COLUMN_MAP[offset - 1] classes.append(f"offset-by-{name}") return hp.div(children, **kwargs).add_class(classes)
def test_json_conversion(): tag = div(class_='foo')[h1('bar'), safe('foo <b>bar</b>')] pprint(tag.json()) assert tag.json() == { 'tag': 'div', 'attrs': { 'class': ['foo'] }, 'children': [ { 'tag': 'h1', 'children': [{ 'text': 'bar' }] }, { 'raw': 'foo <b>bar</b>' }, ] }
def categories(items, select=0, js=True, **kwargs): """ Similar to tabs, but display several categories for the user to select. """ items = items.items() if isinstance(items, Mapping) else items children = [ icon("chevron-left", class_="categories__left", is_element="leftArrow:click") ] if js: kwargs["is-component"] = True for idx, (k, v) in enumerate(items): args = {"href": v} if isinstance(v, str) else v if select == idx or select == v: args["is-selected"] = True children.append(a(args, k)) children.append( icon("chevron-right", class_="categories_right", is_element="rightArrow:click") ) return div(children, **kwargs).add_class("categories", first=True)
def _make_tabs(cls, js, kwargs, children): if js: kwargs["is-component"] = True kwargs.setdefault("role", "tablist") return div(children, **kwargs).add_class(cls, first=True)
def menu_from_sections(sections): # add role="menu" in the future? return div(*sections, class_="page-menu", id="page-menu", is_component=True)