634 lines
23 KiB
JavaScript
634 lines
23 KiB
JavaScript
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
|
|
|
var addAuthorForm = {
|
|
|
|
/* *** 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 = $('#addAuthorForm');
|
|
this.showFormButtonWrapper = $('#showAddForm');
|
|
this.showFormButton = $('#showAddFormButton');
|
|
this.removeAuthorshipLinks = $('a.remove');
|
|
//this.undoLinks = $('a.undo');
|
|
this.submit = this.form.find(':submit');
|
|
this.cancel = this.form.find('.cancel');
|
|
this.acSelector = this.form.find('.acSelector');
|
|
this.labelField = $('#label');
|
|
this.firstNameField = $('#firstName');
|
|
this.middleNameField = $('#middleName');
|
|
this.lastNameField = $('#lastName');
|
|
this.lastNameLabel = $('label[for=lastName]');
|
|
this.personUriField = $('#personUri');
|
|
this.firstNameWrapper = this.firstNameField.parent();
|
|
this.middleNameWrapper = this.middleNameField.parent();
|
|
this.lastNameWrapper = this.lastNameField.parent();
|
|
this.selectedAuthor = $('#selectedAuthor');
|
|
this.selectedAuthorName = $('#selectedAuthorName');
|
|
this.acHelpTextClass = 'acSelectorWithHelpText';
|
|
|
|
},
|
|
|
|
// Initial page setup. Called only at page load.
|
|
initPage: function() {
|
|
|
|
this.initAuthorshipData();
|
|
|
|
// Show elements hidden by CSS for the non-JavaScript-enabled version.
|
|
// NB The non-JavaScript version of this form is currently not functional.
|
|
this.removeAuthorshipLinks.show();
|
|
|
|
//this.undoLinks.hide();
|
|
|
|
this.bindEventListeners();
|
|
|
|
this.initAutocomplete();
|
|
|
|
this.initAuthorDD();
|
|
|
|
if (this.findValidationErrors()) {
|
|
this.initFormAfterInvalidSubmission();
|
|
} else {
|
|
this.initAuthorListOnlyView();
|
|
}
|
|
},
|
|
|
|
|
|
/* *** Set up the various page views *** */
|
|
|
|
// This initialization is done only on page load, not when returning to author list only view
|
|
// after hitting 'cancel.'
|
|
initAuthorListOnlyView: function() {
|
|
|
|
if ($('.authorship').length) { // make sure we have at least one author
|
|
// Reorder authors on page load so that previously unranked authors get a rank. Otherwise,
|
|
// when we add a new author, it will get put ahead of any previously unranked authors, instead
|
|
// of at the end of the list. (It is also helpful to normalize the data before we get started.)
|
|
this.reorderAuthors();
|
|
}
|
|
this.showAuthorListOnlyView();
|
|
},
|
|
|
|
// This view shows the list of existing authors and hides the form.
|
|
// There is a button to show the form. We do this on page load, and after
|
|
// hitting 'cancel' from full view.
|
|
showAuthorListOnlyView: function() {
|
|
this.hideForm();
|
|
this.showFormButtonWrapper.show();
|
|
},
|
|
|
|
// View of form after returning from an invalid submission. On this form,
|
|
// validation errors entail that we were entering a new person, so we show
|
|
// all the fields straightaway.
|
|
initFormAfterInvalidSubmission: function() {
|
|
this.initForm();
|
|
this.showFieldsForNewPerson();
|
|
},
|
|
|
|
// Initial view of add author form. We get here by clicking the show form button,
|
|
// or by cancelling out of an autocomplete selection.
|
|
initFormView: function() {
|
|
|
|
this.initForm();
|
|
|
|
this.hideFieldsForNewPerson();
|
|
|
|
// This shouldn't be needed, because calling this.hideFormFields(this.lastNameWrapper)
|
|
// from showSelectedAuthor should do it. However, it doesn't work from there,
|
|
// or in the cancel action, or if referring to this.lastNameField. None of those work,
|
|
// however.
|
|
$('#lastName').val('');
|
|
// Set the initial autocomplete help text in the acSelector field.
|
|
this.addAcHelpText();
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
// Form initialization common to both a 'clean' form view and when
|
|
// returning from an invalid submission.
|
|
initForm: function() {
|
|
|
|
// Hide the button that shows the form
|
|
this.showFormButtonWrapper.hide();
|
|
|
|
this.hideSelectedAuthor();
|
|
|
|
this.cancel.unbind('click');
|
|
this.cancel.bind('click', function() {
|
|
addAuthorForm.showAuthorListOnlyView();
|
|
return false;
|
|
});
|
|
|
|
// Reset the last name field. It had been hidden if we selected an author from
|
|
// the autocomplete field.
|
|
this.lastNameWrapper.show();
|
|
|
|
// Show the form
|
|
this.form.show();
|
|
//this.lastNameField.focus();
|
|
},
|
|
|
|
hideSelectedAuthor: function() {
|
|
this.selectedAuthor.hide();
|
|
this.selectedAuthorName.html('');
|
|
this.personUriField.val('');
|
|
},
|
|
|
|
showFieldsForNewPerson: function() {
|
|
this.firstNameWrapper.show();
|
|
this.middleNameWrapper.show();
|
|
},
|
|
|
|
hideFieldsForNewPerson: function() {
|
|
this.hideFields(this.firstNameWrapper);
|
|
this.hideFields(this.middleNameWrapper);
|
|
},
|
|
|
|
/* *** Ajax initializations *** */
|
|
|
|
/* Autocomplete */
|
|
initAutocomplete: function() {
|
|
|
|
// Make cache a property of this so we can access it after removing
|
|
// an author.
|
|
this.acCache = {};
|
|
this.setAcFilter();
|
|
|
|
this.lastNameField.autocomplete({
|
|
minLength: 2,
|
|
source: function(request, response) {
|
|
if (request.term in addAuthorForm.acCache) {
|
|
// console.log('found term in cache');
|
|
response(addAuthorForm.acCache[request.term]);
|
|
return;
|
|
}
|
|
// console.log('not getting term from cache');
|
|
|
|
// If the url query params are too long, we could do a post
|
|
// here instead of a get. Add the exclude uris to the data
|
|
// rather than to the url.
|
|
$.ajax({
|
|
url: addAuthorForm.acUrl,
|
|
dataType: 'json',
|
|
data: {
|
|
term: request.term
|
|
},
|
|
complete: function(xhr, status) {
|
|
// Not sure why, but we need an explicit json parse here. jQuery
|
|
// should parse the response text and return a json object.
|
|
var results = jQuery.parseJSON(xhr.responseText),
|
|
filteredResults = addAuthorForm.filterAcResults(results);
|
|
addAuthorForm.acCache[request.term] = filteredResults;
|
|
response(filteredResults);
|
|
}
|
|
|
|
});
|
|
},
|
|
// Select event not triggered in IE6/7 when selecting with enter key rather
|
|
// than mouse. Thus form is disabled in these browsers.
|
|
// jQuery UI bug: when scrolling through the ac suggestions with up/down arrow
|
|
// keys, the input element gets filled with the highlighted text, even though no
|
|
// select event has been triggered. To trigger a select, the user must hit enter
|
|
// or click on the selection with the mouse. This appears to confuse some users.
|
|
select: function(event, ui) {
|
|
addAuthorForm.showSelectedAuthor(ui);
|
|
}
|
|
});
|
|
|
|
},
|
|
|
|
setAcFilter: function() {
|
|
this.acFilter = [];
|
|
|
|
$('.authorship').each(function() {
|
|
var uri = $(this).data('authorUri');
|
|
addAuthorForm.acFilter.push(uri);
|
|
});
|
|
},
|
|
|
|
removeAuthorFromAcFilter: function(author) {
|
|
var index = $.inArray(author, this.acFilter);
|
|
if (index > -1) { // this should always be true
|
|
this.acFilter.splice(index, 1);
|
|
}
|
|
},
|
|
|
|
filterAcResults: function(results) {
|
|
var filteredResults = [];
|
|
if (!this.acFilter.length) {
|
|
return results;
|
|
}
|
|
$.each(results, function() {
|
|
if ($.inArray(this.uri, addAuthorForm.acFilter) == -1) {
|
|
// console.log("adding " + this.label + " to filtered results");
|
|
filteredResults.push(this);
|
|
}
|
|
else {
|
|
// console.log("filtering out " + this.label);
|
|
}
|
|
});
|
|
return filteredResults;
|
|
},
|
|
|
|
// After removing an authorship, selectively clear matching autocomplete
|
|
// cache entries, else the associated author will not be included in
|
|
// subsequent autocomplete suggestions.
|
|
clearAcCacheEntries: function(name) {
|
|
name = name.toLowerCase();
|
|
$.each(this.acCache, function(key, value) {
|
|
if (name.indexOf(key) == 0) {
|
|
delete addAuthorForm.acCache[key];
|
|
}
|
|
});
|
|
},
|
|
|
|
// Action taken after selecting an author from the autocomplete list
|
|
showSelectedAuthor: function(ui) {
|
|
|
|
this.personUriField.val(ui.item.uri);
|
|
this.selectedAuthor.show();
|
|
|
|
// Transfer the name from the autocomplete to the selected author
|
|
// name display, and hide the last name field.
|
|
this.selectedAuthorName.html(ui.item.label);
|
|
// NB For some reason this doesn't delete the value from the last name
|
|
// field when the form is redisplayed. Thus it's done explicitly in initFormView.
|
|
this.hideFields(this.lastNameWrapper);
|
|
// These get displayed if the selection was made through an enter keystroke,
|
|
// since the keydown event on the last name field is also triggered (and
|
|
// executes first). So re-hide them here.
|
|
this.hideFieldsForNewPerson();
|
|
|
|
// Cancel restores initial form view
|
|
this.cancel.unbind('click');
|
|
this.cancel.bind('click', function() {
|
|
addAuthorForm.initFormView();
|
|
return false;
|
|
});
|
|
},
|
|
|
|
/* Drag-and-drop */
|
|
initAuthorDD: function() {
|
|
|
|
var authorshipList = $('#authorships'),
|
|
authorships = authorshipList.children('li');
|
|
|
|
if (authorships.length < 2) {
|
|
return;
|
|
}
|
|
|
|
$('.authorNameWrapper').each(function() {
|
|
$(this).attr('title', 'Drag and drop to reorder authors');
|
|
});
|
|
|
|
authorshipList.sortable({
|
|
cursor: 'move',
|
|
update: function(event, ui) {
|
|
addAuthorForm.reorderAuthors(event, ui);
|
|
}
|
|
});
|
|
},
|
|
|
|
// Reorder authors. Called on page load and after author drag-and-drop and remove.
|
|
// Event and ui parameters are defined only in the case of drag-and-drop.
|
|
reorderAuthors: function(event, ui) {
|
|
var authorships = $('li.authorship').map(function(index, el) {
|
|
return $(this).data('authorshipUri');
|
|
}).get();
|
|
|
|
$.ajax({
|
|
url: addAuthorForm.reorderUrl,
|
|
data: {
|
|
predicate: addAuthorForm.rankPredicate,
|
|
individuals: authorships
|
|
},
|
|
traditional: true, // serialize the array of individuals for the server
|
|
dataType: 'json',
|
|
type: 'POST',
|
|
success: function(data, status, request) {
|
|
var pos;
|
|
$('.authorship').each(function(index){
|
|
pos = index + 1;
|
|
// Set the new position for this element. The only function of this value
|
|
// is so we can reset an element to its original position in case reordering fails.
|
|
addAuthorForm.setPosition(this, pos);
|
|
});
|
|
// Set the form rank field value.
|
|
$('#rank').val(pos + 1);
|
|
},
|
|
error: function(request, status, error) {
|
|
// ui is undefined on page load and after an authorship removal.
|
|
if (ui) {
|
|
// Put the moved item back to its original position.
|
|
// Seems we need to do this by hand. Can't see any way to do it with jQuery UI. ??
|
|
var pos = addAuthorForm.getPosition(ui.item),
|
|
nextpos = pos + 1,
|
|
authorships = $('#authorships'),
|
|
next = addAuthorForm.findAuthorship('position', nextpos);
|
|
|
|
if (next.length) {
|
|
ui.item.insertBefore(next);
|
|
}
|
|
else {
|
|
ui.item.appendTo(authorships);
|
|
}
|
|
|
|
alert('Reordering of authors failed.');
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// On page load, associate data with each authorship element. Then we don't
|
|
// have to keep retrieving data from or modifying the DOM as we manipulate the
|
|
// authorships.
|
|
initAuthorshipData: function() {
|
|
$('.authorship').each(function(index) {
|
|
$(this).data(authorshipData[index]);
|
|
|
|
// RY We might still need position to put back an element after reordering
|
|
// failure. Rank might already have been reset? Check.
|
|
// We also may need position to implement undo links: we want the removed authorship
|
|
// to show up in the list, but it has no rank.
|
|
$(this).data('position', index+1);
|
|
});
|
|
},
|
|
|
|
getPosition: function(authorship) {
|
|
return $(authorship).data('position');
|
|
},
|
|
|
|
setPosition: function(authorship, pos) {
|
|
$(authorship).data('position', pos);
|
|
},
|
|
|
|
findAuthorship: function(key, value) {
|
|
var matchingAuthorship = $(); // if we don't find one, return an empty jQuery set
|
|
|
|
$('.authorship').each(function() {
|
|
var authorship = $(this);
|
|
if ( authorship.data(key) === value ) {
|
|
matchingAuthorship = authorship;
|
|
return false; // stop the loop
|
|
}
|
|
});
|
|
|
|
return matchingAuthorship;
|
|
},
|
|
|
|
|
|
/* *** Event listeners *** */
|
|
|
|
bindEventListeners: function() {
|
|
|
|
this.showFormButton.click(function() {
|
|
addAuthorForm.initFormView();
|
|
return false;
|
|
});
|
|
|
|
this.form.submit(function() {
|
|
// NB Important JavaScript scope issue: if we call it this way, this = addAuthorForm
|
|
// in prepareSubmit. If we do this.form.submit(this.prepareSubmit); then
|
|
// this != addAuthorForm in prepareSubmit.
|
|
addAuthorForm.deleteAcHelpText();
|
|
addAuthorForm.prepareSubmit();
|
|
});
|
|
|
|
this.lastNameField.blur(function() {
|
|
// Cases where this event should be ignored:
|
|
// 1. personUri field has a value: the autocomplete select event has already fired.
|
|
// 2. The last name field is empty (especially since the field has focus when the form is displayed).
|
|
// 3. Autocomplete suggestions are showing.
|
|
if ( addAuthorForm.personUriField.val() || !$(this).val() || $('ul.ui-autocomplete li.ui-menu-item').length ) {
|
|
return;
|
|
}
|
|
addAuthorForm.onLastNameChange();
|
|
});
|
|
|
|
this.acSelector.focus(function() {
|
|
addAuthorForm.deleteAcHelpText();
|
|
});
|
|
|
|
this.acSelector.blur(function() {
|
|
addAuthorForm.addAcHelpText();
|
|
});
|
|
|
|
// When hitting enter in last name field, show first and middle name fields.
|
|
// NB This event fires when selecting an autocomplete suggestion with the enter
|
|
// key. Since it fires first, we undo its effects in the ac select event listener.
|
|
this.lastNameField.keydown(function(event) {
|
|
if (event.which === 13) {
|
|
addAuthorForm.onLastNameChange();
|
|
return false; // don't submit form
|
|
}
|
|
});
|
|
|
|
this.removeAuthorshipLinks.click(function() {
|
|
addAuthorForm.removeAuthorship(this);
|
|
return false;
|
|
});
|
|
|
|
// this.undoLinks.click(function() {
|
|
// $.ajax({
|
|
// url: $(this).attr('href')
|
|
// });
|
|
// return false;
|
|
// });
|
|
|
|
},
|
|
|
|
prepareSubmit: function() {
|
|
var firstName,
|
|
middleName,
|
|
lastName,
|
|
name;
|
|
|
|
// If selecting an existing person, don't submit name fields
|
|
if (this.personUriField.val() != '') {
|
|
this.firstNameField.attr('disabled', 'disabled');
|
|
this.middleNameField.attr('disabled', 'disabled');
|
|
this.lastNameField.attr('disabled', 'disabled');
|
|
}
|
|
else {
|
|
firstName = this.firstNameField.val();
|
|
middleName = this.middleNameField.val();
|
|
lastName = this.lastNameField.val();
|
|
|
|
name = lastName;
|
|
if (firstName) {
|
|
name += ', ' + firstName;
|
|
}
|
|
if (middleName) {
|
|
name += ' ' + middleName;
|
|
}
|
|
|
|
this.labelField.val(name);
|
|
}
|
|
|
|
},
|
|
|
|
onLastNameChange: function() {
|
|
this.showFieldsForNewPerson();
|
|
this.firstNameField.focus();
|
|
// this.fixNames();
|
|
},
|
|
|
|
// User may have typed first name as well as last name into last name field.
|
|
// If so, when showing first and middle name fields, move anything after a comma
|
|
// or space into the first name field.
|
|
// RY Space is problematic because they may be entering "<firstname> <lastname>", but
|
|
// comma is a clear case.
|
|
// fixNames: function() {
|
|
// var lastNameInput = this.lastNameField.val(),
|
|
// names = lastNameInput.split(/[, ]+/),
|
|
// lastName = names[0];
|
|
//
|
|
// this.lastNameField.val(lastName);
|
|
//
|
|
// if (names.length > 1) {
|
|
// //firstName = names[1].replace(/^[, ]+/, '');
|
|
// this.firstNameField.val(names[1]);
|
|
// }
|
|
// },
|
|
|
|
removeAuthorship: function(link) {
|
|
// RY Upgrade this to a modal window
|
|
var message = 'Are you sure you want to remove this author?';
|
|
if (!confirm(message)) {
|
|
return false;
|
|
}
|
|
$.ajax({
|
|
url: $(link).attr('href'),
|
|
type: 'POST',
|
|
data: {
|
|
deletion: $(link).parents('.authorship').data('authorshipUri')
|
|
},
|
|
dataType: 'json',
|
|
context: link, // context for callback
|
|
complete: function(request, status) {
|
|
var authorship,
|
|
authorUri;
|
|
|
|
if (status === 'success') {
|
|
|
|
authorship = $(this).parents('.authorship');
|
|
|
|
// Clear autocomplete cache entries matching this author's name, else
|
|
// autocomplete will be retrieved from the cache, which excludes the removed author.
|
|
addAuthorForm.clearAcCacheEntries(authorship.data('authorName'));
|
|
|
|
// Remove this author from the acFilter so it is included in autocomplete
|
|
// results again.
|
|
addAuthorForm.removeAuthorFromAcFilter(authorship.data('authorUri'));
|
|
|
|
authorship.fadeOut(400, function() {
|
|
var numAuthors;
|
|
|
|
// For undo link: add to a deletedAuthorships array
|
|
|
|
// Remove from the DOM
|
|
$(this).remove();
|
|
|
|
// Actions that depend on the author having been removed from the DOM:
|
|
numAuthors = $('.authorship').length; // retrieve the length after removing authorship from the DOM
|
|
if (numAuthors > 0) {
|
|
// Reorder to remove any gaps
|
|
addAuthorForm.reorderAuthors();
|
|
|
|
// If less than two authors remaining, disable drag-drop
|
|
if (numAuthors < 2) {
|
|
addAuthorForm.disableAuthorDD();
|
|
}
|
|
}
|
|
});
|
|
|
|
// $(this).hide();
|
|
// $(this).siblings('.undo').show();
|
|
// author.html(authorName + ' has been removed');
|
|
// author.css('width', 'auto');
|
|
// author.effect('highlight', {}, 3000);
|
|
} else {
|
|
alert('Error processing request: author not removed');
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Disable DD and associated cues if only one author remains
|
|
disableAuthorDD: function() {
|
|
var authorships = $('#authorships'),
|
|
authorNameWrapper = $('.authorNameWrapper');
|
|
|
|
authorships.sortable({ disable: true } );
|
|
|
|
// Use class dd rather than jQuery UI's class ui-sortable, so that we can remove
|
|
// the class if there's fewer than one author. We don't want to remove the ui-sortable
|
|
// class, in case we want to re-enable DD without a page reload (e.g., if implementing
|
|
// adding an author via Ajax request).
|
|
authorships.removeClass('dd');
|
|
|
|
authorNameWrapper.removeAttr('title');
|
|
},
|
|
|
|
// RY To be implemented later.
|
|
toggleRemoveLink: function() {
|
|
// when clicking remove: remove the author, and change link text to 'undo'
|
|
// when clicking undo: add the author back, and change link text to 'remove'
|
|
},
|
|
|
|
// Set the initial help text in the lastName field and change the class name.
|
|
addAcHelpText: function() {
|
|
var typeText;
|
|
|
|
if (!this.acSelector.val()) {
|
|
this.acSelector.val("Select an existing Author or add a new one.")
|
|
.addClass(this.acHelpTextClass);
|
|
}
|
|
},
|
|
|
|
deleteAcHelpText: function() {
|
|
if (this.acSelector.hasClass(this.acHelpTextClass)) {
|
|
this.acSelector.val('')
|
|
.removeClass(this.acHelpTextClass);
|
|
}
|
|
}
|
|
};
|
|
|
|
$(document).ready(function() {
|
|
addAuthorForm.onLoad();
|
|
});
|