SOW : Utils
SOW plugins are part of Smarty Core, written from scratch!
src/js/sow.core/sow.utils.js
Background Slideshow
Simple background image slideshow (as cover) written from scratch (1.5Kb final size)! Mobile supported with different set of images!
Do not use too many images or too many slideshows on the same page if you want to keep it fast!
data-sow-slideshow-interval="3000"
data-sow-slideshow-fade-delay="1500"
data-sow-slideshow-interval="8000"
data-sow-slideshow-fade-delay="3000"
<div class="sow-util-slideshow position-relative overlay-dark overlay-opacity-3"
data-sow-slideshow-interval="3000"
data-sow-slideshow-fade-delay="1500"
data-sow-slideshow="
demo.files/images/unsplash/restaurant/thumb_600/jade-wulfraat-sIzym-INcvk-unsplash-min-min.jpg,
demo.files/images/unsplash/restaurant/thumb_600/jade-wulfraat-UaOPeqfdW1c-unsplash-min-min.jpg,
.demo.files/images/unsplash/restaurant/thumb_600/jade-wulfraat-yXnPoTkkY94-unsplash-min-min.jpg
" style="width:300px;height:300px">
</div>
<!--
Using mobile attribute for smaller image size
(~768px is ok)
-->
<div class="sow-util-slideshow position-relative overlay-dark overlay-opacity-3"
data-sow-slideshow-interval="3000"
data-sow-slideshow-fade-delay="1500"
data-sow-slideshow="
demo.files/images/unsplash/restaurant/jade-wulfraat-sIzym-INcvk-unsplash-min-min.jpg,
demo.files/images/unsplash/restaurant/jade-wulfraat-UaOPeqfdW1c-unsplash-min-min.jpg,
demo.files/images/unsplash/restaurant/jade-wulfraat-yXnPoTkkY94-unsplash-min-min.jpg
"
data-sow-slideshow-xs="
demo.files/images/unsplash/restaurant/thumb_600/jade-wulfraat-sIzym-INcvk-unsplash-min-min.jpg,
demo.files/images/unsplash/restaurant/thumb_600/jade-wulfraat-UaOPeqfdW1c-unsplash-min-min.jpg,
demo.files/images/unsplash/restaurant/thumb_600/jade-wulfraat-yXnPoTkkY94-unsplash-min-min.jpg
" style="width:300px;height:300px">
</div>
Cookies By Attribute
<!--
data-cookie-val is set by defatul to "1" if not provided!
data-cookie-expire (in days) is set by default to 7 if not pvided!
data-cookie-path="/" (cookie path, default "/")
Note: toast is optional!
data-toast-msg-type-set="primary|success|warning|danger" (default: success)
data-toast-msg-type-del="primary|success|warning|danger" (default: success)
-->
<!-- cookie set -->
<a href="#" class="sow-util-cookie btn btn-primary"
data-cookie-set="_test_"
data-cookie-val="1"
data-cookie-expire="1"
data-toast-msg-set="Set: _test_"
data-toast-msg-pos="bottom-center"
data-toast-msg-type-set="success">
Set Cookie
</a>
<!-- cookie delete -->
<a href="#" class="sow-util-cookie btn btn-danger"
data-cookie-del="_test_"
data-toast-msg-del="Deleted: _test_"
data-toast-msg-pos="bottom-center"
data-toast-msg-type-del="danger">
Delete Cookie
</a>
<!-- cookie toggle -->
<a href="#" class="sow-util-cookie btn btn-warning"
data-cookie-toggle="_test_"
data-cookie-val="1"
data-cookie-expire="1"
data-toast-msg-set="Set: _test_"
data-toast-msg-del="Deleted: _test_"
data-toast-msg-pos="bottom-center"
data-toast-msg-type-set="success"
data-toast-msg-type-del="danger">
Toggle Cookie
</a>
/*
+++ +++ +++ +++
IF YOU NEED IN YOUR CUSTOM JAVASCRIPT
+++ +++ +++ +++
Cookie Plugin is part of Core in Smarty:
https://github.com/js-cookie/js-cookie
So you can also use directly in your app:
*/
Cookies.set('key', 'val', { expires: 365, path: '/' });
Cookies.get('key');
Cookies.remove('key', { path: '/' });
Time ago
<!--
data-lang attribute is opional, for translation
Counter is live - 1 minute refresh interval
data-live="true"
Note: any date format is working on any tag: span, abbr, etc.
Here are just few examples of using them!
Is not important the tag used: abbr, time, span, etc
-->
<time class="sow-util-timeago"
datetime="2019-09-17T23:59:17"
data-live="true"
data-lang='{
"seconds" : "less than a minute ago",
"minute" : "about a minute ago",
"minutes" : "%d minutes ago",
"hour" : "about an hour ago",
"hours" : "about %d hours ago",
"day" : "a day ago",
"days" : "%d days ago",
"month" : "about a month ago",
"months" : "%d months ago",
"year" : "about a year ago",
"years" : "%d years ago"
}'></time>
<!-- using abbr -->
<abbr class="sow-util-timeago" data-time="December 17, 2012">December 17, 2012</abbr>
<abbr class="sow-util-timeago" data-time="2011-12-17T09:24:17Z"></abbr>
<!-- using span (and timestamp example) -->
<span class="sow-util-timeago" data-time="1372218564"></span>
Element Cloner
<!--
Attributes
data-clone-target="..." what to clone
data-clone-destination="..." where to append the clone
data-clone-sortable="true" true = reorder on drag/drop
data-clone-limit="0" max no. of clones
data-clone-method="append" append|prepend (default: append)
.sow-util-cloner class is required on "+" button/link
.btn-clone-remove class is required only for the second example (not for self cloning)
IMPORTANT! (see example 2)
If a field is datepicker, colorpicker (or anything else),
the plugin should know what to reinit for each clone! Will not work for "self cloning"
so the second example with a hidden element should be used instead.
We can't add .datepicker class directly because will be initialized on
page load and the clone will be wrong! So we add this attribute instead of a direct class:
data-cloned-replace-class="datepicker"
Other examples:
data-cloned-replace-class="rangepicker"
data-cloned-replace-class="colorpicker"
... and so on
-->
<!-- self clonning -->
<div id="container_clone_example">
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="Attribute name">
<a href="#"
data-clone-target="#container_clone_example"
data-clone-destination="#container_clones"
data-clone-sortable="true"
data-clone-limit="0"
class="sow-util-cloner btn btn-primary">
<!-- note: current (icon) class is replaced when cloned -->
<i class="fi fi-plus m-0" data-cloned-replace-class="fi fi-close m-0"></i>
</a>
</div>
</div>
<div id="container_clones"><!-- clones appended here --></div>
<!-- using a template -->
<a href="#"
data-clone-target="#container_tpl_hidden>div"
data-clone-destination="#container_clones2"
data-clone-sortable="true"
data-clone-limit="5"
class="sow-util-cloner btn btn-primary">
Add element
</a>
<div class="mt-3" id="container_clones2"><!-- clones appended here --></div>
<!-- hidden container containing the `template` to be cloned -->
<!-- <script type="text/template"> not supported - place it outside of form -->
<div id="container_tpl_hidden" class="hide">
<!-- this will be cloned -->
<div class="clearfix mb-3"><!-- always wrap elements inside a div/container -->
<input type="text" class="form-control" data-cloned-replace-class="form-control rangepicker">
<a href="#" class="btn-clone-remove">remove</a>
</div>
</div>
<!-- PREADDED ITEMS
Preadded items should be present inside the clone container and
this is the strucuture (for the first example):
-->
<div id="container_clones">
<div class="js-cloned">
<input type="text" class="form-control">
<a href="#" data-clone-target="#container_clone_example">remove</a>
</div>
</div>
Advanced Group clonner
<!-- clone container (items are pushed here) -->
<div id="clone_group_list" class="row sortable"></div>
<!-- clone trigger -->
<div class="mt-3">
<a href="#"
data-clone-target="#configurator_tpl>div"
data-clone-destination="#clone_group_list"
data-clone-limit="4"
class="sow-util-cloner btn btn-primary">
Add group
</a>
</div>
<!--
NOTE
Because clonned elements are arrays, fields are replaced (input, select, etc)!
[$] is replaced with group number like this: [1], [2], etc
according to group number (each box is a group)
1. Replace form field name ; example: name="group_on[1]"
data-cloned-replace-name="group_on[$]"
2. Replace data attribute (used by input-suggest in this example)
this will replace the attribute (similar to previous example, but for data attributes)
replaced attribute is used then to apply ajax rules - so is dynamically applied.
data-name="product_id[]"
data-cloned-replace-attribute-name="data-name"
data-cloned-replace-attribute-value="product_id[$][]"
Please use inspect element to see it working.
Dependencies in this demo:
SOW : Input Suggest
Vendor : Sortable
-->
<!-- CLONE TEMPLATE -->
<div id="configurator_tpl" class="hide">
<div class="col-12 col-lg-6 mt-3 py-3 rounded">
<div class="border border-primary rounded border-dashed p-3">
<!-- header -->
<div class="d-flex align-items-center justify-content-lg-between mb-3 sortable-handle">
<!-- on/off -->
<label class="d-flex align-items-center">
<input class="d-none-cloaked" type="checkbox" value="1" data-cloned-replace-name="group_on[$]" checked>
<i class="switch-icon switch-icon-primary"></i>
<span class="px-3 user-select-none">Enable</span>
</label>
<!-- remove button -->
<a href="#" class="sow-util-cloner close mt-n1" data-clone-target="#clone_group_list" tabindex="-1">
<i class="fi fi-close small m-0"></i>
</a>
</div>
<!-- group name -->
<div class="form-label-group mb-3">
<input type="text" placeholder="Group Name" class="form-control" data-cloned-replace-name="group_name[$]" value="">
<label>Group Name</label>
<small class="d-block text-gray-500">Example: <span class="fw-medium">Memory Upgrade</span></small>
</div>
<!--
See the console for Ajax request
Params sent to URL:
{ajax: "true", action: "input_search", key: "_USER_KEYWORDS_", limit: 20}
Note: in this demo, there are always the same results
because the active search is done by the backend!
-->
<div class="input-suggest-group">
<div class="form-label-group">
<!--
NOTE:
data-name="product_id[]"
Is used to generate hidden input fields for each item added to the list
If not provided, name="..." attribute is used!
If none of them found, "item[]" is used by default for input hidden fields!
-->
<input type="text" class="form-control" value=""
data-cloned-replace-class="form-control input-suggest"
placeholder="Product Search..."
data-name="product_id[]"
data-cloned-replace-attribute-name="data-name"
data-cloned-replace-attribute-value="product_id[$][]"
data-input-suggest-mode="append"
data-input-suggest-typing-delay="300"
data-input-suggest-typing-min-char="3"
data-input-suggest-append-container="parent:group"
data-input-suggest-max-items="2"
data-input-suggest-ajax-url="_ajax/input_suggest_append.json"
data-input-suggest-ajax-method="GET"
data-input-suggest-ajax-limit="10">
<label>Product Search...</label>
</div>
<!-- append data -->
<div class="input-suggest-container hide-empty mt-3" data-cloned-replace-class="input-suggest-container sortable hide-empty mt-3"></div>
</div>
</div>
</div>
</div>
Action Util
Many util functions for a single button/checkbox/etc.
Slightly similar to SOW : Button Toggle plugin but extended, more options and more flexible.
<!-- email address -->
<div class="input-group-over">
<div class="form-floating">
<input required readonly placeholder="Email Address" id="account_email" name="account[email]" type="email" class="form-control" value="john.doe@gmail.com">
<label for="account_email"><span class="text-danger">Email</span> Address</label>
</div>
<a id="email_edit_show" href="#" class="btn transition-none sow-util-action"
data-util-self-ignore="true"
data-util-target-hide="#email_edit_show"
data-util-target-show="#email_edit_hide, #email_edit_password_request"
data-util-target-readonly-off="#account_email"
data-util-target-input="#account_email"
data-util-target-input-val=""
data-util-target-focus="#account_email">
<i class="fi fi-pencil m-0"></i>
</a>
<a id="email_edit_hide" href="#" class="btn transition-none sow-util-action hide"
data-util-self-ignore="true"
data-util-target-hide="#email_edit_hide, #email_edit_password_request"
data-util-target-show="#email_edit_show"
data-util-target-readonly-on="#account_email"
data-util-target-input="#account_email"
data-util-target-input-val="john.doe@gmail.com">
<i class="fi fi-close m-0"></i>
</a>
</div>
<!-- pass confirm -->
<div id="email_edit_password_request" class="mt-3 hide">
<!-- password -->
<div class="input-group-over">
<div class="form-floating mb-3">
<input placeholder="Account Password" id="account_password" name="account[current_password]" type="password" class="form-control">
<label for="account_password">Account Password</label>
</div>
<!-- Show Password -->
<a href="#" class="btn btn-password-type-toggle" data-target="#account_password">
<span class="group-icon">
<i class="fi fi-eye m-0"></i>
<i class="fi fi-close m-0"></i>
</span>
</a>
</div>
<!-- /password -->
</div>
<!-- AVAILABLE OPTIONS -->
<!-- Do not toggle self "active" class : on button click -->
data-util-self-ignore="true"
<!-- Show/Hide -->
data-util-target-hide="#container"
data-util-target-show="#container"
<!-- Add Class -->
data-util-target-class-add="#container"
data-util-target-class-add-val="someclass"
<!-- Remove Class -->
data-util-target-class-remove="#container"
data-util-target-class-remove-val="someclass"
<!-- Toggle Class -->
data-util-target-class-toggle="#container"
data-util-target-class-toggle-val="someclass"
<!-- Input/Textarea Value -->
data-util-target-input="#inputElement"
data-util-target-input-val="someValue"
<!-- Input/Textarea Placeholder -->
data-util-target-placeholder="#inputElement"
data-util-target-placeholder-val="someValue"
<!-- Readonly Attribute -->
data-util-target-readonly-on="#inputElement"
data-util-target-readonly-off="#inputElement"
data-util-target-readonly-toggle="#inputElement"
<!-- Disabled Attribute -->
data-util-target-disable-on="#element"
data-util-target-disable-off="#element"
data-util-target-disable-toggle="#element"
<!-- Remove Container -->
data-util-target-remove="#container"
<!-- Focus Input/Textarea -->
data-util-target-focus="#inputElement"
<!-- Toast Message on click (different then ajax toast) -->
data-util-toast-msg="Item Changed"
data-util-toast-position="top-center"
data-util-toast-type="success"
data-util-toast-timeout="2500"
<!-- Ajax Request -->
data-util-ajax-request="URL_HERE"
data-util-ajax-method="GET"
data-util-ajax-params="['param1','value1']['param2','value2']"
data-util-ajax-toast-success="Successfully Updated!"
data-util-ajax-toast-position="top-center"
data-util-ajax-toast-timeout="2500"
data-util-ajax-append-response="#container"
Ajax Ghost Form
Without using form tags so you can use a form inside a form!
Price: $23.99
Sale Price: $18.99
Inventory: 100 items
<!-- updated via ajax -->
<div class="mb-4">
<div>
Price: $<span id="price_info">23.99</span>
</div>
<div>
Sale Price: $<span id="price_sale_info">18.99</span>
</div>
<div>
Inventory: <span id="product_inventory_info">100</span> items
</div>
</div>
<div class="sow-util-form position-relative"
data-util-form-action="../demo.files/php/demo.ajax_request.php"
data-util-form-method="post"
data-util-form-params="['param1','value1']"
data-util-form-toast-success="Successfully Saved!"
data-util-form-toast-error="Unexpected Internal error!"
data-util-form-toast-position="top-center">
<!-- price -->
<div class="form-label-group mb-1">
<input required placeholder="Price" id="price" name="price" type="number" value="23.99" class="form-control form-control-sm" data-util-update="#price_info">
<label for="price">Price</label>
</div>
<!-- sale price -->
<div class="form-label-group mb-1">
<input placeholder="Sale Price" id="price_sale" name="price_sale" type="number" value="18.99" class="form-control form-control-sm" data-util-update="#price_sale_info">
<label for="price_sale">Sale Price</label>
</div>
<!-- inventory -->
<div class="form-label-group mb-1">
<input required placeholder="Inventory" id="inventory" name="inventory" type="number" value="100" class="form-control form-control-sm" data-util-update="#product_inventory_info">
<label for="inventory">Inventory</label>
</div>
<!-- submit -->
<a href="#" class="sow-util-form-submit btn w-100 btn-primary btn-sm">Submit</a>
<!-- loading (optional) -->
<span class="sow-util-loader absolute-full z-index-1 bg-white opacity-3 d-middle hide">
<i class="fi fi-orbit fi-spin fs-1"></i>
</span>
</div>