Sneak Peek: Mailchimp ABANDONED_CART merge tags for custom HTML email

You can use ABANDONED_CART blocks similar to PRODUCT_RECOMMENDATIONS

For example, you can display a list of items in the cart:

*|ABANDONED_CART:[$total=3]|*
<a href="*|CART:URL|*">
<img src="*|PRODUCT:IMAGE_URL|*" />
<br />*|PRODUCT:TITLE|*
<br />*|PRODUCT:PRICE|*
</a>
*|END:ABANDONED_CART|*

Or create a button:

*|ABANDONED_CART:[$total=1]|*
<a href="*|CART:URL|*">Complete your order</a>
*|END:ABANDONED_CART|*

Search suggestions for Shopify

Add a search form to your theme:

<form action="/search" method="get" role="search" id="header-search">
<input type="hidden" name="type" value="product">
<label for="q">{{ 'general.search.placeholder' | t }}</label>
<input type="search" id="q" name="q" value="{{ search.terms | escape }}" class="input-group-field" />
<input type="submit" value="{{ 'general.search.submit' | t }}" class="input-group-field" />
</form>

Create a template called search.json.liquid

{% layout none %}
{% capture results %}
  {% for item in search.results %}
    {% assign product = item %}
    { 
      "vendor"    : {{ product.vendor | json }},
      "title"    : {{ product.title | json }},
      "url"      : {{ product.url | within: product.collections.last | json }},
      "thumbnail": {{ product.featured_image.src | product_img_url: 'thumb' | json }}
    }
    {% unless forloop.last %},{% endunless %}
  {% endfor %}
{% endcapture %}
{
  "results_count": {{ search.results_count }},
  "results": [{{ results }}]
}

Create this snippet and add to theme layout.

<script>
$(function() {
  // Current Ajax request.
  var currentAjaxRequest = null;
  // Grabbing all search forms on the page, and adding a .search-results list to each.
  var searchForms = $('form[action="/search"]').css('position','relative').each(function() {
    // Grabbing text input.
    var input = $(this).find('input[name="q"]');
    // Adding a list for showing search results.
    var offSet = input.position().top + input.innerHeight();
    $('<ul class="search-results"></ul>').appendTo($(this)).hide();    
    // Listening to keyup and change on the text field within these search forms.
    input.attr('autocomplete', 'off').bind('keyup change input propertychange', function() {
      // What's the search term?
      var term = $(this).val();
      // What's the search form?
      var form = $(this).closest('form');
      // What's the search URL?
      var searchURL = '/search?type=product&q=' + term;
      // What's the search results list?
      var resultsList = form.find('.search-results');
      // If that's a new term and it contains at least 3 characters.
      if (term.length > 3 && term != $(this).attr('data-old-term')) {
        // Saving old query.
        $(this).attr('data-old-term', term);
        // Killing any Ajax request that's currently being processed.
        if (currentAjaxRequest != null) currentAjaxRequest.abort();
        // Pulling results.
        currentAjaxRequest = $.getJSON(searchURL + '&view=json', function(data) {
          // Reset results.
          resultsList.empty();
          // If we have no results.
          if(data.results_count == 0) {
            // resultsList.html('<li><span class="title">No results.</span></li>');
            // resultsList.fadeIn(200);
            resultsList.hide();
          } else {
            // If we have results.
            $.each(data.results, function(index, item) {
              var link = $('<a></a>').attr('href', item.url);
              link.append('<span class="thumbnail"><img src="' + item.thumbnail + '" /></span>');
              link.append('<span class="title"><strong>' + item.vendor + '</strong><br >' + item.title + '</span>');
              link.wrap('<li></li>');
              resultsList.append(link.parent());
            });
            // The Ajax request will return at the most 10 results.
            // If there are more than 10, let's link to the search results page.
            if(data.results_count > 10) {
              resultsList.append('<li><span class="title"><a href="' + searchURL + '">See all results (' + data.results_count + ')</a></span></li>');
            }
            resultsList.fadeIn(200);
          }        
        });
      }
    });
  });
  // Clicking outside makes the results disappear.
  $('body').bind('click', function(){
    $('.search-results').hide();
  });
});
</script>

