Coder Social home page Coder Social logo

django-easytag's Introduction

django-easytag

A templatetag utility to greatly simplify the design of block-style tags

EasyTag is a template.Node subclass that adds some automatic mechanisms which greatly simplify the common block-style tasks.

You can add methods to your subclass to handle any custom intermediate tag separators (think the for/empty, if/else).

Basic options

name

Required

The name of the tag as it should be registered with your templatetags library. See Registering with the tag library below to see how to make use of this name attribute without having to repeat yourself.

end_tag

boolean or str

If end_tag is True, this tag will automatically parse to a matching {% end{name} %} tag. If end_tag is a string, it will be taken as-is for the end-block tag. It won't add the "end" prefix either, so pick a smart name!

intermediate_tags

list of strings

intermediate_tags is a list of strings that acts as a declaration of all possible intermediate tag names that can show up in the body of your main tag. For example, if you use intermediate_tags = ["unless"], you could write something like this in your templates:

{% my_tag %}
    some content
{% unless %}
    secondary content
{% endmytag %}

By default, both of these sections are parsed and rendered, but they are handed off to separate methods on your tag class for processing, allowing you to dynamically decide if or how to render the sibling branches.

You can have any number of intermediate tags, and they can appear in any order. If you would like to enforce a particular order, you'll need to add some logic to the specific handlers to verify that some other tag came first.

Setting up handlers for wrapped content

The beginning tag and intermediate tags will attempt to send their influenced template regions to methods on your tag class. These methods are effectively renderers; you should return the rendered content, whether or not you are modifying anything.

The simplest case is a pass-through tag:

class MyTag(EasyTag):
    name = "my_tag"

    def my_tag(self, context, nodelist):
        return nodelist.render(context)

Your my_tag tag should declare a method on itself named the same thing. This is the handler that the wrapped content is sent to. The context and nodelist parameters are required.

Note: In Django parlance, a "nodelist" is a collection of template pieces that all know how to render themselves, including chunks of plain text.

If you have intermediate tags defined note how the definition of "wrapped content" changes:

<!-- example template use -->
{% my_tag %}
    some content
{% unless %}
    secondary content
{% endmytag %}

The main my_tag method only receives everything up until the next intermediate block, and the intermediate block receives everything until the next intermediate block or the end tag, and so on.

Sending parameters

Exactly like the built-in django simple_tag, EasyTag enables automatic forwarding of parameters sent to the tag to a method on your tag.

Your tag's method requires context and nodelist, but you can add any number of normal function arguments to its signature. This includes arguments with default values, *args and **kwargs for catch-alls. The proper errors will be raised if arguments are missing or unexpected.

Example:

# Handler inside of the MyTag class
def my_tag(self, context, nodelist, myflag=False):
    content = nodelist.render(context)
    if myflag is True:
        content = "<div class='hidden'>%s</div>" % (content,)
    return content
<!-- In template -->
{# With optional var specified: #}
{% mytag myflag=True %} Some content {% endmytag %}

{# Without option var: #}
{% mytag %} Other content {% endmytag %}

Registering with the tag library

Subclasses should set the name class attribute, which will be the name used in templates. This helps save you the trouble of writing a separate compiler function just to wrap it in the @register.tag decorator.

Registering the tag becomes very simple:

# A little wordy, if you've named your local library object "register", as is recommended:
MyTagClass.register_tag(register)
    
# This is basically the ``register.tag`` decorator, and looks more like a normal
# registration.  +1 if you're all about vanilla and no magic.
register.tag(MyTagClass.name, MyTagClass.parser)

A simpler register() method cannot be added to EasyTag, since EasyTag won't have an active reference to your tag's specific Library instance, so it would be registered to the wrong library at runtime.

Using variables across intermediate tags

Say you want to open your tag with some arguments, and the intermediate tags should be able to use those values as they are encountered.

For example, the target usage should look something like this:

{% my_tag active_section="Second" %}
    {% section name="First" %}
        Some content
    {% section name="Second" %}
        More content
{% endmy_tag %}

It's simple to save the variable on the my_tag handler (although if you're modifying stateful values after storing them, though please be sure to ready about the Django documentation about templatetag thread safety), and once you've got that done, it's a simple matter of inspecting the values:

def my_tag(self, context, nodelist, active_section=None):
    self.active_section = active_section

    # In the example template use, the nodelist was approximately empty,
    # holding only whitespace, but we should render it anyway.
    return nodelist.render(context)

def section(self, context, nodelist, name):
    wrapper = """<div class="%(active)s">%(content)</div>"""
    content = nodelist.render(context)
    if name == self.active_section:
        active_class = "active"
    else:
        active_class = ""
    return wrapper % {'active': active_class, 'content': content}

This works because the my_tag handler is always executed first, being the opening tag handler.

Wrapping the entire tag output, appending, prefixing, etc

Because of the way your opening handler actually only handles the initial branch of template until the first intermediate tag, you can't use this particular spot as a way to wrap the entire output.

Instead, you should override the built-in render() method of the tag. This method is provided by default on a template.Node object. The EasyTag.render() implementation iterates all of your branches and concatenates their output.

Consequently, if you would like to wrap all of the output in some HTML, append markup to the end, etc, you should call super() to get the normal output, and then modify it as you see fit:

def render(self, context):
    content = super(MyTag, self).render(context)
    return "<div id='wrapper'>%s</div>" % content

Adding an end-tag handler

In some instances, you might want to think of appending extra output to the end of the tag. This is technically possible by overriding the render() method as discussed just above, but it seemed fitting to acknowledge the end tag as a handler itself.

You can optionally define a method on your tag that equals the tag's end-name, whatever you've set that up to be:

class MyTag(EasyTag):
    name = "my_tag"
    end_tag = True

    # ...

    def endmy_tag(self, context):
        return "some trailing content"

Note how there is no nodelist parameter in this case, since the end tag has no content that it is influencing. It is up to you to return some string content.

Also take note that the returned string is automatically sent through django.utils.safestring.mark_safe by Django when the content ultimately gets returned as a single large string from the render() method.

This strategy is helpful for allowing intermediate tags to accumulate some information but not act on it until the end of the tag.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.