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\ORM\Search\FulltextSearchable;
HtmlEditorConfig::get('cms')->enablePlugins([
$config = HtmlEditorConfig::get('cms');
$config->enablePlugins([
'template',
'fullscreen',
'hr',
@ -12,11 +13,12 @@ HtmlEditorConfig::get('cms')->enablePlugins([
'charmap',
'visualblocks',
'lists',
'anchor',
'charcount' => ModuleResourceLoader::resourceURL(
'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();

View File

@ -157,13 +157,13 @@ const AjaxUI = (($) => {
});
}
static preload(items, type = 'text', cache = true) {
static preload(items, type = 'text', cache = true, itemCallback = false) {
if (!items.length) {
return $.Deferred().resolve().promise();
}
const dfds = [];
items.forEach((url) => {
items.forEach((url, i) => {
const dfd = $.Deferred();
$.ajax({
@ -172,6 +172,9 @@ const AjaxUI = (($) => {
url,
}).always(() => {
dfd.resolve();
if (itemCallback) {
itemCallback(i, url);
}
});
dfds.push(dfd);

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 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';
@ -29,25 +29,19 @@ const JqteUI = (($) => {
constructor(element) {
const ui = this;
const $element = $(element);
const $jqteFields = $element.find('textarea.jqte-field');
const validationUI = $element.parents('form').data('jsFormValidate');
const validationUI = $element.data('jsFormValidateField');
$element.data(DATA_KEY, this);
ui._element = element;
$element.data(DATA_KEY, this);
$element.jqte(jqteOptions);
// dynamic error control
if (validationUI) {
$element.parents('.jqte').find('.jqte_editor').on('change', (e) => {
const $field = $(e.target);
const $container = $field.closest('.field');
if (!$field.text().length && $container.hasClass('required')) {
validationUI.setError($container);
} else {
validationUI.removeError($container);
}
validationUI.validate();
});
}
}
static dispose() {
console.log(`Destroying: ${NAME}`);

View File

@ -1,7 +1,7 @@
import $ from 'jquery';
import Events from '../_events';
import LANG from '../lang/_en';
import FormValidate from './_ui.form.validate';
import FormValidateField from "./_ui.form.validate.field";
const SteppedForm = (($) => {
// Constants
@ -13,6 +13,7 @@ const SteppedForm = (($) => {
constructor(element) {
const ui = this;
const $element = $(element);
$element.data(DATA_KEY, this);
if (!$element.find('.steps-counter').length) {
@ -28,16 +29,33 @@ const SteppedForm = (($) => {
ui._steps = $element.find('.step');
ui._stepNext = $element.find('.step-next');
ui._stepPrev = $element.find('.step-prev');
ui._actions = $element.children('.btn-toolbar,.form-actions');
ui._element = element;
ui._currentStep = 1;
ui._totalSteps = ui._steps.length;
ui._totalSteps = ui._steps.last().data('step') || ui._steps.length;
ui._stepsOrder = [];
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._stepNext.on('click', (e) => {
@ -78,8 +96,7 @@ const SteppedForm = (($) => {
return;
}
ui._currentStep++;
ui.step('.step[data-step="' + ui._currentStep + '"]');
ui.step('.step[data-step="' + (ui._currentStep + 1) + '"]');
}
prev() {
@ -89,17 +106,37 @@ const SteppedForm = (($) => {
return;
}
ui._currentStep--;
ui.step(ui._stepsOrder[ui._currentStep]);
ui.step(ui._stepsOrder[ui._currentStep - 1]);
}
step(target) {
const ui = this;
const $element = $(ui._element);
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') {
ui._stepPrev.hide();
$element.trigger(Events.FORM_STEPPED_FIRST_STEP);
} else {
ui._stepPrev.show();
}
@ -107,18 +144,30 @@ const SteppedForm = (($) => {
if (parseInt($target.data('step')) >= ui._totalSteps) {
ui._stepNext.hide();
ui._actions.show();
$element.trigger(Events.FORM_STEPPED_LAST_STEP);
} else {
ui._stepNext.show();
ui._actions.hide();
}
ui._currentStep = parseInt($target.data('step'));
ui._currentStep = targetStep;
ui._stepsOrder[ui._currentStep] = $target;
ui._steps.removeClass('active');
$target.addClass('active');
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() {

View File

@ -31,6 +31,10 @@ const FormStorage = (($) => {
const type = $el.attr('type');
const val = STORAGE.getItem(NAME + id);
if (type === 'file') {
return true;
}
if (id && val && type) {
if (type && (type === 'checkbox' || type === 'radio')) {
$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 Events from "../_events";
import FormValidateField from "./_ui.form.validate.field";
const FormValidate = (($) => {
// Constants
@ -19,23 +20,22 @@ const FormValidate = (($) => {
ui._fields = $fields;
ui._stepped_form = $element.data('jsSteppedForm');
// prevent browsers checks (will do it using JS)
$element.attr('novalidate', 'novalidate');
$element.on(Events.FORM_INIT_STEPPED, () => {
ui._stepped_form = $element.data('jsSteppedForm');
});
// prevent browsers checks (will do it using JS)
$element.attr('novalidate', 'novalidate');
// init fields validation
$fields.each((i, el) => {
el.required = false;
});
$fields.on('change', (e) => {
ui.validateField($(e.target), false);
new FormValidateField(el);
});
// check form
$element.on('submit', (e) => {
ui.validateForm($element, true, () => {
ui.validate(true, () => {
e.preventDefault();
// switch to step
@ -64,52 +64,24 @@ const FormValidate = (($) => {
this._element = null;
}
validateForm($form, scrollTo = true, badCallback = false) {
validate(scrollTo = true, badCallback = false) {
console.log('Checking the form ...');
const ui = this;
ui._fields.each(function(i, el) {
const $el = $(el);
const fieldUI = $el.data('jsFormValidateField');
if (!ui.validateField($el)) {
if (fieldUI && !fieldUI.validate()) {
if (badCallback) {
badCallback();
}
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() {
return this.each(function() {
// attach functionality to element

View File

@ -5,9 +5,15 @@
module.exports = {
AJAX: 'ajax-load',
LOADED: 'load',
SET_TARGET_UPDATE: 'set-target-update',
RESTORE_FIELD: 'restore-field',
FORM_INIT_BASICS: 'form-basics',
FORM_INIT_STEPPED: 'form-init-stepped',
FORM_INIT_VALIDATE: 'form-init-validate',
FORM_INIT_VALIDATE_FIELD: 'form-init-validate-field',
FORM_INIT_STORAGE: 'form-init-storage',
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 Spinner from './_components/_ui.spinner';
import './_components/_ui.shrink';
import './_components/_ui.carousel';
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 SmoothScroll from 'smooth-scroll';
@ -144,27 +150,6 @@ const MainUI = (($) => {
// mark external links
$('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
/*$(D).find('.obm').each(function () {
if ($(this).attr('data-val') !== undefined) {
@ -189,18 +174,23 @@ const MainUI = (($) => {
//
// scroll links
$(D).on('click', '.js-scrollTo', function(e) {
$(D).on('click', '.js-scrollTo', (e) => {
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
if ($('[data-extfont]').length) {
$.getScript('//ajax.googleapis.com/ajax/libs/webfont/1/webfont.js', () => {
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({
google: {
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
Spinner.hide(() => {
$Body.addClass('loaded');
@ -276,14 +290,26 @@ const MainUI = (($) => {
const $imgLazyUrls = [];
// collect image details
$imgs.each(function() {
const src = $(this).attr('src');
const lazySrc = $(this).data('lazy-src');
if (src.length) {
$imgs.each((i, el) => {
const $el = $(el);
const src = $el.attr('src');
const lazySrc = $el.data('lazy-src');
if (src && src.length) {
$imgUrls.push(src);
}
if (lazySrc) {
if (lazySrc && lazySrc.length) {
$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
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!');
$(W).trigger('images-lazy-loaded');

View File

@ -8,10 +8,11 @@ img {
max-width: 100%;
}
a:hover,
a:focus,
button:hover,
button:focus {
a,
button,
.jqte_tool_icon {
&:hover,
&:focus {
opacity: .8;
.fas,
@ -23,10 +24,19 @@ button:focus {
transform: scale(-1, 1);
}
}
}
.jqte_tool_icon {
&:hover,
&:focus {
transform: scale(-1, 1);
}
}
// transactions
.transition,
a, a img,
.jqte_tool_icon,
a .fas, a .fab, a .far,
a.fas, a.fab, a.far,
button .fas, button .fab, button .far,
@ -43,16 +53,73 @@ button, input, optgroup, select, textarea,
transition: all 0.4s ease;
}
.alert-fixed-top {
position: fixed;
top: 0;
z-index: 999;
left: 4rem;
right: 4rem;
}
.btn-toolbar {
margin-top: $grid-gutter-height / 2;
}
.field {
position: relative;
margin: 2rem 0;
margin: ($grid-gutter-height / 4) 0;
&:first-child {
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

View File

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

View File

@ -1 +1,48 @@
@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;
use Sheadawson\Linkable\Forms\LinkField;
use Sheadawson\Linkable\Models\Link;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\DataExtension;
use SilverStripe\Security\Member;
class SocialExtension extends DataExtension
{
@ -59,4 +59,18 @@ class SocialExtension extends DataExtension
$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()
{
$session = $this->getRequest()->getSession();
$message = $session->get('SiteWideMessage');
$session->clear('SiteWideMessage');
return $session->get('SiteWideMessage');
return $message;
}
public function CurrentTime()

View File

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

View File

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

View File

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