Miva Template Language (aka MVT, SMT)¶
Key Points¶
- Follow the specific best practices & examples outlined in this document:
- Follow the broader best practices described in our code-standards; especially in areas like:
Variables¶
Use Snake Case¶
Use Snake Case for Miva variables and input[name]
values.
Incorrect
<mvt:assign name="l.thisIsHardToRead" value="''">
<mvt:assign name="l.settings:product:variantInformation:variants" value="''">
<input type="text" name="fooBar" value="&mvte:global:fooBar;">
Correct
<mvt:assign name="l.this_is_easy_to_read" value="''">
<mvt:assign name="l.settings:product:variant_information:variants" value="''">
<input type="text" name="foo_bar" value="&mvte:global:foo_bar;">
Use Lowest Scope Possible¶
- First use
l.
- If you do not need to output the variable, or use it with an
<mvt:item />
, lean towards utilizing just local. - The
l.
scope is limited to the specific template section it is defined within. For example, if you definel.my_var
in the main template of a page, and then try to reference it within the "Content" template of a page, it will be undefined. - &mvt Output: None
- If you do not need to output the variable, or use it with an
- Then use
l.settings
- Use
l.settings
(orl.all_settings
when applicable) if you need to do any of the following:- Output the variable using
&mvt(a|e|j)
- Use it within an
<mvt:item />
- Make it available across several different templates
- Output the variable using
- Use
- Otherwise use
g.
- Use global variables for constant-like-values; like
g.theme_path
. - Use
g.
sparingly and preferl.
orl.settings:
. Try to reserve this scope for accessing GET/POST variables
- Use global variables for constant-like-values; like
Incorrect
<mvt:assign name="g.show_greeting" value="g.Action EQ 'LOGN'" />
<mvt:assign name="g.full_name" value="g.Customer:bill_fname $ ' ' $ g.Customer:bill_lname" />
<mvt:if expr="g.show_greeting">
Hello &mvte:global:first_name;!
</mvt:if>
Correct
<mvt:assign name="l.show_greeting" value="g.Action EQ 'LOGN'" />
<mvt:assign name="l.settings:full_name" value="g.Customer:bill_fname $ ' ' $ g.Customer:bill_lname" />
<mvt:if expr="l.show_greeting">
Hello &mvte:full_name;!
</mvt:if>
Reserve Global Scope For Accessing Data From POST/GET HTTP Variables¶
In Miva, global variables should be used for accepting GET/POST variables and then validated & sanitized as they get assigned to lower scoped variables. Also, provide fallbacks/defaults when the global variables are not provided.
Incorrect
<mvt:assign name="g.product_code" value="l.settings:product:code" />
<mvt:assign name="g.quantity" value="l.settings:product:quantity" />
Correct
<mvt:assign name="l.product_code" value="trim(g.Product_Code)" />
<mvt:assign name="l.quantity" value="int(trim(g.Quantity))" />
Use A Namespace/Structure To Group Related Variables¶
Incorrect
<mvt:assign name="l.customer_first_name" value="'John'" />
<mvt:assign name="l.customer_address" value="'16870 W Bernardo Dr Ste 100, San Diego, CA 92127'" />
<mvt:assign name="l.customer_email" value="'mivamerchant@miva.com'" />
Correct
<mvt:assign name="l.customer:first_name" value="'John'" />
<mvt:assign name="l.customer:address" value="'16870 W Bernardo Dr Ste 100, San Diego, CA 92127'" />
<mvt:assign name="l.customer:email" value="'mivamerchant@miva.com'" />
Use Descriptive Variable Names¶
See Comments > Best Practices and Clean Code > Names Rules for all best-practices.
Name your variables in relation to what they are. Don't use things like l.test
or l.val
. Instead, specify what they are.
Incorrect
<mvt:assign name="l.my_count" value="0" />
<mvt:assign name="l.my_array" value="''" />
Correct
<mvt:assign name="l.also_bought:count" value="0" />
<mvt:assign name="l.also_bought:items" value="''" />
Incorrect
<mvt:assign name="l.settings:product:my_variable" value="'Acme Inc. Brand'" />
Correct
<mvt:assign name="l.settings:product:brand" value="'Acme Inc. Brand'" />
Set Default Values Of Variables¶
If you're setting variables within a condition and/or using global variables that may/may-not be set, setting default values can save you from some un-intended consequences.
Incorrect
<mvt:if expr="'@miva.com' IN g.Customer:bill_email">
<mvt:assign name="g.Allow_Checkout" value="1" />
</mvt:if>
<mvt:if expr="g.Allow_Checkout">
<a href="&mvte:urls:OCST:auto;">Checkout</a>
</mvt:if>
?Allow_Checkout=1
in the URL and gain access.
Correct
<mvt:assign name="g.Allow_Checkout" value="0" />
<mvt:if expr="'@miva.com' IN g.Customer:bill_email">
<mvt:assign name="g.Allow_Checkout" value="1" />
</mvt:if>
<mvt:if expr="g.Allow_Checkout">
<a href="&mvte:urls:OCST:auto;">Checkout</a>
</mvt:if>
g.Customer:bill_email
with @miva.com
in it.
Avoid the NULL
Keyword¶
Do not use NULL
in variable assignments or checks. Use an empty string (ex: ''
) instead. NULL
evaluates to g.NULL
which could be populated with data from a POST or GET HTTP method which could result in unexpected functionality (at best) or XSS (at worst).
Incorrect
<mvt:assign name="l.product_code" value="NULL" />
<mvt:assign name="l.category_code" value="null" />
Correct
<mvt:assign name="l.product_code" value="''" />
<mvt:assign name="l.category_code" value="''" />
Use Pascal Case For Key Global Variables¶
References to the following Global Miva Variables should be in Pascal Case. This promotes consistency with how the LSK treats these variables:
- g.Basket
- g.Store
- g.Customer
- g.Session
- g.Domain
- g.User
Incorrect
<mvt:if expr="g.customer:id">
Hello &mvte:global:customer:bill_fname;
</mvt:if>
Correct
<mvt:if expr="g.Customer:id">
Hello &mvte:global:Customer:bill_fname;
</mvt:if>
Spacing¶
See Files for general indentation & spacing rules.
Use Proper Indentation¶
Indentation should follow the linter (most likely tabs), and the indentation should be properly formatted.
Incorrect
<mvt:foreach iterator="category" array="categories">
<mvt:foreach iterator="product" array="category:products">
<mvt:foreach iterator="part" array="product:parts">
&mvte:category:name; - &mvte:product:name; - &mvte:part:name;
</mvt:foreach></mvt:foreach></mvt:foreach>
Correct
<mvt:foreach iterator="category" array="categories">
<mvt:foreach iterator="product" array="category:products">
<mvt:foreach iterator="part" array="product:parts">
&mvte:category:name; - &mvte:product:name; - &mvte:part:name;
</mvt:foreach>
</mvt:foreach>
</mvt:foreach>
Two Newlines Before Opening & Closing Tags¶
One extra line space should always both precede AND follow any tag that has an opening and closing that are NOT placed on the same line.
Incorrect
<mvt:assign name="l.offset" value="int( g.Offset )" />
<mvt:assign name="l.hide_hdft" value="0" />
<mvt:if expr="l.offset GT 0">
<mvt:assign name="l.hide_hdft" value="1" />
</mvt:if>
Correct
<mvt:assign name="l.offset" value="int( g.Offset )" />
<mvt:assign name="l.hide_hdft" value="0" />
<mvt:if expr="l.offset GT 0">
<mvt:assign name="l.hide_hdft" value="1" />
</mvt:if>
The only exception to this rule is if there is a single (un-grouped) assignment and the following tag immediately uses that assignment.
Incorrect
<mvt:assign name="l.offset" value="int( g.Offset )" />
<mvt:if expr="l.offset GT 0">
<mvt:assign name="l.hide_hdft" value="1" />
</mvt:if>
Correct
<mvt:assign name="l.offset" value="int( g.Offset )" />
<mvt:if expr="l.offset GT 0">
<mvt:assign name="l.hide_hdft" value="1" />
</mvt:if>
Space Before Self-Closing Tag¶
One space should follow the quotations of a mvt
tag when being closed.
Incorrect
<mvt:assign name="l.example_string" value="'My Value'"/>
Correct
<mvt:assign name="l.example_string" value="'My Value'" />
One Space Between Attributes¶
Spacing between the mvt
tags attributes should only be 1 space, unless aligning with multiple lines.
Incorrect
<mvt:assign name="l.example_value" value="'My Value'" />
<mvt:do file="g.Module_Library_DB" name="l.product_loaded" value="Product_Load_Code_Cached( l.product_code, l.product )" />
Correct
<mvt:assign name="l.example_value" value="'My Value'" />
<mvt:do file="g.Module_Library_DB" name="l.product_loaded" value="Product_Load_Code_Cached( l.product_code, l.product )" />
Incorrect
<mvt:assign name="l.groups" value="''" />
<mvt:assign name="l.groups:one" value="'My Value'" />
<mvt:assign name="l.groups:two" value="'My Other Value'" />
Correct
<mvt:assign name="l.groups" value="''" />
<mvt:assign name="l.groups:one" value="'My Value'" />
<mvt:assign name="l.groups:two" value="'My Other Value'" />
Correct
<mvt:assign name="l.groups" value="''" />
<mvt:assign name="l.groups:one" value="'My Value'" />
<mvt:assign name="l.groups:two" value="'My Other Value'" />
No Spaces Around Equal Signs¶
There should not be any spaces between the mvt-tag's attribute, equal sign, and quotations.
Incorrect
<mvt:assign name = "l.hello" value = "'World'" />
Correct
<mvt:assign name="l.hello" value="'World'" />
Multi-line Large Concatenated Strings¶
When concatenating strings use new lines to make the code easier to visualize.
Incorrect
<mvt:assign name="l.product_code" value="'1234'" />
<mvt:assign name="l.category_code" value="'shirts'" />
<mvt:assign name="l.screen" value="'PROD'" />
<mvt:assign name="l.url_to_redirect" value="'https://www.yourdomain.com?Product_Code=' $ l.product_code $ '&Category_Code=' $ l.category_code $ '&Screen=' $ l.screen" />
Correct
<mvt:assign name="l.product_code" value="'1234'" />
<mvt:assign name="l.category_code" value="'shirts'" />
<mvt:assign name="l.screen" value="'PROD'" />
<mvt:assign name="l.url_to_redirect" value="
'https://www.yourdomain.com?' $
'Product_Code=' $ l.product_code $
'&Category_Code=' $ l.category_code $
'&Screen=' $ l.screen
" />
Conditionals¶
Avoid Maintaining Duplicate HTML Within Conditionals¶
This commonly occurs with HTML inputs that are checked, selected, or disabled. Instead, use variables, mvt:captures, or mvt:do's.
Checked/Required:
Incorrect
<mvt:foreach iterator="option" array="attribute:options">
<label>
<mvt:if expr="l.settings:option:required AND l.settings:option:checked">
<input type="radio" name="Product_Attributes[&mvte:attribute:index;]:value" value="&mvte:option:code;" required checked>
<mvt:elseif expr="l.settings:option:required">
<input type="radio" name="Product_Attributes[&mvte:attribute:index;]:value" value="&mvte:option:code;" required>
<mvt:else>
<input type="radio" name="Product_Attributes[&mvte:attribute:index;]:value" value="&mvte:option:code;">
</mvt:if>
<span class="x-product-option__caption">
&mvte:option:prompt;
</span>
</label>
</mvt:foreach>
Correct
<mvt:foreach iterator="option" array="attribute:options">
<mvt:assign name="l.settings:option:html" value="''" />
<mvt:if expr="l.settings:option:required">
<mvt:assign name="l.settings:option:html:required" value="'required'">
</mvt:if>
<mvt:if expr="l.settings:option:checked">
<mvt:assign name="l.settings:option:html:checked" value="'checked'">
</mvt:if>
<label>
<input type="radio" name="Product_Attributes[&mvte:attribute:index;]:value" value="&mvte:option:code;" &mvte:option:html:required; &mvte:option:html:checked;>
<span class="x-product-option__caption">
&mvte:option:prompt;
</span>
</label>
</mvt:foreach>
Selecting Options:
Use DrawOption()
for to select <option>
elements based on variables.
<mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( value, default, text )" />`
Incorrect
<label class="c-form-label" for="l-sort_by">Sort</label>
<div class="c-form-select">
<select id="l-sort_by" class="c-form-select__dropdown-display" name="Sort_By" onchange="MMProdList_UpdateQuery( this ); return true;">
<mvt:if expr="g.Sort_By EQ 'disp_order'">
<option value="disp_order" selected>Featured</option>
<mvt:else>
<option value="disp_order">Featured</option>
</mvt:if>
<mvt:if expr="g.Sort_By EQ 'bestsellers'">
<option value="bestsellers" selected>Best</option>
<mvt:else>
<option value="bestsellers">Best</option>
</mvt:if>
<mvt:if expr="g.Sort_By EQ 'price_asc'">
<option value="price_asc" selected>Price</option>
<mvt:else>
<option value="price_asc">Price</option>
</mvt:if>
<mvt:if expr="g.Sort_By EQ 'price_desc'">
<option value="price_desc" selected>Price</option>
<mvt:else>
<option value="price_desc">Price</option>
</mvt:if>
<mvt:if expr="g.Sort_By EQ 'newest'">
<option value="newest" selected>Newest</option>
<mvt:else>
<option value="newest">Newest</option>
</mvt:if>
</select>
</div>
Correct
<label class="c-form-label" for="l-sort_by">Sort</label>
<div class="c-form-select">
<select id="l-sort_by" class="c-form-select__dropdown-display" name="Sort_By" onchange="MMProdList_UpdateQuery( this ); return true;">
<mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( 'disp_order', g.Sort_By, 'Featured' )" />
<mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( 'bestsellers', g.Sort_By, 'Best Selling' )" />
<mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( 'price_asc', g.Sort_By, 'Price (Low to High)' )" />
<mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( 'price_desc', g.Sort_By, 'Price (High to Low)' )" />
<mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( 'newest', g.Sort_By, 'Newest Items' )" />
</select>
</div>
Pluralizing Words:
Incorrect
<mvt:if expr="l.settings:basket:item_count EQ 1">
<span class="t-checkout__order-summary-content-label">Your basket has &mvte:basket:item_count; Item</span>
<mvt:else>
<span class="t-checkout__order-summary-content-label">Your basket has &mvte:basket:item_count; Items</span>
</mvt:if>
Correct
<mvt:do file="g.Module_Library_Utilities" name="l.settings:basket:item_count_label" value="Plural( l.settings:basket:item_count, 'Item', 'Items' )" />
<span class="t-checkout__order-summary-content-label">Your basket has &mvte:basket:item_count; &mvte:basket:item_count_label;</span>
Comments¶
- Use Comments > Best Practices.
- Use Clean Code > Comments Rules.
- MVT Comments should follow this specific format Comments > MVT Comments.
mvt:dos¶
- Always confirm that your parameters are utilizing the right type.
- If the function you're calling requires a variable, do not set a string. This will cause an error.
- Always make sure that you have the correct filename, if not miva will error and add to the error log. This log can get large if this is a function running on almost every page load, and it will cause your line of code to not work properly.
- Always make sure to send the necessary data needed for your function call. If the function is expecting a specific variable set-up, make sure you have the necessary info! We don't want an unwanted errors.
Clear DB Insert & Update Structures¶
When utilizing inserts/updates (especially in a loop) clear out the main variable.
When miva does inserts/updates, it will return the variable you sent, back, with the updated information. By not clearing the variable out, you may run into unwanted duplicates.
Incorrect
<mvt:foreach iterator="extra_product" array="extra_products">
<mvt:assign name="l.basketitem:basket_id" value="g.Basket:id" />
<mvt:assign name="l.basketitem:product_id" value="l.settings:extra_product:id" />
<mvt:do file="g.Module_Library_DB" name="l.success" value="BasketItem_Insert( l.basketitem )" />
</mvt:foreach>
line_id
and group_id
onto the l.basketitem
so then next iteration will have that too
Correct
<mvt:foreach iterator="extra_product" array="extra_products">
<mvt:assign name="l.basketitem" value="''" />
<mvt:assign name="l.basketitem:basket_id" value="g.Basket:id" />
<mvt:assign name="l.basketitem:product_id" value="l.settings:extra_product:id" />
<mvt:do file="g.Module_Library_DB" name="l.success" value="BasketItem_Insert( l.basketitem )" />
</mvt:foreach>
Use Meaningful mvt:do[name]
Variables¶
Utilize your mvt:do[name]
variables wisely. If a function returns a value (success/fail, 1/0, count of the array, etc.) utilize that if necessary.
If you have an mvt:do
that returns the count of the array, do not utilize miva_array_elements()
later.
Incorrect
<mvt:do file="g.Module_Library_DB" name="l.result" value="Product_Load_Code( 'test', l.product)" />
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code" value="Product_Load_Code( 'test', l.product)" />
Correct
<mvt:do file="g.Module_Library_DB" name="l.test_product_loaded" value="Product_Load_Code( 'test', l.product)" />
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code_Result" value="Product_Load_Code( 'test', l.product)" />
Use The Return Value From The Function Instead Of The Argument¶
Incorrect
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code" value="Product_Load_Code( 'test', l.product )" />
<mvt:if expr="NOT ISNULL l.product">
<!-- Do something when the product exists -->
</mvt:if>
Correct
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code_Result" value="Product_Load_Code( 'test', l.product )" />
<mvt:if expr="l.Product_Load_Code_Result">
<!-- Do something when the product exists -->
</mvt:if>
_Inserts()
& _Update()
Structures Should Match Software's Structure¶
We recommend downloading the latest Limited Source Kit for Miva https://apps.miva.com/miva-merchant-limited-source-kit.html. Which has the source code for all public functions. This allows you to review the structure and the structure's members and to determine what the function expects.
Incorrect
<mvt:assign name="l.product_page_uri:page_id" value="1" />
<mvt:assign name="l.product_page_uri:cat_id" value="2" />
<mvt:assign name="l.product_page_uri:product_id" value="3" />
<mvt:assign name="l.product_page_uri:feed_id" value="0" />
<mvt:do file="g.Module_Feature_URI_DB" name="l.success" value="URI_Insert( l.product_page_uri )" />
Correct
<mvt:comment>
|
| Product Page URI Insert
|
</mvt:comment>
<mvt:assign name="l.uri:page_id" value="1" />
<mvt:assign name="l.uri:cat_id" value="2" />
<mvt:assign name="l.uri:product_id" value="3" />
<mvt:assign name="l.uri:feed_id" value="0" />
<mvt:do file="g.Module_Feature_URI_DB" name="l.success" value="URI_Insert( l.uri )" />
null¶
MivaScript doesn't have a NULL
reserved keyword so it's important to only use it in the correct context or find another alternative.
NEVER assign or use NULL
or g.NULL
.
In the following examples, NULL
applies to all cases of null
: g.NULL
, NULL
, g.null
, null
, g.Null
, Null
, etc.
Don't Use NULL in Comparisons:¶
Incorrect
<mvt:if expr="g.Product_Code EQ NULL">
<!-- Do something -->
</mvt:if>
Correct
<mvt:if expr="ISNULL g.Product_Code">
<!-- Do something -->
</mvt:if>
Don't Assign Variables to NULL:¶
Incorrect
<mvt:do file="g.Module_Library_DB" name="null" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="l.found_error" value="NULL" />
Correct
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code_Result" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="l.found_error" value="''" />
l.null
Is Ok For Unused Return Values¶
l.null
is ok when you do not need to use the return variable for anything (i.e. running an mvt:do
and not needing the return value).
Incorrect
<mvt:do file="g.Module_Library_DB" name="null" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="NULL" value="miva_array_insert(l.foo, 'Foobar', -1)" />
Correct
<mvt:do file="g.Module_Library_DB" name="l.null" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="l.null" value="miva_array_insert(l.foo, 'Foobar', -1)" />
Correct (Best)
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code_Result" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="l.index" value="miva_array_insert(l.foo, 'Foobar', -1)" />
Don't Pass NULL
into a Function¶
Don't pass NULL
into a function. Try to initialize and utilize l.null
to an empty-string.
Incorrect
<mvt:do file="g.Module_Feature_URI_UT" name="l.basketitem:link" value="Store_Product_URL( l.basketitem:product, NULL )" />
Correct
<mvt:assign name="l.null" value="''" />
<mvt:do file="g.Module_Feature_URI_UT" name="l.basketitem:link" value="Store_Product_URL( l.basketitem:product, l.null )" />
Arrays/Iterators¶
Iterator Comes Before Array¶
The iterator
attribute should always come before the array
attribute in an <mvt:foreach>
.
Incorrect
<mvt:foreach array="basket:groups" iterator="group">
<!-- Do something -->
</mvt:foreach>
Correct
<mvt:foreach iterator="group" array="basket:groups">
<!-- Do something -->
</mvt:foreach>
Prefer l.posN
for Array Indexes¶
Within an mvt:foreach
loop you should reference the current index using l.posN
; rather than POSN
. This is for a couple main reasons:
- It's a best practice to reference variables with their variable scope and to use the lowest scope possible
- When MivaScript attempts to evaluate a variable without a scope (ex
POS1
), it first looks tol.pos1
to see if a value is found there, then if it doesn't find a value then it looks tog.pos1
. - Using
l.posN
avoids a potential case where you end up referencing a global-variable (query-string/form-data POST) instead of one frommvt:foreach
. For example, referencingPOS2
when you're not in two nested foreach loops would be like referencingg.POS2
)
- When MivaScript attempts to evaluate a variable without a scope (ex
- Variable references in MVT are case-insensitive (
l.foo EQ l.FOO
), but using the lower-case better fits with our naming conventions is this is not a constant. - This is how Software writes their pos-counters in the LSK
Incorrect
<mvt:foreach iterator="group" array="basket:groups">
<mvt:assign name="l.is_even" value="(POS1 MOD 2) EQ 0" />
<mvt:if expr="l.is_even">
<mvt:assign name="l.result" value="miva_array_insert( l.evens, l.settings:group, -1 )" />
</mvt:if>
</mvt:foreach>
Correct
<mvt:foreach iterator="group" array="basket:groups">
<mvt:assign name="l.is_even" value="(l.pos1 MOD 2) EQ 0" />
<mvt:if expr="l.is_even">
<mvt:assign name="l.result" value="miva_array_insert( l.evens, l.settings:group, -1 )" />
</mvt:if>
</mvt:foreach>
Avoid Unnecessary Iterations¶
If you are trying to find a single instance of something, it is more beneficial to use miva_array_search
or at least a foreachstop
.
Don't continue looping through if your code is not going to do anything. You never know how many iterations you'll be looping through. If you can stop the loop as soon as possible, then it will be quicker!
Incorrect
<mvt:assign name="l.has_forbidden_items" value="0" />
<mvt:foreach iterator="group" array="basket:groups">
<mvt:if expr="l.settings:group:customfield_values:customfields:forbidden">
<mvt:assign name="l.has_forbidden_items" value="1" />
</mvt:if>
</mvt:foreach>
<mvt:if expr="l.has_forbidden_items">
<!-- Do something with permitted items -->
</mvt:if>
Correct
<mvt:assign name="l.has_forbidden_items" value="0" />
<mvt:foreach iterator="group" array="basket:groups">
<mvt:if expr="l.settings:group:customfield_values:customfields:forbidden">
<mvt:assign name="l.has_forbidden_items" value="1" />
<mvt:foreachstop />
</mvt:if>
</mvt:foreach>
<mvt:if expr="l.has_forbidden_items">
<!-- Do something with permitted items -->
</mvt:if>
Correct (Best)
<mvt:assign name="l.forbidden_item_index" value="miva_array_search( l.settings:basket:groups, 1, l.group, 'NOT ISNULL l.settings:group:customfield_values:customfields:forbidden' )" />
<mvt:if expr="l.forbidden_item_index EQ 0">
<!-- Do something with permitted items -->
</mvt:if>
Prefer miva_array_filter
¶
When iterating through an array, and you only want to display certain iterators that match an expression, determine if you should use miva_array_filter
, or check the opposite expression and utilize mvt:foreachcontinue
.
It would be more useful to use miva_array_filter
when you need to make sure there is data to output (miva_array_filter
returns the new count of your output var
).
Incorrect
<h2>Invalid Addresses</h2>
<div>
<mvt:foreach iterator="address" array="addressfields:validated_addresses">
<mvt:if expr="NOT l.settings:address:validated">
<!-- Do something -->
</mvt:if>
</mvt:foreach>
</div>
<h2>Valid Addresses</h2>
<div>
<mvt:foreach iterator="address" array="addressfields:validated_addresses">
<mvt:if expr="l.settings:address:validated">
<!-- Do something -->
</mvt:if>
</mvt:foreach>
</div>
div
with no content.
Correct
<mvt:assign name="l.settings:addressfields:non_validated_addresses_count" value="miva_array_filter( l.settings:addressfields:validated_addresses, 1, l.address, 'l.address:validated NE 1', l.settings:addressfields:non_validated_addresses )" />
<mvt:assign name="l.settings:addressfields:validated_addresses_count" value="miva_array_filter( l.settings:addressfields:validated_addresses, 1, l.address, 'l.address:validated EQ 1', l.settings:addressfields:validated_addresses )" />
<mvt:if expr="l.settings:addressfields:non_validated_addresses_count">
<h2>Invalid Addresses</h2>
<div>
<mvt:foreach iterator="address" array="addressfields:non_validated_addresses">
<!-- Do something -->
</mvt:foreach>
</div>
</mvt:if>
<mvt:if expr="l.settings:addressfields:validated_addresses_count">
<h2>Valid Addresses</h2>
<div>
<mvt:foreach iterator="address" array="addressfields:validated_addresses">
<!-- Do something -->
</mvt:foreach>
</div>
</mvt:if>
Use foreachcontinue
Early or miva_array_filter
Results¶
Don't have an mvt:if
statement around all/most of the mvt:foreach
output.
It makes it a little bit more difficult to read and unnecessarily increases the indentation; especially when there is a lot of content & conditionals within the mvt:foreach
.
Incorrect
<mvt:foreach iterator="address" array="addressfields:validated_addresses">
<mvt:if expr="l.settings:address:validated">
<!-- Do something -->
</mvt:if>
</mvt:foreach>
Correct
<mvt:foreach iterator="address" array="addressfields:validated_addresses">
<mvt:if expr="NOT l.settings:address:validated">
<mvt:foreachcontinue />
</mvt:if>
<!-- Do something -->
</mvt:foreach>
Correct
<mvt:assign name="l.settings:addressfields:validated_addresses_count" value="miva_array_filter( l.settings:addressfields:validated_addresses, 1, l.address, 'l.address:validated EQ 1', l.settings:addressfields:validated_addresses )" />
<mvt:foreach iterator="address" array="addressfields:validated_addresses">
<!-- Do something -->
</mvt:foreach>
Do Not Have Empty if
Statement Blocks¶
Do not have empty mvt:if
statement blocks. For example, do not use an mvt:if
statement, and only utilize the mvt:else
block.
Incorrect
<mvt:foreach iterator="group" array="basket:groups">
<mvt:if expr="NOT l.settings:group:customfield_values:customfields:forbidden">
<mvt:comment><!-- Do nothing for permitted groups --></mvt:comment>
<mvt:else>
<mvt:assign name="l.forbidden_items_count" value="miva_array_insert_var( l.forbidden_items, l.settings:group, -1 )" />
</mvt:if>
</mvt:foreach>
<mvt:assign name="l.forbidden_items_count" value="miva_array_elements(l.forbidden_items)" />
Correct
<mvt:assign name="l.forbidden_items_count" value="0" />
<mvt:assign name="l.forbidden_items" value="''" />
<mvt:foreach iterator="group" array="basket:groups">
<mvt:if expr="NOT l.settings:group:customfield_values:customfields:forbidden">
<mvt:foreachcontinue />
</mvt:if>
<mvt:assign name="l.forbidden_items_count" value="miva_array_insert_var( l.forbidden_items, l.settings:group, -1 )" />
</mvt:foreach>
Correct (Best)
<mvt:assign name="l.forbidden_items_count" value="miva_array_filter( l.settings:basket:groups, 1, l.group, 'l.group:customfield_values:customfields:forbidden EQ 1', l.forbidden_items )">
Avoid Referencing Array Indexes¶
Avoid referencing variables with the square bracket notation; especially if you haven't checked the length of the array.
Incorrect
<mvt:assign name="l.first_product_code" value="l.settings:basket:items[1]:code" />
Correct
<mvt:assign name="l.first_product_code" value="''" />
<mvt:if expr="miva_array_elements(l.settings:basket:items)">
<mvt:assign name="l.first_product_code" value="l.settings:basket:items[1]:code" />
</mvt:if>
Avoid Square Bracket Notation for Array Creation¶
When arrays are created through square bracket notation it typically requires more lines of code, is repetitive, and can potentially create runtime errors if the indexes are invalid.
Using functions like miva_array_insert
& miva_splitstring
can help you avoid some of the issues with square-bracket notation.
This rule is best used when you are building an array while looping through another array:
Incorrect
<mvt:assign name="l.expensive_item_count" value="1" />
<mvt:foreach iterator="group" array="basket:groups">
<mvt:if expr="l.settings:group:price GT 100">
<mvt:assign name="l.expensive_items[l.expensive_item_count]" value="l.settings:group" />
<mvt:assign name="l.expensive_item_count" value="l.expensive_item_count + 1" />
</mvt:if>
</mvt:foreach>
Correct
<mvt:foreach iterator="group" array="basket:groups">
<mvt:if expr="l.settings:group:price GT 100">
<mvt:assign name="l.expensive_item_count" value="miva_array_insert( l.expensive_items, l.settings:group, -1 )" />
</mvt:if>
</mvt:foreach>
... or when you're looking to convert a static list into an array:
Incorrect
<mvt:assign name="l.fruits[1]" value="'Apple'" />
<mvt:assign name="l.fruits[2]" value="'Banana'" />
<mvt:assign name="l.fruits[2]" value="'Orange'" />
Correct
<mvt:assign name="l.fruits_count" value="miva_array_insert( l.fruits, 'Apple', -1 )" />
<mvt:assign name="l.fruits_count" value="miva_array_insert( l.fruits, 'Banana', -1 )" />
<mvt:assign name="l.fruits_count" value="miva_array_insert( l.fruits, 'Orange', -1 )" />
Correct (Best)
<mvt:assign name="l.fruits_count" value="miva_splitstring( 'Apple, Banana, Orange', ',', l.fruits, 'trim' )" />
JSON¶
Use JSON_Output
For Outputting Complex Structures¶
Incorrect
<mvt:if expr="l.api_error EQ 1">
{
"error": 1,
"message": "Something went wrong!",
"product": {
"id": &mvtj:product:id;,
"code": "&mvtj:product:code;",
"name": "&mvtj:product:name;"
}
}
</mvt:if>
Correct
<mvt:if expr="l.api_error EQ 1">
<mvt:assign name="l.json:error" value="1" />
<mvt:assign name="l.json:message" value="'Something went wrong!'" />
<mvt:assign name="l.json:product" value="l.settings:product" />
</mvt:if>
<mvt:do file="g.Module_JSON" name="l.success" value="JSON_Output( l.json )" />
Load Custom Fields Efficiently¶
To ensure the best page performance, we recommend loading custom fields in the following order; as your use-case allows:
- Load through Admin UI if possible
- Then load by Product ID1
- For example:
<mvt:item name="customfields" param="Read_Product_ID( l.settings:product:id, 'brand,short_desc', l.settings:product:customfield_values:customfields )" />
- For example:
- Then load by Product Code1
- For example:
<mvt:item name="customfields" param="Read_Product_Code( l.settings:product:code, 'brand,short_desc', l.settings:product:customfield_values:customfields )" />
- For example:
Avoid Legacy Modules & Methods¶
Using the following modules is considered an anti-pattern and should be avoided if possible. Attempt to refactored or replace these modules with more modern & maintained features ands you're likely to experience performance increases.
- Toolkit
- Sebenza Modules
- Toolbelt
This is especially true when using functions that now exist within the core software.
Incorrect
<mvt:item name="toolkit" param="sassign|foo|bar" />
<mvt:item name="toolkit" param="vassign|foo|l.all_settings:bar" />
<mvt:item name="toolkit" param="mvassign|foo|'bar' $ '!'" />
<mvt:item name="ry_toolbelt" param="assign|g.foo|toupper('bar')" />
<mvt:item name="sebenzatools" param="var|foo|'bar'" />
Correct
<mvt:assign name="l.foo" value="'bar'" />
<mvt:assign name="l.foo" value="l.settings:bar" />
<mvt:assign name="l.foo" value="'bar' $ '!'" />
<mvt:assign name="l.foo" value="toupper('bar')" />
<mvt:assign name="l.foo" value="'bar'" />
-
For stores with large amounts of custom fields (50-100+), there are times where it may be better to load all custom fields instead of individual ones. For example:
↩↩<mvt:comment>Store has <mvt:item name="customfields" param="Read_Product_ID( l.settings:product:id, '', l.settings:product:customfield_values:customfields )" />`