How to compare date in nunjucks? - nunjucks

So I have an array object.
var abc = [{ "title": "abc", "validUntil": "9/7/2019"];
I'm not sure how to compare date in nunjucks. I also think this can be done in the loop itself.
<div>
{% for a in abc %}
{% if new Date(offer.validUntil) > new Date() %}
{{a.title}}
{% endif %}
{% endfor %}
</div>

You can define a global function toDate
var nunjucks = require('nunjucks');
var env = nunjucks.configure();
// returns `now` if no argument is passed
env.addGlobal('toDate', function(date) {
return date ? new Date(date) : new Date();
});
var html = env.renderString(`
<div>
{% for offer in offers %}
{% if toDate(offer.validUntil) > toDate() %}
{{offer.title}}
{% endif %}
{% endfor %}
</div>
`,
{
offers: [
{title: 'Some offer title', validUntil: '9/7/2019'},
{title: 'Another offer title', validUntil: '1/6/2019'}
]
});
console.log(html);
The another way is to define a custom filter isActual
var nunjucks = require('nunjucks');
var env = nunjucks.configure();
env.addFilter('isActual', function(offers) {
return offers.filter(offer => new Date(offer.validUntil) > new Date());
});
var html = env.renderString(
`
<div>
{% for offer in offers | isActual %}
{{offer.title}}
{% endfor %}
</div>
`,
{
offers: [
{title: 'Some offer title', validUntil: '9/7/2019'},
{title: 'Another offer title', validUntil: '1/6/2019'}
]
});
console.log(html);
P.S. Pass date is a string like 9/7/2019 is a bad idea. The date intepretation (dd.mm.yyyy or mm.dd.yyyy) depends on browser setting. I recommend to use unix-epoch: new Date().getTime().

Related

Pass string as variable name to for loop with nunjucks

I have different sites, with different sitenames and try to get the right array on each site. So basically I want to pass the variable name dynamically to the for loop.
{% set sitename = "user" %}
{% set blockRef = sitename + 'Blocks' %} //result should be userBlocks
{% set userBlocks = [ 'chats', 'profile', 'settings' ] %}
{% set adminBlocks = [ 'chats', 'archive', 'profile', 'settings' ] %}
{% for blockName in blockRef %}
//user values from userBlocks array here
{% endfor %}
However, the passed name is interpreted as text and does not refer to the given array. Is there a way to make my code dynamic?
{% set sitename = "user" %}
{% set userBlocks = [ 'chats', 'profile', 'settings' ] %}
{% set adminBlocks = [ 'chats', 'archive', 'profile', 'settings' ] %}
{% set blocks = userBlocks if sitename == 'user' else adminBlocks %}
{% for blockName in blocks %}
//user values from userBlocks array here
{% endfor %}

How to use values passed by jquery/ajax in django view

How do I retrieve and use the data passed to view via ajax? In this example I'm trying to implement a reddit like upvote/downvote system
html/js:
{% for post in posts %}
<h2>{{ post.title }}</h2>
{{ post.upvotes }}<button data-id="{{post.id}}" data-vote="up" class="vote" type="submit">Upvote</button>
{{ post.downvotes }}<button data-id="{{post.id}}" data-vote="down" class="vote" id="downvote" type="submit">Downvote</button>
{% endfor %}
<script>
$(".vote").click(function () {
var id = $(this).data("id"); //get data-id
var vote_type = $(this).data("vote"); //get data-vote
};
$.ajax({
url: '/ajax/upvote/',
data: {
'id': id,
'vote_type':vote_type,
}
});
</script>
urls.py:
url(r'^ajax/upvote/$', views.upvote, name='upvote'),
view:
def upvote(request):
#how do i use 'id' and 'vote_type' values here?
You need to first edit your javascript to send the csrf token, and register a POST url in your url.py .
{% for post in posts %}
<h2>{{ post.title }}</h2>
{{ post.upvotes }}<button data-id="{{post.id}}" data-vote="up" class="vote" type="submit">Upvote</button>
{{ post.downvotes }}<button data-id="{{post.id}}" data-vote="down" class="vote" id="downvote" type="submit">Downvote</button>
{% endfor %}
<script>
$(".vote").click(function () {
var id = $(this).data("id"); //get data-id
var vote_type = $(this).data("vote"); //get data-vote
};
$.ajax({
url: '/ajax/upvote/',
data: {
'id': id,
'vote_type':vote_type,
csrfmiddlewaretoken: '{{ csrf_token }}'
}
});
</script>
Now you can access the data you sent in your views.py in a usual way:
def upvote(request):
sentence= request.POST.get("id","")
upvote = request.POST.get("upvote","")
#continue doing your stuffs here...
Hope this helps.

Custom pagination in OctoberCMS

I have a code to do query from form:
Painting::where('type',input("type"))->where('material',input("material"))->whereHas('artist', function($q)
{
$q->where('artist_slug', '=', $this->param('slug'));
})->paginate(15);
How can I do custom pagination with page numbers list? Or maybe dinamicaly loading
Check out the RainLab Blog Plugin it has a good example, also see here .
But here is a more complete example if you want to add URL friendly paginations paintings/2 where 2 is the page number and/or handle URL parameters paintings/2?type=something&material=something
In your Painting Model add a scope for listing paintings on the front-end :
public function scopeListPaintings($query, $options)
{
extract(array_merge([
'page' => 1,
'perPage' => 30,
'material' => null,
'type' => null,
'artistSlug' => null,
], $options));
if( !empty($artistSlug) ){
$query->whereHas('artist', function($q)
{
$q->where('artist_slug', $artistSlug );
});
}
if( !empty($material) ){
$query->where( 'material' , $material)
}
if( !empty($type) ){
$query->where( 'type' , $type)
}
return $query->paginate( $perPage, $page );
}
Then in your Painting Component Define the properties and call the previous scope ;
public $paintings ;
public $pageNumber;
public $perPage;
public $urlParams;
public function defineProperties()
{
return [
'pageNumber' => [
'title' => 'Page #',
'description' => 'Paintings Page #',
'type' => 'string',
'default' => '{{ :page }}',
],
'perPage' => [
'title' => 'Paintings per page',
'type' => 'string',
'default' => '30',
]
.. ect make sure to add it to your page markup
];
}
private function propertyOrParam($name, $default = null)
{
$value = $this->property($name, $default);
if (substr($value, 0, 1) == ':')
return $this->param($value, $default);
return $value;
}
public function getPaintings()
{
/** Pagination */
$this->pageNumber = $this->propertyOrParam('pageNumber') ;
$this->perPage = $this->propertyOrParam('perPage');
/** Url Params if exist */
$params = Request::query()
$this->page['urlParams'] = $this->urlParams = !empty( $params ) ? http_build_query( $params ) : null ;
return (new Painting)->ListPaintings([
'page' => $this->pageNumber,
'perPage' => $this->perPage,
'type' => input('type'),
'material' => input('material'),
'artistSlug' => $this->propertyOrParam('slug')
]);
}
public function onRun()
{
$this->page['paintings'] = $this->paintings = $this->getPaintings() ;
// Redirect to Last page if page # not found in request
if ($this->pageNumber > $this->paintings->lastPage() && $this->pageNumber > 1){
return Redirect::to($this->currentPageUrl([ $this->paramName('pageNumber') => $this->paintings->lastPage().$this->urlParams ]));
}
}
Then your can create a global partial in your theme to handle paginations - You can reuse it all over your site - add the following snippet ( Borrowed from the Forum Plugin ) ;
Pagination.htm
{% set paginationEnabled =
records.currentPage > 1 or
records.lastPage > 1 or
records.lastPage > records.currentPage
%}
{% if paginationEnabled %}
{# How many pages to display around the current page #}
{% set n = 2 %}
{% set currentPageZeroBased = records.currentPage-1 %}
{% set pageLinks = [] %}
{% set pageSet = [] %}
{% set startOffset = max(currentPageZeroBased - n, 0) %}
{% if (startOffset + 2*n+1) > (records.lastPage-1) %}
{% set startOffset = max(records.lastPage - 2*n - 1, 0) %}
{% endif %}
{% for page in 1..records.lastPage %}
{% set pageLinks = pageLinks|merge([page]) %}
{% endfor %}
{% set activeBlock = pageLinks|slice(startOffset, 2*n + 1) %}
{% if startOffset > 0 %}
{% set pageSet = pageSet|merge([1]) %}
{% if startOffset > 1 %}
{% set pageSet = pageSet|merge(['...']) %}
{% endif %}
{% endif %}
{% set pageSet = pageSet|merge(activeBlock) %}
{% set diffToEnd = (records.lastPage-1) - (startOffset + 2*n+1) + 1 %}
{% if diffToEnd > 0 %}
{% if diffToEnd > 1 %}
{% set pageSet = pageSet|merge(['...']) %}
{% endif %}
{% set pageSet = pageSet|merge([records.lastPage]) %}
{% endif %}
<div>
<div>
<div>
<div>
<span>Records <b>{{records.firstItem|trim}} - {{records.lastItem|trim}}</b> Of {{ records.total|number_format(0, '.', ',')}}</span>
<span>Page {{ records.currentPage }} of {{ records.lastPage }}</span>
</div>
</div>
</div>
<div>
<ul>
{% if records.currentPage > 1 %}
<li>
<i class="fa fa-angle-left"></i>
</li>
{% endif %}
{% for page in pageSet %}
{% if page == '...' %}
<li>
{{ page }}
</li>
{% else %}
<li class="{{ page == records.currentPage ? 'active' }}">
{{ page }}
</li>
{% endif %}
{% endfor %}
{% if records.lastPage > records.currentPage %}
<li>
<i class="fa fa-angle-right"></i>
</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
Then in your page or component ;
{% for painting in paintings %}
....
{% endfor %}
// Add the pagination
{% partial "pagination" records=paintings %}

Format liquid(Shopify) code in visual studio code

How to format .liquid (Shopify liquid) code in Visual Studio.
By settings language as HTML I can do it but at the same time, I can't use Shopify autocomplete. When I switch to liquid.html then I can use the autocomplete but I can't format code. Is there any way I can use another language and format code as another language in visual studio?
The VSCode Liquid extension provides formatting and syntax highlighting. Also has intellisense and ton of other features.
<div class="page-width">
{% if section.settings.title != blank %}
<header class="section-header">
<h2 class="section-header__title">
{{section.settings.title}}
</h2>
</header>
{% endif %}
<div class="popup-gallery">
{%- for media in product.media -%}
{%- liquid
assign has_video = false
assign video_type = ''
case media.media_type
when 'external_video'
assign has_video = true
assign video_type = media.host
if media.host contains 'youtube'
assign video_id = media.external_id
endif
when 'video'
assign has_video = true
assign video_type = 'mp4'
endcase -%}
<div
href="{%- if has_video -%}
{%- if media.media_type=='video' -%}
{%- for source in media.sources -%}
{{- source.url -}}
{%-endfor-%}
{%- else -%}
{%- assign video_url = media | external_video_url -%}
{%- if video_url contains "youtube" -%}
https://www.youtube.com/watch?v={{- media.external_id -}}
{%- else -%}
https://vimeo.com/{{- media.external_id -}}
{%- endif -%}
{%- endif -%}
{%- else -%}
{{- media | image_url -}}
{%- endif -%}"
class="
{% if has_video %}
video
{% else %}
image
{% endif %}"
title="
{% if has_video %}
This is a video
{% else %}
This is a image
{% endif %}
">
{%- assign img_url = media.preview_image | img_url: '1x1' | replace: '_1x1.', '_{width}x.' -%}
<img
class="lazyload"
data-src="{{ img_url }}"
data-widths="[120, 360, 540, 720]"
data-aspectratio="{{ media.preview_image.aspect_ratio }}"
data-sizes="auto"
alt="GALLERY"
>
<noscript>
<img
class="lazyloaded"
src="{{ media | img_url: '400x' }}"
alt="GALLERY"
>
</noscript>
</div>
{%- endfor -%}
</div>
</div>
{{ 'magnific-popup.min.css' | asset_url | stylesheet_tag }}
<script
type="text/javascript"
src="{{ 'jquery.min.js' | asset_url }}"
></script>
<script
type="text/javascript"
src="{{ 'magnific-popup.min.js' | asset_url }}"
></script>
<script type="text/javascript">
$(".popup-gallery").magnificPopup({
delegate: "div",
type: "image",
gallery: {
enabled: true,
navigateByImgClick: true,
preload: [0, 1] // Will preload 0 - before current, and 1 after the current image
},
callbacks: {
elementParse: function (item) {
console.log(item.el[0].className);
if (item.el[0].className == "video") {
(item.type = "iframe"),
(item.iframe = {
patterns: {
youtube: {
index: "youtube.com/", // String that detects type of video (in this case YouTube). Simply via url.indexOf(index).
id: "v=", // String that splits URL in a two parts, second part should be %id% // Or null - full URL will be returned // Or a function that should return %id%, for example:
// id: function(url) { return 'parsed id'; }
src: "//www.youtube.com/embed/%id%?autoplay=1" // URL that will be set as a source for iframe.
},
vimeo: {
index: "vimeo.com/",
id: "/",
src: "//player.vimeo.com/video/%id%?autoplay=1"
},
gmaps: {
index: "//maps.google.",
src: "%id%&output=embed"
}
}
});
} else {
(item.type = "image"),
(item.tLoading = "Loading image #%curr%..."),
(item.mainClass = "mfp-img-mobile"),
(item.image = {
tError: 'The image #%curr% could not be loaded.'
});
}
}
}
});
</script>
{% schema %}
{
"name": "Product gallery",
"class": "product-gallery-section",
"settings": [
{
"type": "text",
"id": "title",
"label": "Heading"
}
]
}
{% endschema %}

Using AJAX to create an Object which then appears in dropdown box

Hi I've run into a problem where I am trying to make it so when a button is clicked an object can be made. I can't get it to work as for some reason it will not validate, however if I change input type to submit it works fine.
But the problem is when I use it as submit the page redirects which defeats the purpose of using AJAX.
I can't seem to find a good tutorial for what I'd like to do, any help or links would really be appreciated!
Models
class MemberRole(models.Model,get_fields):
name = models.CharField(max_length = 20)
Form
class MemberRoleForm(forms.ModelForm):
class Meta:
model = MemberRole
Views
This view builds the form for the memberrole model
def add_member(request):
model_url = 'member-add'
rform = MemberRoleForm(instance=MemberRole())
return render_to_response('create_model.html', {'role_form': rform,'model_url': model_url,},context_instance=RequestContext(request))
This ajax called view
#require_POST
def ajax(request):
data = request.POST.get("rolename","")
if request.method == 'POST':
rform = MemberRoleForm(request.POST, instance=MemberRole())
if rform.is_valid():
new_role = rform.save()
new_role.name = data
return HttpResponse("SuccessFully Saved")
else:
return HttpResponse("Error in saving")
Template
<div id="addRoleOnMemberForm">
<form id = "addrole" onsubmit= "return false;" method="POST">
{% csrf_token %}
{% for field in role_form %}
{{ field.errors }}
{{ field.label_tag }} {{ field }} //#id_name is in here
{% endfor %}
<input id="addrolebutton" type="button" onclick = "createit()" value="Add New Role"/>
</div>
{% for x in role_list %}
<div>
<p> This shows a role was made </p>
</div>
{% endfor %}
Script
<script>
function createit(){
$.ajax({
type: "POST",
url:"ajax",
dataType:"json",
async: true,
data: {
csrfmiddlewaretoken: '{{ csrf_token }}',
rolename: $('#id_name').val()
},
});
}
</script>
I've finally managed to get it to work. :)
I have an add_member view which allows the creation of a memberrole object if it is not available in the member.role dropdown field. The memberrole object on creation will be added without a page reload to the dropdown field so it can be selected immediatly.
I'm not entirely sure if this would be the correct way of coding it, I've included all the source and hopefully it helps someone like me. Comments would be appreciated!
Models
class MemberRole(models.Model,get_fields):
name = models.CharField(max_length = 20)
class Member(models.Model,get_fields):
first_name = models.CharField(max_length = 20)
last_name = models.CharField(max_length = 20)
role = models.ForeignKey(MemberRole, null = True, blank = True)
Forms
class MemberForm(forms.ModelForm):
class Meta:
model = Member
Script
<script>
function createit(){
$.ajax({
type: "POST",
url:"addrole",
dataType:"json",
async: true,
data: {
csrfmiddlewaretoken: '{{ csrf_token }}',
rolename: $('#role_name').val()
},
success: function (json,rolename){
$('#output').html(json.message); // Prints a message to say that the MemberRole object was created
$('#roleExistsMemberForm').load(document.URL + ' #roleExistsMemberForm'); //Refreshes the dropdown box so that the newly created MemberRole can be selected
}
});
}
</script>
Views
def add_role(request):
model_url = 'role-add'
new_role = request.POST.get("rolename","")
role_list = MemberRole.objects.all()
response_data= {}
if new_role:
x = MemberRole()
x.name = new_role
x.save()
response_data['message'] = new_role + " was created"
else:
response_data['message'] = 'Nothing created'
return HttpResponse(json.dumps(response_data),content_type="application/json")
def add_member(request):
model_url = 'member-add'
if request.method == "POST":
mform = MemberForm(request.POST, instance=Member())
if mform.is_valid():
new_member = mform.save(commit=False)
new_member.save()
return HttpResponseRedirect('members')
else:
mform = MemberForm(instance=Member())
return render_to_response('create_model.html', {'member_form': mform, 'model_url': model_url,},context_instance=RequestContext(request))
create_model.html
{% for field in member_form %}
{% if field.label == 'Role' %}
<div id="roleExistsMemberForm">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% else %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endif %}
{% endfor %}
<div id="addRoleOnMemberForm">
<form onsubmit="return false;">
{% csrf_token %}
<input id= "role_name" type="text"/>
<input id="addrolebut" type="button" onclick = "createit()" value="Add New Role"/>
</div>
<div id="output">
<p>Nothing here</p>
</div>

Resources