Handlebars - rendering server and client side with same template - javascript

I have the following handlebars template that needs to be used in two ways. The first is with rendering initial server side content on page load. The second is to render additional content to the page when the user clicks a CTA to fetch more content.
After following some tutorials online for using handlebars with AJAX I wrapped my template with a <script> tag that had a type of text/x-handlebars-template. Here is the template:
<script id="my-unique-id" type="text/x-handlebars-template">
<div class="element">
\{{name}}
</div>
</script>
The issue here is that I need to escape the curly braces with a \ in order to use my template within a <script> tag. This template works fine with ajax requests. Here is an example request where I would use the script ID to render out content returned from an API:
$.ajax({
dataType: "json",
data: data,
url: url,
success: function(response) {
var Handlebars = window.Handlebars;
var templateId = '#my-unique-id';
for(var i = 0; i < response.items.length; i++) {
var template = Handlebars.compile($(templateId).html());
$('.some-parent-container').append('<div>' + template(response.Articles[i]) + '</div>');
}
}
});
BUT when I try and render the same template on the server it does not render out because it is within a script tag, I also believe that using the \ to escape the curly brackets in the template only works client side and not server side.
Could anyone suggest the correct approach for having a template that could render out both on client and server side without having to duplicate the template file?

Related

Passing a parameter through AJAX URL with Django

Below is my code. 'n' logs correctly in the console, and everything works perfectly if I manually enter the value for 'n' into url: '{% url "delete_photo" iddy=2%}'. Alas, when I try to use 'n' as a variable (seen below) it gives me a reverse match not found error. Can anyone help with this?
javascript
function test(n){
console.log(n);
$.ajax({
type: 'get',
url: '{% url "delete_photo" iddy=n%}',
datatype:'json',
success: function(data){
alert(n)
console.log(data)
console.log(n)
},
error: console.log("SSS"),
});}
html
{% for t in photos %}
<div id="photobox" ><img id="img_pb3"src="{{t.photo.url}}">
<div><a><span onclick="test({{t.id}})" class="closeBtn">×</span></div></a>
</div>
{% endfor %}
urls
urlpatterns = [
path('', views.explore, name='explore'),
path('home/', views.home, name='home'),
path('home/comment', views.comment, name='comment'),
path('home/photo_del/<iddy>', views.delete_photo, name='delete_photo')
]
views
def delete_photo(request, iddy):
data = {'a':iddy, 'b': iddy}
return JsonResponse(data, safe=False)
You can't possibly do this. You have fundamentally misunderstood the relationship between backend Django code and front-end Javascript code. Django templates are fully evaluated on the server, at which point the template tags are converted into plain HTML text. There is therefore no way that a Javascript function - which runs much, much later, on the browser itself - can pass a parameter to be used in a template tag.
The only way to do this would be to render the URL with a dummy value - one you know can't occur in the URL itself - and then replace that value in the JS with the one from the parameter, using normal JS string replace functions.
To be honest, it would be better to remove the ID from the URL altogether. Apart from anything else, a delete action should be a POST, not a GET - you don't want the Googlebot accidentally crawling your URLs and deleting all your items - and with a POST you can send the ID in the posted data.
Maybe something like this:
<a class="closeBtn" href="{% url 'delete_photo' iddy=t.id %}" id="{{t.id}}"></a>
And then you can retrieve the reference using attr:
$.ajax({
url: $('.closeBtn').attr("href"),
data: { "iddy": $('.closeBtn').attr("id")},
.....
})

load more button in nodejs

