Nyla Custom App

Integrate apps with your Nyla site

Warning: this is an advanced feature, and if used incorrectly can lead to performance and functionality issues on your site. If you are unsure what you are doing, we suggest to work with an expert when adding scripts to your site. 


This article covers: 

Introduction


Nyla’s custom app feature enables you to integrate apps with your Nyla site. You can use the following tools to add third party functionality to your site: 


Script Loader: 

  • Integrate script based apps and load scripts on your Nyla site
  • Inject content items to load scripts in specific places on your site
  • You can also load scripts globally

Use cases: 

  • Integrate global script based apps such as the Gorgias Chat widget.   
  • Integrate script based apps that require content items, such as YotPo reviews widgets. 

Form action Loader: 

  • Create form actions for you to use in forms the editor 

This is useful for adding a form action, for example in the case of an app like Klaviyo that requires form entries to be sent to a specific location. 


Link action loader: 

  • Create link actions to use on your site 

Note: Pixels/tracking can be added through Google Tag Manager using Nyla’s data layer as required. 

How to know if an app can be integrated with Nyla? 


To find out if an app is compatible with Nyla, this blurb is a good way to qualify whether the app provider in question offers headless support:


“We have a headless site with nyla.app that comprises a fully custom frontend up until checkout. We connect to Shopify using the Shopify Storefront API. Everything from checkout and beyond is powered by Shopify. Analytics can be added via Google Tag Manager. 


  • Does your app have headless support for Shopify? If so, could you please share documentation on how to install your app on a headless site? 
  • Are there any limitations on your app functionality for a headless implementation vs how it works on a Shopify theme? 
  • Do you have any differences in pricing for using a headless setup? 
  • Are there any additional setup steps we’d need to go through in order to get the functionality up and running?”

If the app does offer headless support and only requires scripts to be added to your site in order to install, then you should be able to add the app using Nyla’s Custom App. 


If the app requires API connection, then this will require you to make a feature request to the Nyla team. 

What knowledge is required to use Nyla Custom App? 


In order to successfully use script, form and link action loaders, you need to have a knowledge of how to use low-code tooling, as well as how to build in Nyla. You need to: 

  • Have a knowledge of how to use Nyla and what the different entities in the product are. For example you should know how to use content items, form actions and link actions.  
  • Know how to use Nyla properties
  • Know how to read/write JavaScript code

Using Script Loader


Script loader enables you to add scripts to your Nyla site. This is particularly useful for integrating apps that rely on scripts being loaded, which tend to be the majority in the Shopify App store. 


Here you can set scripts to load globally (meaning they’ll load across your site on all pages, for example a Live Chat Widget). You can also add content items so that you can use them in specific places within your sections. An example of this would be a reviews widget that you want to load in a specific part of a page. 


Script Loader has the following sections: 

  • Details: Enables you to activate your script, add a name and description, define whether your script is global, as well as set your loading strategy. 

 Global metatags: Add HTML meta tags to your site.

  • Scripts: Enables you to add the list of scripts the content item or global app should load according to the app documentation. You can either load Scripts as Inline Scripts, or a list of Script URLs, along with custom loading conditions if required. 

Custom Loading Condition Examples: 

return typeof window.SmileUI !== 'undefined';
return window.loyaltylion && typeof window.loyaltylion.init === 'function';

Inline Script example:

const productId = {{product.id}}

// fetch some data/script with your product id and inject it in the site

Example of the attribute as a function field (found inside Script URL options): 

 return !['bubbles', 'pinboard'].includes(__nylaResources.self?.layout)
