Bunch of updates

This commit is contained in:
Tony Air 2018-12-03 17:12:41 +01:00
parent d638843f92
commit ff87ba7f16
18 changed files with 761 additions and 345 deletions

View File

@ -4,7 +4,8 @@ use SilverStripe\Forms\HTMLEditor\HtmlEditorConfig;
use SilverStripe\Core\Manifest\ModuleResourceLoader; use SilverStripe\Core\Manifest\ModuleResourceLoader;
use SilverStripe\ORM\Search\FulltextSearchable; use SilverStripe\ORM\Search\FulltextSearchable;
HtmlEditorConfig::get('cms')->enablePlugins([ $config = HtmlEditorConfig::get('cms');
$config->enablePlugins([
'template', 'template',
'fullscreen', 'fullscreen',
'hr', 'hr',
@ -12,11 +13,12 @@ HtmlEditorConfig::get('cms')->enablePlugins([
'charmap', 'charmap',
'visualblocks', 'visualblocks',
'lists', 'lists',
'anchor',
'charcount' => ModuleResourceLoader::resourceURL( 'charcount' => ModuleResourceLoader::resourceURL(
'drmartingonzo/ss-tinymce-charcount:client/dist/js/bundle.js' 'drmartingonzo/ss-tinymce-charcount:client/dist/js/bundle.js'
), ),
]); ]);
$config->addButtonsToLine(2, 'hr');
$config->setOption('block_formats', 'Paragraph=p;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6;Address=address;Pre=pre');
$config->setOption('invalid_elements', 'h1');
HtmlEditorConfig::get('cms')->insertButtonsAfter('sslink', 'anchor');
FulltextSearchable::enable(); FulltextSearchable::enable();

View File

@ -5,255 +5,258 @@ import Events from '../_events';
import Spinner from './_ui.spinner'; import Spinner from './_ui.spinner';
const AjaxUI = (($) => { const AjaxUI = (($) => {
// Constants // Constants
const G = window; const G = window;
const D = document; const D = document;
const $Html = $('html'); const $Html = $('html');
const $Body = $('body'); const $Body = $('body');
const NAME = 'jsAjaxUI'; const NAME = 'jsAjaxUI';
const DATA_KEY = NAME; const DATA_KEY = NAME;
class AjaxUI { class AjaxUI {
// Constructor // Constructor
constructor(element) { constructor(element) {
this._element = element; this._element = element;
const $element = $(this._element); const $element = $(this._element);
$element.addClass(`${NAME}-active`); $element.addClass(`${NAME}-active`);
$element.bind('click', function (e) { $element.bind('click', function(e) {
e.preventDefault(); e.preventDefault();
const $this = $(this); const $this = $(this);
$('.ajax').each(function () { $('.ajax').each(function() {
const $this = $(this); const $this = $(this);
$this.removeClass('active'); $this.removeClass('active');
$this.parents('.nav-item').removeClass('active'); $this.parents('.nav-item').removeClass('active');
}); });
$this.addClass('loading'); $this.addClass('loading');
AjaxUI.load($this.attr('href'), () => { AjaxUI.load($this.attr('href'), () => {
$this.removeClass('loading'); $this.removeClass('loading');
$this.parents('.nav-item').addClass('active'); $this.parents('.nav-item').addClass('active');
$this.addClass('active'); $this.addClass('active');
}); });
});
}
// Public methods
static load(url, callback) {
// show spinner
Spinner.show(() => {
$Body.removeClass('loaded');
});
// update document location
G.MainUI.updateLocation(url);
const absoluteLocation = G.URLDetails['base'] + G.URLDetails['relative'].substring(1);
if (absoluteLocation !== G.location.href) {
G.history.pushState({
ajax: true,
page: absoluteLocation,
}, document.title, absoluteLocation);
}
$.ajax({
sync: false,
async: true,
url,
dataType: 'json',
method: 'GET',
cache: false,
error(jqXHR) {
console.warn(`AJAX request failure: ${jqXHR.statusText}`);
G.location.href = url;
// google analytics
if (typeof G.ga === 'function') {
G.ga('send', 'event', 'error', 'AJAX ERROR', jqXHR.statusText);
}
},
success(data, status, jqXHR) {
AjaxUI.process(data,jqXHR, callback);
// google analytics
if (typeof G.ga === 'function') {
G.ga('set', {
page: G.URLDetails['relative'] + G.URLDetails['hash'],
title: jqXHR.getResponseHeader('X-Title'),
}); });
G.ga('send', 'pageview');
}
},
});
}
static process(data, jqXHR, callback) {
const css = jqXHR.getResponseHeader('X-Include-CSS').split(',') || [];
const js = jqXHR.getResponseHeader('X-Include-JS').split(',') || [];
// Replace HTML regions
if (typeof (data.regions) === 'object') {
for (const key in data.regions) {
if (typeof (data.regions[key]) === 'string') {
AjaxUI.replaceRegion(data.regions[key], key);
}
} }
}
// remove already loaded scripts // Public methods
$('link[type="text/css"]').each(function () { static load(url, callback) {
const i = css.indexOf($(this).attr('href')); // show spinner
if (i > -1) { Spinner.show(() => {
css.splice(i, 1); $Body.removeClass('loaded');
}else if(!$Body.data('unload-blocked')) { });
console.log(`Unloading: ${ $(this).attr('href')}`);
$(this).remove();
}
});
$('script[type="text/javascript"]').each(function () { // update document location
const i = js.indexOf($(this).attr('src')); G.MainUI.updateLocation(url);
if (i > -1) {
js.splice(i, 1);
}else if(!$Body.data('unload-blocked')) {
console.log(`Unloading: ${ $(this).attr('src')}`);
$(this).remove();
}
});
// preload CSS const absoluteLocation = G.URLDetails['base'] + G.URLDetails['relative'].substring(1);
this.preload(css).then(() => { if (absoluteLocation !== G.location.href) {
const $head = $('head'); G.history.pushState({
css.forEach((el) => { ajax: true,
$head.append(`<link rel="stylesheet" type="text/css" href="${el}" />`); page: absoluteLocation,
}); }, document.title, absoluteLocation);
// preload JS
this.preload(js, 'script').then(() => {
js.forEach((el) => {
$Body.append(`<script type="text/javascript" charset="UTF-8" src="${el}"></script>`);
});
console.log('New page is loaded!');
// trigger events
if (typeof (data.events) === 'object') {
for (const eventName in data.events) {
$(D).trigger(eventName, [data.events[eventName]]);
} }
}
if (typeof callback !== 'undefined') { $.ajax({
callback(); sync: false,
} async: true,
url,
dataType: 'json',
method: 'GET',
cache: false,
error(jqXHR) {
console.warn(`AJAX request failure: ${jqXHR.statusText}`);
G.location.href = url;
$(G).trigger(Events.AJAX); // google analytics
}); if (typeof G.ga === 'function') {
}); G.ga('send', 'event', 'error', 'AJAX ERROR', jqXHR.statusText);
} }
},
success(data, status, jqXHR) {
AjaxUI.process(data, jqXHR, callback);
static preload(items, type = 'text', cache = true) { // google analytics
if (!items.length) { if (typeof G.ga === 'function') {
return $.Deferred().resolve().promise(); G.ga('set', {
} page: G.URLDetails['relative'] + G.URLDetails['hash'],
title: jqXHR.getResponseHeader('X-Title'),
const dfds = []; });
items.forEach((url) => { G.ga('send', 'pageview');
const dfd = $.Deferred(); }
},
$.ajax({ });
dataType: type, }
cache,
url, static process(data, jqXHR, callback) {
}).always(() => { const css = jqXHR.getResponseHeader('X-Include-CSS').split(',') || [];
dfd.resolve(); const js = jqXHR.getResponseHeader('X-Include-JS').split(',') || [];
});
// Replace HTML regions
dfds.push(dfd); if (typeof(data.regions) === 'object') {
}); for (const key in data.regions) {
if (typeof(data.regions[key]) === 'string') {
// return a master promise object which will resolve when all the deferred objects have resolved AjaxUI.replaceRegion(data.regions[key], key);
return $.when(...dfds); }
} }
}
static replaceRegion(html, key) {
const $region = $(`[data-ajax-region="${key}"]`); // remove already loaded scripts
$('link[type="text/css"]').each(function() {
if ($region.length) { const i = css.indexOf($(this).attr('href'));
$region.empty().append(html); if (i > -1) {
} else { css.splice(i, 1);
console.warn('Region returned without class or id!'); } else if (!$Body.data('unload-blocked')) {
} console.log(`Unloading: ${ $(this).attr('href')}`);
} $(this).remove();
}
dispose() { });
const $element = $(this._element);
$('script[type="text/javascript"]').each(function() {
$element.removeClass(`${NAME}-active`); const i = js.indexOf($(this).attr('src'));
$.removeData(this._element, DATA_KEY); if (i > -1) {
this._element = null; js.splice(i, 1);
} } else if (!$Body.data('unload-blocked')) {
console.log(`Unloading: ${ $(this).attr('src')}`);
static _jQueryInterface() { $(this).remove();
return this.each(function () { }
// attach functionality to element });
const $element = $(this);
let data = $element.data(DATA_KEY); // preload CSS
this.preload(css).then(() => {
if (!data) { const $head = $('head');
data = new AjaxUI(this); css.forEach((el) => {
$element.data(DATA_KEY, data); $head.append(`<link rel="stylesheet" type="text/css" href="${el}" />`);
});
// preload JS
this.preload(js, 'script').then(() => {
js.forEach((el) => {
$Body.append(`<script type="text/javascript" charset="UTF-8" src="${el}"></script>`);
});
console.log('New page is loaded!');
// trigger events
if (typeof(data.events) === 'object') {
for (const eventName in data.events) {
$(D).trigger(eventName, [data.events[eventName]]);
}
}
if (typeof callback !== 'undefined') {
callback();
}
$(G).trigger(Events.AJAX);
});
});
}
static preload(items, type = 'text', cache = true, itemCallback = false) {
if (!items.length) {
return $.Deferred().resolve().promise();
}
const dfds = [];
items.forEach((url, i) => {
const dfd = $.Deferred();
$.ajax({
dataType: type,
cache,
url,
}).always(() => {
dfd.resolve();
if (itemCallback) {
itemCallback(i, url);
}
});
dfds.push(dfd);
});
// return a master promise object which will resolve when all the deferred objects have resolved
return $.when(...dfds);
}
static replaceRegion(html, key) {
const $region = $(`[data-ajax-region="${key}"]`);
if ($region.length) {
$region.empty().append(html);
} else {
console.warn('Region returned without class or id!');
}
}
dispose() {
const $element = $(this._element);
$element.removeClass(`${NAME}-active`);
$.removeData(this._element, DATA_KEY);
this._element = null;
}
static _jQueryInterface() {
return this.each(function() {
// attach functionality to element
const $element = $(this);
let data = $element.data(DATA_KEY);
if (!data) {
data = new AjaxUI(this);
$element.data(DATA_KEY, data);
}
});
} }
});
} }
}
// jQuery interface // jQuery interface
$.fn[NAME] = AjaxUI._jQueryInterface; $.fn[NAME] = AjaxUI._jQueryInterface;
$.fn[NAME].Constructor = AjaxUI; $.fn[NAME].Constructor = AjaxUI;
$.fn[NAME].noConflict = function () { $.fn[NAME].noConflict = function() {
$.fn[NAME] = JQUERY_NO_CONFLICT; $.fn[NAME] = JQUERY_NO_CONFLICT;
return AjaxUI._jQueryInterface; return AjaxUI._jQueryInterface;
}; };
// auto-apply // auto-apply
$('.ajax').ready(() => { $('.ajax').ready(() => {
$('.ajax').jsAjaxUI(); $('.ajax').jsAjaxUI();
}); });
// AJAX update browser title // AJAX update browser title
$(D).on('layoutRefresh', (e, data) => { $(D).on('layoutRefresh', (e, data) => {
D.title = data.Title; D.title = data.Title;
$Html.attr('class',''); $Html.attr('class', '');
if(data.ClassName){ if (data.ClassName) {
$Html.addClass(data.ClassName); $Html.addClass(data.ClassName);
} }
//data.Link = (data.Link === '/home/') ? '/' : data.Link; //data.Link = (data.Link === '/home/') ? '/' : data.Link;
}); });
// Back/Forward functionality // Back/Forward functionality
G.onpopstate = function(event) { G.onpopstate = function(event) {
const $existingLink = $(`a[href^="${ D.location }"]`); const $existingLink = $(`a[href^="${ D.location }"]`);
if(event.state !== null && event.state.ajax){ if (event.state !== null && event.state.ajax) {
console.log('GOBACK (AJAX state)'); console.log('GOBACK (AJAX state)');
AjaxUI.load(event.state.page); AjaxUI.load(event.state.page);
}else if($existingLink.length && $existingLink.hasClass('ajax')){ } else if ($existingLink.length && $existingLink.hasClass('ajax')) {
console.log('GOBACK (AJAX link)'); console.log('GOBACK (AJAX link)');
$existingLink.trigger('click'); $existingLink.trigger('click');
}else{ } else {
console.log('GOBACK (HTTP)'); console.log('GOBACK (HTTP)');
G.location.href = D.location; G.location.href = D.location;
} }
}; };
return AjaxUI; return AjaxUI;
})($); })($);
export default AjaxUI; export default AjaxUI;

View File

@ -0,0 +1,111 @@
import 'bootstrap-select/js/bootstrap-select';
import $ from 'jquery';
import Events from "../_events";
const FormBasics = (($) => {
// Constants
const NAME = 'jsFormBasics';
const DATA_KEY = NAME;
const $Html = $('html, body');
class FormBasics {
constructor(element) {
const ui = this;
const $element = $(element);
ui._element = element;
$element.data(DATA_KEY, this);
const $fields = $element.find('input,textarea,select');
const $selectFields = $element.find('select:not([readonly])');
const $radioOptions = $element.find('input[type="radio"]');
$selectFields.each((i, el) => {
const $el = $(el);
$el.selectpicker({
liveSearch: $el.data('live-search')
});
});
$fields.each((e, el) => {
const $el = $(el);
if ($el.hasClass('required') || $el.attr('aria-required')) {
$el.closest('.field').addClass('required');
}
});
$radioOptions.each((e, el) => {
const $el = $(el);
if ($el.is(':checked')) {
$el.parents('.radio').addClass('checked');
}
});
$radioOptions.on('change', (e) => {
const $el = $(e.currentTarget);
const $parent = $el.parents('.radio');
$parent.siblings('.radio').removeClass('checked');
if ($el.is(':checked')) {
$parent.addClass('checked');
}
});
$element.addClass(`${NAME}-active`);
$element.trigger(Events.FORM_INIT_BASICS);
}
// Public methods
dispose() {
const $element = $(this._element);
$element.removeClass(`${NAME}-active`);
$.removeData(this._element, DATA_KEY);
this._element = null;
}
static _jQueryInterface() {
return this.each(function() {
// attach functionality to element
const $element = $(this);
let data = $element.data(DATA_KEY);
if (!data) {
data = new FormBasics(this);
$element.data(DATA_KEY, data);
}
});
}
}
// jQuery interface
$.fn[NAME] = FormBasics._jQueryInterface;
$.fn[NAME].Constructor = FormBasics;
$.fn[NAME].noConflict = function() {
$.fn[NAME] = JQUERY_NO_CONFLICT;
return FormBasics._jQueryInterface;
};
// auto-apply
$(window).on(`${Events.AJAX} ${Events.LOADED}`, () => {
$('form').each((i, el) => {
const $el = $(el);
// skip some forms
if ($el.hasClass('no-validation')) {
return true;
}
$el.jsFormBasics();
});
});
return FormBasics;
})($);
export default FormBasics;

View File

@ -4,7 +4,7 @@ import $ from 'jquery';
import Events from '../_events'; import Events from '../_events';
import Spinner from '../_components/_ui.spinner'; import Spinner from '../_components/_ui.spinner';
import FormValidate from './_ui.form.validate'; import FormValidateField from "./_ui.form.validate.field";
import '../../thirdparty/jquery-te/jquery-te.js'; import '../../thirdparty/jquery-te/jquery-te.js';
@ -29,24 +29,18 @@ const JqteUI = (($) => {
constructor(element) { constructor(element) {
const ui = this; const ui = this;
const $element = $(element); const $element = $(element);
const $jqteFields = $element.find('textarea.jqte-field'); const validationUI = $element.data('jsFormValidateField');
const validationUI = $element.parents('form').data('jsFormValidate');
$element.data(DATA_KEY, this);
ui._element = element; ui._element = element;
$element.data(DATA_KEY, this);
$element.jqte(jqteOptions); $element.jqte(jqteOptions);
// dynamic error control // dynamic error control
$element.parents('.jqte').find('.jqte_editor').on('change', (e) => { if (validationUI) {
const $field = $(e.target); $element.parents('.jqte').find('.jqte_editor').on('change', (e) => {
const $container = $field.closest('.field'); validationUI.validate();
});
if (!$field.text().length && $container.hasClass('required')) { }
validationUI.setError($container);
} else {
validationUI.removeError($container);
}
});
} }
static dispose() { static dispose() {

View File

@ -1,7 +1,7 @@
import $ from 'jquery'; import $ from 'jquery';
import Events from '../_events'; import Events from '../_events';
import LANG from '../lang/_en'; import LANG from '../lang/_en';
import FormValidate from './_ui.form.validate'; import FormValidateField from "./_ui.form.validate.field";
const SteppedForm = (($) => { const SteppedForm = (($) => {
// Constants // Constants
@ -13,6 +13,7 @@ const SteppedForm = (($) => {
constructor(element) { constructor(element) {
const ui = this; const ui = this;
const $element = $(element); const $element = $(element);
$element.data(DATA_KEY, this); $element.data(DATA_KEY, this);
if (!$element.find('.steps-counter').length) { if (!$element.find('.steps-counter').length) {
@ -28,16 +29,33 @@ const SteppedForm = (($) => {
ui._steps = $element.find('.step'); ui._steps = $element.find('.step');
ui._stepNext = $element.find('.step-next'); ui._stepNext = $element.find('.step-next');
ui._stepPrev = $element.find('.step-prev'); ui._stepPrev = $element.find('.step-prev');
ui._actions = $element.children('.btn-toolbar,.form-actions'); ui._actions = $element.children('.btn-toolbar,.form-actions');
ui._element = element; ui._element = element;
ui._currentStep = 1; ui._currentStep = 1;
ui._totalSteps = ui._steps.length; ui._totalSteps = ui._steps.last().data('step') || ui._steps.length;
ui._stepsOrder = []; ui._stepsOrder = [];
ui._totalStepsCounter.text(ui._totalSteps); ui._totalStepsCounter.text(ui._totalSteps);
// check if one of the steps already has an error
const $hasError = ui._steps
.find('.field.error,.field.holder-error,.field.holder-validation,.field.holder-info,.field.holder-warning,.field.holder-good')
.first();
if ($hasError.length) {
const $modal = $element.parents('.modal');
// show modal
if ($modal.length && typeof $modal.modal !== 'undefined') {
$modal.modal('show');
}
ui._currentStep = $hasError.parents('.step').data('step') || ui._currentStep;
}
//
ui.step('.step[data-step="' + ui._currentStep + '"]'); ui.step('.step[data-step="' + ui._currentStep + '"]');
ui._stepNext.on('click', (e) => { ui._stepNext.on('click', (e) => {
@ -78,8 +96,7 @@ const SteppedForm = (($) => {
return; return;
} }
ui._currentStep++; ui.step('.step[data-step="' + (ui._currentStep + 1) + '"]');
ui.step('.step[data-step="' + ui._currentStep + '"]');
} }
prev() { prev() {
@ -89,17 +106,37 @@ const SteppedForm = (($) => {
return; return;
} }
ui._currentStep--; ui.step(ui._stepsOrder[ui._currentStep - 1]);
ui.step(ui._stepsOrder[ui._currentStep]);
} }
step(target) { step(target) {
const ui = this; const ui = this;
const $element = $(ui._element); const $element = $(ui._element);
const $target = $element.find(target); const $target = $element.find(target);
const targetStep = parseInt($target.data('step'));
// validate current step
let valid = true;
if (targetStep > ui._currentStep) {
ui.currentStep().find('input,textarea,select').each((i, el) => {
const $el = $(el);
const fieldUI = $el.data('jsFormValidateField');
if (fieldUI && !fieldUI.validate()) {
valid = false;
}
});
}
if (!valid) {
return false;
}
//
if (parseInt($target.data('step')) <= '1') { if (parseInt($target.data('step')) <= '1') {
ui._stepPrev.hide(); ui._stepPrev.hide();
$element.trigger(Events.FORM_STEPPED_FIRST_STEP);
} else { } else {
ui._stepPrev.show(); ui._stepPrev.show();
} }
@ -107,18 +144,30 @@ const SteppedForm = (($) => {
if (parseInt($target.data('step')) >= ui._totalSteps) { if (parseInt($target.data('step')) >= ui._totalSteps) {
ui._stepNext.hide(); ui._stepNext.hide();
ui._actions.show(); ui._actions.show();
$element.trigger(Events.FORM_STEPPED_LAST_STEP);
} else { } else {
ui._stepNext.show(); ui._stepNext.show();
ui._actions.hide(); ui._actions.hide();
} }
ui._currentStep = parseInt($target.data('step')); ui._currentStep = targetStep;
ui._stepsOrder[ui._currentStep] = $target; ui._stepsOrder[ui._currentStep] = $target;
ui._steps.removeClass('active'); ui._steps.removeClass('active');
$target.addClass('active'); $target.addClass('active');
ui._currentStepCounter.text(ui._currentStep); ui._currentStepCounter.text(ui._currentStep);
$target.trigger(Events.FORM_STEPPED_NEW_STEP);
$element.trigger(Events.FORM_STEPPED_NEW_STEP);
}
currentStep() {
const ui = this;
const $element = $(ui._element);
return $element.find('.step.active');
} }
static _jQueryInterface() { static _jQueryInterface() {

View File

@ -31,6 +31,10 @@ const FormStorage = (($) => {
const type = $el.attr('type'); const type = $el.attr('type');
const val = STORAGE.getItem(NAME + id); const val = STORAGE.getItem(NAME + id);
if (type === 'file') {
return true;
}
if (id && val && type) { if (id && val && type) {
if (type && (type === 'checkbox' || type === 'radio')) { if (type && (type === 'checkbox' || type === 'radio')) {
$el.prop('checked', val); $el.prop('checked', val);

View File

@ -0,0 +1,120 @@
import $ from 'jquery';
import Events from "../_events";
const FormValidateField = (($) => {
// Constants
const NAME = 'jsFormValidateField';
const DATA_KEY = NAME;
const $Html = $('html, body');
class FormValidateField {
constructor(element) {
const ui = this;
const $element = $(element);
ui._element = element;
ui._actions = $element.parents('form').children('.btn-toolbar,.form-actions');
$element.data(DATA_KEY, this);
// prevent browsers checks (will do it using JS)
$element.attr('novalidate', 'novalidate');
$element.on('change', (e) => {
ui.validate(false);
});
$element.addClass(`${NAME}-active`);
$element.trigger(Events.FORM_INIT_VALIDATE_FIELD);
}
// Public methods
dispose() {
const $element = $(this._element);
$element.removeClass(`${NAME}-active`);
$.removeData(this._element, DATA_KEY);
this._element = null;
}
validate(scrollTo = true) {
const ui = this;
const $el = $(ui._element);
const $field = $el.closest('.field');
const extraChecks = $el.data(`${NAME}-extra`);
let valid = true;
// browser checks + required
if (!ui._element.checkValidity() ||
($el.hasClass('required') && !$el.val().trim().length)
) {
valid = false;
}
// extra checks
if (extraChecks) {
extraChecks.forEach((check) => {
valid = valid && check();
});
}
if (valid) {
this.removeError();
return true;
}
this.setError(scrollTo);
return false;
}
setError(scrollTo = true) {
const ui = this;
const $field = $(ui._element).closest('.field');
const pos = $field.offset().top;
$field.addClass('error');
if (scrollTo) {
$field.focus();
$Html.scrollTop(pos - 100);
}
}
removeError() {
const ui = this;
const $field = $(ui._element).closest('.field');
$field.removeClass('error');
$field.removeClass('holder-error');
$field.removeClass('holder-validation');
$field.find('.message').remove();
}
static _jQueryInterface() {
return this.each(function() {
// attach functionality to element
const $element = $(this);
let data = $element.data(DATA_KEY);
if (!data) {
data = new FormValidateField(this);
$element.data(DATA_KEY, data);
}
});
}
}
// jQuery interface
$.fn[NAME] = FormValidateField._jQueryInterface;
$.fn[NAME].Constructor = FormValidateField;
$.fn[NAME].noConflict = function() {
$.fn[NAME] = JQUERY_NO_CONFLICT;
return FormValidateField._jQueryInterface;
};
return FormValidateField;
})($);
export default FormValidateField;

View File

@ -1,5 +1,6 @@
import $ from 'jquery'; import $ from 'jquery';
import Events from "../_events"; import Events from "../_events";
import FormValidateField from "./_ui.form.validate.field";
const FormValidate = (($) => { const FormValidate = (($) => {
// Constants // Constants
@ -19,23 +20,22 @@ const FormValidate = (($) => {
ui._fields = $fields; ui._fields = $fields;
ui._stepped_form = $element.data('jsSteppedForm'); ui._stepped_form = $element.data('jsSteppedForm');
// prevent browsers checks (will do it using JS)
$element.attr('novalidate', 'novalidate');
$element.on(Events.FORM_INIT_STEPPED, () => { $element.on(Events.FORM_INIT_STEPPED, () => {
ui._stepped_form = $element.data('jsSteppedForm'); ui._stepped_form = $element.data('jsSteppedForm');
}); });
// prevent browsers checks (will do it using JS) // init fields validation
$element.attr('novalidate', 'novalidate');
$fields.each((i, el) => { $fields.each((i, el) => {
el.required = false; new FormValidateField(el);
});
$fields.on('change', (e) => {
ui.validateField($(e.target), false);
}); });
// check form // check form
$element.on('submit', (e) => { $element.on('submit', (e) => {
ui.validateForm($element, true, () => { ui.validate(true, () => {
e.preventDefault(); e.preventDefault();
// switch to step // switch to step
@ -64,52 +64,24 @@ const FormValidate = (($) => {
this._element = null; this._element = null;
} }
validateForm($form, scrollTo = true, badCallback = false) { validate(scrollTo = true, badCallback = false) {
console.log('Checking the form ...'); console.log('Checking the form ...');
const ui = this; const ui = this;
ui._fields.each(function(i, el) { ui._fields.each(function(i, el) {
const $el = $(el); const $el = $(el);
const fieldUI = $el.data('jsFormValidateField');
if (!ui.validateField($el)) { if (fieldUI && !fieldUI.validate()) {
if (badCallback) { if (badCallback) {
badCallback(); badCallback();
} }
return false; return false;
} }
}); });
} }
validateField($el, scrollTo = true) {
const $field = $el.closest('.field');
if (!$el[0].checkValidity() ||
($el.hasClass('required') && !$el.val().trim().length)
) {
this.setError($field, scrollTo);
return false;
} else {
this.removeError($field);
}
return true;
}
setError($field, scrollTo = true) {
const pos = $field.offset().top;
$field.addClass('error');
if (scrollTo) {
$field.focus();
$Html.scrollTop(pos - 100);
}
}
removeError($field) {
$field.removeClass('error');
}
static _jQueryInterface() { static _jQueryInterface() {
return this.each(function() { return this.each(function() {
// attach functionality to element // attach functionality to element

View File

@ -5,9 +5,15 @@
module.exports = { module.exports = {
AJAX: 'ajax-load', AJAX: 'ajax-load',
LOADED: 'load', LOADED: 'load',
SET_TARGET_UPDATE: 'set-target-update',
RESTORE_FIELD: 'restore-field', RESTORE_FIELD: 'restore-field',
FORM_INIT_BASICS: 'form-basics',
FORM_INIT_STEPPED: 'form-init-stepped', FORM_INIT_STEPPED: 'form-init-stepped',
FORM_INIT_VALIDATE: 'form-init-validate', FORM_INIT_VALIDATE: 'form-init-validate',
FORM_INIT_VALIDATE_FIELD: 'form-init-validate-field',
FORM_INIT_STORAGE: 'form-init-storage', FORM_INIT_STORAGE: 'form-init-storage',
FORM_VALIDATION_FAILED: 'form-validation-failed', FORM_VALIDATION_FAILED: 'form-validation-failed',
FORM_STEPPED_NEW_STEP: 'form-new-step',
FORM_STEPPED_FIRST_STEP: 'form-first-step',
FORM_STEPPED_LAST_STEP: 'form-last-step',
}; };

View File

@ -12,10 +12,16 @@ import './_components/routes/index';
import Events from './_events'; import Events from './_events';
import Spinner from './_components/_ui.spinner'; import Spinner from './_components/_ui.spinner';
import './_components/_ui.shrink';
import './_components/_ui.carousel'; import './_components/_ui.carousel';
import './_components/_ui.menu'; import './_components/_ui.menu';
import './_components/_ui.form.storage';
import FormBasics from './_components/_ui.form.basics';
import FormDatetime from './_components/_ui.form.datetime';
import FormStepped from './_components/_ui.form.stepped';
import FormValidate from './_components/_ui.form.validate';
import FormStorage from './_components/_ui.form.storage';
import FormCroppie from './_components/_ui.form.croppie';
import AjaxUI from './_components/_ui.ajax'; import AjaxUI from './_components/_ui.ajax';
import SmoothScroll from 'smooth-scroll'; import SmoothScroll from 'smooth-scroll';
@ -144,27 +150,6 @@ const MainUI = (($) => {
// mark external links // mark external links
$('a.external,a[rel="external"]').attr('target', '_blank'); $('a.external,a[rel="external"]').attr('target', '_blank');
// data-set links
$('[data-set-target]').on('click', (e) => {
const $el = $(e.currentTarget);
const $target = $($el.data('set-target'));
if (!$target.length) {
return;
}
$target.each((i, targetEl) => {
const $targetEl = $(targetEl);
const tag = $targetEl.prop('tagName').toLowerCase();
if (tag === 'input' || tag === 'select') {
$targetEl.val($el.data('set-val'));
} else if (!$targetEl.hasClass('field')) {
$targetEl.text($el.data('set-val'));
}
});
});
// show encoded emails // show encoded emails
/*$(D).find('.obm').each(function () { /*$(D).find('.obm').each(function () {
if ($(this).attr('data-val') !== undefined) { if ($(this).attr('data-val') !== undefined) {
@ -189,18 +174,23 @@ const MainUI = (($) => {
// //
// scroll links // scroll links
$(D).on('click', '.js-scrollTo', function(e) { $(D).on('click', '.js-scrollTo', (e) => {
e.preventDefault(); e.preventDefault();
ScrollTo(this, $(this).attr('data-target')); const el = e.currentTarget;
const $el = $(e.currentTarget);
ScrollTo(el, $el.attr('data-target'));
}); });
// load external fonts // load external fonts
if ($('[data-extfont]').length) { if ($('[data-extfont]').length) {
$.getScript('//ajax.googleapis.com/ajax/libs/webfont/1/webfont.js', () => { $.getScript('//ajax.googleapis.com/ajax/libs/webfont/1/webfont.js', () => {
const fonts = []; const fonts = [];
$('[data-extfont]').each(function(i) {
fonts[i] = $(this).attr('data-extfont'); $('[data-extfont]').each(function(i, el) {
fonts[i] = $(el).attr('data-extfont');
}); });
W.WebFont.load({ W.WebFont.load({
google: { google: {
families: fonts, families: fonts,
@ -209,6 +199,30 @@ const MainUI = (($) => {
}); });
} }
// data-set links
$('[data-set-target]').on('click', (e) => {
const $el = $(e.currentTarget);
const $target = $($el.data('set-target'));
if (!$target.length) {
return;
}
$target.each((i, targetEl) => {
const $targetEl = $(targetEl);
const tag = $targetEl.prop('tagName').toLowerCase();
if (tag === 'input' || tag === 'select') {
$targetEl.val($el.data('set-val'));
} else if (!$targetEl.hasClass('field')) {
$targetEl.text($el.data('set-val'));
}
});
$el.trigger(Events.SET_TARGET_UPDATE);
$target.closest('form').trigger(Events.SET_TARGET_UPDATE);
});
// hide spinner // hide spinner
Spinner.hide(() => { Spinner.hide(() => {
$Body.addClass('loaded'); $Body.addClass('loaded');
@ -276,14 +290,26 @@ const MainUI = (($) => {
const $imgLazyUrls = []; const $imgLazyUrls = [];
// collect image details // collect image details
$imgs.each(function() { $imgs.each((i, el) => {
const src = $(this).attr('src'); const $el = $(el);
const lazySrc = $(this).data('lazy-src'); const src = $el.attr('src');
if (src.length) { const lazySrc = $el.data('lazy-src');
if (src && src.length) {
$imgUrls.push(src); $imgUrls.push(src);
} }
if (lazySrc) { if (lazySrc && lazySrc.length) {
$imgLazyUrls.push(lazySrc); $imgLazyUrls.push(lazySrc);
$el.addClass('loading');
AjaxUI.preload([lazySrc]).then(() => {
$el.attr('src', lazySrc);
$el.addClass('loaded');
$el.removeClass('loading');
$el.trigger('image-lazy-loaded');
});
} }
}); });
@ -293,14 +319,6 @@ const MainUI = (($) => {
// load lazy images // load lazy images
AjaxUI.preload($imgLazyUrls).then(() => { AjaxUI.preload($imgLazyUrls).then(() => {
// update lazy img src
$('img[data-lazy-src]').each(function() {
if (!$(this).attr('src')) {
return;
}
$(this).attr('src', $(this).data('lazy-src'));
});
console.log('All images are loaded!'); console.log('All images are loaded!');
$(W).trigger('images-lazy-loaded'); $(W).trigger('images-lazy-loaded');

View File

@ -8,18 +8,27 @@ img {
max-width: 100%; max-width: 100%;
} }
a:hover, a,
a:focus, button,
button:hover, .jqte_tool_icon {
button:focus { &:hover,
opacity: .8; &:focus {
opacity: .8;
.fas, .fas,
.fab, .fab,
.far, .far,
&.fas, &.fas,
&.fab, &.fab,
&.far { &.far {
transform: scale(-1, 1);
}
}
}
.jqte_tool_icon {
&:hover,
&:focus {
transform: scale(-1, 1); transform: scale(-1, 1);
} }
} }
@ -27,6 +36,7 @@ button:focus {
// transactions // transactions
.transition, .transition,
a, a img, a, a img,
.jqte_tool_icon,
a .fas, a .fab, a .far, a .fas, a .fab, a .far,
a.fas, a.fab, a.far, a.fas, a.fab, a.far,
button .fas, button .fab, button .far, button .fas, button .fab, button .far,
@ -43,16 +53,73 @@ button, input, optgroup, select, textarea,
transition: all 0.4s ease; transition: all 0.4s ease;
} }
.alert-fixed-top {
position: fixed;
top: 0;
z-index: 999;
left: 4rem;
right: 4rem;
}
.btn-toolbar { .btn-toolbar {
margin-top: $grid-gutter-height / 2; margin-top: $grid-gutter-height / 2;
} }
.field { .field {
position: relative;
margin: 2rem 0;
margin: ($grid-gutter-height / 4) 0; margin: ($grid-gutter-height / 4) 0;
&:first-child { &:first-child {
margin-top: 0; margin-top: 0;
} }
&.required {
&:after {
display: block;
position: absolute;
top: 2rem;
right: .5rem;
content: "*";
color: $red;
z-index: 2;
}
}
&.holder-error,
&.error {
input, select, textarea {
border-color: $red;
}
label {
color: $red;
}
}
label {
font-weight: bold;
}
.bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) {
width: 100%;
}
}
.message {
@extend .alert;
@extend .alert-info;
display: block;
margin: .5rem 0;
}
.message.required,
.message.error {
@extend .alert;
@extend .alert-danger;
} }
// stick navbar to top using mobile layout // stick navbar to top using mobile layout

View File

@ -1,3 +1,5 @@
@import "~bootstrap-select/sass/bootstrap-select.scss";
@import "~bootstrap-datepicker/dist/css/bootstrap-datepicker.css"; @import "~bootstrap-datepicker/dist/css/bootstrap-datepicker.css";
@import "~bootstrap-timepicker/css/bootstrap-timepicker.css"; @import "~bootstrap-timepicker/css/bootstrap-timepicker.css";

View File

@ -1 +1,48 @@
@import "../_variables"; @import "../_variables";
@import "~bootstrap/scss/tables";
.image {
&.left {
float: left;
clear: left;
}
&.center {
display: block;
margin: 0 auto;
}
&.right {
float: right;
clear: right;
}
}
.text-left {
text-align: left;
}
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
.text-justify {
text-align: justify;
}
table {
width: 100%;
max-width: 100%;
border-collapse: collapse;
@extend .table;
@extend .table-bordered;
@extend .table-striped;
}

View File

@ -8,12 +8,12 @@
namespace Site\Extensions; namespace Site\Extensions;
use Sheadawson\Linkable\Forms\LinkField; use Sheadawson\Linkable\Forms\LinkField;
use Sheadawson\Linkable\Models\Link; use Sheadawson\Linkable\Models\Link;
use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField; use SilverStripe\Forms\TextField;
use SilverStripe\ORM\DataExtension; use SilverStripe\ORM\DataExtension;
use SilverStripe\Security\Member;
class SocialExtension extends DataExtension class SocialExtension extends DataExtension
{ {
@ -59,4 +59,18 @@ class SocialExtension extends DataExtension
$fields->addFieldsToTab('Root.Social', $linkFields); $fields->addFieldsToTab('Root.Social', $linkFields);
} }
public static function byPhone($phone)
{
$links = Link::get()->filter('Phone', $phone);
if ($links->exists()) {
return Member::get()->filter(
'PhoneNumberID',
array_keys($links->map('ID', 'Title')->toArray())
)->first();
}
return null;
}
} }

View File

@ -23,9 +23,10 @@ class PageController extends ContentController
public function getSiteWideMessage() public function getSiteWideMessage()
{ {
$session = $this->getRequest()->getSession(); $session = $this->getRequest()->getSession();
$message = $session->get('SiteWideMessage');
$session->clear('SiteWideMessage'); $session->clear('SiteWideMessage');
return $session->get('SiteWideMessage'); return $message;
} }
public function CurrentTime() public function CurrentTime()

View File

@ -1,5 +1,10 @@
<% if $SiteWideMessage %> <% if $SiteWideMessage %>
<div class="alert alert-fixed-top alert-{$Type}"> <% with $SiteWideMessage %>
{$Message} <div class="alert alert-fixed-top alert-{$Type}">
</div> {$Message}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<i class="fas fa-times"></i>
</button>
</div>
<% end_with %>
<% end_if %> <% end_if %>

View File

@ -19,7 +19,6 @@
"dnadesign/silverstripe-elemental-virtual": "*", "dnadesign/silverstripe-elemental-virtual": "*",
"dnadesign/silverstripe-elemental-userforms": "*", "dnadesign/silverstripe-elemental-userforms": "*",
"dynamic/silverstripe-elemental-blocks": "*", "dynamic/silverstripe-elemental-blocks": "*",
"hestec/silverstripe-cookiebar": "^1.0",
"drmartingonzo/ss-tinymce-charcount": "^1.0", "drmartingonzo/ss-tinymce-charcount": "^1.0",
"axllent/silverstripe-version-truncator": "*", "axllent/silverstripe-version-truncator": "*",
"firesphere/googlemapsfield": "^1.0@dev", "firesphere/googlemapsfield": "^1.0@dev",

View File

@ -27,6 +27,7 @@
"bootstrap": "^4.1.1", "bootstrap": "^4.1.1",
"bootstrap-datepicker": "^1.8.0", "bootstrap-datepicker": "^1.8.0",
"bootstrap-offcanvas": "^1.0.0", "bootstrap-offcanvas": "^1.0.0",
"bootstrap-select": "^1.13.2",
"bootstrap-timepicker": "^0.5.2", "bootstrap-timepicker": "^0.5.2",
"core-util-is": "^1.0.2", "core-util-is": "^1.0.2",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
@ -35,6 +36,7 @@
"jquery": "^3.3.1", "jquery": "^3.3.1",
"jquery-hammerjs": "^2.0.0", "jquery-hammerjs": "^2.0.0",
"jquery-zoom": "^1.7.21", "jquery-zoom": "^1.7.21",
"jquery.appear": "^1.0.1",
"mapbox-gl": "^0.48.0", "mapbox-gl": "^0.48.0",
"meta-lightbox": "^1.0.0", "meta-lightbox": "^1.0.0",
"offcanvas-bootstrap": "^2.5.2", "offcanvas-bootstrap": "^2.5.2",