Reference: https://help.shopify.com/themes/customization/store/enable-autocomplete-for-search-boxes

Open Graph Headers for Shopify

The process is:

  • Create theme settings for open graph image
  • Create your image (1200x630px) and upload it to new theme settings
  • Create or replace the ‘social-meta-tags.liquid’ snippet

Theme settings

Add this to settings_schema.json, click customise theme and upload your image

  {
    "name": "Open Graph image",
    "settings": [
      {
        "type": "header",
        "content": "Open graph"
      },
      {
        "type": "checkbox",
        "id": "opengraph_enable",
        "label": "Use custom image?"
      },
      {
        "type": "image",
        "id": "opengraph.png",
        "label": "Image",
        "info": "1200 x 630 ",
        "max-width": 1200,
        "max-height": 630
      }
    ]
  },

Social Meta Tags Snippet

Create or replace the ‘social-meta-tags.liquid‘ snippet. Include the snippet in the HTML head.

This has been modified to include the open graph image (from settings above) and a fall back for articles that do not have a featured image, but contain an image in the content field.

<!-- /snippets/social-meta-tags.liquid -->
{% comment %}
  Add Facebook and Pinterest Open Graph meta tags to product pages
  for friendly Facebook sharing and Pinterest pinning.

  More info Open Graph meta tags
    - https://developers.facebook.com/docs/opengraph/using-objects/
    - https://developers.pinterest.com/rich_pins/

  Use the Facebook Open Graph Debugger for validation (and cache clearing)
    - http://developers.facebook.com/tools/debug

  Validate your Pinterest rich pins
    - https://developers.pinterest.com/rich_pins/validator/
{% endcomment %}
{% if template contains 'product' %}
  <meta property="og:type" content="product">
  <meta property="og:title" content="{{ product.title | strip_html | escape }}">
  {% for image in product.images limit:3 %}
  <meta property="og:image" content="https:{{ image.src | img_url: 'grande' }}">
  <meta property="og:image:secure_url" content="https:{{ image.src | img_url: 'grande' }}">
  {% endfor %}
  <meta property="og:price:amount" content="{{ product.price | money_without_currency | strip_html | escape }}">
  <meta property="og:price:currency" content="{{ shop.currency }}">
{% elsif template contains 'article' %}
  <meta property="og:type" content="article">
  <meta property="og:title" content="{{ article.title | strip_html | escape }}">
  {% assign img_tag = '<' | append: 'img' %}
  {% if article.image %}
    <meta property="og:image" content="http:{{ article | img_url: 'medium ' }}">
    <meta property="og:image:secure_url" content="https:{{ article | img_url: 'medium ' }}">
  {% elsif article.content contains img_tag %}
    {% assign src = article.content | split: 'src="' %}
    {% assign src = src[1] | split: '"' | first | remove: 'https:' | remove: 'http:' %}
    {% if src %}
  <meta property="og:image" content="https:{{ src }}">
  <meta property="og:image:secure_url" content="https:{{ src }}">
    {% endif %}
  {% endif %}
{% elsif template == 'password' %}
  <meta property="og:type" content="website">
  <meta property="og:title" content="{{ shop.name }}">
  <meta property="og:url" content="{{ shop.url }}">
  {% if settings.opengraph_enable %}
  <meta property="og:image" content="https:{{ 'opengraph.png' | asset_url }}">
  <meta property="og:image:secure_url" content="https:{{ 'opengraph.png' | asset_url }}">
  {% endif %}
{% else %}
  <meta property="og:type" content="website">
  <meta property="og:title" content="{{ page_title | escape }}">
  {% if settings.opengraph_enable %}
  <meta property="og:image" content="https:{{ 'opengraph.png' | asset_url }}">
  <meta property="og:image:secure_url" content="https:{{ 'opengraph.png' | asset_url }}">
  {% endif %}
{% endif %}
{% if page_description %}
  <meta property="og:description" content="{{ page_description | escape }}">
{% endif %}
  <meta property="og:url" content="{{ canonical_url }}">
  <meta property="og:site_name" content="{{ shop.name }}">