? {
'data-feed-id': __nylaResources?.self?.feedId,
'data-theme': __nylaResources?.self?.theme,
...((__nylaResources?.self?.scriptAttrs
// @ts-ignore
?.split(',')
?.reduce((acc: Record<string, string>, curr: string) => {
if (!curr.match(/^[^"=]*=[^"=]*$/i)) return acc;
const [_name, _value] = curr.trim().split('=');
const name = _name.trim();
const value = _value.trim();
if (!name || !value) return acc;
acc[name.startsWith('data-') ? name : `data-${name}`] = value;
return acc;
}, {}) ?? {})
}
: {};

Example of the custom enabled script field (the code block that can be added if the custom value is set to enabled within Script URL options): 

return ['slider', 'product-page-gallery'].includes(__nylaResources.self.layout)
  • Content items: Add a content item that will be injected in the editor for you to use on your site.  Content items will load the scripts and show the app on the site. Content items will appear in your content items UI once your custom app is saved. You will be able to find them in the Apps part of content items using the title that use to name each content item. If your app is saved but not published, you'll be able to use and preview custom app content items in your live preview, however you won't be able to use them on your live site until you have published the custom app. 

Editor fields enable you to add fields to your content item so that users can configure each use case of the content item (for example if they need to add a different Gallery ID for a UGC app each time the content item is used.) These fields can be set to be overridden in section templates. You can set the name and type of fields, what values users can choose from when applicable. Finally you can choose to add a field transformer to transform the field value before it is used. This is useful when you need to format the value before using it.

Condition Query example (found in Content Items -> Editor Fields -> Condition Query):

// if you have another field of type ShopifyProduct and name product, 
// then you can hide a field if the product is not set yet by using the following code
{{product.n__shopifyId}} != ""

// if you have another field of type boolean and name useArrows
{{useArrows}} === true

The HTML and HTML on code change apply to your overall content item. 

HTML Code examples: 

<div id="data-oke-carousel" data-oke-carousel></div>
<span style="display: none;" data-lion-price-for-product-id="{{ product.shortId }}">
{{ product.price | money }}
</span>
<span data-lion-points-for-product-id="{{ product.shortId }}"></span>
<div id="foursixty-shoppable-instagram-__nylaResources.self.contentItemId"></div>
<div class="octane-ai-quiz" data-quiz-id="__nylaResources.self.quizId" data-embed-type="fullpage" data-mobile-height="match_browser" data-desktop-height="match_browser"></div>

HTML on code change example: 

const productVariants = {{product.variants}}
const dataAvailable = productVariants.reduce.((acc, item) => {
if (item.availableForSale) {
const variantOptions = item.title.split('/');
acc.push(`${variantOptions[2]}:${variantOptions[0]}`.trim());
}

return acc;
}, []);
const dataAvailableFiltered = [...dataAvailable];
const ssmContainer = document.querySelector('.ssm_pdp');
if (ssmContainer && !ssmContainer.getAttribute('data-availability')) {
ssmContainer.setAttribute('data-availability', dataAvailableFiltered);
}
window.ShoeSizeMe_loader.rebuildAndUpdate({{product.shortId}});
window.yotpo.initWidgets()
  • Code block: A block of code that will be run before, during, or after your scripts have run. You can add up to one code block of each type. 

Code to run before examples (Code Blocks -> Set Run code field as Before Script Loads): 

someAppConfig = window.someAppConfig || {};
someAppConfig.PLATFORM = 'shopify';
someAppConfig.SHOP_DOMAIN = `your-shop-name.myshopify.com`;
someAppConfig.PUBLIC_TOKEN = 'YOUR_PUBLIC_TOKEN';

// code to run before - example 2
document.addEventListener(
'someAppEvent.addToCart',
function (e) {
const { items: itemsToAdd } = e.detail;

if (itemsToAdd.length) {
__nylaResources.cart.dispatch({
type: __nylaResources.cart.dispatchTypes.ADD_VARIANTS_SYNC,
payload: itemsToAdd.map(item => ({
variantId: `gid://shopify/ProductVariant/${item.id}`,
quantity: item.quantity
}))
});
__nylaResources.cart.toggleCart('open');
}
},
false
);
const productId = {{product.shortId}}
const productName = {{product.title}}
const productImageUrl = {{product.media.0.url}}
const productUrl = {{product.url}}
const productPrice = {{product.price}}
const pageName = {{page.title}}

window.someAppConfig = {
apiKey: __nylaResources.self.apiKey,
// eslint-disable-next-line no-invalid-this
productId,
productName,
productImageUrl,
productUrl,
productPrice,
pageName,
}

Code to run after examples (Code Blocks -> Set Run code field as After Script Loads):

window.Findation.init(
document.getElementById(
`findation-widget-button-${__nylaResources.self.contentItemId}`
),
__nylaResources.self.apiKey,
{
productId: {{product.shortId}},
}
)
var okendoWidget = document.getElementById('okendo-widget');
if(okendoWidget) {
window.okeWidgetApi.initWidget(okendoWidget);
}
window.ShoeSizeMe_loader.rebuildAndUpdate.({{product.shortId}});
if (
__nylaResources.self.contentItemType ===
'contentItemTargetbayProductReviews'
) {
window.tbrForms.getReviews();
} else if (
__nylaResources.self.contentItemType === 'contentItemTargetbayReviewCount'
) {
window.tbrForm.productRatings();
}
window.Route.Protection.on('add_to_cart', event => {
const { variantId, product } = event.data.detail;

__nylaResources.cart?.addVariant(null, 1, {
id: closestVariant?.id,
product
}, [])
break;
})
  • Custom styles: This section enables you to add custom styles to your script. By default styles will be scoped to the content item container unless you enable the “Styles should be global” setting. When scoping styles to a specific content item, you use the content item id to target the html you added to the content item you want to style. 
.your-app-selector {
content: __nylaResources.self.someTextField
}

__nylaResources.self.someCSSField

Using Form Action Loader


Form action Loader lets you create form actions for you to use in forms the editor. This is useful for adding a form action, for example in the case of an app like Klaviyo that requires form entries to be sent to a specific location. 


To add form actions, you have the following sections in the Editor: 

  • Details: Enables you to activate your form action, and add a name and description. 
  • Code block: The block of code that will be run when the form is submitted.

See some examples: 

const [listId, klaviyoSourceId] = __nylaResources.self.parameters || [];
const fields = __nylaResources.self.form.elements || {};
const { email, phone_number: phoneNumber } = fields;

if (!listId) {
__nylaResources.self.onError('list id is missing');
return;
}

if (
!((email && email.value) || (phoneNumber && phoneNumber.value))
) {
__nylaResources.self.onError('Email or phone is required');
return;
}

try {
const data = {
type: 'subscription',
attributes: {
...(email && email.value ? { email: email.value } : {}),
...(phoneNumber && phoneNumber.value
? { phone_number: phoneNumber.value }
: {}),
list_id: listId,
custom_source: klaviyoSourceId,
}
};

return fetch(
`https://a.klaviyo.com/client/subscriptions?company_id=YOUR_COMPANY_ID`,
{
method: 'post',
headers: {
'Content-Type': 'application/json',
revision: '2023-01-24'
},
body: JSON.stringify({ data: data })
}
)
.then(response => response.json())
.then(response => {
({ data: response });

if (response && response.status == 200) {
__nylaResources.self.onSuccess();
} else {
const error = response.errors && JSON.stringify(response.errors)
__nylaResources.self.onError({
message: error || 'Error',
});
}
});
} catch (error) {
__nylaResources.self.onError({ message: error });
}
const { referrals } = __nylaResources.self.form.elements;
const email = {{global.customer.email}}

if (
!email ||
(referrals && referrals.value && referrals.value.length === 0)
) {
__nylaResources.self.onError('Email or referrals are empty');
return;
}

const response = await fetch(`/yotpo/refer`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
emails: referrals.value
})
}).then(response => response.json());

