374 lines
13 KiB
JavaScript
374 lines
13 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;
|
|
}
|
|
|
|
$(function() {
|
|
var autosave_timeout_id = null;
|
|
if ($('form[data-has-draft]').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;
|
|
}
|
|
$('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 (known_domains.indexOf(val_domain) > -1) {
|
|
// known domain, don't suggest anything.
|
|
$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 class="action"></button><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();
|
|
}
|
|
});
|
|
$('.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);
|
|
});
|
|
if ($('.widget-with-error').length && $('.global-errors').length == 0) {
|
|
var first_field_with_error = $($('.widget-with-error')[0]).find('input,textarea,select');
|
|
if (first_field_with_error.length) {
|
|
$(first_field_with_error)[0].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);
|
|
}
|
|
return true;
|
|
});
|
|
var live_evaluation = null;
|
|
$('form div[data-live-source]').parents('form').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;
|
|
}
|
|
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) {
|
|
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) {
|
|
// 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');
|
|
}
|
|
$option.appendTo($select);
|
|
}
|
|
$select.trigger('wcs:options-change', {items: value.items});
|
|
}
|
|
if (value.content) {
|
|
var $widget = $('[data-field-id="' + key + '"]');
|
|
if ($widget.hasClass('comment-field')) {
|
|
// replace comment content
|
|
$widget.html(value.content);
|
|
} else {
|
|
// replace text input value
|
|
$widget.find('input, textarea').val(value.content);
|
|
}
|
|
}
|
|
if (value.source_url) {
|
|
// json change of URL
|
|
$widget.find('[data-select2-url]').data('select2-url', value.source_url);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
$('form div[data-live-source] input, form div[data-live-source] select, form div[data-live-source] textarea').on('change input paste wcs:change', 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'});
|
|
|
|
/* searchable select */
|
|
$('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 */
|
|
$('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;
|
|
}
|
|
options.ajax = {
|
|
delay: 250,
|
|
dataType: 'json',
|
|
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')});
|
|
}
|
|
});
|
|
});
|