Flask - Component templates structure? - javascript

I've been experimenting with a Flask template structure to resemble a component workflow. This is inspired by Vue.
Some key ideas :
Each page is treated as a component
A component is just a folder containing all necessary files inside it (except ones that are common)
Each component is encourageg to have several HTML files to avoid large files, we will be using Jinja's include to "bundle" them
togheter
Avoid javascript, or at least, loading unnecessary javascript, each page will include only what's necessary
Here's an example :
The route for PageExample would be the following :
app.route('/example'):
return render_template('PageExample/html.html')
And PageExample/html.html :
{% extends 'base.html' %}
{% block content %}
<div class="tab" id="tab1">
{% include 'PageExample/tab1.html' %}
</div>
<div class="tab" id="tab2">
{% include 'PageExample/tab2.html' %}
</div>
{% endblock %}
PageExample/tab1.html (tab2 follows the same logic)
<!-- block header is in base.html -->
{% block header %}
{{ super() }}
<script src="{{url_for('components', 'PageExample/tab1.js')}}">
{% endblock %}
<div class="tab-content">
Some content here that uses the tab1.js javascript
</div>
So, what's the question ?
I have limited experience with Flask, so I'm not sure if this idea is sound, it seems hacky but it makes developing pages easier, especially with multiple people (the little javascript a page might need can be done in any way). Also increased loading speed by a ton.
Dos this work? Is this structure reccomended? Are there any flaws to it? What sort of problems could I run into? Any recommendation to do it better? Is this already done nowadays in Flask? I couldn't find any examples of a Flask app done this way (specially the Jinja stuff using lots of includes)

I'm currently not at my main computer, so I'll update this later if need be, but if your included html files referenced any data that is stored in the backend then you'd also need to make sure you reference the exact location there as well.
For example:
If you need the first name property from a User class it would be
{{ User.firstName }}
Not only that, but the tab template will also need to be supplied this User.firstName variable, which isn't done implicitly.
You will need to create what is called a Blueprint then a View and tell the view to make the User.firstName variable available to PageExample/tab1.html
tabs.py
import User
from flask import Blueprint, render_template
import """other flask dependencies needed"""
bp = Blueprint('index', __name__, url_prefix='/')
def tabData():
"""data to process"""
render_template("PageExample/tab1.html", User=User)
In that same tabs.py file, you can also write a view for tab2.html as well as html.html. Should be noted that I'm assuming that the contents of PageExample are for the index of the website and that they will all be rendered on that same page.
Also, It seems like your tabs will be in the content/body of the html but it will also render the blocks of base.html and add the defined scripts inline with the body of html.html. Instead, it should be referenced in the html.html file itself
html.html
{% extends 'base.html' %}
{% block header %}
{{ super() }}
<script src="{{url_for('components', 'PageExample/tab1.js')}}">
{% endblock %}
{% block content %}
<div class="tab" id="tab1">
{% include 'PageExample/tab1.html' %}
</div>
<div class="tab" id="tab2">
{% include 'PageExample/tab2.html' %}
</div>
{% endblock %}
with tab1.html being
<div class="tab-content">
Some content here that uses the tab1.js javascript
</div>
Obviously, this is just the surface of what you will need to know about flask. Think the bottom line is that it can work. Although I would suggest that your put all resources such as JS and CSS in a separate folder and reference those resources from those locations.
For more information on Flask and how you can utilise it, check out the Flask Tutorial here:
http://flask.pocoo.org/docs/1.0/tutorial/

Related

Django and HTMX I am looking for not "hacky" way to update class of link that calls HTMX request. Like "refresh-self" or something along those lines

