DEV Community

Rajasegar Chandran
Rajasegar Chandran

Posted on

Using SVGs in Common Lisp web apps with Djula

In this post, we are going to take a look at how to use SVG content or embed SVGs inside your HTML with the Djula templating engine in a Common Lisp web application.

Before diving into the topic, let's understand some tools and technologies we will be needing for the post. This way we can easily understand the development workflow and the challenges that comes with embedding SVGs in our Common Lisp web applications.

Djula

Djula is a port of Python's Django template engine to Common Lisp. It's the default templating engine used by the framework Caveman for building web applications

SVG

Scalable Vector Graphics is an XML-based vector image format for defining two-dimensional graphics, having support for interactivity and animation. The SVG specification is an open standard developed by the World Wide Web Consortium since 1999.

SVGs can look crisp at all screen resolutions, can have super small file sizes, and can be easily edited and modified.

Let's take a look at a sample SVG document copied from Bootstrap Icons called airplane-engines-fill

<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-airplane-engines-fill" viewBox="0 0 16 16">
  <path d="M8 0c-.787 0-1.292.592-1.572 1.151A4.347 4.347 0 0 0 6 3v3.691l-2 1V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.191l-1.17.585A1.5 1.5 0 0 0 0 10.618V12a.5.5 0 0 0 .582.493l1.631-.272.313.937a.5.5 0 0 0 .948 0l.405-1.214 2.21-.369.375 2.253-1.318 1.318A.5.5 0 0 0 5.5 16h5a.5.5 0 0 0 .354-.854l-1.318-1.318.375-2.253 2.21.369.405 1.214a.5.5 0 0 0 .948 0l.313-.937 1.63.272A.5.5 0 0 0 16 12v-1.382a1.5 1.5 0 0 0-.83-1.342L14 8.691V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v.191l-2-1V3c0-.568-.14-1.271-.428-1.849C9.292.591 8.787 0 8 0Z"/>
</svg>
Enter fullscreen mode Exit fullscreen mode

which will actually look like this:

Image description

Creating our demo project

Let's bootstrap a new project using Caveman to showcase embedding SVGs in our web pages.