I have json file in series.pug page. When I click load more button want to make a request JSON file and add new element in the page. How can I make load more with using NodeJS or AJAX in pug page ?
extends layout
block content
.content(style='padding-bottom: 100px;')
#titles
.container
.row
.col-md-12
.form-group.text-center
label.col-md-2.text-right Quick Filter
input.search.col-md-6.text-center(type="text",placeholder='Search series quickly')
.row.list
-var count = 0
each value in data.entries
-if(value.programType =='series')
if(count!=18)
.col-md-3.col-sm-6.col-lg-2.series(data-item-id=++count)
figure.figure
a(href='/details/'+value.title)
img.figure-img.img-fluid.rounded(src=value.images['Poster Art'].url, alt=value.title)
.name.figure-caption.text-center.text-dark=value.title
.col-lg-12
a.btn.btn-primary.text-light.load Load More
script.
$('.load').click(function(){
var lastchild = $('.series').last().data('item-id');
$.ajax({
url: '/request',
method: 'POST',
data:{'lastchild':lastchild},
success: function(response){
}
});
});
You need to create a new route that you can make an API call to that returns only partial HTML. So something like this:
$.ajax('/loadmore?data=jsonFileName&template=pugTemplateName&startIndex=10&load=20');
Then in node, you'd have logic listening for this route, and when it comes in, you have node build out your html using your pug template.
So you're query params in this example would be:
data = .json file to pull data from
template = .pug file to use as template, should not `extend layout`
startingIndex = starting index for getting data from .json file
load = number items to render html for
Finally, you need to use a pug template that does not extend layout, so you only get a <div> back, and not an entire <html> document. In the pug template, pull in the json file, loop through the number of items you want (specified by startingIndex, and load), and then send the result of the pug file back to the browser.
The result of the AJAX call will be partial html, like a <div> or <ul>, but not an entire <html> document. You can then just append it to your webpage where it should be displayed.
Obviously, this is just a rough guide on how to do it, but if you set up the logic like this, you can use this call for any load-more functionality you might need on your site. If you post your source code to github, I might be able to give more specifics.

EJS not rendering ejs code on client side

