Skip to content

Miva Template Language (aka MVT, SMT)#

Key Points#

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 define l.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
  • Then use l.settings
    • Use l.settings (or l.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
  • Otherwise use g.

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))" />

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>
Someone could just pass ?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>
Someone would truly have an 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 or s.null (MivaScript 5.36 and above). 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="s.null" />

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>
Reasoning: The indentation is not utilizing the linter rules (using spaces, not tabs), and it’s not properly formatted..

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>
Reasoning: Indentation is using tabs, and properly formatted*

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'"/>

???+ valid “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#

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>
What happens here is that miva will return the 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 5.36 added support for the system-variable: s.null. It is important to only use s.null when assigning, comparing, or using null values.

Note

In the following examples, NULL could be substitued with any other improper null-variable assignment like: g.NULL, NULL, g.null, null, g.Null, Null, etc.

Don’t assign variables to NULL#

NULL does not exist as variable and when it is used in expressions, it would be a reference to g.NULL leaving you vulnerable to XSS exploits or improper logical opperations.

Incorrect

<mvt:assign name="l.found_error" value="NULL" />
<mvt:assign name="l.product" value="l.null" />

Correct

<mvt:assign name="l.found_error" value="''" />
<mvt:assign name="l.product" value="''" />

Correct (Better)

<mvt:assign name="l.found_error" value="s.null" />
<mvt:assign name="l.product" value="s.null" />

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 Pass NULL into a Function#

Don’t pass NULL into a function. Instead pass s.null.

Incorrect

<mvt:do file="g.Module_Feature_URI_UT" name="l.basketitem:link" value="Store_Product_URL( l.basketitem:product, NULL )" />
<mvt:do file="g.Module_Feature_URI_UT" name="l.basketitem:link" value="Store_Product_URL( l.basketitem:product, l.null )" />

Correct

<mvt:do file="g.Module_Feature_URI_UT" name="l.basketitem:link" value="Store_Product_URL( l.basketitem:product, s.null )" />

Use s.null For Unused Return Values#

It is best to assign variables to s.null when you do not need to use the return variable for anything (like running an mvt:do and not needing the return value).

Assigning a value to s.null does not change the value of s.null and avoids keeping the result of a function or expression in memory. Additionally, by assigning variables to s.null, it more clearly indicates that the variable will not be used (i.e. as opposed to l.result or l.success), and it avoids the possibility of addtional logic being impacted by l.result/l.null/l.success variables having truthy/falsy values in them.

Incorrect

<mvt:do file="g.Module_Library_DB" name="l.success" 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="s.null" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="s.null" value="miva_array_insert(l.foo, 'Foobar', -1)" />

Use Meaningful Return Values#

Incorrect

<mvt:do file="g.Module_Library_DB" name="l.success" value="Product_Load_Code( 'test', l.product )" />
<mvt:do file="g.Module_Library_DB" name="l.result" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="l.null" value="miva_array_insert(l.names, 'Foobar', -1)" />

Correct

<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code_Result" value="Product_Load_Code( 'test', l.product )" />
<mvt:do file="g.Module_Library_DB" name="l.product:is_loaded" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="l.names_count" value="miva_array_insert(l.names, 'Foobar', -1)" />

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:

  1. 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 to l.pos1 to see if a value is found there, then if it doesn’t find a value then it looks to g.pos1.
    • Using l.posN avoids a potential case where you end up referencing a global-variable (query-string/form-data POST) instead of one from mvt:foreach. For example, referencing POS2 when you’re not in two nested foreach loops would be like referencing g.POS2)
  2. 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.
  3. 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>
Above: Could output a heading & empty 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>
Above: Split out validated and non-validated addresses into their own arrays to display different sections.

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>
Note: Miva will throw a fatal error for this.

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:

  1. Load through Admin UI if possible
  2. 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 )" />
      
  3. 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 )" />
      

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.

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'" />

  1. 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 )" />`