I can see with twig.js that you can use namespacing for template paths:
https://github.com/justjohn/twig.js/wiki#user-content-namespaces
Where do you you specify the namespaces? Here it is in the documentation:
var template = Twig.twig({
data: your-template,
namespaces: { 'my-project': 'path/to/views/folder/' }
}).render();
That IS the specification...i.e. the next lines of the doc:
Ex:
{# your-template.twig #}
{% extends "my-project::template.twig" %}
The "my-project::" will now point to "path/to/views/folder/".
Related
I already added a button alongside the save buttons for my django change view:
{% extends 'admin/change_form.html' %}
{% load static %}
{% load i18n %}
{% block submit_buttons_bottom %}
<div class="submit-row">
<input type="submit" value="Download" id="download_profile" name="_continue">
<input type="submit" value="{% trans 'Save' %}" class="default" name="_save">
<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother">
<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue">
<!-- inmport script -->
<script src="{% static '/js/downloadData.js' %}"></script>
<script src="{% static '/js/collapsibleSection.js' %}"></script>
<script src="{% static '/js/investmentAdminScript.js' %}"></script>
{% endblock %}
and I already started adding the script to retrieve individual data per row to download them.
'use strict';
window.addEventListener('load', () => {
const downloadButton = document.querySelector('#download_profile');
downloadButton.addEventListener('click', e => {
if (!confirm('Data will be saved before downloading. Are you sure you want to continue?')) {
e.preventDefault();
return;
}
// get module
const fieldsets = document.querySelectorAll('fieldset');
fieldsets.forEach(thisFieldset => {
const dataRows = thisFieldset.querySelectorAll('.form-row');
dataRows.forEach(dataRow => {
// retrieve each input field and add a download to .xlsx function
});
});
});
});
However, it gets complicated very fast. Is there a better way to do this?
Found a single JS file that allows you to format .xlsx files and start a download with one function
https://github.com/egeriis/zipcelx
Since you're using django, download the "standalone.js" file and add it to your static files. Reference it in your template in a script tag. Now you'll have access to the "zipcelx()" function
Here's a "getting started" example I made
const config = {
filename: "practice-file",
sheet: {
data: [
// Row 1
[
{value: "row1 value1",type:"string"},
{value: "1000",type: 'number'}
],
// Row 2
[
{value: "row2 value1", type: 'string'},
{value: "row2 value2", type: "string"}
]
]
}
}
// call zipcelx to immediately start the download in client's browser
zipcelx(config)
Example of the File Generated
Don't know how large your data sets are, might get tedious with having to format your data in such a way. Some helper functions should do the trick if that became a problem.
Hope this helped!
I am getting Post.match is not a function error when i do this :( Please help, i am a newbie in JavaScript part. (I am getting back an object so have to turn it into an array but still get this error after using the Objects.values Method)
My Views.py File:
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from .models import Post
def Data(request):
items = Post.objects.all()
data = []
for qs in items:
I = {"title":qs.title,
"content": qs.content,
"image": qs.image.url,
}
data.append(I)
return JsonResponse({"data":data})
My HTML File:
{% extends 'blog/base.html' %}
{% load static %}
{% block content %}
<div class = 'w-100 text-center'>
<h1>Search Results</h1>
<form id = "search-form" autocomplete="off">
{% csrf_token %}
<input name = 'game' type="text" id = "search-input" placeholder= "Post Search..">
</form>
<div id = "results-box" class = "results-card">
</div>
</div>
{% endblock content %}
{% block js %}
<script defer src="{% static 'blog/S1.js' %}"> </script>
{% endblock js %}
My Java Script File:
console.log('Heelowwww')
const url = window.location.href
const searchForm = document.getElementById("search-form")
const searchInput = document.getElementById("search-input")
const resultsBox = document.getElementById("results-box")
const csrf = document.getElementsByName("csrfmiddlewaretoken")[0].value
options = {method: "GET",
headers: {
Accept: "application/json"
},
data:{
'csrfmiddlewaretoken': csrf,
}
}
const SearchPosts = async SearchIt => {
const res = await fetch("http://localhost:8000/data/",options)
const Posts = await res.json()
S = Object.values(Posts["data"])
let matches = S.filter(post =>{
const regex = new RegExp(`^${SearchIt}`, 'gi')
return post.match(regex)
})
console.log(matches)
}
searchInput.addEventListener('input', () => SearchPosts(searchInput.value))
My data Json Page:
Json Data Page
Here post.match(regex) your trying to call the match method on a js object. It seems you should be calling it on on of the string properties of it. Something like: post.title.match(regex).
In Apostrophe, I have a custom module where I would like to pass an option from the Nunjucks apos.area call to the construct method of the widget itself. Concretely, I want to adjust the output of getWidgetWrapperClasses based on the options passed to the module in the template. Is this possible?
Here's an example of what I would like to achieve:
lib/modules/example-widgets/index.js
module.exports = {
extend: "apostrophe-widgets",
label: "Example widget",
construct: function(self, options) {
self.getWidgetWrapperClasses = function(widget) {
// templateOptions would be the options object as defined
// in home.html below
return ["column", "column-" + templateOptions.width];
};
}
};
lib/modules/apostrophe-pages/views/pages/home.html
{% extends "layout.html" %}
{% block content %}
<div id="widgets">
{{ apos.area(data.page, "example", {
widgets: {
"example": {
width: "half"
}
}
}) }}
</div>
{% endblock %}
I solved this by not using the getWidgetWrapperClasses method, but instead extending the widget wrapper template and overriding a Nunjucks block in there. This is in fact a documented approach if you look in lib/modules/apostrophe-areas/views/widgetBase.html in Apostrophe's code.
I changed lib/modules/example-widgets/index.js like this:
module.exports = {
extend: "apostrophe-widgets",
label: "Example widget",
wrapperTemplate: "wrapper",
construct: function(self, options) {
// Do something
}
};
Then, I added a lib/modules/example-widgets/views/wrapper.html file. In that file, you can simply override the extraWrapperClasses block to add the classes you want, all the while having access to the template options through data.options.
{% extends "apostrophe-areas:widget.html" %}
{% block extraWrapperClasses %}column column-{{ data.options.width }}{% endblock %}
I know it's possible to add an alias to index.js, but this is not recommended for NPM modules.
So, how do I pass JavaScript variables like the options object to a Nunjucks template?
data.widget only contains an id, the type and editable. Strangely enough, it doesn't contain the label.
index.js
module.exports = {
extend: 'apostrophe-widgets',
label: 'Privacy Cookie Widget',
[...]
}
widget.html
<div class="[...]-widget">
[...]
{{ data.widget.label }} <!-- Nothing. -->
[...]
</div>
home.html
[...]
{{ apos.singleton(data.[global|page], 'widgetName', 'widget-name', {}) }}
[...]
from a template you can get to your widgets root options config like this
{{ apos.log(apos.modules['my-cool-widgets'].options.coolStuff) }}
Where coolStuff is defined in my-cool-widgets/index.js
module.exports = {
extend: 'apostrophe-widgets',
label: 'Cool Widg',
coolStuff: {
array: [1,2,3,4],
hello: 'boom boom',
hehe: true
},
addFields: [...]
};
As nunjucks now supports using set as a block I wanted to do something like this:
{% set navigationItems %}
{% for item in items %}
{ name: item.name, url: item.url }{% if not loop.last %},{% endif %}
{% endif %}
{% endset %}
Then call this variable as the input object on another macro, like so:
{{ navigation(items=[navigationItems]) }}
However, navigationItems is evaluated as a string, not an array-literal. Any idea how, or if this is possible?
Thanks.
I'm not exactly sure what you're trying to accomplish. It looks like you want to loop over one array called items and copy it into a new array called navigationItems. Perhaps items contains more keys than you want to pass to the macro?
I'm going to make that assumption, otherwise you could simply copy items into navigationItems like so:
{% set navigationItems = items %}
This example works:
{% macro navigation(items) %}
<ul>
{% for item in items %}
<li>{{ item.name }} - {{ item.url }}</li>
{% endfor %}
</ul>
{% endmacro %}
{% set websites = [
{
name: 'Google',
url: 'http://google.com',
description: 'A search engine'
},
{
name: 'GitHub',
url: 'http://github.com',
description: 'A webapp for your git repos'
},
{
name: 'StackOverflow',
url: 'http://stackoverflow.com',
description: 'The answer: 42'
}] %}
{% set navigationItems = [] %}
{% for website in websites %}
{% set navigationItems = (navigationItems.push({name: website.name, url: website.url}), navigationItems) %}
{% endfor %}
{{ navigation(items=navigationItems) }}
websites values contain a description key which is not passed on to the navigationItems array. If it were me, I'd just pass website directly to the navigation macro since your keys: name and url are the same in both arrays.
The pattern here is almost like a map method in Javascript or Ruby.