{% comment %}
  This snippet renders meta data needed to create a Twitter card
  for products and articles.

  Your cards must be approved by Twitter to be activated
    - https://dev.twitter.com/docs/cards/validation/validator

  More information:
   - https://dev.twitter.com/docs/cards/types/product-card
   - https://dev.twitter.com/docs/cards/types/summary-card
{% endcomment %}

{% comment %}
  Twitter user name of the site, based on theme settings
{% endcomment %}
{% if settings.twittercard_handle != blank %}
  <meta name="twitter:site" content="{{ settings.twittercard_handle }}">
{% endif %}
{% if template contains 'product' %}
  <meta name="twitter:card" content="product">
  <meta name="twitter:title" content="{{ product.title }}">
  <meta name="twitter:description" content="{{ product.description | strip_html | truncatewords: 140, '' | escape }}">
  <meta name="twitter:image" content="https:{{ product.featured_image.src | img_url: 'medium' }}">
  <meta name="twitter:image:width" content="240">
  <meta name="twitter:image:height" content="240">
  <meta name="twitter:label1" content="Price">
  {% assign price = product.price | money_with_currency | strip_html | escape %}
  <meta name="twitter:data1" content="{% if product.price_varies %}{{ 'products.general.from_text_html' | t: price: price }}{% else %}{{ price }}{% endif %}">
  {% if product.vendor != blank %}
  <meta name="twitter:label2" content="Brand">
  <meta name="twitter:data2" content="{{ product.vendor | escape }}">
  {% else %}
  <meta name="twitter:label2" content="Availability">
  <meta name="twitter:data2" content="In stock">
  {% endif %}
{% elsif template contains 'article' %}
  <meta name="twitter:card" content="summary">
  <meta name="twitter:title" content="{{ article.title }}">
  <meta name="twitter:description" content="{{ article.excerpt_or_content | strip_html | truncatewords: 140, '' | escape }}">
  {% comment %}
    Check if content contains an image to add to the card
      - Source: http://blog.viralica.com/2013/09/twitter-product-cards-on-shopify/
  {% endcomment %}
  {% if article.content contains "<img" %}
    {% assign src = article.content | split: 'src="' %}
    {% assign src = src[1] | split: '"' | first | replace: '//cdn', 'http://cdn' | replace: 'http:http://', 'http://' | remove: 'https:' %}
    {% if src %}
  <meta property="twitter:image" content="{{ src }}">
    {% endif %}
  {% endif %}
{% endif %}

Custom Addthis Social Media Sharing Buttons for Shopify (for free)

The process is:

  1. Create a Google Analtyics account and get your site ID
  2. Sign-up to Addthis and get your profile ID
  3. Sign-up to Bitly for URL shortening on Tweets (and get your API username and key)
  4. Add the Shopify Theme settings to your settings_schema.js
  5. Add the javascript to your Shopify theme layout file
  6. Add HTML for sharing buttons to your Shopify pages
  7. Add custom icon font and CSS to style your buttons
  8. Configure the Addthis Custom HTML Email Template
  9. Create a Facebook App and add the id to the settings

Addthis theme settings

Add this code to your Shopify settings_schema.json then click on “customise theme” to enter the right values.

  {
    "name": "Addthis Sharing Settings",
    "settings": [
      {
        "type": "checkbox",
        "id": "addthis_use",
        "label": "Use Addthis?"
      },
      {
        "type": "text",
        "id": "addthis_id",
        "label": "AddThis ID"
      },
      {
        "type": "text",
        "id": "google_analytics_id",
        "label": "Google Analytics ID"
      },
      {
        "type": "text",
        "id": "addthis_twitter_id",
        "label": "Twitter name"
      },
      {
        "type": "text",
        "id": "addthis_twitter_string",
        "label": "Twitter string",
        "default": "*title* Coming Soon. #signup #exclusiveaccess"
      },
      {
        "type": "checkbox",
        "id": "addthis_bitly_use",
        "label": "Use Bitly?"
      },
      {
        "type": "checkbox",
        "id": "addthis_email_template_use",
        "label": "Use custom email template?"
      },
      {
        "type": "text",
        "id": "addthis_email_template_name",
        "label": "Add this email template name",
        "default": "product_template"
      },
      {
        "type": "text",
        "id": "addthis_facebook_app_id",
        "label": "Facebook App ID"
      },
      {
        "type": "text",
        "id": "addthis_facebook_redirect_url",
        "label": "Facebook Redirect URL"
      }
    ]
  }

