I’d like to make a request in my j=Javascript code to the Apostrophe’s server (like PUT, POST…). I did that but it does not work:
{% extends "layout.html" %}
{% block main %}
<div class="main-content">
<h3>Hello world!
{% if not data.user %}
<a class="login-link" href="/login">Login</a>
{% endif %}
</h3>
<p>This is a very bare bones Apostrophe project. Now, get to work and make a real website!</p>
</div>
{{
apos.area(data.page, 'body', {
widgets: {
'apostrophe-images': {
size: 'full'
},
'apostrophe-rich-text': {
toolbar: [ 'Styles', 'Bold', 'Italic', 'Link', 'Unlink' ],
styles: [
{ name: 'Heading', element: 'h3' },
{ name: 'Subheading', element: 'h4' },
{ name: 'Paragraph', element: 'p' }
]
}
}
})
self.apos.tasks.add(self.__meta.name, 'insert-stuff', function(apos, argv, callback) {
var req = self.apos.tasks.getReq();
return self.find(req, { cool: true }).toArray().then(function(err, pieces) {
if (err) {
return callback(err);
}
});
};
}}
{% endblock %}
When I run the script (going to localhost:3000), I receive an error in my console. I have the error :
e.stack: Template render error: (apostrophe-pages:pages/home.html) [Line 34, Column 3]
expected variable end
at Object.exports.prettifyError (C:\Windows\System32\test-project\node_modules\nunjucks\src\lib.js:34:15)
at new_cls.render (C:\Windows\System32\test-project\node_modules\nunjucks\src\environment.js:469:27)
at Object.self.renderBody (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:309:47)
at Object.self.renderForModule (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:176:19)
at Object.self.render (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-module\index.js:173:34)
at Object.self.renderPageForModule (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:666:28)
at C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-module\index.js:349:31
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:726:13
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:52:16
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:269:32
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:44:16
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:723:17
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:167:37
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:52:16
at iterate (C:\Windows\System32\test-project\node_modules\async\lib\async.js:260:24)
at Object.async.forEachOfSeries.async.eachOfSeries (C:\Windows\System32\test-project\node_modules\async\lib\async.js:281:9)
:: 2018-08-17T10:43:23+0200: template error at /
Current user: admin
{ Template render error: (apostrophe-pages:pages/home.html) [Line 34, Column 3]
expected variable end
at Object.exports.prettifyError (C:\Windows\System32\test-project\node_modules\nunjucks\src\lib.js:34:15)
at new_cls.render (C:\Windows\System32\test-project\node_modules\nunjucks\src\environment.js:469:27)
at Object.self.renderBody (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:309:47)
at Object.self.renderForModule (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:176:19)
at Object.self.render (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-module\index.js:173:34)
at Object.self.renderPageForModule (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:666:28)
at C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-module\index.js:349:31
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:726:13
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:52:16
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:269:32
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:44:16
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:723:17
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:167:37
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:52:16
at iterate (C:\Windows\System32\test-project\node_modules\async\lib\async.js:260:24)
at Object.async.forEachOfSeries.async.eachOfSeries (C:\Windows\System32\test-project\node_modules\async\lib\async.js:281:9) name: ‘Template render error’ }
Related
Goal
Show the default error message for a multi select field (styled with selectpicker) in various languages.
Description
I have a multi step wizard and want to validate a multi select dropdown with the formvalidation.io JS plugin. I don't know, how to make it work, that the default message from the appropriate language file (en_US.js, de_DE.js) is shown.
The thing i don't get is how to use the default error message for a choice validation from formvalidation.io. Docs for single lang are here but i can't figure out how to make it work with multiple languages.
JS code
var _initValidation = function () {
let language = window.uh.localeIso
const plugins = () => {
return {
trigger: new FormValidation.plugins.Trigger(),
bootstrap: new FormValidation.plugins.Bootstrap(),
}
}
if (language === 'en_GB') { language = 'en_US'}
// Step 1
_validations.push(FormValidation.formValidation(
_formEl,
{
locale: language,
localization: FormValidation.locales[language],
fields: {
company: {
validators: {
notEmpty: {}, // <---- this works with multiple languages
},
}
// and so on...
},
plugins: plugins(),
},
))
// Step 2
_validations.push(FormValidation.formValidation(
_formEl,
{
fields: {
'job_ad_visible_country[]': {
validators: {
choice: {
min: 1,
max: 3,
message: {} // <--- what here for the default validation message in multiple languages?
}
},
},
},
plugins: plugins(),
},
))
}
HTML Code
...
<div class="col-xl-6">
<div class="form-group">
<label>{{ "job.wizard.two.country" | trans }}</label>
<select class="form-control form-control-lg selectpicker"
name="job_ad_visible_country[]"
title="{{ "job.wizard.nothing-selected" | trans }}"
multiple="multiple"
data-depend-select="true"
data-dependent="country_region">
{% for country in countries %}
<option value="{{ country.getCountryId() }}" selected="selected">
{{ country.getName() | trans }}
</option>
{% endfor %}
</select>
<span class="form-text text-dark-40">
{{ "job.wizard.two.country.subtitle" | trans }}
</span>
</div>
</div>
...
... and at the bottom of course the JS scripts
<script type="text/javascript" src="{{ '/assets/wizard.js' | asset }}"></script>
<script type="text/javascript" src="/assets/translations/validation/{% if locale_iso() == 'en_GB' %}en_US{% else %}{{ locale_iso() }}{% endif %}.js"></script>
Solution:
In step 2 the language was missing. So it has to look like this (to see how i get the language code, look at my question):
_validations.push(FormValidation.formValidation(
_formEl,
{
locale: language,
localization: FormValidation.locales[language],
fields: {
'job_ad_visible_country[]': {
validators: {
choice: {
min: 1,
max: 3,
}
},
},
},
plugins: plugins(),
},
))
I am using Keystone JS and nunjucks. I have a feature where in the application sends an email. There is no problem in sending the email , the problem is that the data does not pass to the template. It does not adapt.
Code
var sendEmail = function (err, results) {
if (err) return callback(err);
async.each(results.admins, function (admin, done) {
new keystone.Email({ templateName: 'enquiry-notification.html', transport: 'mailgun', engine: cons.nunjucks, root: 'templates/emails' }).send({
}, {
apiKey: '',
domain: '',
title: "Test",
author: 'test',
body: 'Heeeeeeeeeeeeeeeeeeeeeeeeeeeee',
subject: subject,
html: '<b>NodeJS Email Tutorial</b>',
body: "Helloworld",
to: admin.email,
text: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaavbbb',
host: 'helloworld.com',
from: {
name: 'Test Mail',
email: inquiry.email
},
inquiry: inquiry,
brand: brand,
}, done);
}, callback);
}
Template
<h1>Hi %recipient%</h1>
<p class="text-larger">An enquiry was just submitted to %from.name%:</p>
{% if inquiry.email %}
<p class="text-larger">From
{% if inquiry.name.full %}
<strong>{{ inquiry.name.full }}</strong>
{% endif %}
{% endif %}
Because according to the doc, if you need to send locals you need to send it inside the .send() method:
new Email(/* ... */).send({
recipient: {
firstName: 'Max',
name: 'Stoiber',
}
}, {/* ... */}, function (err, result) {/* ... */});
So in your case it will be:
const sendEmail = function (err, results) {
if (err) return callback(err);
async.each(results.admins, function (admin, done) {
new keystone.Email({ templateName: 'enquiry-notification.html', transport: 'mailgun', engine: cons.nunjucks, root: 'templates/emails' })
.send({
inquiry: inquiry,
brand: brand
}, { /*....*/ }, done);
}, callback);
}
I am trying to add a reorder button to my customer's order history pages to automatically reorder items they've already purchased. This would send them back to the cart with items already prefilled and ready for checkout. Each product item has custom properties attached to it. Everything seems to work fine, except that when I add the items back into the cart, I cannot get the custom line item properties to display.
I'm using the order-to-cart.liquid snippet.
On line 18, it seems the custom properties are being called:
properties: {{ line_item.properties | json }}
I've tried to modify the code to add them:
request.send(JSON.stringify({
'quantity':order.items[0].quantity,
'id':order.items[0].variant_id,
'properties':order.items[0].properties
}));
But this does not work.
Based on the comment below, I tried to loop through the item properties:
request.send(JSON.stringify({
'quantity':order.items[0].quantity,
'id':order.items[0].variant_id,
'properties': {
{% for line_item in order.line_items %}
{% for property in line_item.properties %}
'{{ property.first }}': '{{ property.last }}'{% unless forloop.last %},{% endunless %}
{% endfor %}
{% endfor %}
}
}));
But I get syntax error:
SyntaxError: missing } after property list
note: { opened at line 403, column 26
Which seems to reference the property list of request.send
The outputted example json is:
/* Setup the order object. Extend this as needed. */
var order = {
items:[
{
variant_id: 16320547225634,
product_id: 1782978904098,
properties: [["Arrangement Type",""],["Enclosed Card",""],["Occasion \u0026amp; Comments","Condolescens"],["Delivery Date","Mar 29, 2019"]],
available: true
},
{
variant_id: null,
product_id: 1776316743714,
properties: [["Arrangement Type",""],["Enclosed Card",""],["Occasion \u0026amp; Comments",""],["Delivery Date","Mar 24, 2019"]],
available: null
},
{
variant_id: 16319970017314,
product_id: 1782916808738,
properties: [["Arrangement Type","Seasonal"],["Enclosed Card","Love and best wishes"],["Occasion \u0026amp; Comments","Just Because"],["Delivery Date","Mar 25, 2019"]],
available: true
},
{
variant_id: 16311468687394,
product_id: 1780877819938,
properties: [["Arrangement Type","Large vase with orchids"],["Enclosed Card","Steal the warm chair right after you get up when owners are asleep, cry for no apparent reason sleeps on my head."],["Occasion \u0026amp; Comments","Birthday so make extra special!"],["Delivery Date","Apr 10, 2019"]],
available: true
}
]
};
request.send(JSON.stringify({
'quantity':order.items[0].quantity,
'id':order.items[0].variant_id,
'properties': {
'Arrangement Type': '',
'Enclosed Card': '',
'Occasion & Comments': 'Condolescens',
'Delivery Date': 'Mar 29, 2019'
'Arrangement Type': '',
'Enclosed Card': '',
'Occasion & Comments': '',
'Delivery Date': 'Mar 24, 2019'
'Arrangement Type': 'Seasonal',
'Enclosed Card': 'Love and best wishes',
'Occasion & Comments': 'Just Because',
'Delivery Date': 'Mar 25, 2019'
'Arrangement Type': 'Large vase with orchids',
'Enclosed Card': 'Steal the warm chair right after you get up when owners are asleep, cry for no apparent reason sleeps on my head.',
'Occasion & Comments': 'Birthday so make extra special!',
'Delivery Date': 'Apr 10, 2019'
}
}));
My cart.liquid file calls the custom properties like so:
{% if property_size > 0 %}
{% for p in item.properties %}
{% assign first_character_in_key = p.first | truncate: 1, '' %}
{% unless p.last == blank or first_character_in_key == '_' %}
<div class="label-info">{{ p.first }}:
<strong class="input-info">{{ p.last }}</strong>
</div>
{% endunless %}
{% endfor %}
{% endif %}
But I'm not sure if I need to modify this to accommodate any properties already created.
The custom line item properties need to show up when added back to the cart, otherwise the reorder function won't really be a time saver as it will force customers to go back to each product page to re-add info into the custom fields.
I'm not that versed in liquid so not quite sure what needs to be done to modify the snippet or cart templates. Any help you could provide would be greatly appreciated!
Many thanks,
Mark
The reason 'properties':order.items[0].properties does not work is because it contains an array with the name and value of the line item property.
To set this up we will need to turn the array into and object and then pass that into the request.send function.
A function I found that achieves this can be seen in this post. You can copy and paste this function into the orderItems function.
Once that is added we then create the properties variable, passing in order.items[0].properties as an argument to turn this into an object.
This variable is added along with the 'quantity' and 'id' in request.send.
Here is how the orderItems function will look once everything is added:
/* Simple function add to cart */
var orderItems = function(){
if(!order.items.length){ return }
if(!order.items[0].available){
checkQueue();
}
function objectify(array) {
return array.reduce(function(p, c) {
p[c[0]] = c[1];
return p;
}, {});
}
var properties = objectify(order.items[0].properties);
var request = new XMLHttpRequest();
request.open('post', '/cart/add.js', true);
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
request.onload = function() {
var resp = request.responseText;
if (request.status >= 200 && request.status < 400) {
checkQueue();
} else { /* add your error handling in here */ }
};
request.onerror = function() {
/* add your error handling in here */
};
request.send(JSON.stringify({
'quantity':order.items[0].quantity,
'id':order.items[0].variant_id,
properties
}));
};
Hope that helps!
I have data coming from the database as the below format on query execution
[(‘Country-1’, ‘state1’), (‘Country-1’, ‘state2’), (‘Country-1’, ‘state3’),
(‘Country-2’, ‘state1’), (‘Country-2’, ‘state2’), (‘Country-2’, ‘state3’),
(‘Country-3’, ‘state1’), (‘Country-3’, ‘state2’), (‘Country-3’, ‘state3’)]
I want to convert to as this resultset as below format
context = {
'countries': [ { 'Countryname': 'country1’,
'state': [ { 'Statename': 'state1'},
{'Statename': 'state2'},
{'Statename': 'state3'} ]
},
{ 'Countryname': 'country2’,
'state': [ { 'Statename': 'state1'},
{'Statename': 'state2'},
{'Statename': 'state3'} ]
},
{ 'Countryname': 'country3’,
'state': [ { 'Statename': 'state1'},
{'Statename': 'state2'},
{'Statename': 'state3'} ]
}
]
}
So that I can iterate the data in the in HTML in Django to create the tree format:
<ul class = "myUL">
{% for country in data %}
<li class = "caret"> {{ country.countryname }} </li>
<ul class="nested">
{% for state in country.statename %}
<li>{{state.statename}}</li>
{% endfor %}
</ul>
{% endfor %}
Expected output for HTML is:
Country-1
State1
State2
State3
Country -2
State1
State2
State3
Country -3
State1
State2
State3
Try the following:
Data parser:
data = [('Country-1', 'state1'), ('Country-1', 'state2'), ('Country-1', 'state3'), ('Country-2', 'state1'), ('Country-2', 'state2'), ('Country-2', 'state3'), ('Country-3', 'state1'), ('Country-3', 'state2'), ('Country-3', 'state3')]
reformatted_data = {}
for pair in data:
state_list = reformatted_data.get(pair[0], None)
if state_list:
if pair[1] in state_list:
pass
else:
reformatted_data[pair[0]].append(pair[1])
else:
reformatted_data[pair[0]] = [pair[1]]
# Try this print in your console to make sure it's working properly
print(reformatted_data)
Template to display data:
<ul class = "myUL">
{% for country, statelist in data.items %}
<li class = "caret"> {{ country }} </li>
<ul class="nested">
{% for state in statelist %}
<li>{{ state }}</li>
{% endfor %}
</ul>
{% endfor %}
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 %}