I have a problem with HTMX in Django. I basically have two important components on page. List of categories and content that is being shown after you click on category.
I was working nicely with just standard htmx "out of the box". But I started having problems when I wanted to add active css class on category link after you click it (to show user where he is currently).
I did a lot of experiments with hx-swap-oob and hx-swap but the only thing that work was this:
(it is the most relevant part of the code)
<div class="col-sm-4">
<div class="card">
<div class="card-body" hx-boost="true" hx-target="#manual_results">
<div id="manual_categories">
{% include 'partials/manual_categories.html' %}
</div>
</div>
</div>
</div>
<div class="col-sm-8">
<div id="manual_results">
{% include 'partials/manual_entries_list.html' %}
</div>
</div>
and in manual_entries_list.html:
<some html results>
<div id="manual_categories" hx-swap-oob="true">
{% include 'partials/manual_categories.html' %}
</div>
Each category has simple if statement in django template code that is checking if it is selected (based on url path.)
And it is working, thing is, on the first request the categories are rendered twice (which is logical since I have 2 includes on the same HTML). After I select one category, everything goes back to normal because HTMX "starts to understand what is happening" and it switches the categories from manual_entries_list.html into our main page.
And like I said it works, I modified manual_entries_list.html to:
<some html results>
<div class="set_size_to_0px">
<div id="manual_categories" hx-swap-oob="true">
{% include 'partials/manual_categories.html' %}
</div>
</div>
So it is always invisible (so I will have only one set of categories visible).
The thing is, that it is an obvious hack. I am sure that there needs to be a better way of solving this problem but I cannot find it.
(I even tried to do it with plain javascript the thing is that categories are rendered in a for loop so it is pretty much impossible to get IDs correctly etc.)
Could someone please help me?
The easiest way to avoid this issue is to detect the HTMX request in the view function, pass this state to your templates and render HTMX content only if needed. HTMX will add a HX-Request: true header to each request.
For the detection you can use the Django-HTMX package that provides a request.htmx variable in your view functions that will be True if the request is coming from HTMX. Or if you want to check it manually:
def my_view(request):
is_htmx = request.headers.get('HX-Request') == 'true'
return render(request, 'my_template.html', {'is_htmx': is_htmx})
After that in manual_entries_list.html template include HTMX related stuff only in the HTMX requests:
<some html results>
{% if is_htmx %}
<div id="manual_categories" hx-swap-oob="true">
{% include 'partials/manual_categories.html' %}
</div>
{% endif %}

Shopify - using if statements within js.liquid and scss.liquid files

I am trying to merge JS files together in 1 file, however I need to set conditions using the if statements within the js file.
I have created file.js.liquid but it does not seem to working; I added a test for example:
{% if template contains 'index' %}
console.log('homepage loaded');
{% endif %}
Example of what i am trying to do, of course have plenty i need to do.
{% if settings.the_simpsons_enabled and settings.simpsons_video_enable %}
// video popup
$(".video-popup").videoPopup();
{% endif %}
The conditional statements are also not working within scss.liquid files either:
{% if template contains 'index' or template contains 'collection' or template contains 'product' %}
#import url("{{ 'owl.carousel.scss' | asset_url }}");
{% endif %}
Any ideas why if would not be working or if its even possible? I assume it is being a liquid file.
The Liquid extension on such files like CSS or JS allows use of Liquid variables but not statements.
So {{ write_my_liquid_var_here }} will work. {% if liquid_statement %}do something{% endif %} won't.
Regarding Shopify and SASS
The following won't work as you can't use #import in Shopify's SASS files.
#import url("{{ 'owl.carousel.scss' | asset_url }}"); //This will not work
Regarding using conditional liquid statements in .js.liquid files
I don't know everything about this subject, but I do know the following two things.
1) Check out this thread and you'll see that this type of statement won't work because js files are parsed independent of normal liquid templates, so it would have no idea which template you're viewing.
{% if template contains 'index' %} //This will not work
A better place to put the above logic would be at the bottom of your theme.liquid file above the </body> tag. However this won't help you with what seems to be your goal of reducing HTTP requests by using one JavaScript fie.
2) However, you can access values in your settings_data.json file
{% if settings.the_simpsons_enabled and settings.simpsons_video_enable %} //This will work
I hope this helps

How to include properly Javascript using a Javascript Twig Template

