vivo/productMods/edit/forms/js/customFormWithAutocomplete.js

487 lines
17 KiB
JavaScript

/* $This file is distributed under the terms of the license in /doc/license.txt$ */
var customForm = {
/* *** Initial page setup *** */
onLoad: function() {
if (this.disableFormInUnsupportedBrowsers()) {
return;
}
this.mixIn();
this.initObjects();
this.initPage();
},
disableFormInUnsupportedBrowsers: function() {
var disableWrapper = $('#ie67DisableWrapper');
// Check for unsupported browsers only if the element exists on the page
if (disableWrapper.length) {
if (vitro.browserUtils.isIELessThan8()) {
disableWrapper.show();
$('.noIE67').hide();
return true;
}
}
return false;
},
mixIn: function() {
// Mix in the custom form utility methods
$.extend(this, vitro.customFormUtils);
// Get the custom form data from the page
$.extend(this, customFormData);
},
// On page load, create references for easy access to form elements.
// NB These must be assigned after the elements have been loaded onto the page.
initObjects: function(){
this.form = $('#content form');
this.fullViewOnly = $('.fullViewOnly');
this.button = $('#submit');
this.requiredLegend = $('#requiredLegend');
this.typeSelector = this.form.find('#typeSelector');
// These are classed rather than id'd in case we want more than one autocomplete on a form.
// At that point we'll use ids to match them up with one another.
this.acSelector = this.form.find('.acSelector');
this.acSelection = this.form.find('.acSelection');
this.acSelectionInfo = this.form.find('.acSelectionInfo');
this.acUriReceiver = this.form.find('.acUriReceiver');
//this.acLabelReceiver = this.form.find('.acLabelReceiver');
this.verifyMatch = this.form.find('.verifyMatch');
this.acSelectorWrapper = this.acSelector.parent();
this.or = $('span.or');
this.cancel = this.form.find('.cancel');
this.acHelpTextClass = 'acSelectorWithHelpText';
},
// Set up the form on page load
initPage: function() {
if (!this.editMode) {
this.editMode = 'add'; // edit vs add: default to add
}
if (!this.formSteps) { // Don't override formSteps specified in form data
if ( !this.fullViewOnly.length || this.editMode === 'edit' || this.editMode === 'repair' ) {
this.formSteps = 1;
// there may also be a 3-step form - look for this.subTypeSelector
}
else {
this.formSteps = 2;
}
}
this.bindEventListeners();
this.initAutocomplete();
this.initElementData();
this.initFormView();
},
initFormView: function() {
var typeVal = this.typeSelector.val();
// Put this case first, because in edit mode with
// validation errors we just want initFormFullView.
if (this.editMode == 'edit' || this.editMode == 'repair') {
this.initFormFullView();
}
else if (this.findValidationErrors()) {
this.initFormWithValidationErrors();
}
// If type is already selected when the page loads (Firefox retains value
// on a refresh), go directly to full view. Otherwise user has to reselect
// twice to get to full view.
else if ( this.formSteps == 1 || typeVal.length ) {
this.initFormFullView();
}
else {
this.initFormTypeView();
}
},
initFormTypeView: function() {
this.setType(); // empty any previous values (perhaps not needed)
this.hideFields(this.fullViewOnly);
this.button.hide();
this.requiredLegend.hide();
this.or.hide();
this.cancel.unbind('click');
},
initFormFullView: function() {
this.setType();
this.fullViewOnly.show();
this.or.show();
this.requiredLegend.show();
this.button.show();
this.setButtonText('new');
this.setLabels();
// Set the initial autocomplete help text in the acSelector field.
this.addAcHelpText();
this.cancel.unbind('click');
if (this.formSteps > 1) {
this.cancel.click(function() {
customForm.clearFormData(); // clear any input and validation errors
customForm.initFormTypeView();
return false;
});
// In one-step forms, if there is a type selection field, but no value is selected,
// hide the acSelector field. The type selection must be made first so that the
// autocomplete type can be determined. If a type selection has been made,
// unhide the acSelector field.
} else if (this.typeSelector.length) {
this.typeSelector.val() ? this.acSelectorWrapper.show() : this.hideFields(this.acSelectorWrapper);
}
},
initFormWithValidationErrors: function() {
var uri = this.acUriReceiver.val(),
label = this.acSelector.val();
// Call initFormFullView first, because showAutocompleteSelection needs
// acType, which is set in initFormFullView.
this.initFormFullView();
if (uri) {
this.showAutocompleteSelection(label, uri);
}
},
// Bind event listeners that persist over the life of the page. Event listeners
// that depend on the view should be initialized in the view setup method.
bindEventListeners: function() {
this.typeSelector.change(function() {
var typeVal = $(this).val();
// If an autocomplete selection has been made, undo it.
customForm.undoAutocompleteSelection();
// Reinitialize view. If no type selection in a two-step form, go back to type view;
// otherwise, reinitialize full view.
if (!typeVal.length && customForm.formSteps > 1) {
customForm.initFormTypeView();
}
else {
customForm.initFormFullView();
// TW Setting focus here was clearing autocomplete help text, so commented it out
// customForm.acSelector.focus();
}
});
this.verifyMatch.click(function() {
window.open($(this).attr('href'), 'verifyMatchWindow', 'width=640,height=640,scrollbars=yes,resizable=yes,status=yes,toolbar=no,menubar=no,location=no');
return false;
});
this.acSelector.focus(function() {
customForm.deleteAcHelpText();
});
this.acSelector.blur(function() {
customForm.addAcHelpText();
});
this.form.submit(function() {
customForm.deleteAcHelpText();
});
},
initAutocomplete: function() {
if (this.editMode === 'edit') {
return;
}
this.getAcFilter();
this.acCache = {};
this.acSelector.autocomplete({
minLength: 3,
source: function(request, response) {
if (request.term in customForm.acCache) {
// console.log('found term in cache');
response(customForm.acCache[request.term]);
return;
}
// console.log('not getting term from cache');
$.ajax({
url: customForm.acUrl,
dataType: 'json',
data: {
term: request.term,
type: customForm.acType
},
complete: function(xhr, status) {
// Not sure why, but we need an explicit json parse here. jQuery
var results = $.parseJSON(xhr.responseText),
filteredResults = customForm.filterAcResults(results);
customForm.acCache[request.term] = filteredResults;
response(filteredResults);
}
});
},
select: function(event, ui) {
customForm.showAutocompleteSelection(ui.item.label, ui.item.uri);
}
});
},
// Store original or base text with elements that will have text substitutions.
// Generally the substitution cannot be made on the current value, since that value
// may have changed from the original. So we store the original text with the element to
// use as a base for substitutions.
initElementData: function() {
this.placeholderText = '###';
this.labelsWithPlaceholders = this.form.find('label, .label').filter(function() {
return $(this).html().match(customForm.placeholderText);
});
this.labelsWithPlaceholders.each(function(){
$(this).data('baseText', $(this).html());
});
this.button.data('baseText', this.button.val());
this.verifyMatch.data('baseHref', this.verifyMatch.attr('href'));
},
getAcFilter: function() {
if (!this.sparqlForAcFilter) {
//console.log('autocomplete filtering turned off');
this.acFilter = null;
return;
}
//console.log("sparql for autocomplete filter: " + this.sparqlForAcFilter);
// Define this.acFilter here, so in case the sparql query fails
// we don't get an error when referencing it later.
this.acFilter = [];
$.ajax({
url: customForm.sparqlQueryUrl,
data: {
resultFormat: 'RS_JSON',
query: customForm.sparqlForAcFilter
},
success: function(data, status, xhr) {
// Not sure why, but we need an explicit json parse here. jQuery
// should parse the response text and return a json object.
customForm.setAcFilter($.parseJSON(data));
}
});
},
setAcFilter: function(data) {
var key = data.head.vars[0];
$.each(data.results.bindings, function() {
customForm.acFilter.push(this[key].value);
});
},
filterAcResults: function(results) {
var filteredResults;
if (!this.acFilter || !this.acFilter.length) {
//console.log('no autocomplete filtering applied');
return results;
}
filteredResults = [];
$.each(results, function() {
if ($.inArray(this.uri, customForm.acFilter) == -1) {
//console.log('adding ' + this.label + ' to filtered results');
filteredResults.push(this);
}
else {
//console.log('filtering out ' + this.label);
}
});
return filteredResults;
},
// Reset some autocomplete values after type is changed
resetAutocomplete: function(typeVal) {
// Append the type parameter to the base autocomplete url
var glue = this.baseAcUrl.indexOf('?') > -1 ? '&' : '?';
this.acUrl = this.baseAcUrl + glue + 'type=' + typeVal;
// Flush autocomplete cache when type is reset, since the cached values
// pertain only to the previous type.
this.acCache = {};
},
showAutocompleteSelection: function(label, uri) {
this.hideFields(this.acSelectorWrapper);
// If form has a type selector, add type name to label. If form has no type selector,
// type name is coded into the html.
if (this.typeSelector.length) {
this.acSelection.find('label').html('Selected ' + this.typeName + ':');
}
this.acSelection.show();
this.acUriReceiver.val(uri);
this.acSelector.val(label);
this.acSelectionInfo.html(label);
this.verifyMatch.attr('href', this.verifyMatch.data('baseHref') + uri);
this.setButtonText('existing');
this.cancel.unbind('click');
this.cancel.click(function() {
customForm.undoAutocompleteSelection();
customForm.initFormFullView();
return false;
});
},
// Cancel action after making an autocomplete selection: undo autocomplete
// selection (from showAutocomplete) before returning to full view.
undoAutocompleteSelection: function() {
// The test is not just for efficiency: undoAutocompleteSelection empties the acSelector value,
// which we don't want to do if user has manually entered a value, since he may intend to
// change the type but keep the value. If no new value has been selected, form initialization
// below will correctly empty the value anyway.
if (!this.acSelection.is(':hidden')) {
this.acSelectorWrapper.show();
this.hideFields(this.acSelection);
this.acSelector.val('');
this.acUriReceiver.val('');
this.acSelectionInfo.html('');
this.verifyMatch.attr('href', this.verifyMatch.data('baseHref'));
if (this.formSteps > 1) {
this.acSelection.find('label').html('Selected ');
}
}
},
// Set type uri for autocomplete, and type name for labels and button text.
// Note: we still need this in edit mode, to set the text values.
setType: function() {
var selectedType;
// If there's no type selector, these values have been specified in customFormData,
// and will not change over the life of the form.
if (!this.typeSelector.length) {
return;
}
selectedType = this.typeSelector.find(':selected');
if (selectedType.length) {
this.acType = selectedType.val();
this.typeName = selectedType.html();
}
// reset to empty values; may not need
else {
this.acType = '';
this.typeName = '';
}
},
// Set field labels based on type selection. Although these won't change in edit
// mode, it's easier to specify the text here than in the jsp.
setLabels: function() {
var typeName = this.getTypeNameForLabels();
this.labelsWithPlaceholders.each(function() {
var newLabel = $(this).data('baseText').replace(customForm.placeholderText, typeName);
$(this).html(newLabel);
});
},
// Set button text based on both type selection and whether it's an autocomplete selection
// or a new related individual. Called when setting up full view of form, and after
// an autocomplete selection.
setButtonText: function(newOrExisting) {
var typeText,
buttonText,
baseButtonText = this.button.data('baseText');
// Edit mode button doesn't change, so it's specified in the jsp
if (this.editMode === 'edit') {
return;
}
typeText = this.getTypeNameForLabels();
// Creating new related individual
if (newOrExisting === 'new') {
if (this.submitButtonTextType == 'compound') { // use == to tolerate nulls
// e.g., 'Create Grant & Principal Investigator'
buttonText = 'Create ' + typeText + ' & ' + baseButtonText;
} else {
// e.g., 'Create Publication'
buttonText = 'Create ' + baseButtonText;
}
}
// Using existing related individual
else {
// In repair mode, baseButtonText is "Edit X". Keep that for this case.
buttonText = this.editMode == 'repair' ? baseButtonText : 'Add ' + baseButtonText;
}
this.button.val(buttonText);
},
getTypeNameForLabels: function() {
// If this.acType is empty, we are either in a one-step form with no type yet selected,
// or in repair mode in a two-step form with no type selected. Use the default type
// name specified in the form data (this.typeName is 'Select one').
return this.acType ? this.typeName : this.capitalize(this.defaultTypeName);
},
// Set the initial help text that appears in the autocomplete field and change the class name
addAcHelpText: function() {
var typeText;
// First case applies on page load; second case applies when the type gets changed.
if (!this.acSelector.val() || this.acSelector.hasClass(this.acHelpTextClass)) {
typeText = this.getTypeNameForLabels();
this.acSelector.val("Select an existing " + typeText + " or create a new one.")
.addClass(this.acHelpTextClass);
}
},
deleteAcHelpText: function() {
if (this.acSelector.hasClass(this.acHelpTextClass)) {
this.acSelector.val('')
.removeClass(this.acHelpTextClass);
}
}
};
$(document).ready(function() {
customForm.onLoad();
});