Addthis Javascript

Create a new snippet called “addthis.liquid” and add it to the end of your theme (before end of body tags) using {% include ‘addthis’ %}

{% if settings.addthis_use %}
<script>
  jQuery(function($) {
    initAddThis();
    jQuery('.sharenulify').on('click', function (e) {
      return false;
    });
  });
  function initAddThis() 
  {
    if ( typeof( window[ 'addthis' ] ) != "undefined" ) {
      addthis.init()
    } 
  }
</script> 

{% capture addthis_title %}{% endcapture %}
{% capture addthis_description %}{% endcapture %}
{% capture addthis_image %}{% endcapture %}
{% if template contains 'product' %}
  {% capture addthis_title %}{{ product.title | strip_html | escape }}{% endcapture %}
  {% capture addthis_description %}{{ product.description | strip_html | strip_newlines | truncatewords: 30, '' | escape }}{% endcapture %}
  {% capture addthis_image %}https:{{ product.featured_image | img_url: 'large' }}{% endcapture %}
{% elsif template contains 'article' %}
  {% capture addthis_title %}{{ article.title | strip_html | escape }}{% endcapture %}
  {% capture addthis_description %}{{ article.excerpt_or_content | strip_html | strip_newlines | truncatewords: 30, '' | escape }}{% endcapture %}
  {% if article.image %}
    {% capture addthis_image %}https:{{ article.image.src | img_url: 'large' }}{% endcapture %}
  {% endif %}
{% elsif template contains 'page' %}
  {% capture addthis_title %}{{ page.title | strip_html | escape }}{% endcapture %}
  {% capture addthis_description %}{{ page.content | strip_html | strip_newlines | truncatewords: 30, '' | escape }}{% endcapture %}
  {% assign img_tag = '<' | append: 'img' %}
  {% if page.content contains img_tag %}
    {% assign src = page.content | split: 'src="' %}
    {% assign src = src[1] | split: '"' | first | remove: 'https:' | remove: 'http:' | replace: '_small.', '_large.' %}
    {% capture addthis_image %}https:{{ src }}{% endcapture %}
  {% endif %}
{% else %}
  {% capture addthis_title %}{{ page_title | escape }}{% endcapture %}
  {% if page_description %}{% capture addthis_description %}{{ page_description | escape }}{% endcapture %}{% endif %}
{% endif %}

{% capture addthis_twitter_string_calculated %}{{ settings.addthis_twitter_string | replace: '*title*', addthis_title }}{% endcapture %}  
<script type="text/javascript">
  var addthis_config = {
    {% if settings.google_analytics_id.size > 0 %}
    data_ga_property: '{{ settings.google_analytics_id }}',
    data_ga_social : true,
    {% endif %}
    "data_track_addressbar":false,
    ui_use_css : true,
    image_include: "at_include",
    image_exclude: "at_exclude"
  };
  addthis_share = {
    {% if settings.addthis_bitly_use %}
    url_transforms : {
      shorten: {
        twitter: 'bitly',
        facebook: 'bitly'
      }
    }, 
    shorteners : {
      bitly : {} 
    },
    {% endif %}
    url: "{{ canonical_url }}",
    title: "{{ addthis_title }}",
    description: "{{ addthis_description }}",
    passthrough : {
      twitter: {
        {% if settings.addthis_twitter_id.size > 0 %}via: "{{ settings.addthis_twitter_id }}",{% endif %}
        text: "{{ addthis_twitter_string_calculated }}"
      }{% if settings.addthis_facebook_app_id.size > 0 %},
      facebook: {
        app_id: '{{ settings.addthis_facebook_app_id }}',
        redirect_uri: '{{ settings.addthis_facebook_redirect_url }}',
      }{% endif %}
    }
    {% if settings.addthis_email_template_use and addthis_image.size > 0 %},
    email_template: "{{ settings.addthis_email_template_name }}",
    email_vars: { 
      thisTitle: "{{ addthis_title }}" ,
      thisDescription: "{{ addthis_description }}", 
      thisImage: "{{ addthis_image }}" ,
    }
    {% endif %}
  }
