wcs/wcs/qommon/static/js/qommon.forms.js

370 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 = WCS_WELL_KNOWN_DOMAINS;
// existing domains we know but don't want to use in suggestion engine.
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')});
}
});
});