(ql:quickload :caveman2)
(caveman2:make-project #P"~/quicklisp/local-projects/cl-svg-demo")
Enter fullscreen mode Exit fullscreen mode

For more detailed information of setting up and using Caveman framework you can check out my previous post

Using SVGs in Djula templates

Embedding SVG content inside Djula templates is actually straightforward. We are going to make use of the built-in tag in Djula called include to embed our SVG file.

The include tag loads a template and renders it with the current context. This is a way of “including” other templates within a template.

This example includes the contents of the template "foo/bar.html":

{% include "foo/bar.html" %}
Enter fullscreen mode Exit fullscreen mode

A set of parameters can also be added, which become available as context variables when the included template is rendered:

{% include "user.html" :user record.creator %}
{% include "user.html" :user record.updater %}
Enter fullscreen mode Exit fullscreen mode

Let's say we want to embed the airplane-engines-fill svg. First create a folder called svgs inside the templates folder in your Caveman application. The reason for creating a new folder for SVGs inside the existing templates folder is to have a standard convention for putting all our SVG files in one place and also it will be easy to refer them in our templates using the relative path like svgs/my-icon.svg.

Next, create a new file inside the newly created folder called airplane-engines-fill.html and copy the svg content shown above.

templates/svgs/airplane-engines-fill.html
Enter fullscreen mode Exit fullscreen mode

So, if we make use of the include tag to embed SVG files in our templates, let's open a template templates/index.html and add the following code.

{% include "svgs/airplane-engines-fill.html" %}
Enter fullscreen mode Exit fullscreen mode

Voila, that's it, if you check your page, our svg is now rendered in the page. Now I want to customize the SVG rendering, let's say, I want it bigger, since the default height and width of the SVG is just 16px as mentioned in the file.

So you can go and update your SVG file and change the width and height attributes or you can take a copy of the file and make the changes and use the new file. But this is a tedious job, you don't know beforehand how many dimensions you need to support. So the best option here is to override the attributes within your templates and don't touch the original source file for any changes.

You can open the SVG file and add some Djula conditional tags like {% if %} and {% else %} to make the customizations. In our case it will be something like, to override the height attribute

height="{% if height %}{{height}}{% else %}16{% endif %}" 
Enter fullscreen mode Exit fullscreen mode

What this tells us is that, if there is a height value supplied to this template (all things become a template if you use the include tag in Djula), then that value is take otherwise the default height value of 16 is used.

And now in our template we can supply the same values like

{% include "svgs/airplane-engines-fill.html"  :height 64 %}
Enter fullscreen mode Exit fullscreen mode

Bonus tip: Need not to rename SVG files

One surprising thing I found out while working on this demo is that, actually you don't need to rename your SVG files to .html extension. You can just use something like:

{% include "svgs/airplane-engines-fill.svg"  :height 64 %}
Enter fullscreen mode Exit fullscreen mode

This is because, Djula can understand proper markup content inside your files (templates which you want to embed in other templates), be it whatever extension you use, as long as the content is a properly formatted XML/HTML markup and SVGs are just perfect.

In similar way we can customize the attributes of the root element svg in our template like below. We have customized the height, width, fill and class attributes.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="{% if class %}{{class}}{% else %}bi bi-airplane-engines-fill{% endif %}" 
fill="{% if fill %}{{fill}}{% else %}currentColor{% endif %}" 
height="{% if height %}{{height}}{% else %}16{% endif %}" 
width="{% if width %}{{width}}{% else %}16{% endif %}">
<path xmlns="http://www.w3.org/2000/svg" d="M8 0c-.787 0-1.292.592-1.572 1.151A4.347 4.347 0 0 0 6 3v3.691l-2 1V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.191l-1.17.585A1.5 1.5 0 0 0 0 10.618V12a.5.5 0 0 0 .582.493l1.631-.272.313.937a.5.5 0 0 0 .948 0l.405-1.214 2.21-.369.375 2.253-1.318 1.318A.5.5 0 0 0 5.5 16h5a.5.5 0 0 0 .354-.854l-1.318-1.318.375-2.253 2.21.369.405 1.214a.5.5 0 0 0 .948 0l.313-.937 1.63.272A.5.5 0 0 0 16 12v-1.382a1.5 1.5 0 0 0-.83-1.342L14 8.691V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v.191l-2-1V3c0-.568-.14-1.271-.428-1.849C9.292.591 8.787 0 8 0Z"/></svg>
Enter fullscreen mode Exit fullscreen mode

But doing this for multiple SVG files is a time consuming process and is error-prone if we do it manually. And also you want to keep your original SVG files untouched for any other modification if the Designers in your team wanted to improve it or give a replacement for the icons or images.

So, ideally you want two versions of your SVG files, one is the original untouched and un-optimized files which doesn't include any override logic and the other one is the modified version which is we use it in our templates.

To solve these problems and have a better development workflow, I have created a package called cl-djula-svg which automates all the above steps for you. The inspiration for this package came from svgr which exactly does the same thing in React applications. It takes a SVG file, process some override logic, optimize it and create React wrapper components based on the SVG content.

cl-djula-svg

A Common Lisp package that will help process SVG files and copy them to folders so that they can be used in Djula templates in a Caveman web application framework.

Installation

You can install cl-djula-svg via quicklisp once it becomes available (at present, it is not, you have clone the repo manually):

(ql:quickload :cl-djula-svg)
Enter fullscreen mode Exit fullscreen mode

The tracking issue for adding cl-djula-svg to quicklisp is here. You may want to check it for any updates.

Usage

Please ensure you keep your original SVG files in a folder static/svgs and it will be copied to templates/svgs

You can use the copy-svg function from this package by supplying the source and destination directories:

You can add the below code to your src/web.lisp before your route definitions.

(cl-djula-svg:copy-svg (merge-pathnames #P"static/svgs/" *application-root*) (merge-pathnames #P"templates/svgs/" *application-root*))
Enter fullscreen mode Exit fullscreen mode

This is it, so everytime before your server is started, cl-djula-svg ensures that all the SVG files are processed by adding the necessary override logic for the attributes and copy it to the destination directory templates/svgs.

And in the template, templates/index.html you can embed and customize your SVG content like below:

    <h2>width and height</h2>
    <div class="flex">
        <div>
            {% include "svgs/airplanes-engine-fill.svg" :width 128 :height 128 %}
        </div>
        <div>
            {% include "svgs/airplanes-engine-fill.svg" :width 64 :height 64 %}
        </div>
        <div>
            {% include "svgs/airplanes-engine-fill.svg" :width 32 :height 32 %}
        </div>
    </div>
    <h2>fill</h2>
    <div class="flex">
        <div>
            {% include "svgs/airplanes-engine-fill.svg" :width 128 :height 128 :fill "purple" %}
        </div>
        <div style="color:green">
            {% include "svgs/airplanes-engine-fill.svg" :width 64 :height 64 %}
        </div>
        <div style="color:blue">
            {% include "svgs/airplanes-engine-fill.svg" :width 32 :height 32 %}
        </div>
        </div>
    <h2>class</h2>
            <div>
            {% include "svgs/airplanes-engine-fill.svg" :width 128 :height 128 :class "fill-red" %}
        </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

Image description

What's next

There are still a lot of things cl-djula-svg is capable of doing. For the immediate future, I am looking at adding optimization capabilities something like what svgo is doing for svgr. If you know anything else needs to be done to improve the package, please open an issue in the repository.

Source Code

The source code for cl-djula-svg is hosted in Github and the demo project is here

References

Hope you enjoyed the post and start using SVGs in your Common Lisp web applications to get better images resolutions, smaller file sizes and better performance with Djula and Caveman. Please let me know your queries and feedback in the comments section.

Top comments (0)