+
+#if>
+
+<#assign literalValues = "${editConfiguration.dataLiteralValuesAsString}" />
+
+
+
+<#if editConfiguration.includeDeletionForm = true>
+<#include "defaultDeletePropertyForm.ftl">
+#if>
+<#--Not including defaultFormScripts.ftl which would trigger tinyMce-->
+<#assign sparqlQueryUrl = "${urls.base}/ajax/sparqlQuery" >
+<#--Passing in object types only if there are any types returned, otherwise
+the parameter should not be passed at all to the solr search.
+Also multiple types parameter set to true only if more than one type returned-->
+
+
+${stylesheets.add('')}
+ ${stylesheets.add('')}
+ ${stylesheets.add('')}
+
+${scripts.add('',
+ '',
+ '',
+ '')}
diff --git a/productMods/templates/freemarker/edit/forms/js/customFormWithAutocomplete.js b/productMods/templates/freemarker/edit/forms/js/customFormWithAutocomplete.js
index b42da315..a025e838 100644
--- a/productMods/templates/freemarker/edit/forms/js/customFormWithAutocomplete.js
+++ b/productMods/templates/freemarker/edit/forms/js/customFormWithAutocomplete.js
@@ -184,8 +184,7 @@ var customForm = {
this.initFormFullView();
}
//Disable submit button until selection made
- this.button.attr('disabled', 'disabled');
- this.button.addClass('disabledSubmit'); // tlw
+ this.disableSubmit(); // tlw
},
// Bind event listeners that persist over the life of the page. Event listeners
@@ -392,8 +391,7 @@ var customForm = {
}
if(this.supportEdit) {
//On initialization in this mode, submit button is disabled
- this.button.removeAttr('disabled');
- this.button.removeClass('disabledSubmit'); // tlw
+ this.enableSubmit(); // tlw
}
this.setButtonText('existing');
@@ -427,8 +425,7 @@ var customForm = {
//Resetting so disable submit button again for object property autocomplete
if(this.supportEdit) {
- this.button.attr('disabled', 'disabled');
- this.button.addClass('disabledSubmit');
+ this.disableSubmit();
}
}
@@ -539,6 +536,15 @@ var customForm = {
this.acSelector.val('')
.removeClass(this.acHelpTextClass);
}
+ },
+ disableSubmit: function() {
+ //Disable submit button until selection made
+ this.button.attr('disabled', 'disabled');
+ this.button.addClass('disabledSubmit'); // tlw
+ },
+ enableSubmit:function() {
+ this.button.removeAttr('disabled');
+ this.button.removeClass('disabledSubmit');
}
};
diff --git a/productMods/templates/freemarker/edit/forms/js/customFormWithDataAutocomplete.js b/productMods/templates/freemarker/edit/forms/js/customFormWithDataAutocomplete.js
new file mode 100644
index 00000000..92ef8b63
--- /dev/null
+++ b/productMods/templates/freemarker/edit/forms/js/customFormWithDataAutocomplete.js
@@ -0,0 +1,435 @@
+/* $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 = $('form.customForm');
+ this.fullViewOnly = $('.fullViewOnly');
+ this.button = $('#submit');
+ this.requiredLegend = $('#requiredLegend');
+
+
+ // 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.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() {
+
+ // Put this case first, because in edit mode with
+ // validation errors we just want initFormFullView.
+ if (this.editMode == 'repair') {
+ this.initFormFullView();
+ } else if(this.editMode == 'edit') {
+ this.initFormEditFullView();
+ }
+ 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.hideFields(this.fullViewOnly);
+ this.button.hide();
+ this.requiredLegend.hide();
+ this.or.hide();
+
+ this.cancel.unbind('click');
+ },
+
+
+ initFormFullView: function() {
+
+ 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.
+ }
+
+ },
+
+ initFormWithValidationErrors: function() {
+ var label = this.acSelector.val();
+
+ // Call initFormFullView first, because showAutocompleteSelection needs
+ // acType, which is set in initFormFullView.
+ this.initFormFullView();
+
+ //See if value exists, either b/c editing or label is in input if validation error
+ if(label.length > 0) {
+ this.showAutocompleteSelection(label);
+ }
+
+
+ },
+
+ initFormEditFullView: function() {
+ var label = this.acSelector.val();
+
+ // Call initFormFullView first, because showAutocompleteSelection needs
+ // acType, which is set in initFormFullView.
+ this.initFormFullView();
+
+ //See if value exists, either b/c editing or label is in input if validation error
+ if(this.editMode == 'edit' || label.length > 0) {
+ this.showAutocompleteSelection(label);
+ }
+ },
+
+
+ // 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() {
+
+ //no longer need type selector and verify match
+
+ this.acSelector.focus(function() {
+ customForm.deleteAcHelpText();
+ });
+
+ this.acSelector.blur(function() {
+ customForm.addAcHelpText();
+ });
+
+ this.form.submit(function() {
+ customForm.deleteAcHelpText();
+ });
+
+ },
+
+ initAutocomplete: function() {
+
+ this.getAcFilter();
+ this.acCache = {};
+
+ this.acSelector.autocomplete({
+ minLength: 3,
+ source: customForm.doAutoComplete,
+ select: function(event, ui) {
+ customForm.showAutocompleteSelection(ui.item.value);
+ }
+ });
+ },
+
+ //For debugging, trying to extract auto complete method
+ doAutoComplete: 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,
+ property: customForm.property
+ },
+ complete: function(xhr, status) {
+ // Not sure why, but we need an explicit json parse here.
+ var results = $.parseJSON(xhr.responseText),
+ filteredResults = customForm.filterAcResults(results);
+ customForm.acCache[request.term] = filteredResults;
+ response(filteredResults);
+ }
+ });
+ },
+
+
+ // 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());
+
+
+ },
+ //get autocomplete filter with sparql query
+ 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,
+ dataType: "json",
+ data: {
+ query: customForm.sparqlForAcFilter
+ },
+ success: function(data, status, xhr) {
+ customForm.setAcFilter(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() {
+ //Here this should refer to the results array value being iterated through
+ if ($.inArray(String(this), customForm.acFilter) == -1) {
+ filteredResults.push(String(this));
+ }
+ else {
+
+ }
+ });
+ 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;
+
+ // Flush autocomplete cache when type is reset, since the cached values
+ // pertain only to the previous type.
+ this.acCache = {};
+ },
+ //in our case, we have only the literal value itself
+ showAutocompleteSelection: function(label) {
+
+ this.hideFields(this.acSelectorWrapper);
+ this.acSelection.show();
+
+ this.acSelector.val(label);
+ this.acSelectionInfo.html(label);
+
+ 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.acSelectionInfo.html('');
+
+ if (this.formSteps > 1) {
+ this.acSelection.find('label').html('Selected ');
+ }
+ }
+ },
+
+ // 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 = "string";
+
+ 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 = "string";
+
+ // 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 {
+ // In repair mode, baseButtonText is "Edit X". Keep that for this case.
+ // In add mode, baseButtonText is "X", so we get, e.g., "Create Publication"
+ buttonText = this.editMode == 'repair' ? baseButtonText : '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);
+ },
+
+
+ // Set the initial help text that appears in the autocomplete field and change the class name
+ addAcHelpText: function() {
+ var typeText = "string";
+
+ // First case applies on page load; second case applies when the type gets changed.
+ if (!this.acSelector.val() || this.acSelector.hasClass(this.acHelpTextClass)) {
+ var helpText = "Select an existing " + typeText + " or create a new one.";
+ //Different for object property autocomplete
+ this.acSelector.val(helpText)
+ .addClass(this.acHelpTextClass);
+ }
+ },
+
+ deleteAcHelpText: function() {
+ if (this.acSelector.hasClass(this.acHelpTextClass)) {
+ this.acSelector.val('')
+ .removeClass(this.acHelpTextClass);
+ }
+ }
+
+};
+
+$(document).ready(function() {
+ customForm.onLoad();
+});
diff --git a/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/AutocompleteDataPropertyFormGenerator.java b/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/AutocompleteDataPropertyFormGenerator.java
new file mode 100644
index 00000000..90ab4546
--- /dev/null
+++ b/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/AutocompleteDataPropertyFormGenerator.java
@@ -0,0 +1,78 @@
+
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
+import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
+import edu.cornell.mannlib.vitro.webapp.beans.Individual;
+import edu.cornell.mannlib.vitro.webapp.beans.VClass;
+import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
+import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
+import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
+import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationUtils;
+import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTwo;
+import edu.cornell.mannlib.vitro.webapp.utils.FrontEndEditingUtils;
+import edu.cornell.mannlib.vitro.webapp.utils.FrontEndEditingUtils.EditMode;
+
+/**
+ * Generates the edit configuration for a default property form.
+ *
+ */
+public class AutocompleteDataPropertyFormGenerator extends DefaultDataPropertyFormGenerator {
+
+ //The only thing that changes here are the templates
+ private Log log = LogFactory.getLog(AutocompleteObjectPropertyFormGenerator.class);
+ private String dataPropertyTemplate = "autoCompleteDataPropForm.ftl";
+
+
+ @Override
+ public EditConfigurationVTwo getEditConfiguration(VitroRequest vreq, HttpSession session) {
+ EditConfigurationVTwo ec = super.getEditConfiguration(vreq, session);
+ this.addFormSpecificData(ec, vreq);
+ return ec;
+ }
+
+ public void addFormSpecificData(EditConfigurationVTwo editConfiguration, VitroRequest vreq) {
+ HashMap formSpecificData = new HashMap();
+ //Filter setting - i.e. sparql query for filtering out results from autocomplete
+ formSpecificData.put("sparqlForAcFilter", getSparqlForAcFilter(vreq));
+ editConfiguration.setTemplate(dataPropertyTemplate);
+ //Add edit model
+ formSpecificData.put("editMode", getEditMode(vreq));
+ editConfiguration.setFormSpecificData(formSpecificData);
+ }
+
+ public String getSparqlForAcFilter(VitroRequest vreq) {
+ String subject = EditConfigurationUtils.getSubjectUri(vreq);
+ String predicate = EditConfigurationUtils.getPredicateUri(vreq);
+ //Get all objects for existing predicate, filters out results from addition and edit
+ String query = "SELECT ?dataLiteral WHERE { " +
+ "<" + subject + "> <" + predicate + "> ?dataLiteral .} ";
+ return query;
+ }
+
+ //Get edit mode
+ public String getEditMode(VitroRequest vreq) {
+ if(isUpdate(vreq))
+ return "edit";
+ else
+ return "add";
+ }
+
+ private boolean isUpdate(VitroRequest vreq) {
+ Integer dataHash = EditConfigurationUtils.getDataHash(vreq);
+ return ( dataHash != null );
+ }
+
+}