I know how to access my assets, but i wanted to create a template working with Javascript and Twig, which would allow me to use Twig variables in my script.
For example, I have a template called 'details.html.twig', which extends a 'layout.html.twig':
{% extends '#AcmeBundle/Front/layout.html.twig %}
{# SOME CODE AND CONTENT #}
{% block page_script %}
{{ parent() }}
<script>
{# Part of my script #}
</script>
{% endblock page_script %}
All of this worked perfectly, but to be able to reuse some js code (using twig variables), I cut some part of my script and put I in an external template, called 'viewer.html.twig'. To be able to use it in my 'details.html.twig', I used:
{% include '#AcmeBundle/Front/Model/viewer.html.twig' %}
I put this line right after the {{ parent() }} one and before the <script> tag.
However, my script which used to work when directly written in 'details' template seems to be quite ignored right now.
If any of you could have a clue of what makes my template being included but skipped, or a way to include the template easier or properly, I'm open to all kind of observations.
G'day,
Jérémy
If you code a file containing only javascript you should name your file:
{% include '#AcmeBundle/Front/Model/viewer.js.twig' %}
Then Twig (as of 1.17) automatically defines its escaping strategy based on the extension.
See the twig docs and an example of js twig file in symfony.

How to render a component with dynamic content in a page using an npm-based static site generator?

The goal is to render a component with dynamic content in a page using an npm-based static site generator:
I can't seem to find a solution to this in in Harp, Brunch or Metalsmith. I have been playing with each of these and it feels like I am going about this the wrong way.
The structure will be something like:
/pages
|_ home.??
|_ about.??
/partial
|_ header.??
|_ body.??
|_ portfolio-item.??
/data
/home
partial-1.??
partial-2.??
partial-3.??
partial-4.??
Now I need home to include each partial with the data passed to the partial.
//home.??
include header with partial-1
include body with partial-2
include portfolio-item with partial-3
include portfolio-item with partial-4
And each partial needs markup:
//portfolio-item.??
<p>
{{project-description}}
</p>
And each data item needs to have the relating id for data:
//sort of like json
{
project-description: whatever i need
}
Why do this? I want to:
Separate markup from data
Have files act like a CMS
Speed up development as I am not copying partials around if I am reusing them. (For example, harp supports unique values for files but not reusing files with unique values)
Any tips will be great! I think jade or Ejs might be solutions, but the data might have to be manipulated by some custom code. This, however, feels like someone has done this before.
After #MikeC pointed me in the direction of Jekyll I went ahead and explored it.
By using the documentation I came up with the following solution to the problem:
The standard Jekyll structure is was used.
We must make the builder partial that will add components from data files
{% assign page_data = site.data.[page.context] %}
{% assign counter = 0 %}
{% for partial_name in page_data.partials %}
{% assign counter = counter | plus:1 %}
{% include {{ partial_name }}.html %}
{% endfor %}
In about.md add the following
---
layout: page
title: About
context: about
permalink: /about/
---
{% include builder.html %}
Then add a partial that you would like to test on like _includes/hello-text.html.
{% capture data_file %}{{ page.context }}-{{ counter }}{% endcapture %}
{% assign context = site.data.[data_file] %}
<p>
{{ context.content }}
</p>
Finally add your data files _data/about.json
{
"partials": ["foobar", "foobar", "foobar"],
}
and _data/about-1.json
{
"content": "helloworld"
}
You can add about-2.json for the second partial and so on. I would still appreciate any improvements on this issue if anyone has any suggestions you can check out the repo on github to bash it out.

a better way to do ajax in django

The other day I wrote some AJAX for a Django app that i have been working on.
I come from Ruby on Rails, so I haven't done much in the way of raw JS.
So based on Rails' partials, I something similar to the following in a sort of pseudocode, don't sweat the details:
1) JS function using prototype's Ajax.Updater ('tablediv' being the id of the table i wanted to update Ajaxily, and url pointing to the proper django view)
function updateTable(){
new Ajax.Updater('tablediv',url {params: params....etc
2) django view that got new data to populate the table with:
def ajaxTable
objects = Objects.object.all...
return render_to_response('ajaxtable.html',objects)
3) ajaxtable.html was just a sort of Rails "partial" so basically a table w/o <table> </table> ...:
<th>{{object.data}}</th>
<td>{{object.moredata}}</td>
so to my actual question:
This seemed hacky to me, I sort of threw it together after getting tired of searching online for what i wanted.
Is this the way it's done? It works fine, I just don't know enough to know, you know?
It kinda depends what you want to do I think. Ajax being quite a wide range of scenarios from Google Maps to a simple auto-complete varys greatly in complexity and the best approach.
However, there are some useful things you can do that help.
1) Template level
Make sure you have "django.core.context_processors.request" in your TEMPLATE_CONTEXT_PROCESSORS setting. Then you can do this;
{% if not request.is_ajax %}
<html>
<head>
...
</head>
<body>
...
{% endif %}
actual content
{% if not request.is_ajax %}
</body>
</html>
{% endif %}
Basically then say this page is /test/ you can do a browser request and get the full content or a request via JavaScript and just get the content. There is a blogpost somewhere that explains this in more detail but I can't find it at the moment.
2) In the view
In the template we are just accessing the request object in the template. In the view you can do very similar things.
def my_view(request):
if requst.is_ajax():
# handle for Ajax requests
# otherwise handle 'normal' requests
return HttpResponse('Hello world')
The above methods don't really do it differently than you do but allow you to re-use views and write it bit more concisely. I wouldn't really say what you are doing is wrong or hacky but you could write it to make it more concise and re-use the templates and views.
say for example you could have just one template and if its a Ajax request have it only return the section that will need to be updated. In your case it would be the tables views.
I am quite late, but I want to document how to combine and adapt the solutions presented by d0ugal
in a way, that it will resolve a much cleaner template-code.
I have a model representing contact persons.
The (generic) view to get one ContactPerson looks like this:
def contcactperson_detail_view(request, name):
try:
person = ContactPerson.objects.get(slug=name)
except:
raise Http404
if request.is_ajax():
return contcactperson_detail_view_ajax(request, person)
return list_detail.object_detail(
request,
queryset = ContactPerson.objects.all(),
object_id = person.id,
template_object_name = "contactperson",
)
#render_to('cms/contactperson_detail_ajax.html')
def contcactperson_detail_view_ajax(request, person):
return {'contactperson':person, 'is_ajax':True}
The template to render the view that handles one ContactPerson is called contcactperson_detail_view.html:
{% extends "index.html" %}
{% block textpane %}
<h1 id="mainheader">{{ contactperson.first_name }} {{ contactperson.family_name }} </h1>
<div class="indentation"> </div>
{% include 'cms/contactperson_detail_photo.html' %}
<div id="text_pane">
{% include 'cms/contactperson_detail_textpane.html' %}
</div>
{% endblock %}
It includes two sub-templates
contactperson_detail_textpane.html
<p>{{ contactperson.description }}</p>
<ul>
<li>
<dl>
<dt>Email</dt>
<dd>
{{ contactperson.mail }}
</dd>
</dl>
</li>
<li>
<dl>
<dt>Contact Person for</dt>
<dd>
<ul>
{% for c in contactperson.categories.all %}
<li>{{ c }}</li>
{% endfor %}
</ul>
</dd>
</dl>
</li>
</ul>
and contactperson_detail_photo.html
{% with contactperson.photo.detailphoto as pic %}
{% with pic.url as pic_url %}
<div {% if not is_ajax %}id='imageContainer'{% endif %} style="float: right;padding-right:0.5em;
padding-bottom: 1em; padding-left:0.5em;clear:both;
width:{{ pic.width }}px">
<div style="width:{{ pic.width}}px">
<img style="clear:both" src="{{ pic_url }}" alt="{{ i.name }}"/>
</div>
</div>
{% endwith %}
{% endwith %}
this 3 templates will be used, if the request isn't ajax.
But if the request is ajax, contcactperson_detail_view will return the view contcactperson_detail_view_ajax, that uses the template contactperson_detail_ajax.html for rendering. And this template looks like this:
<h1>{{ contactperson.first_name }} {{ contactperson.family_name }}</h1>
{% include 'cms/contactperson_detail_photo.html' %}
{% include 'cms/contactperson_detail_textpane.html' %}
So it uses the same sub-templates but isn't extending anything, therefore only the needed markup delivered. As the ajax view passes is_ajax = True to the template, it can be used to adjust minor things, like setting correct id-attributes.
No context-processor or additional url-conf needed.
Finally the Javascript code:
$("#contact_person_portlet a").click(function(event){
event.preventDefault();
$.ajax({
type: "GET",
url: event.target.getAttribute('href'),
success: function(msg){
overlay(msg);
}
});
});
Hope that it will be useful for some people. If so, please leave a comment!
No matter what, you're going to need at least two things:
Your javascript code to make the call (you have this)
Server side code to handle the request (this is your view and url-config)
There is absolutely nothing "hacky" about this.
The third thing, your template file, is optional - but is generally good practice. You want to separate your markup from the code, for many reasons.
So I think you've got the right idea. Carry on.
What exactly seems hacky about it? Seems like a perfectly valid way of doing something.
I guess an alternative would be serialising to json and sending it back to a javascript templating snippet.

Categories

Resources