diff --git a/productMods/WEB-INF/web.xml b/productMods/WEB-INF/web.xml index df38773f..dfbf484d 100644 --- a/productMods/WEB-INF/web.xml +++ b/productMods/WEB-INF/web.xml @@ -894,7 +894,16 @@ AutocompleteController /populateselect - + + + ReorderController + edu.cornell.mannlib.vitro.webapp.controller.edit.ReorderController + + + ReorderController + /edit/reorder + + AdminController edu.cornell.mannlib.vitro.webapp.controller.AdminController diff --git a/productMods/edit/forms/addAuthorsToInformationResource1.jsp b/productMods/edit/forms/addAuthorsToInformationResource1.jsp new file mode 100644 index 00000000..5939d5d4 --- /dev/null +++ b/productMods/edit/forms/addAuthorsToInformationResource1.jsp @@ -0,0 +1,430 @@ +<%-- $This file is distributed under the terms of the license in /doc/license.txt$ --%> + +<%-- Custom form for adding authors to information resources + +Classes: +core:InformationResource - the information resource being edited +core:Authorship - primary new individual being created +foaf:Person - new or existing individual being linked to + +Data properties of Authorship: +core:authorRank + +Object properties (domain : range): + +core:informationResourceInAuthorship (InformationResource : Authorship) +core:linkedInformationResource (Authorship : InformationResource) - inverse of informationResourceInAuthorship + +core:linkedAuthor (Authorship : Person) +core:authorInAuthorship (Person : Authorship) - inverse of linkedAuthor + +--%> + +<%@ page import="java.util.List" %> +<%@ page import="java.util.ArrayList" %> +<%@ page import="java.util.Arrays" %> +<%@ page import="java.util.Collections" %> + +<%@ page import="com.hp.hpl.jena.rdf.model.Model" %> +<%@ page import="com.hp.hpl.jena.vocabulary.XSD" %> + +<%@ page import="edu.cornell.mannlib.vitro.webapp.beans.Individual" %> +<%@ page import="edu.cornell.mannlib.vitro.webapp.beans.DataPropertyComparator" %> +<%@ page import="edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatement" %> +<%@ page import="edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary" %> +<%@ page import="edu.cornell.mannlib.vitro.webapp.edit.n3editing.EditConfiguration" %> +<%@ page import="edu.cornell.mannlib.vitro.webapp.edit.n3editing.PublicationHasAuthorValidator" %> +<%@ page import="edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory" %> +<%@ page import="edu.cornell.mannlib.vitro.webapp.controller.VitroRequest" %> +<%@ page import="edu.cornell.mannlib.vitro.webapp.web.MiscWebUtils" %> +<%@ page import="edu.cornell.mannlib.vitro.webapp.utils.StringUtils" %> +<%@ page import="edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.JavaScript" %> +<%@ page import="edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.Css" %> + +<%@ page import="org.json.JSONObject" %> +<%@ page import="org.apache.commons.logging.Log" %> +<%@ page import="org.apache.commons.logging.LogFactory" %> + +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core"%> +<%@ taglib prefix="v" uri="http://vitro.mannlib.cornell.edu/vitro/tags" %> + +<%! + public static Log log = LogFactory.getLog("edu.cornell.mannlib.vitro.webapp.jsp.edit.forms.addAuthorsToInformationResource.jsp"); +%> +<% + VitroRequest vreq = new VitroRequest(request); + WebappDaoFactory wdf = vreq.getWebappDaoFactory(); + vreq.setAttribute("defaultNamespace", ""); //empty string triggers default new URI behavior + + vreq.setAttribute("stringDatatypeUriJson", MiscWebUtils.escape(XSD.xstring.toString())); + + String intDatatypeUri = XSD.xint.toString(); + vreq.setAttribute("intDatatypeUri", intDatatypeUri); + vreq.setAttribute("intDatatypeUriJson", MiscWebUtils.escape(intDatatypeUri)); +%> + + + + + + + +<%-- Unlike other custom forms, this form does not allow edits of existing authors, so there are no +SPARQL queries for existing values. --%> + +<%-- Data properties --%> + + + @prefix foaf: <${foaf}> . + ?newPerson foaf:firstName ?firstName . + + + + @prefix core: <${vivoCore}> . + ?newPerson core:middleName ?middleName . + + + + @prefix foaf: <${foaf}> . + ?newPerson foaf:lastName ?lastName . + + + + @prefix core: <${vivoCore}> . + ?authorshipUri core:authorRank ?rank . + + +<%-- This applies to both a new and an existing person --%> + + @prefix core: <${vivoCore}> . + + ?authorshipUri a core:Authorship ; + core:linkedInformationResource ?infoResource ; + core:authorRank ?rank . + + ?infoResource core:informationResourceInAuthorship ?authorshipUri . + + + + @prefix core: <${vivoCore}> . + ?authorshipUri core:linkedAuthor ?personUri . + ?personUri core:authorInAuthorship ?authorshipUri . + + + + @prefix foaf: <${foaf}> . + @prefix core: <${vivoCore}> . + + ?newPerson a foaf:Person ; + <${label}> ?label . + + ?authorshipUri core:linkedAuthor ?newPerson . + ?newPerson core:authorInAuthorship ?authorshipUri . + + +${personClassUri} + + + + +{ + "formUrl" : "${formUrl}", + "editKey" : "${editKey}", + "urlPatternToReturnTo" : "${returnPathAfterSubmit}", + + "subject" : ["infoResource", "${subjectUriJson}" ], + "predicate" : ["predicate", "${predicateUriJson}" ], + "object" : ["authorshipUri", "${objectUriJson}", "URI" ], + + "n3required" : [ "${n3ForNewAuthorship}", "${authorshipRankAssertion}" ], + + "n3optional" : [ "${newPersonFirstNameAssertion}", "${newPersonMiddleNameAssertion}", + "${newPersonLastNameAssertion}", + "${n3ForNewPerson}", "${n3ForExistingPerson}" ], + + "newResources" : { "authorshipUri" : "${defaultNamespace}", + "newPerson" : "${defaultNamespace}" }, + + "urisInScope" : { }, + "literalsInScope": { }, + "urisOnForm" : [ "personUri" ], + "literalsOnForm" : [ "firstName", "middleName", "lastName", "rank", "label" ], + "filesOnForm" : [ ], + "sparqlForLiterals" : { }, + "sparqlForUris" : { }, + "sparqlForExistingLiterals" : { }, + "sparqlForExistingUris" : { }, + "fields" : { + "label" : { + "newResource" : "false", + "validators" : [ "datatype:${stringDatatypeUriJson}" ], + "optionsType" : "UNDEFINED", + "literalOptions" : [ ], + "predicateUri" : "", + "objectClassUri" : "", + "rangeDatatypeUri" : "${stringDatatypeUriJson}", + "rangeLang" : "", + "assertions" : [ "${n3ForNewPerson}" ] + }, + "firstName" : { + "newResource" : "false", + "validators" : [ "datatype:${stringDatatypeUriJson}" ], + "optionsType" : "UNDEFINED", + "literalOptions" : [ ], + "predicateUri" : "", + "objectClassUri" : "", + "rangeDatatypeUri" : "${stringDatatypeUriJson}", + "rangeLang" : "", + "assertions" : [ "${newPersonFirstNameAssertion}" ] + }, + "middleName" : { + "newResource" : "false", + "validators" : [ "datatype:${stringDatatypeUriJson}" ], + "optionsType" : "UNDEFINED", + "literalOptions" : [ ], + "predicateUri" : "", + "objectClassUri" : "", + "rangeDatatypeUri" : "${stringDatatypeUriJson}", + "rangeLang" : "", + "assertions" : [ "${newPersonMiddleNameAssertion}" ] + }, + "lastName" : { + "newResource" : "false", + "validators" : [ "datatype:${stringDatatypeUriJson}" ], + "optionsType" : "UNDEFINED", + "literalOptions" : [ ], + "predicateUri" : "", + "objectClassUri" : "", + "rangeDatatypeUri" : "${stringDatatypeUriJson}", + "rangeLang" : "", + "assertions" : [ "${newPersonLastNameAssertion}" ] + }, + "rank" : { + "newResource" : "false", + "validators" : [ "nonempty" ], + "optionsType" : "UNDEFINED", + "literalOptions" : [ ], + "predicateUri" : "", + "objectClassUri" : "", + "rangeDatatypeUri" : "${intDatatypeUriJson}", + "rangeLang" : "", + "assertions" : ["${authorshipRankAssertion}"] + }, + "personUri" : { + "newResource" : "false", + "validators" : [ ], + "optionsType" : "UNDEFINED", + "literalOptions" : [ ], + "predicateUri" : "", + "objectClassUri" : "${personClassUriJson}", + "rangeDatatypeUri" : "", + "rangeLang" : "", + "assertions" : ["${n3ForExistingPerson}"] + } + } +} + + +<% + log.debug(request.getAttribute("editjson")); + + EditConfiguration editConfig = EditConfiguration.getConfigFromSession(session,request); + if (editConfig == null) { + editConfig = new EditConfiguration((String) request.getAttribute("editjson")); + EditConfiguration.putConfigInSession(editConfig,session); + } + + editConfig.addValidator(new PublicationHasAuthorValidator()); + + Model model = (Model) application.getAttribute("jenaOntModel"); + String objectUri = (String) request.getAttribute("objectUri"); + + //for some reason we are comming from the add new and that is working + //but we also come from the edit, and that is not working. + editConfig.setObject(""); //this will force the edit config to always be an add, never an update + + editConfig.prepareForNonUpdate(model); // we're only adding new, not editing existing + + String subjectUri = vreq.getParameter("subjectUri"); + String predicateUri = vreq.getParameter("predicateUri"); + + String vivoCore = "http://vivoweb.org/ontology/core#"; + + //Individual infoResource = vreq.getWebappDaoFactory().getIndividualDao().getIndividualByURI(subjectUri); + Individual infoResource = ((Individual) request.getAttribute("subject")); + vreq.setAttribute("infoResourceName", infoResource.getName()); + + List authorships = infoResource.getRelatedIndividuals(predicateUri); + + List customJs = new ArrayList(Arrays.asList(JavaScript.JQUERY_UI.path(), + JavaScript.CUSTOM_FORM_UTILS.path(), + "/js/browserUtils.js", + "/edit/forms/js/addAuthorsToInformationResource1.js" + )); + request.setAttribute("customJs", customJs); + + List customCss = new ArrayList(Arrays.asList(Css.JQUERY_UI.path(), + Css.CUSTOM_FORM.path(), + "/edit/forms/css/autocomplete.css", + "/edit/forms/css/addAuthorsToInformationResource.css" + )); + request.setAttribute("customCss", customCss); + + String ulClass = ""; + List ulClasses = new ArrayList(); + + if (authorships.size() > 1) { + // This class triggers application of dd styles. Don't wait for js to add + // the ui-sortable class, because then the page flashes as the styles are updated. + ulClasses.add("dd"); + } + + if (ulClasses.size() > 0) { + ulClass="class=\"" + StringUtils.join(ulClasses, " ") + "\""; + } +%> + + + + + + + +<%-- DO NOT CHANGE IDS, CLASSES, OR HTML STRUCTURE ON THIS PAGE WITHOUT UNDERSTANDING THE IMPACT ON THE JAVASCRIPT! --%> +