</script>        
<script type="text/javascript" src="https://s7.addthis.com/js/300/addthis_widget.js#pubid={{ settings.addthis_id }}" async="async"></script>
{% endif %}

HTML for share buttons

Create a new snippet called “addthis-share-buttons.liquid” and add it on any page you like using {% include ‘addthis-share-buttons’ %}

<div class="product-share">
  <a class="product-share-button" href="#">Share</a>
  <div class="share">
    <ul>
      <li><a class="tooltip sharebutton addthis_button_facebook" id="share_facebook" title="Share {{ mysharetitle }} on Facebook"><i class="fa fa-facebook"></i></a></li>
      <li><a class="tooltip sharebutton addthis_button_twitter" id="share_twitter" title="Share {{ mysharetitle }} on Twitter"><i class="fa fa-twitter"></i></a></li>
      <li><a class="tooltip sharebutton addthis_button_tumblr" id="share_tumblr" title="Share {{ mysharetitle }} on Tumblr"><i class="fa fa-tumblr"></i></a></li>
      <li><a class="tooltip sharebutton sharenulify addthis_button_pinterest_share" id="share_pinterest" title="Share {{ mysharetitle }} on Pinterest"><i class="fa fa-pinterest"></i></a></li>
      <li><a class="tooltip sharebutton addthis_button_email" id="share_email" title="Send  {{ mysharetitle }} to a friend by Email"><i class="fa fa-envelope-o"></i></a></li>
      <li><a class="tooltip sharebutton addthis_button_compact" id="share_other" title="More share options"><i class="fa fa-bookmark"></i></a></li>
    </ul>
  </div>
</div>

Use CSS to style your buttons

For Fontawesome icons, remember to add the font css to your theme file.

.product-share-button, .share { display:inline-block; }
.share ul { display:inline-block; list-style:none; margin:0; padding:0; }
.share li {  margin:0; padding:0; display:inline-block; }
.share li a { display:inline-block; margin:0; padding:10px; font-size:18px; line-height: 38px;}

Add coupon code form to Magento Onepage Checkout Review Step

Sometimes you want customers to be able to apply the discount at the end of checkout. For example, when you have a mini cart that links customers straight to “checkout” without viewing the “cart” page.

1. Create a new module (with namespace Ronnyew_Checkout)

File: app/etc/modules/Ronnyew_Checkout.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Ronnyew_Checkout>
            <active>true</active>
            <codePool>local</codePool>
        </Ronnyew_Checkout>
    </modules>
</config>

2. Override the core Onepagecontroller

File: app/local/Ronneyw/Checkout/etc/config.xml

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <modules>
        <Ronnyew_Checkout>
            <version>0.2.0</version>
        </Ronnyew_Checkout>
    </modules>  

    <frontend>
        <routers>
            <checkout>
                <args>
                    <modules>
                        <Ronnyew_Checkout before="Mage_Checkout">Ronnyew_Checkout</Ronnyew_Checkout>
                    </modules>
                </args>
            </checkout>
        </routers>
    </frontend>
</config>

3. Create a new method that will process the coupon code

File: app/local/Ronneyw/Checkout/controllers/OnepageController.php

<?php

require_once 'Mage/Checkout/controllers/OnepageController.php';

class Ronnyew_Checkout_OnepageController extends Mage_Checkout_OnepageController
{
    
	public function couponAction() {
		$this->loadLayout('checkout_onepage_review');
		$this->couponCode = (string) $this->getRequest()->getParam('coupon_code');
		Mage::getSingleton('checkout/cart')->getQuote()->getShippingAddress()->setCollectShippingRates(true);
		Mage::getSingleton('checkout/cart')->getQuote()->setCouponCode(strlen($this->couponCode) ? $this->couponCode : ' ')->collectTotals()->save();
		$result['goto_section'] = 'review';
		$result['update_section'] = array( 'name' => 'review', 'html' => $this->_getReviewHtml() );
		$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result));
	}

}

4. Add this form to the “review” template

File: app/design/frontend/YOURPACKAGE/YOURTHEME/template/checkout/onepage/review/info.phtml

<?php $getCouponCode = Mage::getSingleton('checkout/cart')->getQuote()->getCouponCode(); ?>
<form id="discount-coupon-form" action="<?php echo $this->getUrl('checkout/onepage/coupon')?>" method="post">
<label for="coupon_code"> <?php echo $this->__('Enter your coupon code if you have one.') ?></label><br />
<input id="coupon_code" name="coupon_code" value="<?php echo $getCouponCode; ?>"/>
<?php  if(strlen($getCouponCode)) { ?>
<button type="button" onclick="updateCoupon(2); return false;" value="<?php echo $this->__('Remove Coupon')?>"><span><?php echo $this->__('Remove Coupon') ?></span></button>
<?php } ?>
<button type="button" onclick="updateCoupon(1); return false;" value="<?php echo $this->__('Apply Coupon')?>"><span><?php echo $this->__('Apply Coupon') ?></span></button>
</form>

5. Add this javascript to the onepage.phtml template

File: app/design/frontend/YOURPACKAGE/YOURTHEME/template/checkout/onepage.phtml

<script type="text/javascript">
//<![CDATA[
function updateCoupon(reqid) {
	var reqid;
	if(reqid == 2) {  $('coupon_code').setValue('');  }
	$('discount-coupon-form').request({
		method: 'post',
		onComplete: payment.onComplete,
		onSuccess: payment.onSave,
		onFailure: checkout.ajaxFailure.bind(checkout),
	})
}
//]]>
</script>

6. Style it and add HTML markup to suit your theme

Check if a WordPress custom image size exists and provide a fallback

Sometimes you want to regenerate thumbnails to new sizes to suit a new theme, or make it retina-ready.

When a custom image size does not exist, WordPress returns the “full size version”.

But what if you want to check if a specific size exists, and fallback to the old resized version.

Let’s say you add a new custom image sizes:

<?php
if ( function_exists( 'add_image_size' ) ) {
   add_image_size( 'small', 100, 100, true ); 
   add_image_size( 'custom_size', 300, 300, true ); // this one is new
}
?>

Then you use the regenerate thumbnails plugin to resize all your images. But this can take a long time, so you might want to provide a fallback in your theme for the duration.

In the loop, check if the size you want exists before output.

Note: This example requires the Advanced Custom Fields plugin but you can modify it for any template.

<?php

// check for custom field
if( $image_id = get_field('field_name', false, false) ): 
    // if there is a custom field value in the database
	
	// get the full size version
	$full_size_image = wp_get_attachment_image_src( $image_id, 'full'); 
	
	// get the one we want
    $custom_size_image = wp_get_attachment_image_src( $image_id, 'custom_size'); 
	
	// check if they are the same
    if (!isset($full_size_image[0]) || !isset($size_we_need_image[0]) || $full_size_image[0] == $size_we_need_image[0]):
        // show the small version as a fallback, or show the full verison if you want
		$show = wp_get_attachment_image( $image_id, 'small' ); 
    else:
        // show the custom version that exists
        $show = wp_get_attachment_image( $image_id, 'custom_size' );
    endif;
    echo $show;
else : 
    // there is no custom field, so let's just grab the featured image for this post
    $image_id = get_post_thumbnail_id();
	
	// get the full size version
	$full_size_image = wp_get_attachment_image_src( $image_id, 'full'); 
	
	// get the one we want
    $custom_size_image = wp_get_attachment_image_src( $image_id, 'custom_size'); 
	
    // check if they are the same
    if (!isset($full_size_image[0]) || !isset($size_we_need_image[0]) || $full_size_image[0] == $size_we_need_image[0]):
        // show the small version as a fallback, or show the full verison if you want
		$show = wp_get_attachment_image( $image_id, 'small' ); 
    else:
        // show the custom version that exists
        $show = wp_get_attachment_image( $image_id, 'custom_size' );
    endif;
    echo $show;
    //echo 'thumb';
endif;
?>

Validate Magento form fields as you type with jQuery


jQuery('.input-text').on('blur', function(){
		Validation.validate(this);
	});