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

443 lines
16 KiB
JavaScript
Raw Normal View History

/* $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() {
this.disableWrapper = $('#ie67DisableWrapper');
// Check for unsupported browsers only if the element exists on the page
if (this.disableWrapper.length) {
if (vitro.browserUtils.isIELessThan8()) {
this.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.baseButtonText = this.button.val();
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.verifyMatchBaseHref = this.verifyMatch.attr('href');
this.acSelectorWrapper = this.acSelector.parent();
this.relatedIndLabel = $('#relatedIndLabel');
this.labelFieldLabel = $('label[for=' + this.relatedIndLabel.attr('id') + ']');
// Get this on page load, so we can prepend to it. We can't just prepend to the current label text,
// because it may have already been modified for a previous selection.
this.baseLabelText = this.labelFieldLabel.html();
// Label field for new individual being created
this.newIndLabel = $('#newIndLabel');
this.newIndLabelFieldLabel = $('label[for=' + this.newIndLabel.attr('id') + ']');
this.newIndBaseLabelText = this.newIndLabelFieldLabel.html();
this.dateHeader = $('#dateHeader');
this.baseDateHeaderText = this.dateHeader.html();
this.or = $('span.or');
this.cancel = this.form.find('.cancel');
this.placeHolderText = '###';
},
// Set up the form on page load
initPage: function() {
if (!this.editMode) {
this.editMode = 'add'; // edit vs add: default to add
}
if (!this.typeSelector.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.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() {
2010-07-16 14:59:20 +00:00
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();
if( this.formSteps > 1 ){ // NB includes this.editMode == 1
this.cancel.unbind('click');
this.cancel.click(function() {
customForm.clearFormData(); // clear any input and validation errors
customForm.initFormTypeView();
return false;
});
}
},
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);
}
this.cancel.unbind('click');
this.cancel.click(function() {
// Cancel back to full view with only type selection showing
customForm.undoAutocompleteSelection();
customForm.clearFields(customForm.fullViewOnly);
customForm.initFormFullView();
return false;
});
},
// 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();
// If no selection, go back to type view. This prevents problems like trying to run autocomplete
// or submitting form without a type selection. Exception: in repair editMode, stay in full view,
// else we lose the role information.
(typeVal.length || customForm.editMode == 'repair') ? customForm.initFormFullView() : customForm.initFormTypeView();
});
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;
});
},
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);
}
});
},
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.acSelectorWrapper.hide();
//this.acSelector.attr('disabled', 'disabled');
// If only one form step, type is pre-selected, and the label is coded in the html.
if (this.formSteps > 1) {
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.verifyMatchBaseHref + 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() {
this.acSelectorWrapper.show();
this.hideFields(this.acSelection);
this.acSelector.val('');
this.acUriReceiver.val('');
this.acSelectionInfo.html('');
this.verifyMatch.attr('href', this.verifyMatchBaseHref);
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 newLabelTextForNewInd,
// if this.acType is empty, we are in repair mode with no activity type selected.
// Prevent the labels from showing 'Select one' by using the generic term 'Activity'
typeText = this.acType ? this.typeName : 'Activity';
this.labelFieldLabel.html(typeText + ' ' + this.baseLabelText);
if (this.dateHeader.length) {
this.dateHeader.html(this.baseDateHeaderText + typeText);
}
if (this.newIndLabel.length) {
newLabelTextForNewInd = this.newIndBaseLabelText.replace(this.placeHolderText, typeText);
this.newIndLabelFieldLabel.html(newLabelTextForNewInd);
}
},
// 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;
// Edit mode button doesn't change, so it's specified in the jsp
if (this.editMode === 'edit') {
return;
}
// if this.acType is empty, we are in repair mode with no activity type selected.
// Prevent the labels from showing 'Select one' by using the generic term 'Activity'
typeText = this.acType ? this.typeName : 'Activity';
2010-07-16 14:59:20 +00:00
// Creating new related individual
if (newOrExisting === 'new') {
if (this.submitButtonTextType == 'compound') { // use == to tolerate nulls
// e.g., 'Create Grant & Principal Investigator'
buttonText = 'Create ' + typeText + ' & ' + this.baseButtonText;
} else {
// e.g., 'Create Publication'
buttonText = 'Create ' + this.baseButtonText;
}
}
2010-07-16 14:59:20 +00:00
// Using existing related individual
else {
// In repair mode, baseButtonText is "Edit X". Keep that for this case.
buttonText = this.editMode == 'repair' ? this.baseButtonText : 'Add ' + this.baseButtonText;
}
this.button.val(buttonText);
}
};
$(document).ready(function() {
customForm.onLoad();
});