${title}

+ +<%@ include file="unsupportedBrowserMessage.jsp" %> + +
+

Manage Authors

+ +
    > +<% + String rankPredicateUri = vivoCore + "authorRank"; + + // RY We should use whatever is used on the individual profile page to list + // this property in rank order... + DataPropertyComparator comp = new DataPropertyComparator(rankPredicateUri); + Collections.sort(authorships, comp); + + int maxRank = 0; + int authorshipCount = authorships.size(); + + // for ( ObjectPropertyStatement stmt : authorshipStmts) { + // Individual authorship = stmt.getObject(); +%> + + +<% + for ( Individual authorship : authorships ) { + + request.setAttribute("authorshipUri", authorship.getURI()); + request.setAttribute("authorshipName", authorship.getName()); + + DataPropertyStatement rankStmt = authorship.getDataPropertyStatement(rankPredicateUri); + if (rankStmt != null) { + maxRank = Integer.parseInt(rankStmt.getData()); + } + + request.setAttribute("author", authorship.getRelatedIndividual(vivoCore + "linkedAuthor")); + +%> +
  • + <%-- span.author will be used in the next phase, when we display a message that the author has been + removed. That text will replace the a.authorName, which will be removed. --%> + + <%-- This span is here to assign a width to. We can't assign directly to the a.authorName, + for the case when it's followed by an em tag - we want the width to apply to the whole thing. --%> + + + + + + + + + ${authorName} + + + + + + + + + ${authorshipName} (no linked author) + + + + + Remove + <%-- Undo --%> + +
  • + + +<% + } + + // A new author will be ranked last when added. + // This value is now inserted by JavaScript, but leave it here as a safety net in case page + // load reordering returns an error. + request.setAttribute("newRank", maxRank + 1); + System.out.println("request rank: " + request.getAttribute("newRank")); + request.setAttribute("rankPredicate", rankPredicateUri); +%> + +