I tried rendering EJS code that I sent from home.js to home.ejs as a string but it didn't work. I'm trying this now but nothing seems to work. My code is:
home.js
var templates = {};
templates.onScroll = fs.readFileSync('views/partials/onScrollAppendPost.ejs', 'utf-8');
res.render("users/home", {templates: templates});`
home.ejs
var template = new EJS({text: <%- JSON.stringify(templates.onScroll) %>}).update('#mainContainer', {postArr: postArr});
Edit:
What im trying to do is I want to detect when a user gets to the bottom of the page. With this code:
$(window).scroll(function () {
if ($(window).scrollTop() == $(document).height() - $(window).height()) {
//get post data and append post
}});
When the user gets to the bottom of the page i want to append ejs code to the page. Basically i want to communicate between ejs and js.
Based on the example at http://ejs.co, you first need to include a browser build of ejs in your page:
<script src="ejs.js"></script>
Then you can use EJS client-side like this:
var html = ejs.render('your template here', {postArr: postArr});
$('#mainContainer').html(html);
Here's I'm assuming postArr is set appropriately, possibly as the result of an AJAX request.
As you're using EJS to generate EJS it all gets a lot more difficult to understand but the relevant code in your home.ejs would be something like this:
var html = ejs.render(<%- JSON.stringify(templates.onScroll) %>, {postArr: postArr});
$('#mainContainer').html(html);
This assumes that the onScroll template doesn't include any other templates. You haven't specified whether home.ejs is being used to generate HTML or JavaScript so it's unclear precisely what other escaping considerations might apply but it's possible that won't be a problem.

How do I include Django 1.2's CSRF token in a Javascript-generated HTML form?

I recently upgraded to Django 1.2.3 and my upload forms are now broken. Whenever I attempt to upload, I receive a "CSRF verification failed. Request aborted." error message.
After reading Django's documentation on this subject, it states that I need to add the {% csrf_token %} template tag within the HTML <form> in my template. Unfortunately, my <form> is generated via JavaScript (specifically, ExtJs's "html" property on a Panel).
Long story short, how do I add the required CSRF token tag to my <form> when my <form> is not included in a Django template?
Another option would be to adapt the cookie/header based solution shown in the Django docs with Ext - preferable if you have a lot of templates and don't want to change every single one.
Just drop the following snippet in your overrides.js (or wherever you put global modifications):
Ext.Ajax.on('beforerequest', function (conn, options) {
if (!(/^http:.*/.test(options.url) || /^https:.*/.test(options.url))) {
if (typeof(options.headers) == "undefined") {
options.headers = {'X-CSRFToken': Ext.util.Cookies.get('csrftoken')};
} else {
options.headers.extend({'X-CSRFToken': Ext.util.Cookies.get('csrftoken')});
}
}
}, this);
(edit: Ext already has cookie reading function, no need to duplicate it)
The easiest way is to create a hidden form on your page using django that doesn't do anything. Then use JavaScript to fetch the form and specifically the token input out of the form. Lastly, insert or copy that token input into the form you are dynamically generating.
Here are two examples of how you might publish the token for JavaScript.
<input id="csrf_token" value="{{ csrf_token }}"/>
<script type="text/javascript">
var CSRF_TOKEN = document.getElementById('csrf_token').value;
</script>
or
<script type="text/javascript">
var CSRF_TOKEN = "{{ csrf_token }}";
</script>
A better solution is to generate the code of the js file from a view/template.
Then, in the view you can set the csrf token up in the context like so...
from django.core.context_processors import csrf
context = RequestContext(request)
context.update(csrf(request))
Then, in the template, you can use {{ csrf_token }} to get the raw value of the csrf token, and then use that to build a hidden field into your form with the name csrfmiddlewaretoken.
Does the view you are POSTing to also respond to GET? In that the JS code can make a GET request to the view in question and parse the output to extract the CSRF token. My JS-fu is weak and I am not sure how best you can do the parsing from the client side.
For a broadly related example see this question. In this case the user was attempting to POST using a Python script and failing for the same reason. The solution was the same, except he had to do it from a Python script rather than JavaScript.
This may not be ideal, but it was the fastest solution for me. In my main template at the bottom of the "body", I added a javascript function to my library.
<script type="text/javascript">
MyToolkit.Utils.getCSRFToken = function () {
return "{% csrf_token %}";
};
</script>
This will use the csrf token or auto generate a new one if needed. It will handle all form submits on the page. If you have off-site forms you'll need to make sure they don't run this code.
<script>
$(document).on('submit', 'form[method=post]', function(){
if(!document.cookie.match('csrftoken=([a-zA-Z0-9]{32})')) {
for(var c = ''; c.length < 32;) c += 'abcdefghijklmnopqrstuvwxyz'.charAt(Math.random() * 26)
document.cookie = 'csrftoken=' + c + '; path=/'
}
if(!this.csrfmiddlewaretoken) $(this).append('<input type="hidden" name="csrfmiddlewaretoken">')
$(this.csrfmiddlewaretoken).val(document.cookie.match('csrftoken=([a-zA-Z0-9]{32})')[1])
})
</script>
requires jQuery 1.7+

Calling Django `reverse` in client-side Javascript

I'm using Django on Appengine. I'm using the django reverse() function everywhere, keeping everything as DRY as possible.
However, I'm having trouble applying this to my client-side javascript. There is a JS class that loads some data depending on a passed-in ID. Is there a standard way to not-hardcode the URL that this data should come from?
var rq = new Request.HTML({
'update':this.element,
}).get('/template/'+template_id+'/preview'); //The part that bothers me.
There is another method, which doesn't require exposing the entire url structure or ajax requests for resolving each url. While it's not really beautiful, it beats the others with simplicity:
var url = '{% url blog_view_post 999 %}'.replace (999, post_id);
(blog_view_post urls must not contain the magic 999 number themselves of course.)
Having just struggled with this, I came up with a slightly different solution.
In my case, I wanted an external JS script to invoke an AJAX call on a button click (after doing some other processing).
In the HTML, I used an HTML-5 custom attribute thus
<button ... id="test-button" data-ajax-target="{% url 'named-url' %}">
Then, in the javascript, simply did
$.post($("#test-button").attr("data-ajax-target"), ... );
Which meant Django's template system did all the reverse() logic for me.
The most reasonable solution seems to be passing a list of URLs in a JavaScript file, and having a JavaScript equivalent of reverse() available on the client. The only objection might be that the entire URL structure is exposed.
Here is such a function (from this question).
Good thing is to assume that all parameters from JavaScript to Django will be passed as request.GET or request.POST. You can do that in most cases, because you don't need nice formatted urls for JavaScript queries.
Then only problem is to pass url from Django to JavaScript. I have published library for that. Example code:
urls.py
def javascript_settings():
return {
'template_preview_url': reverse('template-preview'),
}
javascript
$.ajax({
type: 'POST',
url: configuration['my_rendering_app']['template_preview_url'],
data: { template: 'foo.html' },
});
Similar to Anatoly's answer, but a little more flexible. Put at the top of the page:
<script type="text/javascript">
window.myviewURL = '{% url myview foobar %}';
</script>
Then you can do something like
url = window.myviewURL.replace('foobar','my_id');
or whatever. If your url contains multiple variables just run the replace method multiple times.
I like Anatoly's idea, but I think using a specific integer is dangerous. I typically want to specify an say an object id, which are always required to be positive, so I just use negative integers as placeholders. This means adding -? to the the url definition, like so:
url(r'^events/(?P<event_id>-?\d+)/$', events.views.event_details),
Then I can get the reverse url in a template by writing
{% url 'events.views.event_details' event_id=-1 %}
And use replace in javascript to replace the placeholder -1, so that in the template I would write something like
<script type="text/javascript">
var actual_event_id = 123;
var url = "{% url 'events.views.event_details' event_id=-1 %}".replace('-1', actual_event_id);
</script>
This easily extends to multiple arguments too, and the mapping for a particular argument is visible directly in the template.
I've found a simple trick for this. If your url is a pattern like:
"xyz/(?P<stuff>.*)$"
and you want to reverse in the JS without actually providing stuff (deferring to the JS run time to provide this) - you can do the following:
Alter the view to give the parameter a default value - of none, and handle that by responding with an error if its not set:
views.py
def xzy(stuff=None):
if not stuff:
raise Http404
... < rest of the view code> ...
Alter the URL match to make the parameter optional: "xyz/(?P<stuff>.*)?$"
And in the template js code:
.ajax({
url: "{{ url views.xyz }}" + js_stuff,
... ...
})
The generated template should then have the URL without the parameter in the JS, and in the JS you can simply concatenate on the parameter(s).
Use this package: https://github.com/ierror/django-js-reverse
You'll have an object in your JS with all the urls defined in django. It's the best approach I found so far.
The only thing you need to do is add the generated js in the head of your base template and run a management command to update the generated js everytime you add a url
One of the solutions I came with is to generate urls on backend and pass them to browser somehow.
It may not be suitable in every case, but I have a table (populated with AJAX) and clicking on a row should take the user to the single entry from this table.
(I am using django-restframework and Datatables).
Each entry from AJAX has the url attached:
class MyObjectSerializer(serializers.ModelSerializer):
url = SerializerMethodField()
# other elements
def get_url(self, obj):
return reverse("get_my_object", args=(obj.id,))
on loading ajax each url is attached as data attribute to row:
var table = $('#my-table').DataTable({
createdRow: function ( row, data, index ) {
$(row).data("url", data["url"])
}
});
and on click we use this data attribute for url:
table.on( 'click', 'tbody tr', function () {
window.location.href = $(this).data("url");
} );
I always use strings as opposed to integers in configuring urls, i.e.
instead of something like
... r'^something/(?P<first_integer_parameter>\d+)/something_else/(?P<second_integer_parameter>\d+)/' ...
e.g: something/911/something_else/8/
I would replace 'd' for integers with 'w' for strings like so ...
... r'^something/(?P<first_integer_parameter>\w+)/something_else/(?P<second_integer_parameter>\w+)/' ...
Then, in javascript I can put strings as placeholders and the django template engine will not complain either:
...
var url = `{% url 'myapiname:urlname' 'xxz' 'xxy' %}?first_kwarg=${first_kwarg_value}&second_kwarg=${second_kwarg_value}`.replace('xxz',first_integer_paramater_value).replace('xxy', second_integer_parameter_value);
var x = new L.GeoJSON.AJAX(url, {
style: function(feature){
...
and the url will remain the same, i.e something/911/something_else/8/.
This way you avoid the integer parameters replacement issue as string placeholders (a,b,c,d,...z) are not expected in as parameters

Categories

Resources