PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0
Showing posts with label shopify. Show all posts
Showing posts with label shopify. Show all posts

Tuesday, July 26, 2022

[FIXED] How can i add dynamic sections withing dynamic sections in shopify custom theme developement?

 July 26, 2022     dynamic, liquid, shopify, shopify-app     No comments   

Issue

I am developing an sections which contains dynamic sections , one section contain more dynamic section in it as there is no documentation about it at shopify platform


Solution

Shopify provide us two type of sections

  • Static
  • Dynamic
  1. If you are using dynamic section (which is only available at homepage) then you can only create one dynamic section at one time you cannot make nested sections.
  2. But if you are trying to use the dynamic section ability just like homepage on anyother page then you can also do this.
  • You only need to make a static section and use the blocks in your schema code. Is that why you can add multiple sections in any page and also reorder them just like the homepage.


Answered By - Jahanzaib Muneer
Answer Checked By - David Marino (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to access a token in session (Shopify Access Token)

 July 26, 2022     koa, koa-session, node.js, shopify, shopify-app     No comments   

Issue

I mostly work on front-end so I'm not super familiar with NodeJS. I'm working on a Shopify Custom App and purpose of this app is when order placed it will receive webhook request and with that request it will send some data to other API (billing application)

I built shopify app with shopify app cli and my server.js file is like this;

import "@babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth";
import graphQLProxy, { ApiVersion } from "@shopify/koa-shopify-graphql-proxy";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
import session from "koa-session";
const { receiveWebhook } = require("@shopify/koa-shopify-webhooks");
import * as handlers from "./handlers/index";
dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8081;
const dev = process.env.NODE_ENV !== "production";
const app = next({
    dev,
});
const handle = app.getRequestHandler();
const { SHOPIFY_API_SECRET, SHOPIFY_API_KEY, SCOPES } = process.env;
app.prepare().then(() => {
    const server = new Koa();
    const router = new Router();

    server.use(
        session(
            {
                sameSite: "none",
                secure: true,
            },
            server
        )
    );
    server.keys = [SHOPIFY_API_SECRET];
    server.use(
        createShopifyAuth({
            apiKey: SHOPIFY_API_KEY,
            secret: SHOPIFY_API_SECRET,
            scopes: [SCOPES],

            async afterAuth(ctx) {
                //Auth token and shop available in session
                //Redirect to shop upon auth
                const { shop, accessToken } = ctx.session;
                // This accessToken is what I need on other scope

                ctx.cookies.set("shopOrigin", shop, {
                    httpOnly: false,
                    secure: true,
                    sameSite: "none",
                });

                // Register Webhook
                handlers.registerWebhooks(
                    shop,
                    accessToken,
                    "ORDERS_PAID",
                    "/webhooks/orders/paid",
                    ApiVersion.October20
                );

                console.log(accessToken);

                ctx.redirect("/");
            },
        })
    );

    const webhook = receiveWebhook({ secret: SHOPIFY_API_SECRET });
    router.post("/webhooks/orders/paid", webhook, (ctx) => {
        let user_id = ctx.state.webhook.payload.customer.id;

        console.log("received webhook, user_id: ", user_id);

        //console.log("ctx", ctx);

        // I need that accessToken here to get some more info from Admin API with GraphQL
        let accessToken = "???"
        
        handlers
            .graphqlRequest(
                accessToken,
                "https://my-store.myshopify.com/admin/api/2020-10/graphql.json",
                `{
                    customer(id: "gid://shopify/Customer/${user_id}") {
                        email
                        metafields(first: 5) {
                            edges {
                                node {
                                    key
                                    value
                                }
                            }
                        }
                    }
                }`
            )
            .then((res) => {
                console.log("res => ", res);
            })
            .catch((err) => {
                console.log("err => ", err);
            });
    });

    server.use(
        graphQLProxy({
            version: ApiVersion.October20,
        })
    );
    router.get("(.*)", verifyRequest(), async (ctx) => {
        await handle(ctx.req, ctx.res);
        ctx.respond = false;
        ctx.res.statusCode = 200;
    });
    server.use(router.allowedMethods());
    server.use(router.routes());
    server.listen(port, () => {
        console.log(`> Ready on http://localhost:${port}`);
    });
});

createShopifyAuth() method gets an accessToken with my app secret and app api key and I can use it in afterAuth() method but I also need that token in router.post() method to get more info from Shopify Admin API.

According to the Shopify docs(or what I understand), that key is available in session but how can I access that session data? or what can I use that token in router.push()?


Solution

The session is created only for the user who logged in the app from the admin panel. This is true for the online method for creating an access token.

If you are requesting the access token from a webhook request (a.k.a a request that doesn't require you to relogin in the app) you will not be able to access the session and you will not be able to get the access token. In addition the session expires at some point.

In order to use the Access Token in a webhook request you need to create an offline access token which is valid indefinitely. createShopifyAuth has an option for creating an offline access token, you just need to add accessMode: 'offline' to your request (more on this here)

Example:

createShopifyAuth({
      apiKey: SHOPIFY_API_KEY,
      secret: SHOPIFY_API_SECRET_KEY,
      accessMode: 'offline',
      scopes: ['read_products', 'read_orders'],

After you create the Offline Access Token you need to save that in a database (or some other way) and request it from the webhook route in order make your graphql request.

That's pretty much the just of it.



Answered By - drip
Answer Checked By - David Goodson (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to auto close newsletter popup in shopify after 10-15 seconds?

 July 26, 2022     javascript, jquery, liquid, shopify, shopify-app     No comments   

Issue

I developed a newsletter popup modal in liquid for a Shopify store. I want to make the popup close automatically after 15 seconds whether the user clicks on the close button or not.

Following is the link to my code file: https://textdoc.co/index.php/x6slY3CVRXZFUtrD
Review it live here: https://www.desire4leather.com/


Solution

Trigger a click of .jsclosepoup after a 15 seconds timeout.

Add this javascript snippet at the end of your code file:

{% javascript %}
  function closePopup() {
    $('.jsclosepoup').click()
  }

  var closePopupDelay = 15 * 1000
  setTimeout(closePopup, closePopupDelay)
{% endjavascript %}


Answered By - Jimmy
Answer Checked By - Senaida (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Saturday, July 2, 2022

[FIXED] How to pass query string from Shopify to an embedded app?

 July 02, 2022     next.js, reactjs, shopify     No comments   

Issue

I'm building a Shopify app with Next.js and I need to grab the query string so I can check in getServerSideProps the identity of the merchant (bear in mind that Cookies are not recommended for Shopify apps)

When visiting some apps I noticed some of them are getting the query string passed down from Shopify in each request.

This image shows how it should look on each request

Shopify app

This image shows how my app behaves

enter image description here

In this image you can see that when you hover the routes no query strings are present, meaning that are passed somehow by the parent app.

enter image description here

As of right now I'm using a Cookie to pass the shopOrigin but I feel like it's not necessary if somehow I'm able to get the query string in each request, also with the HMAC I will be able to verify that the requests are coming from Shopify.


Solution

The solution was pretty straightforward.

Shopify provides a TitleBar AppBridge component that you can use to to handle the App's navigation. What it does is that on each route change it reloads the iframe and the hmac, shop, code and timestamp are coming in the request. It's a tad slower then client side routing but it works as expected.

In order to use it you just need to go to: Partner's dashboard / Your App / Extensions / Embedded App (click Manage) / Navigation (click Configure) and add navigation links, then you just need to import TitleBar from app-bridge-react and put it in index.js

enter image description here



Answered By - Ivor
Answer Checked By - Terry (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to add custom select in product page?

 July 02, 2022     shopify     No comments   

Issue

how to add custom select in product page and i want two option value one is default value and second is when i select second number option from select than product price decrese Rs -50 is fixed in every product and show one input box below our select button when select second option from select than add product in cart and show that input value inserted by user. Can it possible in product page?


Solution

Very Good Question....

Please see this link for more info :- https://community.shopify.com/c/Shopify-Design/Product-pages-Get-customization-information-for-products/td-p/616503

I have alredy Implemented in my site and it's works great at prouct page.



Answered By - Magento_Bhurio
Answer Checked By - Candace Johnson (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How do check whether a shopify product option is unavailable

 July 02, 2022     liquid, shopify     No comments   

Issue

I have a dropdown menu to select specific sizes and I want to show sizes with zero quantity as disabled.

{% for value in option.values %}
<option
  value="{{ value | escape }}"
  {% if option.selected_value == value %}selected="selected"{% endif %}
  {{ value }}
</option>
{% endfor %}

The questions I have seen on SO all check if variant.available or something equivalent but I want to know if there is a way to check if a particular option is available.


Solution

I think variant.available is a better option to check it is available or not something like this one

<select name="id" id="ProductSelect--{{ section.id }}" class="product-single__variants no-js">
   {% for variant in product.variants %}
   {% if variant.available %}
      <option {% if variant == product.selected_or_first_available_variant %}
         selected="selected" {% endif %}
         data-sku="{{ variant.sku }}"
         value="{{ variant.id }}">
         {{ variant.title }} - {{ variant.price | money_with_currency }}
      </option>
   {% else %}
      <option disabled="disabled">
         {{ variant.title }} - {{ 'products.product.sold_out' | t }}
      </option>
   {% endif %}
   {% endfor %}
</select>


Answered By - Onkar
Answer Checked By - Marie Seifert (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to install product carousel on homepage on Shopify Brooklyn Theme?

 July 02, 2022     liquid, shopify, shopify-template, slick.js     No comments   

Issue

I am trying to change the featured product to a carousel on the homepage on Shopify - Brooklyn theme. I followed some instructions here which was supposed to work on the Brooklyn theme: https://www.youtube.com/watch?v=r7I3T4wB2cQ

However the carousel came up vertical when I installed it. Here is my store: http://sacredcoffeeco.com -- the carousel is at the bottom of the page.

Does anyone know how to make the slider horizontal?

Thank you!


Solution

It seems you don't init slider JS to newly created HTML, you need to add the code to your theme.js.liquid and also need some changes to CSS code to design purpose.

$('ul.slick').slick({
  dots: true,
  infinite: false,
  speed: 300,
  slidesToShow: 3,
  slidesToScroll: 3,
  responsive: [
    {
      breakpoint: 1024,
      settings: {
        slidesToShow: 3,
        slidesToScroll: 3,
        infinite: true,
        dots: true
      }
    },
    {
      breakpoint: 600,
      settings: {
        slidesToShow: 2,
        slidesToScroll: 2
      }
    },
    {
      breakpoint: 480,
      settings: {
        slidesToShow: 1,
        slidesToScroll: 1
      }
    }    
  ]
});


Answered By - Onkar
Answer Checked By - Marilyn (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to Create Shopify Order Via Api using Php?

 July 02, 2022     api, php, shopify     No comments   

Issue

I am trying to Create a Shopify Order using Api this is my code :

            $arrOrder= array(
              "email"=> "foo@example.com",
              "fulfillment_status"=> "fulfilled",
              "send_receipt"=> true,
              "send_fulfillment_receipt"=> true,
              "line_items"=> array(
                array(
                  "product_id"=>875744960642,
                  "variant_id"=> 3558448932592,
                  "quantity"=> 1
                )
            ),
                "customer"=> array(
                  "id"=> 458297751235
                ),
                "financial_status"=> "pending"
              
            
            );            echo json_encode($arrOrder);
            echo "<br />";
            $url = "https://AkiKey:Password@Store.myshopify.com/admin/api/2021-01/orders.json";
            
            $curl = curl_init();
            curl_setopt($curl, CURLOPT_URL, $url);
            curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($curl, CURLOPT_VERBOSE, 0);
            curl_setopt($curl, CURLOPT_HEADER, 1);
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
            curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($arrOrder));
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
            $response = curl_exec($curl);
            curl_close($curl);
            echo "<pre>";
            print_r($response);
and the response is : {"errors":{"order":"Required parameter missing or invalid"}}


Solution

I think there is some data mismatch that is sent using API Call, according to the documentation this the format to create an order. enter image description here

So I think your request is something like this one demo code

$arrOrder= [
   "order" =>[
      "email"                    => "foo@example.com",
      "fulfillment_status"       => "fulfilled",
      "send_receipt"             => true,
      "send_fulfillment_receipt" => true,
      "line_items" => [
         [
            "product_id" => 875744960642,
            "variant_id" => 3558448932592,
            "quantity"   => 1
         ]
      ],
      "customer" => [
                     "id"=> 458297751235
                ],
      "financial_status"=> "pending"
   ]
];


Answered By - Onkar
Answer Checked By - Senaida (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] how to access the blog content through shopify API

 July 02, 2022     api, graphql, shopify, shopify-api, shopify-api-node     No comments   

Issue

I recently created a Shopify Admin account and created a private App to access the Shopify store content from an external app. I called the Shopify blogs API from Postman to fetch the blogs using the API key and password, but I am not able to retrieve the blogs' content.

I called this endpoint for accessing the blogs https://${API_KEY}:${PASSWORD}@${STORE_NAME}.myshopify.com/admin/api/2021-01/blogs.json

This is the response I get in the postman:

{
    "blogs": [
        {
            "id": 75486953641,
            "handle": "news",
            "title": "News",
            "updated_at": "2021-01-18T04:58:06-08:00",
            "commentable": "no",
            "feedburner": null,
            "feedburner_location": null,
            "created_at": "2021-01-13T05:03:38-08:00",
            "template_suffix": null,
            "tags": "about",
            "admin_graphql_api_id": "gid://shopify/OnlineStoreBlog/75486953641"
        }
    ]
}

this actually shows that I have a blog, but it does not show the blog content that I wrote, am I missing something here, How can I actually access the blog content?


Solution

A Blog is a thing. You can have 1, 2 or more. You asked for a blog, you got a blog. A blog is a thing that has zero, one or more articles. I think you want the articles belonging to a blog. So if you want the content of a blog, you should look into the articles endpoint perhaps? Is that what you are missing?



Answered By - David Lazar
Answer Checked By - Mary Flores (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How can I hide parent element if child element is hidden?

 July 02, 2022     css, html, jquery, liquid, shopify     No comments   

Issue

Trying to hide a reviews section stars badge on a Shopify store if the product has 0 reviews. Want this to automatically show up once a product receives a review. Here is the code for it below:

HTML

 <div class="spr-badge-container">
    <span class="stamped-product-reviews-badge stamped-main-badge" data-id="{{ product.id }}" data-product-sku="{{ product.handle }}" data-product-type="{{product.type}}" data-product-title="{{product.title}}"  style="display: inline-block;">{{ product.metafields.stamped.badge }}</span>
 </div>

CSS

.stamped-container[data-count="0"][data-version="2"] { 
display:none !important; 
}

This css hides the review stars inside of the "spr-badge-container". What I'm trying to achieve is hide the "spr-badge-container" div if the span inside it is hidden.

Here's what I've tried so far with no results.

jQuery

if($('.stamped-container[data-count="0"][data-version="2"]').is(":hidden")){
$(".spr-badge-container").hide();
}

----

if($('.stamped-container').is(":hidden")){
$(".spr-badge-container").hide();
}

----

if($('.stamped-container').is(":hidden")){
$(".spr-badge-container").css({display:none});
}

----

if($('.stamped-container[data-count="0"][data-version="2"]').is(":hidden")){
$(".spr-badge-container").css({display:none});
}

None of the methods I've tried gave any results, so wanted to ask if I'm missing something or if there is another, perhaps easier way of going about this.

Thanks in advance, J

EDIT - Here is the HTML as rendered by the browser

HTML for review stars section

<div class="spr-badge-container review-margin-cust">
 <span class="stamped-product-reviews-badge stamped-main-badge" data-id="6208427819182" data-product-sku="PRODUCT" data-product-type="" data-product-title="1/2" diameter="" .140"="" height="" clear="" rubber="" bumper"="" style="display: inline-block;">
 <span class="stamped-badge" data-rating="0.0" data-lang="en" aria-label="Rated 0.0 out of 5 stars">
 <span class="stamped-starrating stamped-badge-starrating" aria-hidden="true">
 <i class="stamped-fa stamped-fa-star-o" aria-hidden="true"></i>
 <i class="stamped-fa stamped-fa-star-o" aria-hidden="true"></i>
 <i class="stamped-fa stamped-fa-star-o" aria-hidden="true"></i>
 <i class="stamped-fa stamped-fa-star-o" aria-hidden="true"></i>
 <i class="stamped-fa stamped-fa-star-o" aria-hidden="true"></i>
 </span><span class="stamped-badge-caption" data-reviews="0" data-rating="0.0" data-label="reviews" aria-label="0 reviews" data-version="2">
 </span>
 </span>
 </span>
 </div>

HTML for main reviews section

<div class="stamped-container" data-count="0" data-widget-style="standard2" data-widget-language=""
    data-widget-sort="recent" data-widget-load-type="pagination" data-widget-show-graph="false"
    data-widget-show-sort="false" data-widget-show-date="true" data-widget-type="standard2"
    data-widget-show-summary-photo="true" data-widget-show-summary-recommend="true" data-widget-show-tab-reviews="true"
    data-widget-show-avatar="true" data-widget-show-location="true" data-widget-show-verified="true"
    data-widget-show-share="true" data-widget-show-votes="true" data-widget-show-qna="true"
    data-widget-show-product-variant="true" data-version="2">
    <div class="stamped-header-title"> Customer Reviews </div>
    <div class="stamped-header">
        <div class="stamped-summary" data-count="0">
            <div style="width:200px;float:left;" class="summary-overview">
                <span class="stamped-summary-caption stamped-summary-caption-1">
                    <span class="stamped-summary-text-1" data-count="0" data-rating="0.0">0.0</span>

                </span>
                <span class="stamped-starrating stamped-summary-starrating" aria-hidden="true"> <i
                        class="stamped-fa stamped-fa-star-o" aria-hidden="true"></i><i
                        class="stamped-fa stamped-fa-star-o" aria-hidden="true"></i><i
                        class="stamped-fa stamped-fa-star-o" aria-hidden="true"></i><i
                        class="stamped-fa stamped-fa-star-o" aria-hidden="true"></i><i
                        class="stamped-fa stamped-fa-star-o" aria-hidden="true"></i> </span>
                <span class="stamped-summary-caption stamped-summary-caption-2">
                    <span class="stamped-summary-text" data-count="0" data-rating="0.0">Based on 0 Reviews</span>
                </span>
            </div>
            <div class="stamped-summary-ratings" data-count="0" aria-hidden="true" style="">
                <div class="summary-rating" data-count="0">
                    <div class="summary-rating-title">5 ★</div>
                    <div class="summary-rating-bar" data-rating="5">
                        <div class="summary-rating-bar-content" style="width:0%;" data-rating="0">0%&nbsp;</div>
                    </div>
                    <div class="summary-rating-count">0</div>
                </div>
                <div class="summary-rating" data-count="0">
                    <div class="summary-rating-title">4 ★</div>
                    <div class="summary-rating-bar" data-rating="4">
                        <div class="summary-rating-bar-content" style="width:0%;" data-rating="0">0%&nbsp;</div>
                    </div>
                    <div class="summary-rating-count">0</div>
                </div>
                <div class="summary-rating" data-count="0">
                    <div class="summary-rating-title">3 ★</div>
                    <div class="summary-rating-bar" data-rating="3">
                        <div class="summary-rating-bar-content" style="width:0%;" data-rating="0">0%&nbsp;</div>
                    </div>
                    <div class="summary-rating-count">0</div>
                </div>
                <div class="summary-rating" data-count="0">
                    <div class="summary-rating-title">2 ★</div>
                    <div class="summary-rating-bar" data-rating="2">
                        <div class="summary-rating-bar-content" style="width:0%;" data-rating="0">0%&nbsp;</div>
                    </div>
                    <div class="summary-rating-count">0</div>
                </div>
                <div class="summary-rating" data-count="0">
                    <div class="summary-rating-title">1 ★</div>
                    <div class="summary-rating-bar" data-rating="1">
                        <div class="summary-rating-bar-content" style="width:0%;" data-rating="0">0%&nbsp;</div>
                    </div>
                    <div class="summary-rating-count">0</div>
                </div>
            </div>
            <div class="stamped-summary-photos stamped-summary-photos-container" aria-hidden="true"
                style="display:none;">
                <div class="stamped-photos-title"> Customer Photos </div>
                <div style="position:relative;overflow: hidden;">
                    <div class="stamped-photos-carousel" data-total="0">
                        <div> </div>
                    </div>
                </div>
                <div class="stamped-photos-carousel-btn-left"> <span class="btn-slide-left" data-direction="left"><i
                            class="fa fa-chevron-left" aria-hidden="true"></i></span> </div>
                <div class="stamped-photos-carousel-btn-right"> <span class="btn-slide-right" data-direction="right"><i
                            class="fa fa-chevron-right" aria-hidden="true"></i></span> </div>
            </div>


            <span class="stamped-summary-actions">
                <span class="stamped-summary-actions-newreview"
                    onclick="StampedFn.toggleForm('review','6208427819182');return false;" style="cursor: pointer;"
                    tabindex="0">Write a Review</span>
                <span class="stamped-summary-actions-newquestion" style="cursor: pointer;display:none;"
                    onclick="StampedFn.toggleForm('question', '6208427819182');return false;" tabindex="0">Ask a
                    Question</span>
            </span>
        </div>
    </div>
    <div class="stamped-content">
        <div class="stamped-tab-container" style="display:none;">
            <ul class="stamped-tabs">
                <li id="tab-reviews" class="active" data-type="reviews" data-count="0" aria-label="Reviews"
                    tabindex="0">Reviews</li>
                <li id="tab-questions" data-type="questions" style="display:none;" data-count="0" aria-label="Questions"
                    tabindex="0" data-new-tab="">Questions</li>
            </ul>
        </div>

        <form method="post" id="new-review-form_6208427819182" class="new-review-form"
            onsubmit="event.preventDefault(); StampedFn.submitForm(this);" style="display:none;">

            <input type="hidden" name="productId" value="6208427819182">
            <div class="stamped-form-title" style=" display:none;">Write a review</div>
            <fieldset class="stamped-form-contact">
                <legend style="display:none;">Author</legend>
                <div class="stamped-form-contact-name">
                    <label class="stamped-form-label" for="review_author_6208427819182">Name</label>
                    <input class="stamped-form-input stamped-form-input-text " id="review_author_6208427819182"
                        type="text" name="author" required="" value="" placeholder="Enter your name"
                        autocomplete="name">
                </div>
                <div class="stamped-form-contact-email">
                    <label class="stamped-form-label" for="review_email_6208427819182">Email</label>
                    <input class="stamped-form-input stamped-form-input-email " id="review_email_6208427819182"
                        type="email" name="email" required="" value="" placeholder="john.smith@example.com"
                        autocomplete="email">
                </div>
                <div class="stamped-form-contact-location">
                    <label class="stamped-form-label" for="review_location_6208427819182">Location</label>
                    <input class="stamped-form-input stamped-form-input-text " id="review_location_6208427819182"
                        type="text" name="location" value="" placeholder="e.g Paris, France"
                        autocomplete="shipping country">
                </div>
            </fieldset>
            <fieldset class="stamped-form-review">
                <legend style="display:none;">Rating</legend>
                <div class="stamped-form-review-rating">
                    <label class="stamped-form-label" for="reviewRating">Rating</label>
                    <input type="text" id="reviewRating" name="reviewRating"
                        style="font-size: 0px; border: none; height: 1px; width: 1px; margin: 0; padding: 0; line-height: 0px; min-height: 0px;"
                        required="">

                    <div class="stamped-form-input stamped-starrating"> <a
                            onclick="StampedFn.setRating(this);return false;" class="stamped-fa stamped-fa-star-o"
                            data-value="1" tabindex="0" aria-label="1 star rating"><span
                                style="display:none;">1</span></a> <a onclick="StampedFn.setRating(this);return false;"
                            class="stamped-fa stamped-fa-star-o" data-value="2" tabindex="0"
                            aria-label="2 stars rating"><span style="display:none;">2</span></a> <a
                            onclick="StampedFn.setRating(this);return false;" class="stamped-fa stamped-fa-star-o"
                            data-value="3" tabindex="0" aria-label="3 stars rating"><span
                                style="display:none;">3</span></a> <a onclick="StampedFn.setRating(this);return false;"
                            class="stamped-fa stamped-fa-star-o" data-value="4" tabindex="0"
                            aria-label="4 stars rating"><span style="display:none;">4</span></a> <a
                            onclick="StampedFn.setRating(this);return false;" class="stamped-fa stamped-fa-star-o"
                            data-value="5" tabindex="0" aria-label="5 stars rating"><span
                                style="display:none;">5</span></a> </div>
                </div>
                <div class="stamped-form-review-title">
                    <label class="stamped-form-label" for="review_title_6208427819182">Title of Review</label>
                    <input class="stamped-form-input stamped-form-input-text" id="review_title_6208427819182"
                        type="text" name="reviewTitle" required="" value="" placeholder="Give your review a title">
                </div>
                <div class="stamped-form-review-body">
                    <label class="stamped-form-label" for="review_body_6208427819182">How was your overall
                        experience?</label>
                    <div class="stamped-form-input">
                        <textarea class="stamped-form-input stamped-form-input-textarea" id="review_body_6208427819182"
                            data-product-id="6208427819182" name="reviewMessage" required="" rows="10"
                            maxlength="5000"></textarea>
                    </div>
                </div>
            </fieldset>
            <fieldset class="stamped-form-custom-questions">
                <legend style="display:none;">Questions</legend>

            </fieldset>
            <fieldset class="stamped-form-actions">
                <legend style="display:none;">Photos</legend>
                <span class="stamped-file-holder">

                </span>
                <span class="stamped-file-uploader" style="display:none;">
                    <label for="stamped-file-uploader-input">
                        <span style="display:none;">Upload</span>
                        <input id="stamped-file-uploader-input" type="file" name="stamped-file-uploader-input"
                            class="stamped-file-uploader-input" multiple="" data-product-id="6208427819182"
                            style="display:none;">

                        <span class="stamped-file-uploader-btn"
                            style="border:1px solid #333;padding: 6px 10px; font-size:13px; border-radius: .3em;">
                            <i class="stamped-fa stamped-fa-camera"></i>
                            <span class="stamped-file-uploader-btn-label2"></span>
                        </span>
                    </label>
                </span>
                <span class="stamped-file-loading hide" style="display:none;">
                    <i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
                </span>

                <input id="stamped-button-submit" type="submit"
                    class="stamped-button stamped-button-primary button button-primary btn btn-primary" value="Submit">
            </fieldset>
        </form>
        <div class="stamped-messages">
            <div class="stamped-thank-you">
                <p>Thank you for submitting a review!</p>
                <p>Your input is very much appreciated. Share it with your friends so they can enjoy it too!</p>
                <div class="stamped-share-links"><a class="facebook"><span><i
                                class="stamped-fa stamped-fa-facebook"></i>Facebook</span></a><a
                        class="twitter"><span><i class="stamped-fa stamped-fa-twitter"></i>Twitter</span></a></div>
            </div>
            <div class="stamped-empty-state" style="display:none;">
                <div>Be the first to review this item</div>
            </div>
        </div>
        <div class="stamped-reviews-filter" id="stamped-reviews-filter" data-show-filters="">
            <span class="stamped-sort-select-wrapper">
                <select id="stamped-sort-select" class="stamped-sort-select" onchange="StampedFn.sortReviews(this);"
                    aria-label="Sort reviews" tabindex="0">
                    <option value="featured" selected="">Sort</option>
                    <option value="recent">Most Recent</option>
                    <option value="highest-rating">Highest Rating</option>
                    <option value="lowest-rating">Lowest Rating</option>
                    <option value="most-votes">Most Helpful</option>
                </select>
            </span>

            <div class="stamped-reviews-filter-label">Filter Reviews:</div>

            <div class="stamped-reviews-search-text" style="display:none;">
                <span class="stamped-reviews-search-icon stamped-fa stamped-fa-search"></span>
                <input id="stamped-reviews-search-input" class="stamped-reviews-search-input" type="text"
                    placeholder="Search Reviews" aria-label="Search reviews input">
                <span class="stamped-reviews-search-button"></span>
                <span class="stamped-reviews-search-clear" tabindex="0" aria-label="Clear search input"
                    style="display:none;">×</span>
            </div>

            <div class="stamped-filters-wrapper">
                <div class="stamped-summary-keywords">
                    <ul class="stamped-summary-keywords-list">

                    </ul>
                </div>

                <div class="stamped-filter-selects">

                    <span class="stamped-sort-select2-wrapper" style="display:none;">
                        <select id="stamped-sort-select2" class="stamped-sort-select"
                            onchange="StampedFn.sortReviews(this);" aria-label="Sort reviews and ratings" tabindex="0"
                            style="display:none;">
                            <option value="featured" selected="">Sort</option>
                            <option value="recent">Most Recent</option>
                            <option value="highest-rating">Highest Rating</option>
                            <option value="lowest-rating">Lowest Rating</option>
                            <option value="most-votes">Most Helpful</option>
                        </select>
                    </span>
                </div>
            </div>

            <div class="stamped-summary-actions-clear" tabindex="0" role="button" aria-label="Clear filters"
                style="cursor: pointer;display:none;">Clear filters</div>

            <div class="stamped-summary-actions-mobile-filter" tabindex="0" role="button" aria-label="More filters"
                style="cursor: pointer;display:none;"><i class="stamped-fa stamped-fa-params"></i> More Filters</div>
        </div>
        <div class="stamped-reviews"></div>
    </div>
</div>

Also, the full css hiding these elements

CSS

 #stamped-main-widget .stamped-container[data-count="0"] { display: none !important; }
     .stamped-container[data-count="0"][data-version="2"] { display:none !important; }

Solution

Okay, according to your description you try this approach, by default hide them all .spr-badge-container and then on page load check the count and if there is value then show or otherwise leave it hidden.

CSS:

.spr-badge-container{ display: none;}

JS:

winodw.onload = ()=>{
 if(jQuery('.stamped-container').data('count') != '0' ){
   $(".spr-badge-container").css('display', 'block');
   // use CSS or show to display the container
   $(".spr-badge-container").show();
 }
}

Note: JS code using jQuery so must be loaded after the jQuery lib load, otherwise code not works.



Answered By - Onkar
Answer Checked By - Robin (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to ignore missing nodes when deserialising json in .NET

 July 02, 2022     deserialization, json, json.net, shopify, vb.net     No comments   

Issue

Some of the Shopify API's don't return nodes when they are not configured rather than returning null values.

JSON EXAMPLE

{
  "orders": [
    {
      "id": 1,
      "customer": {
        "id": 001,
      }
    },
    {
      "id": 2
    }
  ]
}

In order to deserialise the Json I need to include all properties within the class object.

Public Class AllOrders
  Public Property orders() As List(Of Order)
End Class

Public Class Order
  Public Property id As Long?
  Public Property customer() As Customer
End Class

Public Class Customer
  Public Property id As Long?
End Class

How I can efficiently skip missing nodes without convoluted if statements.

The issue is not the deserialisation of the json but trying to read a named member that does not exist.

Dim AllOrders = JsonConvert.DeserializeObject(Of AllOrders)(GetOrders())

For Each order As Shopify.Order In AllOrders.orders
  Dim test as order.customer.id
  'Error NullReferenceException on customer
Next

Can this be handled within the Json.Net framework?


JsonSerializerSettings

I should note I have already tried the following settings.

Dim settings = New JsonSerializerSettings With {
  .NullValueHandling = NullValueHandling.Ignore,
  .MissingMemberHandling = MissingMemberHandling.Ignore
}

Solution

There are a couple of client-side alternatives for handling this in one line.

You could use the null conditional operator ?., as in

Dim oneId = order.Customer?.Id

In this case, the result is a nullable Long, but Id is already a nullable long, so it shouldn't change subsequent handling as you already needed to account for the possibility that it might be null. If there is a single "sensible" value for a missing value, you can then use GetValueOrDefault to have a guaranteed value.

You could use the null coalescing form of the If operator, as in

Dim guaranteedCustomer = If(order.Customer, defaultCustomer)

This would require some caution as using a single mutable object as your default is potentially a recipe for nasty bugs later when one of the defaults gets mutated thus changing all of the defaulted items.



Answered By - Craig
Answer Checked By - Cary Denson (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How can I move my shopify product price to another line?

 July 02, 2022     shopify     No comments   

Issue

I am currently trying to adjust the location of my price in shopify. On desktop view, it is to the right of the title which fits perfectly. For mobile, I want to move the price to below the title since phones don't have as much space to work with. I will insert the code below and what it looks like on mobile and desktop.

Code:

Code

Desktop:

Desktop

Mobile:

Mobile


Solution

Use the CSS at the end of the CSS file

@media screen and (max-width:767px){
    .template-collection .product-details-div,
    .product-recommendations .product-details-div{ 
      flex-direction:column; 
     }


    .template-collection .product-details-div-price, 
    .product-recommendationsproduct-details-div-price {
      margin-top: 3px;
    }
}

and it looks like this if no conflict with other CSS. enter image description here



Answered By - Onkar
Answer Checked By - Marie Seifert (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to ad different navigation menus to shopify Debut theme?

 July 02, 2022     html, liquid, nav, shopify     No comments   

Issue

So I am trying to create different navigation menus for different customer. So when I give a customer a specific tag in shopify (e.g. wholesale) he will see another menu bar than other visitors. Thsi will only happen after he logs into his account.

Now I tried to find the following snippet: {% for link in linklists.main-menu.links %}

to replace it with the following code:

{% assign menu_handle = 'main-menu' %}
{% if customer %} 
{% if customer.tags contains 'wholesale' %} 
{% assign menu_handle = 'main-menu-wholesale' %} 
{% endif %} 
{% endif %} 
{% for link in linklists[menu_handle].links %}

But I couldn't find that snippet in the debut code that looks like this:

{% comment %}
Renders a list of menu items
Accepts:
- linklist: {Object} Linklist Liquid object (required)
- wrapper_class: {String} CSS wrapper class for the navigation (optional)

Usage:
{% include 'site-nav', linklist: section.settings.main_linklist, wrapper_class: 'site-nav--centered' %}
{% endcomment %}
<ul class="site-nav list--inline{% if wrapper_class != blank %} {{ wrapper_class }}{% endif %}" id="SiteNav">
{% for link in linklists[linklist].links %}
{%- assign child_list_handle = link.title | handleize -%}

{% comment %}
  Check if third-level nav exists on each parent link.
{% endcomment %}
{%- assign three_level_nav = false -%}
{% if link.links != blank %}
  {% if link.levels == 2 %}
    {%- assign three_level_nav = true -%}
  {% endif %}
{% endif %}

{% if link.links != blank %}
  <li class="site-nav--has-dropdown{% if three_level_nav %} site-nav--has-centered-dropdown{% endif %}{% if link.active %} site-nav--active{% endif %}" data-has-dropdowns>
    <button class="site-nav__link site-nav__link--main site-nav__link--button{% if link.child_active %} site-nav__link--active{% endif %}" type="button" aria-expanded="false" aria-controls="SiteNavLabel-{{ child_list_handle }}">
      <span class="site-nav__label">{{ link.title | escape }}</span>{% include 'icon-chevron-down' %}
    </button>

    <div class="site-nav__dropdown{% if three_level_nav %} site-nav__dropdown--centered{% endif %} critical-hidden" id="SiteNavLabel-{{ child_list_handle }}">
      {% if three_level_nav %}
        <div class="site-nav__childlist">
          <ul class="site-nav__childlist-grid">
            {% if link.links != blank %}
              {% for childlink in link.links %}
                <li class="site-nav__childlist-item">
                  <a href="{{ childlink.url }}"
                    class="site-nav__link site-nav__child-link site-nav__child-link--parent"
                    {% if childlink.current %} aria-current="page"{% endif %}
                  >
                    <span class="site-nav__label">{{ childlink.title | escape }}</span>
                  </a>

                  {% if childlink.links != blank %}
                    <ul>
                    {% for grandchildlink in childlink.links %}
                      <li>
                        <a href="{{ grandchildlink.url }}"
                        class="site-nav__link site-nav__child-link"
                        {% if grandchildlink.current %} aria-current="page"{% endif %}
                      >
                          <span class="site-nav__label">{{ grandchildlink.title | escape }}</span>
                        </a>
                      </li>
                    {% endfor %}
                    </ul>
                  {% endif %}

                </li>
              {% endfor %}
            {% endif %}
          </ul>
        </div>

      {% else %}
        <ul>
          {% for childlink in link.links %}
            <li>
              <a href="{{ childlink.url }}"
              class="site-nav__link site-nav__child-link{% if forloop.last %} site-nav__link--last{% endif %}"
              {% if childlink.current %} aria-current="page"{% endif %}
            >
                <span class="site-nav__label">{{ childlink.title | escape }}</span>
              </a>
            </li>
          {% endfor %}
        </ul>
      {% endif %}
    </div>
  </li>
{% else %}
  <li {% if link.active %} class="site-nav--active"{% endif %}>
    <a href="{{ link.url }}"
      class="site-nav__link site-nav__link--main{% if link.active %} site-nav__link--active{% endif %}"
      {% if link.current %} aria-current="page"{% endif %}
    >
      <span class="site-nav__label">{{ link.title | escape }}</span>
    </a>
  </li>
{% endif %}
{% endfor %}
</ul>

Is there another way to add this code snippet into the debut code? How would that code look like?

Every hint is appreciated!


Solution

I think you need to find the code this one {% include 'site-nav', linklist: section.settings.main_linklist, wrapper_class: 'site-nav--centered' %}

and after the changes, your code looks like

{% assign menu_handle = 'main-menu' %}
{% if customer %} 
  {% if customer.tags contains 'wholesale' %} 
    {% assign menu_handle = 'main-menu-wholesale' %} 
  {% endif %} 
{% endif %} 

{% include 'site-nav', linklist: menu_handle, wrapper_class: 'site-nav--centered' %}

Update: Here I am changing the menu listing over debut theme latest versions.

  1. in the header.liquid file I have made an update and add the snippets that check for customer and customer tags if the customer is login and tags contain wholesale. enter image description here

  2. Register as a new user and from the admin, backend assign tag wholesale to the new user account. enter image description here

  3. Here is the frontend of the non-login users and log in as a wholesale tagged customer. enter image description here

enter image description here



Answered By - Onkar
Answer Checked By - Pedro (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How display all top 1 product on collection list one one page?

 July 02, 2022     collections, shopify     No comments   

Issue

I'm trying to display all top 1 product on the collection on one page. but I know how to do that.

I share upload three screenshots I want them to display on one page. Any tips?

enter image description here


Solution

There are a lot of ways to do this.

1 way. Start by creating a new collection with a condition, select product type is equal to top 1. In the product you want to appear in that collection, add top 1 to the type.

  1. {% for collection in collections %}
     {% for product in collection.products %}
         {% if forloop.index == 1%}
         {% comment %}display product{% endcomment %}
         {% endif %}
     {% endfor %} {% endfor %}
    

The above code will select first product on every collections in the shop. To control which to appear as first, go to the collection and select the sort orders.



Answered By - Charles C.
Answer Checked By - Gilberto Lyons (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to charge RecurringApplicationCharge for Partner-Friendly apps?

 July 02, 2022     shopify     No comments   

Issue

Shopify says that in order to qualify for Partner-Friendly apps, we need to "not charge" for development stores. Here is the link (TIP section): https://help.shopify.com/en/api/app-store/charging-for-your-app

It says we should grant free access if { "plan_name" : "affiliate" })

When we set the price 0 on RecurringApplicationCharge, we get an error. How do we grant free access ?


Solution

You can set the test attribute to be true. That way no charge is levied for that install. Just add test: true if partner is affiliate.

One thing to monitor is the change in plan. If an affiliate plan goes real, remove the old test charge and replace it with a non-test charge subscription.

It remains painful for that reason, to deal in the freebies like this.



Answered By - David Lazar
Answer Checked By - David Goodson (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] Why is attribute changing of HTML using JS not working?

 July 02, 2022     html, javascript, jquery, shopify     No comments   

Issue

I want to disable the button or change the attribute of the field so that if there are less than 12 characters in the input field it will disable the button I tried everything i know of.(commented) (Possible Duplicate of - Can't change html attribute with external script )

Html code

<button name="button" type="submit" id="continue_button" class="step__footer__continue-btn btn" aria-busy="false">
  <span class="btn__content" data-continue-button-content="true">Continue to shipping</span>
  <svg class="icon-svg icon-svg--size-18 btn__spinner icon-svg--spinner-button" aria-hidden="true" focusable="false"> <use xlink:href="#spinner-button"></use> </svg>
</button> 

JS code -

$("#checkout_shipping_address_address1").attr('maxlength','15');
      var valueLength = $('#checkout_shipping_address_address1').val();
      if(valueLength.length < 12){
     //   console.log(valueLength.length);
     //   $("#checkout_shipping_address_address1").aria-invalid="true";
       // var attrChange = $("#error-for-address1");
      //  $("#checkout_shipping_address_address1").innerHTML("aria-describedby" = attrChange);
       // $("#checkout_shipping_address_address1").innerHTML("aria-descibedby = attrChange"); 
     //   $("#checkout_shipping_address_address1").setAttribute("aria-invalid", "true");
      //  $('#continue_btn').attr("disabled", true);
      //  $('#continue_btn').disabled = true;
     //   $('#continue_btn').prop('disabled', true);
      }
      else
      {
       // $('#continue_btn').disabled=false;
      }

The attribute is not changing on the webpage neither can i disable the button.

Note- I cant change HTML/CSS code as i dont have access to it

P.S - I am quite new to JS/JQuery.


Solution

The input needs an event handler function (added with on()) so that something happens when the user changes the input value:

$("#checkout_shipping_address_address1").attr('maxlength', '15').on('input', function() {
  $('#continue_button').prop('disabled', $(this).val().length < 12);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" id="checkout_shipping_address_address1" />
<button name="button" disabled type="submit" id="continue_button" class="step__footer__continue-btn btn" aria-busy="false">
  <span class="btn__content" data-continue-button-content="true">Continue to shipping</span>
  <svg class="icon-svg icon-svg--size-18 btn__spinner icon-svg--spinner-button" aria-hidden="true" focusable="false"> <use xlink:href="#spinner-button"></use> </svg>
</button>



Answered By - sbgib
Answer Checked By - David Marino (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Friday, July 1, 2022

[FIXED] How to translate an Underscore template to a Handlebars template?

 July 01, 2022     handlebars.js, javascript, shopify, underscore.js     No comments   

Issue

I'm upgrading a Shopify store that's using an old theme.

In the (old) Cart page is code for a 'Shipping Estimator' which (because it works well) they want to re-use in the new theme. I've copied across the relevant files but on execution and pressing the Calculate button, we get the following displayed:

class="success" <% } else { %> class="error" <% } %>> <% if (success) { %> <% if (rates.length > 1) { %> There are <%= rates.length %> shipping rates available for <%= address %>, starting at <%= rates[0].price %>. <% } else if (rates.length == 1) { %> ....

This comes from the following code:

<script id="shipping-calculator-response-template" type="text/template">
  <p id="shipping-rates-feedback" <% if (success) { %> class="success" <% } else { %> class="error" <% } %>>
  <% if (success) { %>
    <% if (rates.length > 1) { %> 
    There are <%= rates.length %> shipping rates available for <%= address %>, starting at <%= rates[0].price %>.
    <% } else if (rates.length == 1) { %>
    ... 
</script>

So, I guess the script is not being recognise/treated as 'text/template'

The new theme includes a reference to:

<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.10/handlebars.min.js"></script>

And the old theme:

<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js" type="text/javascript"></script>

So I commented out handlebar, and replaced with underscore. But still the same result.

Am I on the right track, or is the above irrelevant?

The full code that I need to decipher from Underscore - and re-code for HandleBars is as follows:

<script id="shipping-calculator-response-template" type="text/template">
  <p id="shipping-rates-feedback" <% if (success) { %> class="success" <% } else { %> class="error" <% } %>>
  <% if (success) { %>
    <% if (rates.length > 1) { %> 
    There are <%- rates.length %> shipping rates available for <%- address %>, starting at <%= rates[0].price %>.
    <% } else if (rates.length == 1) { %>
    There is one shipping rate available for <%- address %>.
    <% } else { %>
    We do not ship to this destination.
    <% } %>
  <% } else { %>
    <%- errorFeedback %>
  <% } %>
  </p>
  <ul id="shipping-rates">
    <% for (var i=0; i<rates.length; i++) { %>
    <li><%- rates[i].name %> at <%= rates[i].price %></li>
    <% } %>
  </ul> 
</script>

If we can get this to work, there are a lot of shopify merchants that will be very happy ;)


Solution

Normally, Underscore and Handlebars are not really alternatives to each other. Underscore is a toolkit with general functional utilities, which helps you write shorter, more maintainable code in functional style. Handlebars, on the other hand, is a library entirely dedicated to template rendering, which helps you write cleaner, more maintainable templates.

When using Underscore, you may find its functions being called everywhere throughout your JavaScript code, while Handlebars is only called in places where you'll be rendering a template. For this reason, these libraries normally don't conflict at all; it is perfectly possible to write an application that depends on both (in fact I've been doing this for a while in most of my applications). Just link both libraries into your page,

<script src="https://cdn.jsdelivr.net/npm/underscore@1.12.0/underscore-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/handlebars@4.7.7/dist/handlebars.js"></script>

or import both as modules,

import _ from 'underscore';
import * as Handlebars from 'handlebars';

or do the same with an older module format such as AMD or CommonJS.

However, Underscore does have a template function, which is a very minimal templating implementation that can be used as an alternative to a library like Handlebars. This appears to be the case in the old version of your application, and it is causing conflicts because Underscore's minimal templating language is different from Handlebars's templating language. The compilation and rendering steps are also a bit different between these libraries.

Comparing the template languages

Both template languages let you insert special tags in a piece of text in order to make parts of that text parametric, conditional or repetitive. The tags that they support are however different.

Underscore:

  • <%= expression %> will insert the string value of the JavaScript expression in the text. This is called an interpolation tag.
  • <%- expression %> will do the same, but HTML-escaped.
  • <% code %> lets you write arbitrary JavaScript code that will make parts of the template conditional or repetitive. Often, you'll find that one such tag goes something like <% for (...) { %> and then a bit further down the template, there is a matching <% } %>. The part of the template between those two code tags is then a loop that will repeat by the logic of the for. Similarly, you may find <% if (...) { %>...<% } %> to make the ... part of the template conditional. (Honestly, this is quite ugly, but it helps the implementation to be minimal. Underscore's template module is only one page.)
  • Inside the code part of <% code %> you may occasionally find print(expression). This is a shorthand meant to avoid having to break out of the code tag, insert an interpolation tag with expression and then immediately resume with a new code tag. In other words, <% code1 print(expression) code2 %> is a shorthand for <% code1 %><%= expression %><% code2 %>.

Handlebars:

  • {{name}} inserts the HTML-escaped string value of the property with key name in the template.
  • {{{name}}} does the same, but without HTML-escaping.
  • {{#if condition}}...{{/if}} will insert the ... part only if condition is met.
  • {{#each name}}...{{/each}} will repeat ... for each element or property of name. name becomes the context of the ...; that is, if you write {{otherName}} within ..., Handlebars will try to find otherName as a property of the object identified by name.
  • {{#name}}...{{/name}} is a notation that Handlebars inherits from Mustache. It behaves similar to {{#each name}} when name is an array and similar to {{#if name}} otherwise (also changing the context to name if it is an object). The idea behind this (in Mustache) is to make the template even more declarative, or "logic-free" as the authors call it.
  • There are more tags that I won't go into now.

Translating Underscore templates to Handlebars

Since Underscore allows the insertion of arbitrary JavaScript code in a template, it is not always possible to translate an Underscore template to an equivalent Handlebars template. Fortunately, however, templates don't really need the full expressive power of JavaScript, so Underscore templates are likely to be written in a way that can be ported to a more restrictive template language ("lucky by accident"). When it is possible, the following strategy will be sufficient most of the time:

  1. Replace any occurrences of print(_.escape(expression)) that appear inside any <%...%> tag by %><%- expression %><%.
  2. Replace any other occurrences of print(expression) inside <%...%> by %><%= expression %><%.
  3. Replace all occurrences of <%- expression %> (including ones that were already there before step 1) by {{expression}}.
  4. Replace all occurrences of <%= expression %> (including ones that were already there before step 2) by {{{expression}}}.
  5. If you find aliases of the form var name = otherName.propertyName anywhere (except within the parentheses of a for (...) statement), substitute otherName.propertyName for name everwhere this variable is in scope and drop the variable. Don't do this with loop variables inside for (...); those are replaced in step 7.
  6. If you find any pattern of the form <% if (condition1) { %>...<% } else if (condition2) { %>...<% } else ... if (conditionN) { %>...<% } else { %>...<% } %>, start with the last, innermost block and work your way outwards from there, as follows:
    • Replace the final <% } else { %> by {{else}} (Handlebars recognizes this as a special notiation).
    • Replace the final intermediate <% } else if (conditionN) { %>...<% } %> by {{else}}{{#if conditionN }}...{{/if}}<% } %>. Repeat this step until there is no more else if. Note that the final <% } %> stays in place; you're inserting one additional {{/if}} in front of it for every intermediate else if.
    • Replace the outermost <% if (condition1) { %>...<% } %> by {{#if condition1}}...{{/if}}. This time, the final <% } %> disappears.
  7. Replace loops, again starting with the innermost expressions and working your way outwards from there:
    • Replace functional loops over objects of the form <% _.each(objectName, function(valueName, keyName, collectionName) { %>...<% }) %> by the notation {{#each objectName}}...{{/each}}. Check nested {{}}/{{{}}}/{{#if}}/{{#each}} tags within ... for any appearances of keyName, collectionName or objectName and replace them by by @key, .. and .. respectively (that is not a typo; collectionName and objectName should both be replaced by .. because they refer to the same object). Note that the function passed to _.each in the Underscore version may take fewer arguments, in which case collectionName and even keyName may be absent; the replacement works the same regardless.
    • Replace functional loops over arrays of the form <% _.each(arrayName, function(valueName, indexName, collectionName) { %>...<% }) %> by the notation {{#each arrayName}}...{{/each}}. Check nested {{}}/{{{}}}/{{#if}}/{{#each}} tags within ... for any appearances of indexName, collectionName or arrayName and replace them by by @index, .. and .. respectively. Again, the function passed to _.each in the Underscore version may take fewer arguments; the replacement works the same regardless.
    • Replace procedural loops over objects of the form <% for (var keyName in objectName) { %>...<% } %> by the notation {{#each objectName}}...{{/each}}. Check nested {{}}/{{{}}}/{{#if}}/{{#each}} tags within ... for any appearances of keyName, objectName[keyName] or objectName and replace them by by @key, this and .. respectively.
    • Replace procedural loops over arrays of the form <% for (var indexName = 0; indexName < arrayName.length; ++indexName) { %>...<% } %> by the notation {{#each arrayName}}...{{/each}}. Check nested {{}}/{{{}}}/{{#if}}/{{#each}} tags within ... for any appearances of indexName, arrayName[indexName] or arrayName and replace them by by @index, this and .. respectively.
  8. Fix expression syntax:
    • If you have created expressions of the form ...propertyName in the previous step, where the first two periods .. were originally a name (objectName or arrayName as described under step 7), replace this by ../propertyName. You may have longer paths of this form, for example ../../propertyName.
    • Subexpressions of the form name[index1][index2] should be turned into name.[index].[index2] (note the periods).
  9. Check whether all expressions and conditions that you translated can be evaluated as-is by Handlebars. As a rule of thumb, Handlebars can only directly evaluate (nested) property names of the current context (for example keyName or keyName.subProperty) and some special notations that it recognizes such as @key, @index, @root, this and ... Use helpers to evaluate expressions that are more than just the name of some object and anchor names as necessary to @root or ..:
    • Note that this.propertyName is equivalent to just propertyName, because this refers to the current context.
    • When passing an object like {a: foo, b: {c: baz}} to the template, the properties of this outermost object can always be referenced with @root.a, @root.b.c, etcetera. Note that this object may have been given a name of its own inside the original Underscore template; in that case, this name itself can be replaced by @root.
    • .. is for referencing the parent context inside loops, as we have seen in steps 7-8. Occasionally, a loop in the original Underscore template may reference a property of a parent context directly by closing over it; in such cases, you can help Handlebars find the right property by prefixing the name of this property with ../ as necessary.
  10. You may have leftover empty <% %> tags after the previous transformations; these can be safely removed.

If, after the above steps, you still have <% code %> notation in your template, or expressions that Handlebars cannot evaluate, you may need to use other facilities from the Handlebars language or create special workarounds. If you are very unlucky, the template cannot be translated at all, but most of the time there will be a way.

Demonstration: your template

Repeating the Underscore template from your question here:

<p id="shipping-rates-feedback" <% if (success) { %> class="success" <% } else { %> class="error" <% } %>>
<% if (success) { %>
  <% if (rates.length > 1) { %>
  There are <%- rates.length %> shipping rates available for <%- address %>, starting at <%= rates[0].price %>.
  <% } else if (rates.length == 1) { %>
  There is one shipping rate available for <%- address %>.
  <% } else { %>
  We do not ship to this destination.
  <% } %>
<% } else { %>
  <%- errorFeedback %>
<% } %>
</p>
<ul id="shipping-rates">
  <% for (var i=0; i<rates.length; i++) { %>
  <li><%- rates[i].name %> at <%= rates[i].price %></li>
  <% } %>
</ul>

Following the above algorithm and using the expression rates.[1] instead of rates.length > 1 (because Handlebars cannot evaluate comparisons out of the box), we successfully obtain the following Handlebars template:

<p id="shipping-rates-feedback" {{#if success}} class="success" {{else}} class="error" {{/if}}>
{{#if success}}
  {{#if rates.[1]}}
  There are {{rates.length}} shipping rates available for {{address}}, starting at {{{rates.[0].price}}}.
  {{else}}{{#if rates}}
  There is one shipping rate available for {{address}}.
  {{else}}
  We do not ship to this destination.
  {{/if}}{{/if}}
{{else}}
  {{errorFeedback}}
{{/if}}
</p>
<ul id="shipping-rates">
  {{#each rates}}
  <li>{{name}} at {{{price}}}</li>
  {{/each}}
</ul>

You might find other templates that need to be translated as well. You can follow the same approach for those other templates.

Final note: templates embedded in HTML

Your theme includes the template in the page with the following notation.

<script id="shipping-calculator-response-template" type="text/template">
    // the template
</script>

It is important to realize that, although this is a <script> tag, the browser does not actually interpret the contents as a script. Instead, because the tag has a type that the browser doesn't know, the browser just leaves the tag as-is in the DOM and moves on to interpret the next element. This is a common trick to embed arbitrary text data in an HTML page, so that it can be picked up by a script later without the user having to see it. In this particular case, there will be a piece of your JavaScript somewhere that will do something like

var templateText = document.querySelector('#shipping-calculator-response-template').textContent;

and then pass templateText to Handlebars in order to process it. This is also the reason why replacing Handlebars back by Underscore didn't solve your problem; that script will still try to pass the template to Handlebars. Concluding, in your particular case, there is probably no need to put back the Underscore reference.



Answered By - Julian
Answer Checked By - Pedro (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to only display the available product variant prices in the products overview

 July 01, 2022     shopify     No comments   

Issue

I would like to display the lowest price of the available/instock product variants, instead of "From " + the lowest price of all product variants, even if they are not in stock. I am using the theme Prestige, any hints on how I could achieve this?

If I am right, I found the following code part in product-item.liquid which is displaying the price and also rendered in the flickity carousel and products overview pages:

<div class="ProductItem__PriceList {% if show_price_on_hover %}ProductItem__PriceList--showOnHover{% endif %} Heading">
        {%- if product.compare_at_price > product.price -%}
          <span class="ProductItem__Price Price Price--highlight Text--subdued">{{ product.price | money_without_trailing_zeros }}</span>
          <span class="ProductItem__Price Price Price--compareAt Text--subdued">{{ product.compare_at_price | money_without_trailing_zeros }}</span>
        {%- elsif product.price_varies -%}
          {%- capture formatted_min_price -%}{{ product.price_min | money_without_trailing_zeros }}{%- endcapture -%}
          {%- capture formatted_max_price -%}{{ product.price_max | money_without_trailing_zeros }}{%- endcapture -%}
          <span class="ProductItem__Price Price Text--subdued">{{ 'collection.product.from_price_html' | t: min_price: formatted_min_price, max_price: formatted_max_price }}</span>
        {%- else -%}
          <span class="ProductItem__Price Price Text--subdued">{{ product.price | money_without_trailing_zeros }}</span>
        {%- endif -%}
      </div>

      {%- if product.selected_or_first_available_variant.unit_price_measurement -%}
        <div class="ProductItem__UnitPriceMeasurement">
          <div class="UnitPriceMeasurement Heading Text--subdued">
            <span class="UnitPriceMeasurement__Price">{{ product.selected_or_first_available_variant.unit_price | money_without_trailing_zeros }}</span>
            <span class="UnitPriceMeasurement__Separator">/ </span>

            {%- if product.selected_or_first_available_variant.unit_price_measurement.reference_value != 1 -%}
              <span class="UnitPriceMeasurement__ReferenceValue">{{ product.selected_or_first_available_variant.unit_price_measurement.reference_value }}</span>
            {%- endif -%}

            <span class="UnitPriceMeasurement__ReferenceUnit">{{ product.selected_or_first_available_variant.unit_price_measurement.reference_unit }}</span>
          </div>
        </div>
      {%- endif -%}

I tried to use the following code from the Shopify-Blog https://www.shopify.com/partners/blog/collection-page-price-range

{% if available %}
  {% if product.price_varies and template == 'collection' %}
    From {{ product.price_min | money }} to {{ product.price_max | money }} 
  {% else %}
    {{ money_price }}
  {% endif %}
{% else %}
  {{ 'products.product.sold_out' | t }}
{% endif %}

This code however, displays the product as sold out.


Solution

It will be better if you set product_pricetocompare to the value of first variant. This will check if there is a variant. If there is no variant, you will see 999999, which is not good.

example:

{% for variant in product.variants %}
  {% if forloop.index == 1 %}
     {% assign product_pricetocompare = variant.price %}
  {% else %}
    {% if variant.available %}
      {% if variant.price < product_pricetocompare %}
        {% assign product_pricetocompare = variant.price %}
      {% endif %}
    {% endif %}
  {% endif %}
{% endfor %}


Answered By - Charles C.
Answer Checked By - Mary Flores (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Older Posts Home

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
All Comments
Atom
All Comments

Copyright © PHPFixing