572 lines
21 KiB
JavaScript
572 lines
21 KiB
JavaScript
String.prototype.similarity = function(string) {
|
|
// adapted from https://github.com/jordanthomas/jaro-winkler (licensed as MIT)
|
|
var s1 = this, s2 = string;
|
|
var m = 0;
|
|
var i;
|
|
var j;
|
|
|
|
// Exit early if either are empty.
|
|
if (s1.length === 0 || s2.length === 0) {
|
|
return 0;
|
|
}
|
|
|
|
// Convert to upper
|
|
s1 = s1.toUpperCase();
|
|
s2 = s2.toUpperCase();
|
|
|
|
// Exit early if they're an exact match.
|
|
if (s1 === s2) {
|
|
return 1;
|
|
}
|
|
|
|
var range = (Math.floor(Math.max(s1.length, s2.length) / 2)) - 1;
|
|
var s1Matches = new Array(s1.length);
|
|
var s2Matches = new Array(s2.length);
|
|
|
|
for (i = 0; i < s1.length; i++) {
|
|
var low = (i >= range) ? i - range : 0;
|
|
var high = (i + range <= (s2.length - 1)) ? (i + range) : (s2.length - 1);
|
|
|
|
for (j = low; j <= high; j++) {
|
|
if (s1Matches[i] !== true && s2Matches[j] !== true && s1[i] === s2[j]) {
|
|
++m;
|
|
s1Matches[i] = s2Matches[j] = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exit early if no matches were found.
|
|
if (m === 0) {
|
|
return 0;
|
|
}
|
|
|
|
// Count the transpositions.
|
|
var k = 0;
|
|
var numTrans = 0;
|
|
|
|
for (i = 0; i < s1.length; i++) {
|
|
if (s1Matches[i] === true) {
|
|
for (j = k; j < s2.length; j++) {
|
|
if (s2Matches[j] === true) {
|
|
k = j + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (s1[i] !== s2[j]) {
|
|
++numTrans;
|
|
}
|
|
}
|
|
}
|
|
|
|
var weight = (m / s1.length + m / s2.length + (m - (numTrans / 2)) / m) / 3;
|
|
var l = 0;
|
|
var p = 0.1;
|
|
|
|
if (weight > 0.7) {
|
|
while (s1[l] === s2[l] && l < 4) {
|
|
++l;
|
|
}
|
|
|
|
weight = weight + l * p * (1 - weight);
|
|
}
|
|
|
|
return weight;
|
|
}
|
|
|
|
/* Make table widget responsive
|
|
* new Responsive_table_widget(table)
|
|
*/
|
|
const Responsive_table_widget = function (table) {
|
|
'use strict';
|
|
this.table = table;
|
|
this.col_headers = table.querySelectorAll('thead th');
|
|
this.col_headers_text = [];
|
|
this.body_rows = table.querySelectorAll('tbody tr');
|
|
this.parent = table.parentElement;
|
|
this.init();
|
|
};
|
|
Responsive_table_widget.prototype.storeHeaders = function () {
|
|
'use strict';
|
|
let _self = this;
|
|
$(this.col_headers).each(function (i, header) {
|
|
_self.col_headers_text.push(header.innerText);
|
|
});
|
|
$(this.body_rows).each(function (i, tr) {
|
|
$(tr.querySelectorAll('td')).each(function (i, td) {
|
|
td.dataset.colHeader = _self.col_headers_text[i];
|
|
});
|
|
});
|
|
};
|
|
Responsive_table_widget.prototype.fit = function () {
|
|
'use strict';
|
|
this.table.style.width = "auto";
|
|
if (this.parent.clientWidth < this.table.clientWidth)
|
|
this.table.style.width = "100%";
|
|
};
|
|
Responsive_table_widget.prototype.init = function () {
|
|
'use strict';
|
|
let _self = this;
|
|
this.table.classList.add('responsive-tableWidget');
|
|
this.storeHeaders();
|
|
this.fit();
|
|
// debounce resize event
|
|
let callback;
|
|
window.addEventListener("resize", function () {
|
|
clearTimeout(callback);
|
|
callback = setTimeout( function () {
|
|
_self.fit.call(_self)
|
|
}, 200);
|
|
});
|
|
};
|
|
|
|
$(function() {
|
|
var autosave_timeout_id = null;
|
|
if ($('form[data-has-draft]:not([data-autosave=false])').length == 1) {
|
|
var last_auto_save = $('form[data-has-draft]').serialize();
|
|
function autosave() {
|
|
var $form = $('form[data-has-draft]');
|
|
if ($form.hasClass('disabled-during-submit')) return;
|
|
var new_auto_save = $form.serialize();
|
|
if (last_auto_save == new_auto_save) {
|
|
autosave_timeout_id = window.setTimeout(autosave, 5000);
|
|
return;
|
|
}
|
|
$.ajax({
|
|
type: 'POST',
|
|
url: window.location.pathname + 'autosave',
|
|
data: new_auto_save,
|
|
success: function(json) {
|
|
if (json.result == 'success') {
|
|
last_auto_save = new_auto_save;
|
|
}
|
|
},
|
|
complete: function() {
|
|
autosave_timeout_id = window.setTimeout(autosave, 5000);
|
|
}
|
|
});
|
|
}
|
|
autosave_timeout_id = window.setTimeout(autosave, 5000);
|
|
$('#tracking-code a').on('click', autosave);
|
|
$(document).on('wcs:set-last-auto-save', function() {
|
|
last_auto_save = $('form[data-has-draft]').serialize();
|
|
});
|
|
}
|
|
|
|
// common domains we want to offer suggestions for.
|
|
var well_known_domains = Array();
|
|
// existing domains we know but don't want to use in suggestion engine.
|
|
var known_domains = Array();
|
|
if (typeof WCS_WELL_KNOWN_DOMAINS !== 'undefined') {
|
|
var well_known_domains = WCS_WELL_KNOWN_DOMAINS;
|
|
var known_domains = WCS_VALID_KNOWN_DOMAINS;
|
|
}
|
|
|
|
function add_js_behaviours($base) {
|
|
$base.find('input[type=email]').on('change wcs:change', function() {
|
|
var $email_input = $(this);
|
|
var val = $email_input.val();
|
|
var val_domain = val.split('@')[1];
|
|
var $domain_hint_div = this.domain_hint_div;
|
|
var highest_ratio = 0;
|
|
var suggestion = null;
|
|
|
|
if (typeof val_domain === 'undefined' || known_domains.indexOf(val_domain) > -1) {
|
|
// domain not yet typed in, or known domain, don't suggest anything.
|
|
if ($domain_hint_div) {
|
|
$domain_hint_div.hide();
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (var i=0; i < well_known_domains.length; i++) {
|
|
var domain = well_known_domains[i];
|
|
var ratio = val_domain.similarity(domain);
|
|
if (ratio > highest_ratio) {
|
|
highest_ratio = ratio;
|
|
suggestion = domain;
|
|
}
|
|
}
|
|
if (highest_ratio > 0.80 && highest_ratio < 1) {
|
|
if ($domain_hint_div === undefined) {
|
|
$domain_hint_div = $('<div class="field-live-hint"><span class="message"></span><button type="button" class="action"></button><button type="button" class="close"><span class="sr-only"></span></button></div>');
|
|
this.domain_hint_div = $domain_hint_div;
|
|
$(this).after($domain_hint_div);
|
|
$domain_hint_div.find('button.action').on('click', function() {
|
|
$email_input.val($email_input.val().replace(/@.*/, '@' + $(this).data('suggestion')));
|
|
$email_input.trigger('wcs:change');
|
|
$domain_hint_div.hide();
|
|
return false;
|
|
});
|
|
$domain_hint_div.find('button.close').on('click', function() {
|
|
$domain_hint_div.hide();
|
|
return false;
|
|
});
|
|
}
|
|
$domain_hint_div.find('span').text(WCS_I18N.email_domain_suggest + ' @' + suggestion + ' ?');
|
|
$domain_hint_div.find('button.action').text(WCS_I18N.email_domain_fix);
|
|
$domain_hint_div.find('button.action').data('suggestion', suggestion);
|
|
$domain_hint_div.find('button.close span.sr-only').text(WCS_I18N.close);
|
|
$domain_hint_div.show();
|
|
} else if ($domain_hint_div) {
|
|
$domain_hint_div.hide();
|
|
}
|
|
});
|
|
$base.find('.date-pick').each(function() {
|
|
if (this.type == "date" || this.type == "time") {
|
|
return; // prefer native date/time widgets
|
|
}
|
|
var $date_input = $(this);
|
|
$date_input.attr('type', 'text');
|
|
if ($date_input.data('formatted-value')) {
|
|
$date_input.val($date_input.data('formatted-value'));
|
|
}
|
|
var options = Object();
|
|
options.autoclose = true;
|
|
options.weekStart = 1;
|
|
options.format = $date_input.data('date-format');
|
|
options.minView = $date_input.data('min-view');
|
|
options.maxView = $date_input.data('max-view');
|
|
options.startView = $date_input.data('start-view');
|
|
if ($date_input.data('start-date')) options.startDate = $date_input.data('start-date');
|
|
if ($date_input.data('end-date')) options.endDate = $date_input.data('end-date');
|
|
$date_input.datetimepicker(options);
|
|
});
|
|
|
|
/* searchable select */
|
|
$base.find('select[data-autocomplete]').each(function(i, elem) {
|
|
var required = $(elem).data('required');
|
|
var options = {
|
|
language: {
|
|
errorLoading: function() { return WCS_I18N.s2_errorloading; },
|
|
noResults: function () { return WCS_I18N.s2_nomatches; },
|
|
inputTooShort: function (input, min) { return WCS_I18N.s2_tooshort; },
|
|
loadingMore: function () { return WCS_I18N.s2_loadmore; },
|
|
searching: function () { return WCS_I18N.s2_searching; }
|
|
}
|
|
};
|
|
options.placeholder = $(elem).find('[data-hint]').data('hint');
|
|
if (!required) {
|
|
if (!options.placeholder) options.placeholder = '...';
|
|
options.allowClear = true;
|
|
}
|
|
$(elem).select2(options);
|
|
});
|
|
|
|
/* searchable select using a data source */
|
|
$base.find('select[data-select2-url]').each(function(i, elem) {
|
|
var required = $(elem).data('required');
|
|
// create an additional hidden field to hold the label of the selected
|
|
// option, it is necessary as the server may not have any knowledge of
|
|
// possible options.
|
|
var $input_display_value = $('<input>', {
|
|
type: 'hidden',
|
|
name: $(elem).attr('name') + '_display',
|
|
value: $(elem).data('initial-display-value')
|
|
});
|
|
$input_display_value.insertAfter($(elem));
|
|
var options = {
|
|
minimumInputLength: 1,
|
|
formatResult: function(result) { return result.text; },
|
|
language: {
|
|
errorLoading: function() { return WCS_I18N.s2_errorloading; },
|
|
noResults: function () { return WCS_I18N.s2_nomatches; },
|
|
inputTooShort: function (input, min) { return WCS_I18N.s2_tooshort; },
|
|
loadingMore: function () { return WCS_I18N.s2_loadmore; },
|
|
searching: function () { return WCS_I18N.s2_searching; }
|
|
}
|
|
};
|
|
if (!required) {
|
|
options.placeholder = '...';
|
|
options.allowClear = true;
|
|
}
|
|
var url = $(elem).data('select2-url');
|
|
if (url.indexOf('/api/autocomplete/') == 0) { // local proxying
|
|
var data_type = 'json';
|
|
} else {
|
|
var data_type = 'jsonp';
|
|
}
|
|
options.ajax = {
|
|
delay: 250,
|
|
dataType: data_type,
|
|
data: function(params) {
|
|
return {q: params.term, page_limit: 10};
|
|
},
|
|
processResults: function (data, params) {
|
|
return {results: data.data};
|
|
},
|
|
url: function() {
|
|
var url = $(elem).data('select2-url');
|
|
url = url.replace(/\[var_.+?\]/g, function(match, g1, g2) {
|
|
// compatibility: if there are [var_...] references in the URL
|
|
// replace them by looking for other select fields on the same
|
|
// page.
|
|
var related_select = $('#' + match.slice(1, -1));
|
|
var value_container_id = $(related_select).data('valuecontainerid');
|
|
return $('#' + value_container_id).val() || '';
|
|
});
|
|
return url;
|
|
}
|
|
};
|
|
var select2 = $(elem).select2(options);
|
|
$(elem).on('change', function() {
|
|
// update _display hidden field with selected text
|
|
var text = $(elem).find(':selected').first().text();
|
|
$input_display_value.val(text);
|
|
});
|
|
if ($input_display_value.val()) {
|
|
// if the _display hidden field was created with an initial value take it
|
|
// and create a matching <option> in the real <select> widget, and use it
|
|
// to set select2 initial state.
|
|
var option = $('<option></option>', {value: $(elem).data('value')});
|
|
option.appendTo($(elem));
|
|
option.text($input_display_value.val());
|
|
select2.val($(elem).data('value')).trigger('change');
|
|
$(elem).select2('data', {id: $(elem).data('value'), text: $(elem).data('initial-display-value')});
|
|
}
|
|
});
|
|
|
|
/* Make table widgets responsive */
|
|
$base.find('.TableWidget, .SingleSelectTableWidget, .TableListRowsWidget').each(function (i, elem) {
|
|
const table = elem.querySelector('table');
|
|
new Responsive_table_widget(table);
|
|
});
|
|
|
|
}
|
|
|
|
add_js_behaviours($('form[data-live-url], form[data-backoffice-preview]'));
|
|
|
|
// Form with error
|
|
const first_widget_with_error = document.querySelector('.widget-with-error');
|
|
if (first_widget_with_error) {
|
|
document.body.classList.add('form-with-error');
|
|
if (!document.querySelector('.global-errors')) {
|
|
const first_field_with_error = first_widget_with_error.querySelector('input,textarea,select');
|
|
first_field_with_error.focus();
|
|
}
|
|
}
|
|
|
|
$(window).bind('pageshow', function(event) {
|
|
$('form').removeClass('disabled-during-submit');
|
|
});
|
|
$('form button').on('click', function(event) {
|
|
if ($(this).hasClass('download')) {
|
|
$(this).parents('form').addClass('download-button-clicked');
|
|
} else {
|
|
$(this).parents('form').removeClass('download-button-clicked');
|
|
}
|
|
return true;
|
|
});
|
|
$('form').on('submit', function(event) {
|
|
var $form = $(this);
|
|
if (autosave_timeout_id) {
|
|
window.clearTimeout(autosave_timeout_id);
|
|
}
|
|
$form.addClass('disabled-during-submit');
|
|
if ($form.hasClass('download-button-clicked')) {
|
|
/* form cannot be disabled for download buttons as the user will stay on
|
|
* the same page; enable it back after a few seconds. */
|
|
setTimeout(function() { $form.removeClass('disabled-during-submit'); }, 3000);
|
|
}
|
|
if ($form[0].wait_for_changes) {
|
|
var waited = 0;
|
|
var $button = $(event.originalEvent.submitter);
|
|
if (! $button.is('button')) {
|
|
$button = $('form .buttons .submit-button button');
|
|
}
|
|
var wait_id = setInterval(function() {
|
|
waited += 1;
|
|
if (! $form[0].wait_for_changes) {
|
|
clearInterval(wait_id);
|
|
$button.click();
|
|
return;
|
|
} else if (waited > 5) {
|
|
$form[0].wait_for_changes = false;
|
|
}
|
|
}, 200);
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
var live_evaluation = null;
|
|
if ($('div[data-live-source]').length || $('.submit-user-selection').length) {
|
|
$('form[data-live-url]').on('wcs:change', function(ev, data) {
|
|
if (live_evaluation) {
|
|
live_evaluation.abort();
|
|
}
|
|
var new_data = $(this).serialize();
|
|
if (data && data.modified_field) {
|
|
new_data += '&modified_field_id=' + data.modified_field;
|
|
}
|
|
$('.widget-prefilled').each(function(idx, elem) {
|
|
new_data += '&prefilled_' + $(elem).data('field-id') + '=true';
|
|
});
|
|
var live_url = $(this).data('live-url');
|
|
live_evaluation = $.ajax({
|
|
type: 'POST',
|
|
url: live_url,
|
|
dataType: 'json',
|
|
data: new_data,
|
|
headers: {'accept': 'application/json'},
|
|
success: function(json) {
|
|
$.each(json.result, function(key, value) {
|
|
if (value.block_id) {
|
|
var $widget = $('[data-field-id="' + value.block_id + '"] [data-field-id="' + value.field_id + '"]');
|
|
} else {
|
|
var $widget = $('[data-field-id="' + key + '"]');
|
|
}
|
|
if (value.visible) {
|
|
var was_visible = $widget.is(':visible');
|
|
$widget.css('display', '');
|
|
if ($widget.hasClass('MapWidget') && !was_visible) {
|
|
$widget.find('.qommon-map').trigger('qommon:invalidate');
|
|
}
|
|
} else {
|
|
$widget.hide();
|
|
}
|
|
if (value.items && $widget.is('.RadiobuttonsWidget')) {
|
|
var current_value = $widget.find('input:checked').val();
|
|
var $hint = $widget.find('.hint').detach();
|
|
var input_name = $widget.data('widget-name');
|
|
var $content = $widget.find('.content');
|
|
$content.empty();
|
|
for (var i=0; i<value.items.length; i++) {
|
|
var $label = $('<label></label>');
|
|
var $input = $('<input>', {type: 'radio', value: value.items[i].id, name: input_name});
|
|
if (value.items[i].id == current_value) {
|
|
$input.attr('checked', 'checked');
|
|
}
|
|
if (value.items[i].disabled) {
|
|
$input.prop('disabled', true);
|
|
$label.addClass('disabled');
|
|
}
|
|
var $span = $('<span></span>', {text: value.items[i].text});
|
|
$input.appendTo($label);
|
|
$span.appendTo($label);
|
|
$label.appendTo($content);
|
|
}
|
|
$hint.appendTo($content);
|
|
} else if (value.items) {
|
|
// replace <select> contents
|
|
var $select = $widget.find('select');
|
|
var current_value = $select.val();
|
|
var hint = $widget.find('option[data-hint]').data('hint');
|
|
$select.empty();
|
|
if (hint) {
|
|
var $option = $('<option></option>', {value: '', text: hint});
|
|
$option.attr('data-hint', hint);
|
|
$option.appendTo($select);
|
|
}
|
|
for (var i=0; i<value.items.length; i++) {
|
|
var $option = $('<option></option>', {value: value.items[i].id, text: value.items[i].text});
|
|
if (value.items[i].id == current_value) {
|
|
$option.attr('selected', 'selected');
|
|
value.items[i].selected = true;
|
|
}
|
|
if (value.items[i].disabled) {
|
|
$option.prop('disabled', true);
|
|
}
|
|
$option.appendTo($select);
|
|
}
|
|
$select.trigger('wcs:options-change', {items: value.items});
|
|
}
|
|
if (typeof value.content !== 'undefined') {
|
|
$widget.each(function(idx, widget) {
|
|
if ($widget.hasClass('comment-field')) {
|
|
// replace comment content
|
|
$widget.html(value.content);
|
|
} else {
|
|
if ($(widget).is('.widget-prefilled') || $(widget).is('.widget-readonly') || data.modified_field == 'user') {
|
|
// replace text input value
|
|
$(widget).find('input[type=text], input[type=tel], input[type=numeric], input[type=email], input[type=date], textarea').val(value.content);
|
|
if ($widget.hasClass('CheckboxWidget')) {
|
|
// replace checkbox input value
|
|
$widget.find('input[type=checkbox]').prop('checked', value.content);
|
|
}
|
|
// replace select value
|
|
$(widget).find('select').val(value.content);
|
|
// replace radio value
|
|
$(widget).find('input[type=radio]').prop('checked', false);
|
|
$(widget).find('input[type=radio][value="'+value.content+'"]').prop('checked', true);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (value.source_url) {
|
|
// json change of URL
|
|
$widget.find('[data-select2-url]').data('select2-url', value.source_url);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
if ($('div[data-live-source]').length) {
|
|
$('form').on('change input paste wcs:change',
|
|
'div[data-live-source] input:not([type=file]), div[data-live-source] select, div[data-live-source] textarea',
|
|
function(ev) {
|
|
var modified_field = $(this).parents('[data-field-id]').data('field-id');
|
|
$(this).parents('form').trigger('wcs:change', {modified_field: modified_field});
|
|
});
|
|
}
|
|
$('form div[data-live-source]').parents('form').trigger('wcs:change', {modified_field: 'init'});
|
|
$('div.widget-prefilled').on('change input paste', function(ev) {
|
|
$(this).removeClass('widget-prefilled');
|
|
});
|
|
$('div.widget-prefilled input[type=radio], div.widget-prefilled input[type=checkbox]').on('change', function(ev) {
|
|
$(this).closest('div.widget').removeClass('widget-prefilled');
|
|
});
|
|
|
|
|
|
function disable_single_block_remove_button() {
|
|
$('.BlockSubWidget button.remove-button').each(function(i, elem) {
|
|
if ($(this).parents('.BlockWidget').find('.BlockSubWidget').length == 1) {
|
|
$(this).prop('disabled', true);
|
|
}
|
|
});
|
|
}
|
|
|
|
if ($('.BlockWidget').length) {
|
|
disable_single_block_remove_button();
|
|
$('form').on('click', '.BlockSubWidget button.remove-button', function() {
|
|
if ($(this).parents('.BlockWidget').find('.BlockSubWidget').length > 1) {
|
|
$(this).parents('.BlockWidget').find('.list-add').show();
|
|
$(this).parents('.BlockSubWidget').remove();
|
|
disable_single_block_remove_button();
|
|
}
|
|
return false;
|
|
});
|
|
|
|
$('form').on('click keyup', 'div.BlockWidget .list-add button', function(ev) {
|
|
if (event.type == 'keyup' && event.keyCode !== 13) {
|
|
return;
|
|
}
|
|
ev.preventDefault();
|
|
const $block = $(this).parents('.BlockWidget');
|
|
const block_id = $block.data('field-id');
|
|
const $button = $(this);
|
|
const $form = $(this).parents('form');
|
|
var form_data = $form.serialize();
|
|
form_data += '&' + $button.attr('name') + '=' + $button.val();
|
|
$.ajax({
|
|
type: 'POST',
|
|
url: $form.attr('action') || window.location.pathname,
|
|
data: form_data,
|
|
headers: {'x-wcs-ajax-action': 'block-add-row'},
|
|
success: function(result, text_status, jqXHR) {
|
|
result = $(result);
|
|
$block.replaceWith(result.find('[data-field-id="' + block_id + '"]'));
|
|
add_js_behaviours($('[data-field-id="' + block_id + '"]'));
|
|
$('form').trigger('wcs:block-row-added');
|
|
}
|
|
});
|
|
});
|
|
|
|
const $added_sub_widget = $('.wcs-block-add-clicked').find('.BlockSubWidget').last();
|
|
if ($added_sub_widget.length) {
|
|
$added_sub_widget[0].scrollIntoView({behavior: "instant", block: "center", inline: "nearest"});
|
|
}
|
|
}
|
|
});
|