Implementing Shopify's Form Tag #84
-
Hi, I have been trying to recreate Shopify's form tag and am having a little bit of trouble. I was hoping I could get some guidance here. I followed the tutorial on how to create a new tag and got it to work somewhat. I have this so far: class FormTag(Tag):
name = TAG_FORM
end = TAG_ENDFORM
def __init__(self, env: Environment):
super().__init__(env)
self.parser = get_parser(self.env)
def parse(self, stream: TokenStream) -> Node:
expect(stream, TOKEN_TAG, value=TAG_FORM)
tok = stream.current
stream.next_token()
expect(stream, TOKEN_EXPRESSION)
expr_stream = TokenStream(tokenize_with_expression(stream.current.value))
key = str(parse_string_literal(expr_stream))
stream.next_token()
block = self.parser.parse_block(stream, (TAG_ENDFORM, TOKEN_EOF))
expect(stream, TOKEN_TAG, value=TAG_ENDFORM)
return FormNode(tok=tok, block=block, key=key)
class FormNode(Node, ABC):
def __init__(self, tok: Token, block: BlockNode, key: str):
self.tok = tok
self.block = block
self.key = key.replace("'", "")
def render_to_output(self, context: Context, buffer: TextIO) -> Optional[bool]:
template = context.get_template_with_context('forms/' + self.key + '.liquid')
template.render_with_context(context, buffer, partial=True, form_content=self.block)
return True This works great when the only content between the two tags is pure HTML. The issue I am having is when there are other liquid tags in between. // Works Great and renders
// <form>
// <h1>Hello</h1>
// </form>
{% form 'login' %}
<h1>Hello</h1>
{% endform %}
// Doesn't work great and renders
// <form>
// `2 | plus: 2`
// <h1>Hello</h1>
// </form>
{% form 'login' %}
{{ 2 | plus: 2 }}
<h1>Hello</h1>
{% endform %} Because of this, I figured I needed to render Also, the <form>
{{ form_content }}
</form> |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
Hi @AustinWildgrube,
Here you are writing the internal string representation of Instead, as you have pointed out, you will want to call You could use an intermediate buffer to capture the rendered block content. from io import StringIO
# ...
def render_to_output(self, context: Context, buffer: TextIO) -> Optional[bool]:
buf = StringIO()
self.block.render(context, buf)
template = context.get_template_with_context('forms/' + self.key + '.liquid')
template.render_with_context(context, buffer, partial=True, form_content=buf.getvalue())
return True Alternatively, this might be slightly closer to the behaviour of Shopify's def render_to_output(self, context: Context, buffer: TextIO) -> Optional[bool]:
# TODO: Populate this `form` dictionary with keys and values for the
# given form type (`self.key`).
form = {
"posted_successfully?": True,
"errors": [],
}
# Write the HTML form tag to the output buffer.
# TODO: Set default attributes for the given form type.
buffer.write(
"<form "
'method="post" '
'action="/account/login" '
'id="customer_login" '
'accept-charset="UTF-8">\n'
)
# Write Shopify-specific hidden form input to the output buffer.
# XXX: This hidden input is specific to the login form.
buffer.write(
'<input type="hidden" name="form_type" value="customer_login" />'
'<input type="hidden" name="utf8" value="✓" />'
)
# Render the block while temporarily adding `form` to the render context.
with context.extend({"form": form}):
self.block.render(context, buffer)
# Close the HTML form tag.
buffer.write("\n</form>") Then, if you do want to load form content from a snippet, usage might look like this: {% form 'login' %}
{% render 'form-login' %}
{% endform %} In which case a Custom Loader would be required to follow Shopify's theme directory structure conventions. |
Beta Was this translation helpful? Give feedback.
Hi @AustinWildgrube,
Here you are writing the internal string representation of
liquid.ast.BlockNode
to the output buffer, which is why HTML block content looks OK, but Liquid tags and variables don't.Instead, as you have pointed out, you will want to call
self.block.render(context, buffer)
. Rather than returning rendered content,BlockNode.render()
writes to the output buffer that you give it.You could use an intermediate buffer to capture the rendered block content.