How to include liquid inside javascript? - javascript

I've been trying all day long to include this liquid code inside javascript with no luck so far..
I'm simply trying to update a div with the cart data to show the image and name, this is what I've got.
$('.openCart').click(function(e)
{
$.ajax({
type: 'POST',
url: '/cart/add.js',
data: data,
dataType: 'json',
success: function()
{
{% capture content %}{% include 'addItemToCartDetails' %}{% endcapture %}
var content = {{ content | json }};
$("._second").html(content);
}
});
});
Overall the following code doesn't work(simply because of the for loop, and I have no clue how to get around this..): If I remove the for loop then the code retrieves the divs and everything, besides the item data since the loop isn't working.
This is the addItemToCartDetails.liquid,
{% for item in cart.items %}
<div class="_second_1">
<a href="{{ item.url | widivin: collections.all }}" class="cart-image">
<img width="320" src="{{ item | img_url: '700x700' }}" alt="{{ item.title | escape }}">
</a>
</div>
<div class="_second_2"><h3>Product Name</h3>{{ item.product.title }}</div>
<div class="_second_3"><h3>Remove Product</h3><span class="clearCart">Remove</span></div>
<div class="_second_4"><h3>Quantity</h3><input class="quantity" type="input" name="updates[]" id="updates_{{ item.id }}" value="{{ item.quantity }}" min="0" data-id="{{ item.id }}" title="If you'd like another subscription please checkout separately" alt="If you'd like another subscription please checkout separately" disabled></div>
<div class="_second_5">
<h3>Total Price</h3>
<div class="totalDetails">Total: {{ item.line_price | plus: 0 | money }} / month</div>
</div>
<div class="_third">
<input type="submit" name="checkout" value="PROCEED TO CHECKOUT">
</div>
{% endfor %}

You are trying to use Liquid when you should be using Javascript
All of the Liquid processing happens on the backend to construct an HTML file that gets passed to the browser. Once the HTML page has been passed to the user's browser, Javascript can be used to manipulate the document and make this appear/disappear/change.
Practically, this means that your {% for item in cart.items %} in addItemToCartDetails.liquid will be rendered once, before page load, and never again afterwards. No matter how many items are added to the cart, the results of that snippet will only ever be current as of page load.
You should be using the Javascript variables instead
Shopify passes the line item object representing the most recently-added product to your success callback function. Your code should look something like this:
$('.openCart').click(function(e)
{
$.ajax({
type: 'POST',
url: '/cart/add.js',
data: data,
dataType: 'json',
success: function(line_item)
{
var content = '<h3>You added a ' + line_item.title + '!</h3>' +
'<p> We appreciate that you want to give us ' + Shopify.formatMoney(line_item.line_price, {{ shop.money_format }}) + '!</p>' +
'<p>That is very nice of you! Cheers!</p>';
// Note: Not every theme has a Shopify.formatMoney function. If you are inside an asset file, you won't have access to {{ shop.money_format }} - you'll have to save that to a global javascript variable in your theme.liquid and then reference that javascript variable in its place instead.
$("._second").html(content);
}
});
});
If you're curious about what all you can access in the response object, add a console.log(line_item) to the beginning of your success function to print the object to your browser's console. (In most browsers you can right-click any element on the page and select 'Inspect Element' to bring up your developer tools. There should be a tab called 'Console' where the logs get printed to, and once your information is there you should be able to click on the object to expand its contents.)

In your first snippet, pass cart.items as the variable items to the template:
{% include 'addItemToCartDetails', items: cart.items %}
In the file addItemToCartDetails.liquid template, modify the for loop statement accordingly:
{% for item in items %}

In your case, you can put this {% capture content %}{% include 'addItemToCartDetails' %}{% endcapture %} in your liquid code somewhere in your HTML probably hidden and append it to the element where you want it to appear like so.
success: function()
{
var content = $("#addItemToCartDetails-wrapper").html();
$("._second").html(content);
}
Something like that. Hope that helps!

Related

How to trigger an action for html element that has been clicked?