+ +<% if (authorshipCount == 0) { %> +

This publication currently has no authors specified.

+<% } %> + +
+ +
+ +<%-- DO NOT CHANGE IDS, CLASSES, OR HTML STRUCTURE IN THIS FORM WITHOUT UNDERSTANDING THE IMPACT ON THE JAVASCRIPT! --%> +
" > + +

Add an Author

+ +

+

+

+ + +
+ <%-- RY maybe make this a label and input field. See what looks best. --%> +

+ +
+ + +

+ +

* required fields

+
+
+ + + + + + + + diff --git a/productMods/edit/forms/js/addAuthorsToInformationResource.js b/productMods/edit/forms/js/addAuthorsToInformationResource.js index fe88b424..abe38aa2 100644 --- a/productMods/edit/forms/js/addAuthorsToInformationResource.js +++ b/productMods/edit/forms/js/addAuthorsToInformationResource.js @@ -5,7 +5,7 @@ var addAuthorForm = { /* *** Initial page setup *** */ onLoad: function() { - + console.log($('#rank').val()); if (this.disableFormInUnsupportedBrowsers()) { return; } diff --git a/productMods/edit/forms/js/addAuthorsToInformationResource1.js b/productMods/edit/forms/js/addAuthorsToInformationResource1.js new file mode 100644 index 00000000..e525d815 --- /dev/null +++ b/productMods/edit/forms/js/addAuthorsToInformationResource1.js @@ -0,0 +1,633 @@ +/* $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() { + 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 = $('#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.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'); + + }, + + // 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(''); + + 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', + stop: 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); + }, + + // Get the authorship rank value, which includes xsd type + getRankStrVal: function(authorship) { + return $(authorship).data('rankVal'); + }, + + // Get the authorship numeric rank + getRankIntVal: function(authorship) { + var rankVal = this.getRankStrVal(authorship); + return this.getRankIntValFromRankVal(rankVal); + }, + + getRankIntValFromRankVal: function(rankVal) { + return parseInt(rankVal.split('_')[0]); + }, + + setRank: function(authorship, rankVal) { + $(authorship).data('rankVal', rankVal); + }, + + makeRankDataPropVal: function(rank, xsdType) { + var rankVal = '"' + rank + '"'; + if (xsdType) { + rankVal += '^^<' + xsdType + '>' + } + return rankVal; + }, + + 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.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(); + }); + + // 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 " ", 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' + } + +}; + +$(document).ready(function() { + addAuthorForm.onLoad(); +}); +