if (response !== null) {
__nylaResources.self.onSuccess();
} else {
__nylaResources.self.onError('Unable to refer');
}

Using Link Action Loader


Link Action Loader enables you to create link actions to use on your site. 

  • Details: Enables you to link your form action, and add a name and description. 
  • Code block: The block of code that will be run when the form is submitted.
  • Data Attributes: Add HTML data attributes to your content item
if (typeof window.SmileUI !== 'undefined') {
const deepLink = __nylaResources.self.linkActionParams[0];
window.SmileUI.openPanel(deepLink ? { deep_link: deepLink } : undefined);
}
window.open(
encodeURIComponent({{apps.yotpoLoyalty.referral.link}}),
'social-refer',
'scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,width=626,height=436'
)
 __nylaResources.rivoloyaltyrewards.dispatch({
  type: 'SET_REDEEEMING_CODE',
  payload: {
    rewardId: __nylaResources.self.linkActionParams[0]
  }
});
fetch(`/api/proxy/rivo-loyalty/redeem`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    rewardId: __nylaResources.self.linkActionParams[0]
  })
})
  .then(async response => {
    const data = await response.json();
    if (data.error) {
      throw new Error(data.error);
    }
    __nylaResources.rivoloyaltyrewards.dispatch({
      type: 'SET_REDEEMED_CODE',
      payload: {
        rewardId: __nylaResources.self.linkActionParams[0],
        code: data.code
      }
    });
  })
  .catch(err => {
    console.error(err);
    __nylaResources.rivoloyaltyrewards.dispatch({
      type: 'SET_REDEEMED_CODE',
      payload: {
        rewardId: __nylaResources.self.linkActionParams[0],
        error: err.message
      }
    });
  });