diff --git a/webapp/web/config/listViewConfig-hasElement.xml b/webapp/web/config/listViewConfig-hasElement.xml
new file mode 100644
index 000000000..0ef5f399a
--- /dev/null
+++ b/webapp/web/config/listViewConfig-hasElement.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+ PREFIX display: <http://vitro.mannlib.cornell.edu/ontologies/display/1.1#>
+ PREFIX afn: <http://jena.hpl.hp.com/ARQ/function#>
+
+ SELECT ?menuItem
+ (afn:localname(?menuItem) AS ?menuItemName)
+ ?linkText
+ ?menuPosition WHERE {
+ ?subject ?property ?menuItem
+ OPTIONAL { ?menuItem display:linkText ?linkText }
+ OPTIONAL { ?menuItem display:menuPosition ?menuPosition }
+ } ORDER BY ?menuPosition
+
+
+
+ CONSTRUCT {
+ ?subject ?property ?menuItem .
+ ?menuItem ?menuItemProp ?menuItemObj
+ } WHERE {
+ { ?subject ?property ?menuItem }
+ UNION {
+ ?subject ?property ?menuItem .
+ ?menuItem ?menuItemProp ?menuItemObj
+ }
+ }
+
+
+ propStatement-hasElement.ftl
+
\ No newline at end of file
diff --git a/webapp/web/css/individual/menuManagement.css b/webapp/web/css/individual/menuManagement.css
new file mode 100644
index 000000000..0db52ece1
--- /dev/null
+++ b/webapp/web/css/individual/menuManagement.css
@@ -0,0 +1,20 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+/* MENU ITEMS------> */
+ul.menuItems {
+ width: 35%;
+ margin-bottom: 1.5em;
+}
+ul.menuItems li {
+ margin: .2em 0;
+ padding: .5em;
+ background: #f3f3f0;
+}
+ul.menuItems.dragNdrop li {
+ padding-left: 1em;
+ background: url("../../edit/forms/images/sortable_icon.png") no-repeat .4em center #f3f3f0;
+ cursor: move;
+}
+span.controls {
+ float: right;
+}
\ No newline at end of file
diff --git a/webapp/web/js/individual/menuManagement.js b/webapp/web/js/individual/menuManagement.js
new file mode 100644
index 000000000..5fd2362e1
--- /dev/null
+++ b/webapp/web/js/individual/menuManagement.js
@@ -0,0 +1,150 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+var menuManagement = {
+
+ // Initial page setup
+ onLoad: function() {
+ this.mergeFromTemplate();
+ this.initMenuItemData();
+ this.initObjects();
+ this.initMenuItemsDD();
+ },
+
+ // Add variables from menupage template
+ mergeFromTemplate: function() {
+ $.extend(this, menuManagementData);
+ },
+
+ // Create references to frequently used elements for convenience
+ initObjects: function() {
+ this.menuItemsList = $('ul.menuItems');
+ },
+
+ // Drag-and-drop
+ initMenuItemsDD: function() {
+ var menuItems = this.menuItemsList.children('li');
+
+ if (menuItems.length < 2) {
+ return;
+ }
+
+ this.menuItemsList.addClass('dragNdrop');
+
+ menuItems.attr('title', 'Drag and drop to reorder menu items');
+
+ this.menuItemsList.sortable({
+ cursor: 'move',
+ update: function(event, ui) {
+ alert("Congrats! You just reordered a menu item. (not really)");
+ // Once we figure out how the editing will work, we'll call reorderMenuItems
+ // and get rid of the debug alert.
+ // menuManagement.reorderMenutItems(event, ui);
+ }
+ });
+ },
+
+ // Reorder menu items. Called after menu item drag-and-drop
+ // This is all make-believe at the moment. Just an example to demonstrate to Huda how this could work.
+ reorderMenuItems: function(event, ui) {
+ var menuItems = $('li.menuItem').map(function(index, el) {
+ return $(this).data('menuItemUri');
+ }).get();
+
+ $.ajax({
+ url: menuManagement.reorderUrl,
+ data: {
+ predicate: menuManagement.positionPredicate,
+ individuals: menuItems
+ },
+ traditional: true, // serialize the array of individuals for the server
+ dataType: 'json',
+ type: 'POST',
+ success: function(data, status, request) {
+ var pos;
+ $('.menuItem').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.
+ menuManagement.setPosition(this, pos);
+ });
+ // Set the form rank field value.
+ $('#rank').val(pos + 1);
+ },
+ error: function(request, status, error) {
+ // ui is undefined after removal of a menu item.
+ 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 = menuManagement.getPosition(ui.item),
+ nextpos = pos + 1,
+ menuItems = menuManagement.menuItemsList,
+ next = menuManagement.findMenuItem('position', nextpos);
+
+ if (next.length) {
+ ui.item.insertBefore(next);
+ }
+ else {
+ ui.item.appendTo(menuItems);
+ }
+
+ alert('Reordering of menu items failed.');
+ }
+ }
+ });
+ },
+
+ // On page load, associate data with each menu item list element. Then we don't
+ // have to keep retrieving data from or modifying the DOM as we manipulate the
+ // menu items.
+ initMenuItemData: function() {
+ $('.menuItem').each(function(index) {
+ $(this).data(menuItemData[index]);
+
+ // RY We might still need position to put back an element after reordering
+ // failure. Position might already have been reset? Check.
+ // We also may need position to implement undo links: we want the removed menu item
+ // to show up in the list, but it has no position.
+ $(this).data('position', index+1);
+ });
+ },
+
+ getPosition: function(menuItem) {
+ return $(menuItem).data('position');
+ },
+
+ setPosition: function(menuItem, pos) {
+ $(menuItem).data('position', pos);
+ },
+
+ findMenuItem: function(key, value) {
+ var matchingMenuItem = $(); // if we don't find one, return an empty jQuery set
+
+ $('.menuItem').each(function() {
+ var menuItem = $(this);
+ if ( menuItem.data(key) === value ) {
+ matchingMenuItem = menuItem;
+ return false; // stop the loop
+ }
+ });
+
+ return matchingMenuItem;
+ },
+
+ // Event listeners
+
+ // Disable drag-n-drop and associated cues if only one menu item remains
+ // Good chance we won't need this if Huda's able to hook into standard delete for n3 editing (loads confirmation in separate page)
+ disableMenuItemsDD: function() {
+ var menuItems = this.menuItemsList.children('li');
+
+ this.menuItemsList.sortable({ disable: true } );
+
+ this.menuItemsList.removeClass('dragNdrop');
+
+ menuItems.removeAttr('title');
+ }
+};
+
+$(document).ready(function() {
+ menuManagement.onLoad();
+});
\ No newline at end of file
diff --git a/webapp/web/templates/freemarker/body/individual/individual-menu.ftl b/webapp/web/templates/freemarker/body/individual/individual-menu.ftl
index 5770ae720..f9f26a641 100644
--- a/webapp/web/templates/freemarker/body/individual/individual-menu.ftl
+++ b/webapp/web/templates/freemarker/body/individual/individual-menu.ftl
@@ -4,16 +4,20 @@
<#include "individual-setup.ftl">
+
+
Menu management
<#assign hasElement = propertyGroups.pullProperty("${namespaces.display}hasElement")>
<#-- List the menu items -->
-<#list hasElement.statements as statement>
- Position | <#include "${hasElement.template}"> | <@p.editingLinks "hasElement" statement editable />
-#list>
-
- <#-- remove this once styles are applied -->
+
<#-- Link to add a new menu item -->
<#if editable>
@@ -23,6 +27,23 @@
#if>
#if>
-${stylesheets.add('')}
+${stylesheets.add('',
+ '')}
-${scripts.add('')}
\ No newline at end of file
+${headScripts.add('')}
+
+<#assign positionPredicate = "${namespaces.display}menuPosition" />
+
+
+
+<#-- Since the individual page can currently be viewed by anonymous users, only invoke sortable if logged in for now
+ Jim is working on this (see NIHVIVO-2749) -->
+<#if editable>
+ ${scripts.add('')}
+#if>
\ No newline at end of file
diff --git a/webapp/web/templates/freemarker/body/partials/individual/propStatement-hasElement.ftl b/webapp/web/templates/freemarker/body/partials/individual/propStatement-hasElement.ftl
new file mode 100644
index 000000000..bbff0e3a7
--- /dev/null
+++ b/webapp/web/templates/freemarker/body/partials/individual/propStatement-hasElement.ftl
@@ -0,0 +1,16 @@
+<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
+
+<#-- Template for display:hasElement (used for menu system).
+
+ This template must be self-contained and not rely on other variables set for the individual page, because it
+ is also used to generate the property statement during a deletion.
+ -->
+
+<#-- Anchor is here for convenience during development. Should be removed before release -->
+${statement.linkText}
+
+
\ No newline at end of file
diff --git a/webapp/web/templates/freemarker/body/siteAdmin/siteAdmin-siteConfiguration.ftl b/webapp/web/templates/freemarker/body/siteAdmin/siteAdmin-siteConfiguration.ftl
index 4a45fc1a5..ec45cfaee 100644
--- a/webapp/web/templates/freemarker/body/siteAdmin/siteAdmin-siteConfiguration.ftl
+++ b/webapp/web/templates/freemarker/body/siteAdmin/siteAdmin-siteConfiguration.ftl
@@ -14,11 +14,13 @@
#if>
<#if siteConfig.urls.menuN3Editor??>
-