I want to use a click event handler but I have a lot of html tags with the same "upvote" or "downvote" id. When one of them has been clicked it should trigger a function that will send some data to my view and add a user to the database. How can I separate this events.
Here is my html code:
{% extends "base.html"%}
{% load static %}
{% load static_thumbnails %}
{% load thumbnail %}
{% block title %}
News
{% endblock %}
{% block content %}
{% for article in news %}
<h1>{{ article.title }}</h1>
<img src="{{ article.url }}">
<p>
Read from the resource
{% with article.upvotes.count as total_upvotes and article.downvotes.count as total_downvotes%}
<span id = 'upvotes'>{{ total_upvotes }}</span>
<img src = '{% static "images/news/upvote.png"%}'>
<span id = 'downvotes'>{{ total_downvotes }}</span>
<img src = '{% static "images/news/downvote.png" %}'>
{% endwith %}
</p>
{% endfor %}
{% endblock %}
{% block domready %}
$('#upvote').click(function(e){
e.preventDefault();
var id = $(this).data('id');
var action = 'upvote';
$.ajax({
type:"POST",
url:"{% url 'news:upvote'%}",
dataType: 'json',
data: {
id:id,
action:action
},
success:function(data){
$('#upvotes').text(data.upvotes)
$('#downvotes').text(data.downvotes)
}
})
})
$('#downvote').click(function(e){
e.preventDefault();
var id = $(this).data('id');
var action = 'downvote';
$.ajax({
type:"POST",
url:"{% url 'news:upvote'%}",
dataType: 'json',
data: {
id:id,
action:action
},
success:function(data){
$('#upvotes').text(data.upvotes)
$('#downvotes').text(data.downvotes)
}
})
})
{% endblock %}
Now I can use only first button, I don`t know why.
Other tags just send me to the top of the page.
You can try using the class attribute instead of the id attribute. Then you can get all elements having that class and loop through them in your event listener, and check which one was clicked by comparing the current element of the loop to the event's target:
Here's a detailed example:
// Identifies some DOM elements
const
list = document.getElementById("list"),
classyItems = document.getElementsByClassName("my-class");
// Calls `hightlightItem` when user clicks inside the list element
list.addEventListener("click", highlightItem);
// Defines the listener function
function highlightItem(event){ // Listener can access the triggering event
const clickedThing = event.target; // Event's `target` property is useful
// Makes sure the click was on an appropriate element before proceeding
if(clickedThing.classList.contains("my-class")){
// Loops through the collection
for(let item of classyItems){
// Updates the classList for the current item in the loop
item.classList.remove("highlight");
// Maybe updates it again before continuing to next item in loop
if(item == clickedThing){ item.classList.add("highlight"); }
}
}
}
.highlight{ background-color: yellow; }
<ul id="list">
<li class="my-class">Elf</li>
<li class="my-class">Dwarf</li>
<li class="my-class">Human</li>
</ul>
**Nerdy details: getElementsByClassName and similar methods get a "live" collection, meaning if more items with that class get added to the page, the collection will update itself. In contrast, the (still very cool) querySelectorAll method returns a static collection.
If you modify the HTML you generate in your template ( presume that is templated html ) change the id attributes of the span elements to classNames and modify the hyperlinks so that id = 'upvote' becomes data-action='upvote' etc then, using fetch as an example to send the ajax request rather than jQuery, you could try like this:
{% for article in news %}
<h1>{{ article.title }}</h1>
<img src="{{ article.url }}">
<p>
Read from the resource
{% with article.upvotes.count as total_upvotes and article.downvotes.count as total_downvotes%}
<span class='upvotes'>{{ total_upvotes }}</span>
<a href='#' data-action='upvote' data-id="{{ article.id }}">
<img src = '{% static "images/news/upvote.png"%}'>
</a>
<span class='downvotes'>{{ total_downvotes }}</span>
<a href="#" data-action="downvote" data-id="{{ article.id }}">
<img src = '{% static "images/news/downvote.png" %}'>
</a>
{% endwith %}
</p>
{% endfor %}
And the Javascript:
document.querySelectorAll('a[data-task]').forEach(a=>{
a.addEventListener('click', function(e){
let spup=this.parentNode.querySelector('span.upvotes');
let spdown=this.parentNode.querySelector('span.downvotes');
let fd=new FormData();
fd.append('id',this.dataset.id);
fd.append('action',this.dataset.action);
fetch( "{% url 'news:upvote'%}", { method:'post', body:fd })
.then( r=>r.json() )
.then( json => {
spup.textContent=json.upvotes;
spdown.textContent=json.downvotes;
})
})
})

Show and hide text of different posts

I have several posts each of them composed of three parts : a title, a username/date and a body. What I want to do is to show the body when I click on either the title or the username/date and hide it if I click on it again. What I've done so far works but not as expected because when I have two or more posts, it only shows the body of the last post even if I click on another post than the last one. So my goal is only to show the hidden text body corresponding to the post I'm clicking on. Here is my code:
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Test page{% endblock %}</h1>
<a class="action" href="{{ url_for('main_page.create') }}">New</a>
{% endblock %}
{% block content %}
{% for post in posts %}
<article class="post">
<header>
<script language="JavaScript">
function showhide(newpost)
{var div = document.getElementById(newpost);
if (div.style.display !== "block")
{div.style.display = "block";}
else {div.style.display = "none";}}
</script>
<div onclick="showhide('newpost')">
<h1>{{ post['title'] }}</h1>
<div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%d-%m-%Y') }}</div>
</div>
</header>
<div id="newpost">
<p class="body">{{ post['body'] }}</p>
</div>
</article>
{% if not loop.last %}
<hr>
{% endif %}
{% endfor %}
{% endblock %}
Of course I looked for a solution as much as I could but I'm kind of stuck plus I'm a complete beginner in HTML/JS/CSS. And one last thing, I'm currently using Python's framework Flask. Thank you by advance.
You need to give each of your posts a unique id for your approach to work.
Change your code to
<div id="{{post_id}}">
<p class="body">{{ post['body'] }}</p
</div>
where post_id is that post's unique id e.g. its id in the database you are using that you pass to the template in your view. Then, change the call to the onclick event handler to
<div onclick="showhide('{{post_id}}')">
If you don't have a unique id you can also use the for loop's index: replace all post_id instances above with loop.index. See Jinja's for loop docs for more information.

Twig JS (gulp-twig) - Traversing large data structures with DRY principles in mind

TL;DR
Is there a way of programatically reassigning where a Twig partial reads its data from? I have a blob of JSON that provides the data structure for elements in a pattern library, and need to ensure that Twig serves the correct data to the corresponding .twig partial files.
Background
I've got the following JSON data:
button.json (simplified)
{
"element": {
"button": {
"attr": {
"class": "btn"
},
"subelement": {
"span": {
"attr": {
"class": "btn__text"
},
"content": {
"text": "Button Text"
}
}
}
}
}
}
And the following Twig templates:
button.twig (simplified)
{% set base = 'button' %}
{% set el = element[base] %}
<button type="button"
{% include '_partial/_attr.class.twig' %}
>
{% set el = element[base].subelement.span %}
{% include 'span.twig' with { el: el } %}
</span> {# <span> closes here as it could have subsequent elements inside it #}
</button>
span.twig (simplified)
<span
{% include '_partial/_attr.class.twig' %}
>
{% if el.content.text %}
{{ el.content.text }}
{% endif %}
And a Twig partial:
_attr.class.twig
{% if el.attr.class %}
class="{{ el.attr.class }}"
{% endif %}
The above renders the following HTML:
<button type="button" class="btn">
<span class="btn__text">Button Text</span>
</button>
These files all contribute towards a pattern library where I intend to go fully DRY (hence the partial file (one of many)). I would like to make everything as reusable as possible, so that a <button> or a <span> could be declared once but used under many different contexts.
Problem
In button.twig I hard-code {% set el = element[base].subelement.span %} to change the value of el (so that <span> gets the correct data). This syntax only works as long as there is only level of subelement.
Consider this structure:
<form>
<div class="form-element>
<label>Label Example</label>
<input type="text">
</span>
</form>
I'm stumped by how to access the data for <label> and <input> as all permutations I've tried (e.g. element[base].subelement.div.subelement.label and element[base.subelement.div].subelement.label) don't work.
What I'm trying to achieve is a reliable way of traversing the data object so that I can have markup structures x elements deep.
My thoughts so far have been:
Creating the object key reference with a JS function (have had issues getting the returned string back into a Twig object reference)
Creating a sort of 'tracker' which keeps track of how deep in the data structure I am (again, I'm thinking a JS function that can tell Twig how deep x element is in the data structure)
Reformatting the data structure so that the hierarchy is expressed another way)
Am I even correct in reassigning el so that it always represents the current element?
I've also tried using attribute(base, xxx) to no avail
Turns out I was drastically over-thinking it as Twig has excellent 'content switching' already built in, making use of its with statement.
Here's how everything now works...
Firstly, I turned all subelement objects into arrays, so my button.json file would look like this:
button.twig (simplified)
{
"element": {
"button": {
"attr": {
"class": "btn"
},
"subelement": [
{
"span": {
"attr": {
"class": "btn__text"
},
"content": {
"text": "Button Text"
}
}
]
}
}
}
Then I could do what I called 'context switching' in my twig templates by using something like:
button.twig (simplified)
{% set root = root.subcomponent[0] %}
{# _button_text_ #}
{# <span> #}
{% include '../_partial/_text.span.twig' with { root: root._button_text_ } %}
</span>
{# /_button_text_ #}
The key is to use with { foo: bar } with the include as that 'shifts' where in the JSON the component reads its data from. Also, I know that span is always going to be subcomponent[0] so this is totally safe to use. If required the statement could always be wrapped in a conditional such as:
{% if root.subcomponent[0] == '_button_text_' %}
{% set root = root.subcomponent[0] %}
{% endif %}
Or for multiple (repeating) subelements:
{% for subcomponent in root.subcomponent %}
{# _description_list_item_ #}
{# <div> #}
{% include '../../../atom/structure/_partial/_structure.division.twig' with { root: subcomponent._description_list_item_ } %}
{% set root = subcomponent._description_list_item_ %}
{# _description_term_ #}
{% include '../../../atom/text/_partial/_text.description-term.twig' with { root: root.subcomponent[0]._description_term_ } %}
{# /_description_term_ #}
{# _description_details_ #}
{% include '../../../atom/text/_partial/_text.description-details.twig' with { root: root.subcomponent[1]._description_details_ } %}
{# /_description_details_ #}
</div>
{# /_description_list_item_ #}
{% endfor %}

Using javascript variables in Shopify liquid

I am trying to use a javascript variable inside the image tag but it is not working.
I want to create a filter for my collection in a developing project, where I can filter products by the textures of products. I have coded the following:
<div class="collection-filter-navbar-nav">
{% assign tags = 'white, yellow, golden' | split: ',' %}
<ul class="fabric-filter">
<li><a href="javascript:void(0);" >All</a></li>
{% for t in tags %}
{% assign tag = t | strip %}
{% if current_tags contains tag %}
<li><a href="javascript:void(0);" data-value="{{ tag | handle }}" >{{ tag }}</a></li>
{% elsif collection.all_tags contains tag %}
<li>{{ tag }}</li>
{% endif %}
{% endfor %}
</ul>
</div>
The html is showing in the front end, but what I need, I want to add a texture image in each tag in reference to the tag name.
So I scripted :
jQuery(document).ready(function(){
var filter_tabs = jQuery('.fabric-filter > li > a.filter-lists');
jQuery.each( filter_tabs, function(index, element){
var data_value = jQuery(this).data('value');
{% assign value = data_value %}
var img_append = '<img src="{{ 'f1-'+value+'.png' | asset_url }}">'
jQuery(img_append).appendTo(jQuery(this));
console.log(data_value);
});
});
But it is showing error. I know this can be done by css, but I am using javascript just for dynamism.
You won't be able to do this as the liquid code all runs before the jquery code.
By the time you are running the jquery liquid has already outputted the line {% assign value = data_value %}

Fade out pop-up window on click of a button?

I've followed this tutorial to implement a pop-up email newsletter sign-up to add to my Shopify theme which uses fancybox.js & cookie.js. Everything works fine except when you enter an email address & click 'Sign Up' even though an additional tab opens to complete the Mailchimp sign up process, on the original shop tab, the email pop-up stays open as though nothing has changed.
I wondered if there is a way I could adapt the code so that on click of 'Sign Up' the new tab opens as normal but the email pop-up fades out so when the user goes back to the shop the pop-up has disappeared. I'm not great with JS so any help would be really appreciated!
My current code:
theme.liquid -
Liquid HTML:
{% if settings.popup_newsletter_enable %}
<div id="email-popup" style="display: none;" >
{% include 'popup-newsletter-form' %}
</div>
{% endif %}
JS:
{{ 'jquery.fancybox.js' | asset_url | script_tag }}
<script type="text/javascript">
(function($) {
// set a variable for the cookie check
var check_cookie = $.cookie('popup_box');
// check whether the cookie is already set for this visitor
if( check_cookie != 'active' ){
// if the cookie does not exist do the following:
// (1) set the cookie so it's there for future visits (including an expiration time in days)
$.cookie('popup_box', 'active', {
expires: 3,
path: '/'
});
// (2) trigger the fancybox pop up, specifying that it's inline
$( '#trigger' ).fancybox({
type: 'inline',
});
setTimeout(function () {
$('#trigger').eq(0).trigger('click'); // make the click event happen on load
}, 5000);
}
})(jQuery); // this is a noconflict wrapper for WP
</script>
popup-newsletter-form.liquid (snippet include):
Liquid HTML:
<!-- Mailchimp Form Integration -->
{% if settings.newsletter_form_action != blank %}
{% assign form_action = settings.newsletter_form_action %}
{% else %}
{% assign form_action = '#' %}
{% endif %}
<form action="{{ form_action }}" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" target="_blank" class="input-group">
<input type="email" value="{% if customer %}{{ customer.email }}{% endif %}" placeholder="{{ 'general.newsletter_form.newsletter_email' | t }}" name="EMAIL" id="mail" class="input-group-field" aria-label="{{ 'general.newsletter_form.newsletter_email' | t }}" autocorrect="off" autocapitalize="off">
<span class="input-group-btn">
<input type="submit" class="btn" name="subscribe" id="subscribe" value="{{ 'general.newsletter_form.submit' | t }}">
</span>
</form>
$('#pro_image').click(function(){
$("#pro_image").fancybox({
closeSpeed : 250,
closeEasing : 'swing',
closeOpacity : true,
closeMethod : 'zoomOut',
});
})
;
Try this one.
Usually they put js inside header, also possible to put it any where inside tag, but they say better to place in at the bottom of code ( inside ) to let all code receive by browser and the js execute.

Categories

Resources