mirror of
https://github.com/django-components/django-components.git
synced 2025-11-25 08:21:20 +00:00
Add slots to components that can be filled when using component.
This commit is contained in:
parent
f6d2c5e003
commit
d1405dda13
6 changed files with 316 additions and 13 deletions
|
|
@ -1,5 +1,9 @@
|
|||
from inspect import getfullargspec
|
||||
|
||||
from django.forms.widgets import MediaDefiningClass
|
||||
from django.template.loader import render_to_string
|
||||
from django.template import Context
|
||||
from django.template.base import NodeList, TokenType, TextNode
|
||||
from django.template.loader import get_template
|
||||
from six import with_metaclass
|
||||
|
||||
# Allow "component.AlreadyRegistered" instead of having to import these everywhere
|
||||
|
|
@ -16,9 +20,44 @@ class Component(with_metaclass(MediaDefiningClass)):
|
|||
def render_dependencies(self):
|
||||
return self.media.render()
|
||||
|
||||
def render(self, *args, **kwargs):
|
||||
context = self.context(*args, **kwargs)
|
||||
return render_to_string(self.template(context), context)
|
||||
def slots_in_template(self, template):
|
||||
nodelist = NodeList()
|
||||
for node in template.template.nodelist:
|
||||
if (
|
||||
node.token.token_type == TokenType.BLOCK
|
||||
and node.token.split_contents()[0] == "slot"
|
||||
):
|
||||
nodelist.append(node)
|
||||
|
||||
return nodelist
|
||||
|
||||
def render(self, slots_filled=None, *args, **kwargs):
|
||||
slots_filled = slots_filled or []
|
||||
context_args_variables = getfullargspec(self.context).args[1:]
|
||||
context_args = {key: kwargs[key] for key in context_args_variables if key in kwargs}
|
||||
context = self.context(**context_args)
|
||||
template = get_template(self.template(context))
|
||||
slots_in_template = self.slots_in_template(template)
|
||||
|
||||
if slots_in_template:
|
||||
valid_slot_names = set([slot.name for slot in slots_in_template])
|
||||
nodelist = NodeList()
|
||||
for node in template.template.nodelist:
|
||||
if (
|
||||
node.token.token_type == TokenType.BLOCK
|
||||
and node.token.split_contents()[0] == "slot"
|
||||
):
|
||||
if node.name in valid_slot_names and node.name in slots_filled:
|
||||
nodelist.append(TextNode(slots_filled[node.name]))
|
||||
else:
|
||||
for node in node.nodelist:
|
||||
nodelist.append(node)
|
||||
else:
|
||||
nodelist.append(node)
|
||||
|
||||
return nodelist.render(Context(context))
|
||||
|
||||
return template.render(context)
|
||||
|
||||
class Media:
|
||||
css = {}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.template.base import Node, NodeList, TokenType, TemplateSyntaxError, token_kwargs
|
||||
from django.template.library import parse_bits
|
||||
|
||||
from django_components.component import registry
|
||||
|
||||
register = template.Library()
|
||||
|
||||
COMPONENT_CONTEXT_KEY = "component_context"
|
||||
|
||||
|
||||
@register.simple_tag(name="component_dependencies")
|
||||
def component_dependencies_tag():
|
||||
unique_component_classes = set(registry.all().values())
|
||||
|
|
@ -16,8 +21,123 @@ def component_dependencies_tag():
|
|||
|
||||
return mark_safe("\n".join(out))
|
||||
|
||||
|
||||
@register.simple_tag(name="component")
|
||||
def component_tag(name, *args, **kwargs):
|
||||
component_class = registry.get(name)
|
||||
component = component_class()
|
||||
return component.render(*args, **kwargs)
|
||||
|
||||
|
||||
class SlotNode(Node):
|
||||
def __init__(self, name, nodelist, component=None):
|
||||
self.name, self.nodelist, self.component = name, nodelist, component
|
||||
|
||||
def __repr__(self):
|
||||
return "<Slot Node: %s. Contents: %r>" % (self.name, self.nodelist)
|
||||
|
||||
def render(self, context):
|
||||
if COMPONENT_CONTEXT_KEY not in context.render_context:
|
||||
context.render_context[COMPONENT_CONTEXT_KEY] = {}
|
||||
|
||||
if self.component not in context.render_context[COMPONENT_CONTEXT_KEY]:
|
||||
context.render_context[COMPONENT_CONTEXT_KEY][self.component] = {}
|
||||
|
||||
rendered_slot = self.nodelist.render(context)
|
||||
|
||||
if self.component:
|
||||
context.render_context[COMPONENT_CONTEXT_KEY][self.component][self.name] = rendered_slot
|
||||
|
||||
return ''
|
||||
|
||||
|
||||
@register.tag("slot")
|
||||
def do_slot(parser, token, component=None):
|
||||
bits = token.split_contents()
|
||||
if len(bits) != 2:
|
||||
raise TemplateSyntaxError("'%s' tag takes only one argument" % bits[0])
|
||||
|
||||
slot_name = bits[1].strip('"')
|
||||
nodelist = parser.parse(parse_until=["endslot"])
|
||||
parser.delete_first_token()
|
||||
|
||||
return SlotNode(slot_name, nodelist, component=component)
|
||||
|
||||
|
||||
class ComponentNode(Node):
|
||||
def __init__(self, component, extra_context, slots):
|
||||
extra_context = extra_context or {}
|
||||
self.component, self.extra_context, self.slots = component, extra_context, slots
|
||||
|
||||
def __repr__(self):
|
||||
return "<Component Node: %s. Contents: %r>" % (self.component, self.slots)
|
||||
|
||||
def render(self, context):
|
||||
extra_context = {
|
||||
key: filter_expression.resolve(context)
|
||||
for key, filter_expression in self.extra_context.items()
|
||||
}
|
||||
context.update(extra_context)
|
||||
|
||||
self.slots.render(context)
|
||||
|
||||
if COMPONENT_CONTEXT_KEY in context.render_context:
|
||||
slots_filled = context.render_context[COMPONENT_CONTEXT_KEY][self.component]
|
||||
return self.component.render(slots_filled=slots_filled, **context.flatten())
|
||||
|
||||
return self.component.render()
|
||||
|
||||
|
||||
@register.tag("component_block")
|
||||
def do_component(parser, token):
|
||||
"""
|
||||
{% component_block "name" variable="value" variable2="value2" ... %}
|
||||
"""
|
||||
|
||||
bits = token.split_contents()
|
||||
tag_args, tag_kwargs = parse_bits(
|
||||
parser=parser,
|
||||
bits=bits,
|
||||
params=["tag_name", "component_name"],
|
||||
varargs=None,
|
||||
varkw=[],
|
||||
defaults=None,
|
||||
kwonly=[],
|
||||
kwonly_defaults=None,
|
||||
takes_context=False,
|
||||
name="component_block"
|
||||
)
|
||||
print(tag_args)
|
||||
tag_name = tag_args.pop(0)
|
||||
# assert False, tag_args[0]
|
||||
|
||||
if len(bits) < 2:
|
||||
raise TemplateSyntaxError("Call the '%s' tag with a component name as the first parameter" % tag_name)
|
||||
|
||||
component_name = bits[1]
|
||||
if not component_name.startswith(('"', "'")) or not component_name.endswith(('"', "'")):
|
||||
raise TemplateSyntaxError("Component name '%s' should be in quotes" % component_name)
|
||||
|
||||
component_name = component_name.strip('"')
|
||||
component_class = registry.get(component_name)
|
||||
component = component_class()
|
||||
|
||||
extra_context = {}
|
||||
if len(bits) > 2:
|
||||
extra_context = component.context(**token_kwargs(bits[2:], parser))
|
||||
|
||||
slots_filled = NodeList()
|
||||
tag_name = bits[0]
|
||||
while tag_name != "endcomponent_block":
|
||||
token = parser.next_token()
|
||||
if token.token_type != TokenType.BLOCK:
|
||||
continue
|
||||
|
||||
tag_name = token.split_contents()[0]
|
||||
|
||||
if tag_name == "slot":
|
||||
slots_filled += do_slot(parser, token, component=component)
|
||||
elif tag_name == "endcomponent_block":
|
||||
break
|
||||
|
||||
return ComponentNode(component, extra_context, slots_filled)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue