Skip to content

Commit

Permalink
Merge branch 'release/0.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
gavinballard committed Oct 19, 2022
2 parents 7688472 + af6a21b commit f7144f3
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 12 deletions.
46 changes: 42 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ The simplest method for integration is to load the compiled and distributed `giv
"environment": "staging"
}
</script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/givex-js@0.2.0/dist/givex.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/givex-js@0.3.0/dist/givex.js"></script>
```

The easiest way to do this if you're using a Liquid-based Shopify theme is to copy-paste the Liquid snippet found in `snippets/givex-js.liquid` into your theme, and then rendering that snippet in your `theme.liquid` and `checkout.liquid`:
The easiest way to do this if you're using a Liquid-based Shopify theme is to copy-paste the Liquid snippet found in `snippets/givex-js.liquid` into your theme, and then render that snippet in your `theme.liquid` and `checkout.liquid`:

```liquid
<head>
Expand All @@ -28,8 +28,12 @@ The easiest way to do this if you're using a Liquid-based Shopify theme is to co
</head>
```

The library will automatically instantiate and initialise a top-level `givex` object in the document window, which your own code can then use to leverage Givex functionality.
When loaded in the checkout, the library will also automatically hook into the gift card input present on the page to add Givex gift card support for redemptions.
The library will automatically instantiate and initialise a top-level `givex` object in the document window, which your own code can then use to leverage Givex functionality, if you need it.

However, the library will also automatically hook into key elements on your theme's pages to provide key functionality:

* When loaded in the checkout, the library will automatically hook into the gift card input present on the page to add Givex gift card support for redemptions. See [Integrating Gift Card Redemption in the Checkout](#integrating-gift-card-redemption-in-the-checkout).
* When loaded on a page with elements marked up with specific data attributes, the library will automatically hook into a form to provide out of the box balance checking functionality. See [Integrating Balance Checking on a Page](#integrating-balance-checking-on-a-page).

### Making API Calls
Once you have an initialised Givex object, making API calls is pretty simple:
Expand Down Expand Up @@ -83,6 +87,31 @@ givex.api.preauth({
});
```

### Integrating Gift Card Redemption in the Checkout
If you've copied the `givex-js.liquid` snippet into your theme, and rendered it anywhere inside your `checkout.liquid` layout file, you should be done!

The library will automatically hook into the gift card application box in the checkout, show and hide the gift card security code input as needed, and pass requests off to the Givex Integration without any further coding required.

### Integrating Balance Checking on a Page
While you're welcome to use the API client directly to make requests to the `checkBalance` API endpoint, if you render the `givex-js.liquid` snippet on a page with a specially-marked-up form, the library will automatically hook into it and provide an out of the box balance checker for you.

You're welcome to style your form however you like, the key data attributes you'll need to add are:

```html
<form data-givex-balance-checker="form">
<div data-givex-balance-checker="result"></div>
<input type="text" data-givex-balance-checker="number" />
<input type="text" data-givex-balance-checker="pin" />
<button type="submit" data-givex-balance-checker="submit"></button>
</form>
```

If these are present, then when a user submits the form via button click or hitting enter, the submission will be automatically intercepted and passeed to the Givex Integration.
The result of the balance check lookup will be rendered into the `<div data-givex-balance-checker="result">` DOM element.
If you'd like to customise how that rendered message is displayed, you can update the default templates in `givex-js.liquid`.

The most common approach we've seen for balance checkers is to create a new specific page template in your theme (`page.balance-checker.liquid` or `page.balance-checker.json`) and create a dedicated balance checker page on your store.

### Translations
All text rendered by the library is translatable via Shopify's default locale functionality -- indeed, there's an expectation that translation keys will be added to the store's default locale file, whether that's `en.default.json` or something else.

Expand All @@ -91,6 +120,15 @@ To apply the default translations, the following can be copied as a top-level ob
```json
{
"givex": {
"balance_checker": {
"title": "Check your gift card balance",
"number_label": "Enter card number",
"number_placeholder": "Enter card number",
"security_code_label": "Enter PIN",
"security_code_placeholder": "Enter PIN",
"submit": "Submit",
"loading": "Checking balance..."
},
"checkout": {
"security_code_label": "Gift card? Enter PIN",
"security_code_placeholder": "Gift card? Enter PIN"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "givex-js",
"version": "0.2.0",
"version": "0.3.0",
"description": "Javascript library for Disco Labs' Givex Integration for Shopify.",
"repository": {
"type": "git",
Expand Down
27 changes: 24 additions & 3 deletions snippets/givex-js.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
{%- liquid

assign DEFAULT_CARD_CODE_LENGTH = 20
assign DEFAULT_CDN_URL = 'https://cdn.jsdelivr.net/npm/givex-js@0.2.0/dist/givex.js'
assign DEFAULT_CDN_URL = 'https://cdn.jsdelivr.net/npm/givex-js@0.3.0/dist/givex.js'
assign DEFAULT_ENDPOINT = 'https://givex-integration.discolabs.com/api/v1'
assign DEFAULT_SECURITY_CODE_POLICY = 'security_code_is_required'

-%}

{%- comment -%}
HTML templates that may be rendered by the integration.
HTML templates that may be rendered by the integration on the checkout or balance checker pages.
{%- endcomment -%}
{%- capture HTML_TEMPLATE_CHECKOUT_SECURITY_CODE -%}
<div class="fieldset fieldset--security-code">
Expand All @@ -41,6 +41,24 @@
</div>
{%- endcapture -%}

{%- capture HTML_TEMPLATE_BALANCE_CHECKER_LOADING -%}
<div>
{{ 'givex.balance_checker.loading' | t }}
</div>
{%- endcapture -%}

{%- capture HTML_TEMPLATE_BALANCE_CHECKER_SUCCESS -%}
<div>
{% raw %}{{ message }}{% endraw %}
</div>
{%- endcapture -%}

{%- capture HTML_TEMPLATE_BALANCE_CHECKER_ERROR -%}
<div>
{% raw %}{{ message }}{% endraw %}
</div>
{%- endcapture -%}

{%- comment -%}
Attempt to parse translations as a JSON object.
{%- endcomment -%}
Expand Down Expand Up @@ -86,7 +104,10 @@
"endpoint": "{{ shop.metafields.givex.endpoint | default: DEFAULT_ENDPOINT }}",
"security_code_policy": "{{ shop.metafields.givex.security_code_policy | default: DEFAULT_SECURITY_CODE_POLICY }}",
"templates": {
"checkout_security_code": {{ HTML_TEMPLATE_CHECKOUT_SECURITY_CODE | json }}
"checkout_security_code": {{ HTML_TEMPLATE_CHECKOUT_SECURITY_CODE | json }},
"balance_checker_loading": {{ HTML_TEMPLATE_BALANCE_CHECKER_LOADING | json }},
"balance_checker_success": {{ HTML_TEMPLATE_BALANCE_CHECKER_SUCCESS | json }},
"balance_checker_error": {{ HTML_TEMPLATE_BALANCE_CHECKER_ERROR | json }}
},
"translations": {{ givex_translations }}
}
Expand Down
40 changes: 40 additions & 0 deletions src/lib/balance_checker/balance_checker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
SELECTOR_BALANCE_CHECKER_FORM,
} from "../constants";
import { BalanceCheckerForm } from "./balance_checker_form";

export class BalanceChecker {

constructor(document, api, config) {
this.document = document;
this.api = api;
this.config = config;

this.initialise();
}

initialise() {
this.debug('initialise()');

const { document, api, config } = this;

// define an event handler for page changes
document.querySelectorAll(SELECTOR_BALANCE_CHECKER_FORM).forEach(formElement => {
// skip if the form is already initialised
if(formElement.dataset.givex === 'true') {
return;
}

new BalanceCheckerForm(formElement, api, config);
});
}

debug(...args) {
if(!this.config.debug) {
return;
}

console.log('[Givex Balance Checker]', ...args);
}

}
110 changes: 110 additions & 0 deletions src/lib/balance_checker/balance_checker_form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
SECURITY_CODE_POLICY_IS_REQUIRED,
SELECTOR_BALANCE_CHECKER_NUMBER,
SELECTOR_BALANCE_CHECKER_PIN,
SELECTOR_BALANCE_CHECKER_RESULT,
SELECTOR_BALANCE_CHECKER_SUBMIT
} from "../constants";
import {renderHtmlTemplate} from "../helpers";

export class BalanceCheckerForm {

constructor(formElement, api, config) {
this.formElement = formElement;
this.api = api;
this.config = config;

this.initialise();
}

initialise() {
this.debug('initialise()');

const { formElement } = this;

// store references to other elements
this.numberElement = formElement.querySelector(SELECTOR_BALANCE_CHECKER_NUMBER);
this.pinElement = formElement.querySelector(SELECTOR_BALANCE_CHECKER_PIN);
this.resultElement = formElement.querySelector(SELECTOR_BALANCE_CHECKER_RESULT);
this.submitElement = formElement.querySelector(SELECTOR_BALANCE_CHECKER_SUBMIT);

// register event listeners
this.formElement.addEventListener('submit', this.handleSubmit.bind(this));

// mark this form element as initialised
formElement.dataset.givex = 'true';
}

handleSubmit(e) {
this.debug('handleSubmit()', e);

const { numberElement, pinElement, submitElement, resultElement, api, config } = this;

// prevent form submission
e.preventDefault();
e.stopPropagation();

// if the security code input is required, present and empty, focus it and return
if((config.security_code_policy === SECURITY_CODE_POLICY_IS_REQUIRED) && pinElement && pinElement.value.trim().length === 0) {
pinElement.focus();
return false;
}

// add loading spinner and disable the button to prevent resubmission.
submitElement.classList.add('btn--loading');
submitElement.disabled = true;

// render the blank result state
this.resultElement = renderHtmlTemplate(config, resultElement, "balance_checker_loading", {}, "replaceWith");

// build values for preauthorisation request
const number = numberElement.value;
const pin = pinElement ? pinElement.value : null;

// make balance check request
api.checkBalance({
number,
pin,
onSuccess: this.handleBalanceCheckSuccess.bind(this),
onFailure: this.handleBalanceCheckFailure.bind(this)
});
}

handleBalanceCheckSuccess(result) {
this.debug('handleBalanceCheckSuccess', result);

const { config, resultElement } = this;
this.resultElement = renderHtmlTemplate(config, resultElement, "balance_checker_success", {
message: result.message
}, "replaceWith");

this.handleBalanceCheckComplete();
}

handleBalanceCheckFailure(error) {
this.debug('handleBalanceCheckFailure', error);

const { config, resultElement } = this;
this.resultElement = renderHtmlTemplate(config, resultElement, "balance_checker_error", {
message: error.message
}, "replaceWith");

this.handleBalanceCheckComplete();
}

handleBalanceCheckComplete() {
const { submitElement } = this;

submitElement.classList.remove('btn--loading');
submitElement.disabled = false;
}

debug(...args) {
if(!this.config.debug) {
return;
}

console.log('[Givex BalanceCheckerForm]', ...args);
}

}
6 changes: 6 additions & 0 deletions src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ export const STEP_CONTACT_INFORMATION = 'contact_information';
export const STEP_SHIPPING_METHOD = 'shipping_method';
export const STEP_PAYMENT_METHOD = 'payment_method';

export const SELECTOR_BALANCE_CHECKER_FORM = '[data-givex-balance-checker="form"]';
export const SELECTOR_BALANCE_CHECKER_NUMBER = '[data-givex-balance-checker="number"]';
export const SELECTOR_BALANCE_CHECKER_PIN = '[data-givex-balance-checker="pin"]';
export const SELECTOR_BALANCE_CHECKER_RESULT = '[data-givex-balance-checker="result"]';
export const SELECTOR_BALANCE_CHECKER_SUBMIT = '[data-givex-balance-checker="submit"]';

export const SELECTOR_DISCOUNT_INPUT = '[data-discount-field="true"]';
export const SELECTOR_FIELDSET = '.fieldset';
export const SELECTOR_FORM = 'form';
Expand Down
2 changes: 2 additions & 0 deletions src/lib/givex.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ApiClient } from "./api_client";
import { Checkout } from "./checkout/checkout";
import { BalanceChecker } from "./balance_checker/balance_checker";

export class Givex {

constructor(document, Shopify, config = {}) {
const api = new ApiClient(config);
this.api = api;
this.checkout = new Checkout(document, Shopify, api, config);
this.balanceChecker = new BalanceChecker(document, api, config);
}

}
16 changes: 12 additions & 4 deletions src/lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ export const parseJSONScript = (document, id) => {
} catch { return null; }
};

// render a HTML template after the given target element
export const renderHtmlTemplate = (config, targetElement, templateName) => {
const templateDocument = new DOMParser().parseFromString(config.templates[templateName], 'text/html');
// render a HTML template after the given target element, with optional context interpolation
// the newly rendered element is returned
export const renderHtmlTemplate = (config, targetElement, templateName, context = {}, renderMethod = 'after') => {
const interpolatedTemplate = Object.entries(context).reduce((output, value) => {
const [k, v] = value;
return output.replace(new RegExp(`{{ ${k} }}`, 'g'), v);
}, config.templates[templateName]);

const templateDocument = new DOMParser().parseFromString(interpolatedTemplate, 'text/html');
const templateElement = templateDocument.querySelector('body > *');

targetElement.after(templateElement);
targetElement[renderMethod](templateElement);

return templateElement;
};

0 comments on commit f7144f3

Please sign in to comment.