This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resource-editor.git
commit d8866ccf02638031c37d4cc4b7295223cc8925de Author: Sandro Boehme <[email protected]> AuthorDate: Fri Jun 26 10:58:10 2015 +0000 SLING-4736 Resource Editor :: properties page :: add CRUD for String and Path and prepare for other types: o Added crud for String and Path properties o Added path-editor, path-viewer and string-editor tag files o Added e2e tests for the new crud o Added shortcuts and a cheat sheet for properties to explain them o Added bootstrap-notify to notify after saving and deleting properties o Added Glyphicons-halflings icon font for property icons o Manually tested on IE9 o Styles adapted for IE9 o Added toolbar with dropdown for the properties page o Replaced some css colors by variables in less files o Added vertical scrolling to the properties page git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1687749 13f79535-47bb-0310-9956-ffa450edef68 --- frontend/Gruntfile.js | 58 ++- frontend/package.json | 3 +- pom.xml | 8 +- src/main/img/save-bigger_white.xcf | Bin 0 -> 1467 bytes src/main/less/forms.less | 5 + src/main/less/glyphicons.less | 15 + src/main/less/navs.less | 22 + src/main/less/reseditor.less | 64 ++- src/main/less/scaffolding.less | 4 + src/main/less/variables.less | 2 + src/main/resources/META-INF/resource-editor.tld | 17 + src/main/resources/META-INF/tags/path-editor.tag | 13 + src/main/resources/META-INF/tags/path-viewer.tag | 24 + src/main/resources/META-INF/tags/string-editor.tag | 17 + .../resource-editor-static-content/css/ie.css} | 17 +- .../js/properties/PropertyController.js | 196 +++++++++ .../js/tree/AddNodeController.js | 6 +- .../SLING-INF/libs/sling/resource-editor/html.jsp | 86 +--- .../libs/sling/resource-editor/nodes.json.incl.jsp | 4 +- .../libs/sling/resource-editor/nodes.json.jsp | 2 +- .../libs/sling/resource-editor/properties.jsp | 180 ++++++++ .../libs/sling/resource-editor/rootnodes.json.jsp | 2 +- src/test/javascript/e2e/spec/e2e_spec.js | 485 +++++++++++++-------- 23 files changed, 923 insertions(+), 307 deletions(-) diff --git a/frontend/Gruntfile.js b/frontend/Gruntfile.js index 49559d1..e5c1718 100644 --- a/frontend/Gruntfile.js +++ b/frontend/Gruntfile.js @@ -71,33 +71,49 @@ module.exports = function(grunt) { 'jquery/dist/jquery.min.js', 'jquery/dist/jquery.min.map', 'bootbox/bootbox.min.js', - 'jstree/dist/jstree.min.js' + 'jstree/dist/jstree.min.js', + 'bootstrap-notify/dist/bootstrap-notify.min.js' ], // Actual pattern(s) to match. dest: staticContentFolder+'/generated/3rd_party/js', // Destination path prefix. flatten: true }, ], }, - css_dependencies: { - files: [ - { - expand: true, // Enable dynamic expansion. - cwd: 'node_modules/', // Src matches are relative to this path. - src: [ - 'select2/select2.css', - 'select2/select2.png', - 'select2/select2-spinner.gif', - 'animate.css/animate.min.css', - 'jstree/dist/themes/default/style.min.css', - 'jstree/dist/themes/default/32px.png', - 'jstree/dist/themes/default/40px.png', - 'jstree/dist/themes/default/throbber.gif', - ], // Actual pattern(s) to match. - dest: staticContentFolder+'/generated/3rd_party/css', // Destination path prefix. - flatten: true - }, - ], - } + css_dependencies: { + files: [ + { + expand: true, // Enable dynamic expansion. + cwd: 'node_modules/', // Src matches are relative to this path. + src: [ + 'select2/select2.css', + 'select2/select2.png', + 'select2/select2-spinner.gif', + 'animate.css/animate.min.css', + 'jstree/dist/themes/default/style.min.css', + 'jstree/dist/themes/default/32px.png', + 'jstree/dist/themes/default/40px.png', + 'jstree/dist/themes/default/throbber.gif', + ], // Actual pattern(s) to match. + dest: staticContentFolder+'/generated/3rd_party/css', // Destination path prefix. + flatten: true + }, + ], + }, + font_dependencies: { + files: [ + { + expand: true, // Enable dynamic expansion. + cwd: 'node_modules/', // Src matches are relative to this path. + src: [ + 'bootstrap/fonts/glyphicons-halflings-regular.ttf', + 'bootstrap/fonts/glyphicons-halflings-regular.woff2', + 'bootstrap/fonts/glyphicons-halflings-regular.woff' + ], // Actual pattern(s) to match. + dest: staticContentFolder+'/generated/3rd_party/fonts', // Destination path prefix. + flatten: true + }, + ], + } }, karma: { options: { diff --git a/frontend/package.json b/frontend/package.json index 7fe4321..6966d1e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,8 @@ "select2": "3.5.2-browserify", "jstree": "3.0.9", "animate.css": "3.1.1", - "jquery-scrollto": "1.4.4" + "jquery-scrollto": "1.4.4", + "bootstrap-notify": "3.0.0" }, "devDependencies": { "grunt": "0.4.5", diff --git a/pom.xml b/pom.xml index 08ad5fa..e75dd64 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,11 @@ <extensions>true</extensions> <configuration> <instructions> - <Sling-Initial-Content>SLING-INF/libs/sling/resource-editor-static-content;overwrite:=true;path:=/libs/sling/resource-editor-static-content,SLING-INF/libs/sling/resource-editor;overwrite:=true;path:=/libs/sling/resource-editor</Sling-Initial-Content> + <Sling-Initial-Content> + META-INF/tags;overwrite:=true;path:=/libs/sling/resource-editor-tags/property-editor, + SLING-INF/libs/sling/resource-editor-static-content;overwrite:=true;path:=/libs/sling/resource-editor-static-content, + SLING-INF/libs/sling/resource-editor;overwrite:=true;path:=/libs/sling/resource-editor + </Sling-Initial-Content> <Import-Package>!org.slf4j.impl,*, org.apache.sling.commons.js.nodetypes.javascript, javax.el, @@ -80,6 +84,8 @@ org.xml.sax.helpers </Import-Package> <Export-Package>!org.apache.sling.reseditor</Export-Package> + <Include-Resource>META-INF/resource-editor.tld=${project.build.outputDirectory}/META-INF/resource-editor.tld,{maven-resources}</Include-Resource> + <Sling-Bundle-Resources>/META-INF/tags</Sling-Bundle-Resources> </instructions> <remoteOBR>www.jcrbrowser.org</remoteOBR> </configuration> diff --git a/src/main/img/save-bigger_white.xcf b/src/main/img/save-bigger_white.xcf new file mode 100644 index 0000000..bf58185 Binary files /dev/null and b/src/main/img/save-bigger_white.xcf differ diff --git a/src/main/less/forms.less b/src/main/less/forms.less index ea3ae31..3b6aa85 100644 --- a/src/main/less/forms.less +++ b/src/main/less/forms.less @@ -53,4 +53,9 @@ // vertical-form is a custom class .modal-body.vertical-form label { display: block; +} + + +#properties fieldset { + margin-bottom: 5px; } \ No newline at end of file diff --git a/src/main/less/glyphicons.less b/src/main/less/glyphicons.less new file mode 100644 index 0000000..4650880 --- /dev/null +++ b/src/main/less/glyphicons.less @@ -0,0 +1,15 @@ +// +// Glyphicons for Bootstrap +// +// Since icons are fonts, they can be placed anywhere text is placed and are +// thus automatically sized to match the surrounding child. To use, create an +// inline element with the appropriate classes, like so: +// +// <a href="#"><span class="glyphicon glyphicon-star"></span> Star</a> + + +#properties .glyphicon { + font-size: 16px; + color: @textColor; +} + diff --git a/src/main/less/navs.less b/src/main/less/navs.less index ca7862c..0d880a8 100644 --- a/src/main/less/navs.less +++ b/src/main/less/navs.less @@ -79,3 +79,25 @@ padding: 5px 10px 5px 10px; } + +.nav { + > li > a { + &:hover, + &:focus { + background-color: @selectedBackground; + border: 1px solid black; + } + border: 1px solid transparent; + color: @textColor; + } + // Open dropdowns + .open { + text-shadow: none; + } + .dropdown-menu > li > a { + &:hover, + &:focus { + background-color: @textColor; + } + } +} diff --git a/src/main/less/reseditor.less b/src/main/less/reseditor.less index bf16646..18942d4 100755 --- a/src/main/less/reseditor.less +++ b/src/main/less/reseditor.less @@ -31,6 +31,7 @@ @import "forms.less"; @import "buttons.less"; @import "modals.less"; +@import "glyphicons.less"; /* ################################################################# */ @@ -120,7 +121,7 @@ body font-family: 'Michroma', sans-serif; font-weight: bold; font-size: 24px; - color: #2D373D; + color: @brand-primary; text-shadow: 0 2px 2px #dddddd; display: inline; margin: 10px; @@ -134,16 +135,18 @@ body margin-left: 4px; } -.logout a { +#login .logout a { .rounded; - border: 1px solid #2D373D; - color: #2D373D; + border: 1px solid @brand-primary; + color: @brand-primary; text-shadow: 0 1px 1px #EEE; font-size: 15px; margin: 10px 10px 0 0; - padding: 0 10px; box-shadow: inset 0px 0px 1px black, 1px 1px 0px white; } +#login .logout a:hover { + background-color: @textColor; +} #login_error { width: 200px; @@ -171,28 +174,12 @@ body padding: 5px; } - -fieldset -{ - border: none; - margin-top: 5px; - padding: 0; -} .proplabel { - width: 25%; - float: left; - display: inline; line-height: 24px; - color: #C0C0C0; + color: @textColor; font-weight: normal; } -.propinput -{ - float: left; - display: inline; - width: 65%; -} .propinputmultival { float: left; @@ -202,7 +189,6 @@ fieldset .propmultival_fieldset { float: left; - width: 65%; margin: 0 5px 0 0 } @@ -247,6 +233,23 @@ fieldset background-image: url(../../img/remove.png); } +.property-icon { + margin-top: 5px; +} + +.icon { + cursor: pointer; + border: 1px solid transparent; + padding: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +.property-icon:hover { + border: 1px solid black; + background-color: @selectedBackground; +} + .jstree-default .jstree-icon.add-icon{ background-image: url(../../img/add.png); margin-right: 3px; @@ -282,7 +285,7 @@ fieldset #tree .jstree-clicked { - background:#303030; + background: @selectedBackground; border:1px solid black; padding:0px 1px; -moz-border-radius: 5px; @@ -349,4 +352,17 @@ input.jstree-rename-input{ } #addNodeDialog .form-group label a{ font-weight: normal; +} + +.propertyTypeMenu { + text-shadow: none; +} + +#properties .ie9filter-plate-div { + overflow-y: auto; + overflow-x: hidden; +} + +#properties-info-icon { + margin-top: -4px; } \ No newline at end of file diff --git a/src/main/less/scaffolding.less b/src/main/less/scaffolding.less index 75b563c..1c90e95 100644 --- a/src/main/less/scaffolding.less +++ b/src/main/less/scaffolding.less @@ -27,4 +27,8 @@ body { .plate-text-shadow; +} + +.property-row a { + color: @textColor; } \ No newline at end of file diff --git a/src/main/less/variables.less b/src/main/less/variables.less index 9d692a2..25f14f5 100644 --- a/src/main/less/variables.less +++ b/src/main/less/variables.less @@ -26,6 +26,7 @@ // ------------------------- @bodyBackground: transparent; @textColor: #c0c0c0; +@selectedBackground: #303030; // Links // ------------------------- @@ -34,3 +35,4 @@ // Forms // ------------------------- @inputBorderRadius: 3px; +@icon-font-path: "../3rd_party/fonts/"; \ No newline at end of file diff --git a/src/main/resources/META-INF/resource-editor.tld b/src/main/resources/META-INF/resource-editor.tld new file mode 100644 index 0000000..b4a0288 --- /dev/null +++ b/src/main/resources/META-INF/resource-editor.tld @@ -0,0 +1,17 @@ +<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee"> + <tlib-version>1.0-SNAPSHOT</tlib-version> + <short-name>test</short-name> + <uri>http://sling.apache.org/resource-editor</uri> + <tag-file> + <name>string-editor</name> + <path>/META-INF/tags/string-editor.tag</path> + </tag-file> + <tag-file> + <name>path-editor</name> + <path>/META-INF/tags/path-editor.tag</path> + </tag-file> + <tag-file> + <name>path-viewer</name> + <path>/META-INF/tags/path-viewer.tag</path> + </tag-file> +</taglib> \ No newline at end of file diff --git a/src/main/resources/META-INF/tags/path-editor.tag b/src/main/resources/META-INF/tags/path-editor.tag new file mode 100644 index 0000000..90df44b --- /dev/null +++ b/src/main/resources/META-INF/tags/path-editor.tag @@ -0,0 +1,13 @@ +<%@ tag body-content="empty" isELIgnored="false" display-name="Path-Editor" %> + +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> + +<%@attribute name="component_id" %> +<%@attribute name="value" %> + +<c:if test="${not empty value}" > + <c:set var="escapedValue" value="${fn:escapeXml(value)}" /> +</c:if> + +<input id="${component_id}" class="propinput form-control property-value path-editor" name="new-property-value" value="${escapedValue}"/> diff --git a/src/main/resources/META-INF/tags/path-viewer.tag b/src/main/resources/META-INF/tags/path-viewer.tag new file mode 100644 index 0000000..6a1fa84 --- /dev/null +++ b/src/main/resources/META-INF/tags/path-viewer.tag @@ -0,0 +1,24 @@ +<%@ tag body-content="empty" isELIgnored="false" display-name="Path-Editor" %> + +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> + +<%@attribute name="component_id" %> +<%@attribute name="value" %> +<%@attribute name="editor_component_id" %> + +<c:if test="${not empty value}" > + <c:set var="escapedValue" value="${fn:escapeXml(value)}" /> +</c:if> + +<script type="text/javascript"> +$(document).ready(function() { + + $('\#${editor_component_id}').on("keyup", function(){ + $('\#${component_id}').attr('href', '/reseditor'+$(this).val()+'.html'); + $('\#${component_id}').text($(this).val()+'.html'); + }); +}); +</script> + +<a id="${component_id}" class="path-viewer" href="/reseditor${value}.html">${escapedValue}.html</a> \ No newline at end of file diff --git a/src/main/resources/META-INF/tags/string-editor.tag b/src/main/resources/META-INF/tags/string-editor.tag new file mode 100644 index 0000000..07615de --- /dev/null +++ b/src/main/resources/META-INF/tags/string-editor.tag @@ -0,0 +1,17 @@ +<%@ tag body-content="empty" isELIgnored="false" display-name="String-Editor" %> + +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> + +<%@attribute name="property_name" %> +<%@attribute name="value" %> +<%@attribute name="rows" %> + +<c:choose> + <c:when test="${empty rows}"> + <textarea data-property-name="${fn:escapeXml(property_name)}" name="new-property-value" class="propinput form-control property-value">${value}</textarea> + </c:when> + <c:otherwise> + <textarea data-property-name="${fn:escapeXml(property_name)}" name="new-property-value" class="propinput form-control property-value" rows="${rows}">${value}</textarea> + </c:otherwise> +</c:choose> diff --git a/src/main/less/scaffolding.less b/src/main/resources/SLING-INF/libs/sling/resource-editor-static-content/css/ie.css similarity index 77% copy from src/main/less/scaffolding.less copy to src/main/resources/SLING-INF/libs/sling/resource-editor-static-content/css/ie.css index 75b563c..287decd 100644 --- a/src/main/less/scaffolding.less +++ b/src/main/resources/SLING-INF/libs/sling/resource-editor-static-content/css/ie.css @@ -17,14 +17,13 @@ * under the License. */ -// -// Scaffolding -// -------------------------------------------------- +#login .nav-tabs > li > a, #login .tabbable .tab-content { + background-color: #202020; +} - -// Body reset -// ------------------------- - -body { - .plate-text-shadow; +#login_tab_content { + border-top-right-radius: 7px; + border-top-left-radius: 7px; + border-bottom-right-radius: 7px; + border-bottom-left-radius: 7px; } \ No newline at end of file diff --git a/src/main/resources/SLING-INF/libs/sling/resource-editor-static-content/js/properties/PropertyController.js b/src/main/resources/SLING-INF/libs/sling/resource-editor-static-content/js/properties/PropertyController.js new file mode 100644 index 0000000..ebddeab --- /dev/null +++ b/src/main/resources/SLING-INF/libs/sling/resource-editor-static-content/js/properties/PropertyController.js @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// creating the namespace +var org = org || {}; +org.apache = org.apache || {}; +org.apache.sling = org.apache.sling || {}; +org.apache.sling.reseditor = org.apache.sling.reseditor || {}; + +/* + * The PropertyController is responsible for the property page functionality. + */ + +//defining the module +org.apache.sling.reseditor.PropertyController = (function() { + + function PropertyController(settings, mainController){ + this.mainController = mainController; + this.settings = settings; + + var thisPropertyController = this; + + $(document).ready(function() { + $.notifyDefaults({ + delay: 500, + template: '<div data-notify="container" class="col-xs-11 col-sm-3 growl-notify alert alert-{0}" role="alert">' + + '<button type="button" aria-hidden="true" class="close" data-notify="dismiss">×</button>' + + '<span data-notify="icon"></span> ' + + '<span data-notify="title">{1}</span> ' + + '<span data-notify="message">{2}</span>' + + '<a href="{3}" target="{4}" data-notify="url"></a>' + + '</div>' + }); + + $( "#properties" ).on( "click", ".dropdown-menu.add-property-menu li a", function() { + var dataType = $(this).attr('data-property-type'); + thisPropertyController.openAddPropertyDialog(dataType); + }); + $('#addPropertyDialog .submit').click(function(){ + thisPropertyController.addProperty(); + }); + $( "#properties" ).on( "click", ".property-icon.glyphicon-remove", function() { + var parentRow = $(this).parents(".row:first"); + var propertyKey = parentRow.find(".proplabel").attr("for"); + thisPropertyController.removeProperty(propertyKey, parentRow); + }); + $( "#properties" ).on( "click", ".property-icon.glyphicon-save", function() { + var parentRow = $(this).parents(".row:first"); + var key, value; + if (parentRow.hasClass('new-property')){ + key = $('#newStringPropertyKey').val(); + } else { + key = parentRow.find(".proplabel").attr("for"); + } + value = parentRow.find(".form-control").val(); + + thisPropertyController.saveProperty(key, value); + }); + + $("#properties-info-icon").on("click", function(e, data) { + $('#properties .info-content-container').slideToggle(); + }); + $("#properties .info-content-container .close").on("click", function(e, data) { + $('#properties .info-content-container').slideToggle(); + }); + $( "#properties" ).on( "keydown", function(event, data) { + // see http://www.javascripter.net/faq/keycodes.htm + if (event.ctrlKey || event.metaKey) { + var pressedKey = String.fromCharCode(event.which).toLowerCase(); + var n = 78; + var s = 83; + var del = 46; + switch (event.which){ + case s: + event.preventDefault(); + var parentRow = $( document.activeElement ).parents(".row:first"); + var key = parentRow.find(".proplabel").attr("for"); + var value = parentRow.find(".form-control").val(); + thisPropertyController.saveProperty(key, value); + break; + case del: + event.preventDefault(); + var parentRow = $( document.activeElement ).parents(".row:first"); + var key = parentRow.find(".proplabel").attr("for"); + thisPropertyController.removeProperty(key, parentRow); + break; + case n: + event.preventDefault(); + $('#properties .add-property-menu-item.dropdown-toggle').dropdown('toggle'); + break; + } + } + }); + }); + }; + + PropertyController.prototype.openAddPropertyDialog = function(dataType){ + $('#addPropertyDialog').modal('show'); + $('#addPropertyDialog .property-editor').hide(); + $("#addPropertyDialog .property-editor[data-property-type='"+dataType+"']").show(); + }; + + PropertyController.prototype.removeProperty = function(key, row){ + var thisPropertyController = this; + var confirmationMsg = "You are about to delete the property with the key '"+key+"'. Are you sure?"; + bootbox.confirm(confirmationMsg, function(result) { + if (result){ + var data = {}; + data[key+"@Delete"] = ""; + $.ajax({ + type: 'POST', + url: location.href, + dataType: "json", + data: data + }) + .done(function() { + row.remove(); + $.notify({ + message: 'Property \''+key+'\' deleted.' + },{ + type: 'success' + }); + }) + .fail(function(errorJson) { + thisPropertyController.mainController.displayAlert(errorJson); + }); + } + }); + }; + + PropertyController.prototype.saveProperty = function(key, value){ + var thisPropertyController = this; + var data = {}; + data[key] = value; + data["_charset_"] = "utf-8"; + $.ajax({ + type: 'POST', + url: location.href, + dataType: "json", + data: data + }) + .done(function() { + $.notify({ + message: 'Property \''+key+'\' saved.' + },{ + type: 'success' + }); + }) + .fail(function(errorJson) { + thisPropertyController.mainController.displayAlert(errorJson); + }); + }; + + PropertyController.prototype.addProperty = function(){ + var thisPropertyController = this; + var key = $('#new-property-key').val(); + var value = $("#addPropertyDialog .property-editor:visible .property-value").val(); + var data = {}; + var propertyType = $("#addPropertyDialog .property-editor:visible").attr("data-property-type"); + data[key] = value; + data[key+"@TypeHint"] = propertyType; + data["_charset_"] = "utf-8"; + $.ajax({ + type: 'POST', + "_charset_": "utf-8", + url: location.href, + dataType: "json", + data: data + }) + .done(function() { + location.reload(); + }) + .fail(function(errorJson) { + $('#addPropertyDialog').modal('hide'); + thisPropertyController.mainController.displayAlert(errorJson); + }); + } + + return PropertyController; +}()); diff --git a/src/main/resources/SLING-INF/libs/sling/resource-editor-static-content/js/tree/AddNodeController.js b/src/main/resources/SLING-INF/libs/sling/resource-editor-static-content/js/tree/AddNodeController.js index 3c2571e..e5f0231 100644 --- a/src/main/resources/SLING-INF/libs/sling/resource-editor-static-content/js/tree/AddNodeController.js +++ b/src/main/resources/SLING-INF/libs/sling/resource-editor-static-content/js/tree/AddNodeController.js @@ -244,8 +244,6 @@ org.apache.sling.reseditor.AddNodeController = (function() { } }); - $('#addNodeDialog').modal('show'); - var contextPath = this.mainController.getContextPath() == "/" && resourcePath.length>0 && resourcePath.charAt(0)=="/" ? "" : this.mainController.getContextPath(); this.lastAddNodeURL = contextPath+resourcePath; @@ -270,8 +268,10 @@ org.apache.sling.reseditor.AddNodeController = (function() { return {id:searchTerm, text:searchTerm}; } }).data("select2"); + + $('#addNodeDialog').modal('show'); + $('#addNodeDialog').addClass('add-node-finished'); -// $('#addNodeDialog').append('<div id="add-node-finished"></div>'); }); } diff --git a/src/main/resources/SLING-INF/libs/sling/resource-editor/html.jsp b/src/main/resources/SLING-INF/libs/sling/resource-editor/html.jsp index 170e0e0..86c0d50 100644 --- a/src/main/resources/SLING-INF/libs/sling/resource-editor/html.jsp +++ b/src/main/resources/SLING-INF/libs/sling/resource-editor/html.jsp @@ -22,7 +22,8 @@ <%@ page import="javax.jcr.*,org.apache.sling.api.resource.Resource, org.apache.sling.api.resource.ValueMap"%> <%@ page import="java.security.Principal"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling/1.0"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> +<%@ taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> @@ -36,6 +37,10 @@ <link href='<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/css/font.css' rel='stylesheet' type='text/css'> <!--[if lt IE 9]> <link href='<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/css/font_ie.css' rel='stylesheet' type='text/css'> + <![endif]--> + + <!--[if IE]> +<link href='<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/css/ie.css' rel='stylesheet' type='text/css'> <![endif]--> <!-- @@ -50,10 +55,12 @@ original <script type="text/javascript" src="<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/generated/3rd_party/js/bootbox.min.js"></script> <script type="text/javascript" src="<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/generated/3rd_party/js/jstree.min.js"></script> <script type="text/javascript" src="<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/generated/3rd_party/js/select2.min.js"></script> +<script type="text/javascript" src="<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/generated/3rd_party/js/bootstrap-notify.min.js"></script> <script type="text/javascript" src="<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/js/tree/JSTreeAdapter.js"></script> <script type="text/javascript" src="<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/js/tree/TreeController.js"></script> <script type="text/javascript" src="<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/js/tree/AddNodeController.js"></script> +<script type="text/javascript" src="<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/js/properties/PropertyController.js"></script> <script type="text/javascript" src="<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/js/LoginController.js"></script> <script type="text/javascript" src="<%= request.getContextPath() %>/libs/sling/resource-editor-static-content/js/MainController.js"></script> @@ -109,6 +116,8 @@ var jsTreeAdapterSettings = { }; new org.apache.sling.reseditor.JSTreeAdapter(jsTreeAdapterSettings, treeController, mainController); +new org.apache.sling.reseditor.PropertyController({}, mainController); + </script> </head> @@ -157,6 +166,7 @@ new org.apache.sling.reseditor.JSTreeAdapter(jsTreeAdapterSettings, treeControll </div> </div> </div> + <ul class="nav nav-tabs"> <li class="active"> <a id="login_tab" href="#login_tab_content" data-toggle="tab">Login</a> @@ -203,78 +213,8 @@ new org.apache.sling.reseditor.JSTreeAdapter(jsTreeAdapterSettings, treeControll </div> </div> </div> - <div class="col-sm-8"> - <div id="outer_content" class="plate"> - <div class="ie9filter-plate-div"> - <div id="inner_content_margin"> - <form action="not_configured_yet.change.properties" method="post"> - <c:set var="resourceIsNode" scope="request" value="<%=resource.adaptTo(Node.class) !=null %>"/> - <c:choose> - <c:when test="${resourceIsNode}" > - <%-- - For some reason I get the following exception when using the JSTL expression '${currentNode.properties}' - instead of the scriptlet code 'currentNode.getProperties()': - org.apache.sling.scripting.jsp.jasper.JasperException: Unable to compile class for JSP: - org.apache.sling.scripting.jsp.jasper.el.JspValueExpression cannot be resolved to a type - see https://issues.apache.org/jira/browse/SLING-2455 - --%> - <c:forEach var="property" items="<%=currentNode.getProperties()%>"> - <% Property property = (Property) pageContext.getAttribute("property");%> - <fieldset> - <label class="proplabel" for='${property.name}'>${property.name} [<%=PropertyType.nameFromValue(property.getType())%>${property.multiple ? ' multiple' : ''}]</label> - <c:choose> - <c:when test="${property.multiple}" > - <fieldset class="propmultival_fieldset"> - <div> </div> - <c:forEach var="value" items="<%=property.getValues()%>"> - <c:choose> - <c:when test="${property.type == PropertyType.BINARY}" > - <p>I'm a binary property</p> - </c:when> - <c:otherwise> - <input class="propinputmultival form-control" value="${value.string}"/> - </c:otherwise> - </c:choose> - </c:forEach> - </fieldset> - </c:when> - <c:when test="${false}" > - </c:when> - <c:otherwise> - <c:choose> - <c:when test="<%=property.getType() == PropertyType.BINARY%>" > - <c:choose> - <c:when test='<%=currentNode.getParent().isNodeType("nt:file") %>'> - <a class="propinput" href="<%= request.getContextPath() %>${resource.parent.path}">Download</a> - </c:when> - <c:otherwise> - <a class="propinput" href="<%= request.getContextPath() %>${resource.path}.property.download?property=${property.name}">View (choose "Save as..." to download)</a> - </c:otherwise> - </c:choose> - </c:when> - <c:otherwise> - <input class="propinput form-control" id="${property.name}" name="${property.name}" value="${property.string}"/> - </c:otherwise> - </c:choose> - </c:otherwise> - </c:choose> - </fieldset> - </c:forEach> - </c:when> - <c:otherwise> - <c:forEach var="property" items="<%=resource.adaptTo(ValueMap.class)%>"> - <fieldset> - <label class="proplabel" for='${property.key}'>${property.key}</label> - <input class="propinput form-control" id="${property.key}" name="${property.key}" value="${property.value}"/> - </fieldset> - </c:forEach> - </c:otherwise> - </c:choose> - </form> - </div> - </div> - </div> - </div> + + <%@ include file="properties.jsp" %> </div> <div class="row" style="visibility:hidden; display:none;"> <div class="col-sm-12"> diff --git a/src/main/resources/SLING-INF/libs/sling/resource-editor/nodes.json.incl.jsp b/src/main/resources/SLING-INF/libs/sling/resource-editor/nodes.json.incl.jsp index c6a44bc..72cb589 100644 --- a/src/main/resources/SLING-INF/libs/sling/resource-editor/nodes.json.incl.jsp +++ b/src/main/resources/SLING-INF/libs/sling/resource-editor/nodes.json.incl.jsp @@ -17,8 +17,8 @@ under the License. --%> [ - <c:forEach var="theResource" items="<%=resource.listChildren()%>" varStatus="status"> - <c:set var="resourceIsNode" scope="request" value="<%=resource.adaptTo(Node.class) !=null %>"/> + <c:forEach var="theResource" items="${sling:listChildren(resource)}" varStatus="status"> + <c:set var="resourceIsNode" scope="request" value="${sling:adaptTo(resource,'javax.jcr.Node') != null}"/> <%--Hiding the resource provider root. --%> <c:if test="${theResource.path != '/reseditor'}"> <% Resource theResource = (Resource) pageContext.getAttribute("theResource"); diff --git a/src/main/resources/SLING-INF/libs/sling/resource-editor/nodes.json.jsp b/src/main/resources/SLING-INF/libs/sling/resource-editor/nodes.json.jsp index faa6f80..09f90d9 100644 --- a/src/main/resources/SLING-INF/libs/sling/resource-editor/nodes.json.jsp +++ b/src/main/resources/SLING-INF/libs/sling/resource-editor/nodes.json.jsp @@ -26,7 +26,7 @@ <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> -<%@ taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling/1.0"%> +<%@ taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling"%> <sling:defineObjects /> <% response.setContentType("application/json"); %> diff --git a/src/main/resources/SLING-INF/libs/sling/resource-editor/properties.jsp b/src/main/resources/SLING-INF/libs/sling/resource-editor/properties.jsp new file mode 100644 index 0000000..6b75ad5 --- /dev/null +++ b/src/main/resources/SLING-INF/libs/sling/resource-editor/properties.jsp @@ -0,0 +1,180 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> +<%@ taglib prefix="re" uri="http://sling.apache.org/resource-editor"%> + +<sling:defineObjects /> + + <div id="properties" class="col-sm-8"> + <div id="outer_content" class="plate"> + <div class="ie9filter-plate-div"> + <div id="inner_content_margin"> + <div class="row"> + <div class="col-sm-12"> + <div style="display: none;" class="info-content-container" > + <div class="well well-sm info-content"> + <button type="button" class="close"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button> + <h4>Cheat Sheet</h4> + <p>You can use</p> + <ul> + <li><kbd><kbd>ctrl</kbd> + <kbd>s</kbd></kbd> or <kbd><kbd>cmd</kbd> + <kbd>s</kbd></kbd> (for Mac) for saving a property.</li> + <li><kbd><kbd>ctrl</kbd> + <kbd>del</kbd></kbd> or <kbd><kbd>cmd</kbd> + <kbd>del</kbd></kbd> (for Mac) for removing a property.</li> + <li><kbd><kbd>ctrl</kbd> + <kbd>n</kbd></kbd> or <kbd><kbd>cmd</kbd> + <kbd>n</kbd></kbd> (for Mac) for opening the add property menu.</li> + </ul> + </div> + </div> + <span id="properties-info-icon" class="info-icon info-icon-lightgray pull-right clearfix" ></span> + <ul class="nav nav-pills"> + <li class="dropdown" role="presentation"> + <a class="add-property-menu-item dropdown-toggle" data-toggle="dropdown" href="#"><span class=""><span class="glyphicon glyphicon-plus"></span><span class="caret"></span></span> + </a> + <ul class="dropdown-menu add-property-menu" role="menu" aria-labelledby="propertyTypeMenu"> + <li><a tabindex="-1" href="#" data-property-type="String">String</a></li> + <li><a tabindex="-1" href="#" data-property-type="Date">Date</a></li> + <li><a tabindex="-1" href="#" data-property-type="Boolean">Boolean</a></li> + <li><a tabindex="-1" href="#" data-property-type="Long">Long</a></li> + <li><a tabindex="-1" href="#" data-property-type="Double">Double</a></li> + <li><a tabindex="-1" href="#" data-property-type="Decimal">Decimal</a></li> + <li><a tabindex="-1" href="#" data-property-type="Binary">Binary</a></li> + <li><a tabindex="-1" href="#" data-property-type="Name">Name</a></li> + <li><a tabindex="-1" href="#" data-property-type="Path">Path</a></li> + <li><a tabindex="-1" href="#" data-property-type="Reference">Reference</a></li> + <li><a tabindex="-1" href="#" data-property-type="Uri">URI</a></li> + </ul> + </li> + </ul> + </div> + </div> + <form action="not_configured_yet.change.properties" method="post"> + <c:set var="resourceIsNode" scope="request" value="<%=resource.adaptTo(Node.class) !=null %>"/> + <c:choose> + <c:when test="${resourceIsNode}" > + <%-- + For some reason I get the following exception when using the JSTL expression '${currentNode.properties}' + instead of the scriptlet code 'currentNode.getProperties()': + org.apache.sling.scripting.jsp.jasper.JasperException: Unable to compile class for JSP: + org.apache.sling.scripting.jsp.jasper.el.JspValueExpression cannot be resolved to a type + see https://issues.apache.org/jira/browse/SLING-2455 + --%> + <c:forEach var="property" items="<%=currentNode.getProperties()%>" varStatus="propertyLoopStatus"> + <% Property property = (Property) pageContext.getAttribute("property");%> + <div id="property-${propertyLoopStatus.index}" class="row property-row" data-property-name="${fn:escapeXml(property.name)}" > + <fieldset> + <div class="col-sm-3"> + <label class="proplabel" for='${property.name}'>${property.name} [<%=PropertyType.nameFromValue(property.getType())%>${property.multiple ? ' multiple' : ''}]</label> + </div> + <div class="col-sm-7 property-col"> + <c:choose> + <c:when test="${property.multiple}" > + <fieldset class="propmultival_fieldset"> + <div> </div> + <c:forEach var="value" items="<%=property.getValues()%>"> + <c:choose> + <c:when test="${property.type == PropertyType.BINARY}" > + <p>I'm a binary property</p> + </c:when> + <c:when test="${property.type == PropertyType.STRING}" > + <re:string-editor></re:string-editor> + </c:when> + <c:otherwise> + <input class="propinputmultival form-control" value="${value.string}"/> + </c:otherwise> + </c:choose> + </c:forEach> + </fieldset> + </c:when> + <c:otherwise> + <c:choose> + <c:when test="<%=property.getType() == PropertyType.BINARY%>" > + <c:choose> + <c:when test='<%=currentNode.getParent().isNodeType("nt:file") %>'> + <a class="propinput" href="<%= request.getContextPath() %>${resource.parent.path}">Download</a> + </c:when> + <c:otherwise> + <a class="propinput" href="<%= request.getContextPath() %>${resource.path}.property.download?property=${property.name}">View (choose "Save as..." to download)</a> + </c:otherwise> + </c:choose> + </c:when> + <c:when test="<%=property.getType() == PropertyType.STRING%>" > + <re:string-editor property_name="${fn:escapeXml(property.name)}-editor" value="${property.string}"></re:string-editor> + </c:when> + <c:when test="<%=property.getType() == PropertyType.PATH%>" > + <re:path-editor value="${property.string}" component_id="property-${propertyLoopStatus.index}-path-editor"></re:path-editor> + <re:path-viewer value="${property.string}" component_id="property-${propertyLoopStatus.index}-path-viewer" editor_component_id="property-${propertyLoopStatus.index}-path-editor"></re:path-viewer> + </c:when> + <c:otherwise> + <input class="propinput form-control" id="${property.name}" name="${property.name}" value="${property.string}"/> + </c:otherwise> + </c:choose> + </c:otherwise> + </c:choose> + </div> + <div class="col-sm-2"> + <span class="icon property-icon glyphicon glyphicon-save" aria-hidden="true"></span> + <span class="icon property-icon glyphicon glyphicon-remove" aria-hidden="true"></span> + </div> + </fieldset> + </div> + </c:forEach> + </c:when> + <c:otherwise> + <c:forEach var="property" items="<%=resource.adaptTo(ValueMap.class)%>"> + <div class="row"> + <fieldset> + <div class="col-sm-3"> + <label class="proplabel" for='${property.key}'>${property.key}</label> + </div> + <div class="col-sm-7"> + <input class="propinput form-control" id="${property.key}" name="${property.key}" value="${property.value}"/> + </div> + </fieldset> + </div> + </c:forEach> + </c:otherwise> + </c:choose> + </form> + </div> + </div> + </div> + </div> + <div class="modal fade" id="addPropertyDialog" tabindex="-1" role="dialog" aria-labelledby="addPropertyDialogLabel" aria-hidden="true"> + <div class="modal-dialog modal-lg"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button> + <h4 class="modal-title" id="addPropertyDialogLabel">Add Property</h4> + </div> + <div class="modal-body"> + <div class="container-fluid"> + <div class="row new-property"> + <fieldset> + <div class="col-sm-1"> + <label class="proplabel" for='new-property-key'>Key</label> + </div> + <div class="col-sm-3"> + <input id="new-property-key" class="propinput form-control" name="newPropertyKey" /> + </div> + <div class="col-sm-1"> + <label class="proplabel" for='new-property-value'>Value</label> + </div> + <div class="col-sm-7"> + <fieldset> + <label class="sr-only" for='new-property-value'>Value</label> + <div class="property-editor" data-property-type="Path"> +<%-- <sling:include resource="${resource}" resourceType="sling/resource-editor/property-editor" addSelectors="path-editor"/> --%> + <re:path-editor value="" component_id="property-path-editor-new"></re:path-editor> + </div> + <div class="property-editor" data-property-type="String"> + <re:string-editor rows="4"></re:string-editor> + </div> + </fieldset> + </div> + </fieldset> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + <button type="button" class="btn btn-primary submit">Save changes</button> + </div> + </div> + </div> + </div> \ No newline at end of file diff --git a/src/main/resources/SLING-INF/libs/sling/resource-editor/rootnodes.json.jsp b/src/main/resources/SLING-INF/libs/sling/resource-editor/rootnodes.json.jsp index 1457dbe..b2821b7 100644 --- a/src/main/resources/SLING-INF/libs/sling/resource-editor/rootnodes.json.jsp +++ b/src/main/resources/SLING-INF/libs/sling/resource-editor/rootnodes.json.jsp @@ -27,7 +27,7 @@ <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> -<%@ taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling/1.0"%> +<%@ taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling"%> <sling:defineObjects /> <% response.setContentType("application/json"); %> [{ diff --git a/src/test/javascript/e2e/spec/e2e_spec.js b/src/test/javascript/e2e/spec/e2e_spec.js index 9143602..984cf83 100644 --- a/src/test/javascript/e2e/spec/e2e_spec.js +++ b/src/test/javascript/e2e/spec/e2e_spec.js @@ -29,28 +29,7 @@ describe('A user of the Apache Sling Resource Editor', function() { client.timeouts("script", 1000); client.timeouts("implicit", 1000); client.timeouts("page load", 1000); - - describe('can open the add node dialog with', function() { - it('the icon', function(done) { - client.url(homeURL).waitForExist('#last-element'). - click('#root_anchor i.add-icon').waitForVisible('#addNodeDialog', function(err, visible) { - assert(typeof err === "undefined" || err === null); - assert(true === visible); - }) - .call(done); - }); - - it('the shortcut', function(done) { - client.url(homeURL).waitForExist('#last-element') - .click('#root_anchor i.jstree-themeicon') - .keys("c").waitForVisible('#addNodeDialog', function(err, visible) { - assert(typeof err === "undefined" || err === null); - assert(true === visible); - }) - .call(done); - }); - }); - + it('can login as admin', function(done) { client.url(homeURL).waitForExist('#last-element') .click('#login_tab').waitForVisible('#login_submit') @@ -58,170 +37,334 @@ describe('A user of the Apache Sling Resource Editor', function() { .setValue('#login_form input[name="j_password"]', 'admin') .click('#login_submit').waitForExist('#login .logout') .getText('#login_tab', function(err, text) { - assert(typeof err === "undefined" || err === null); + assert(typeof err === "undefined" || err === null); assert("Logout admin" === text); }) .call(done); }); - - it('can add an unstructured node to the root node', function(done) { - client.url(homeURL).waitForExist('#last-element') - .click("#root i.add-icon").waitForVisible('#addNodeDialog.add-node-finished .node_name_dd_container input') - .setValue('.node_name_dd_container input', "aTestNode") - .addValue('.node_name_dd_container input', 'Return') // presses the 'return' key - .click('#addNodeDialog .btn.btn-primary.submit').waitForExist("#root li[nodename=\"aTestNode\"]", function(err, existed) { - assert(typeof err === "undefined" || err === null); - assert(existed === true); - }) - .call(done); - }); - - it('can add a node with an encoded name on the second level ', function(done) { - client.url(homeURL).waitForExist('#last-element') - .click("#root li[nodename=\"aTestNode\"] i.add-icon").waitForVisible('#addNodeDialog.add-node-finished .node_name_dd_container input') - .setValue('.node_name_dd_container input', "täßt ?<>") - .addValue('.node_name_dd_container input', 'Return') // presses the 'return' key - .click('#addNodeDialog .btn.btn-primary.submit').waitForExist("#root li[nodename=\"aTestNode\"].opened li[nodename=\"täßt ?<>\"]", function(err, existed) { - assert(typeof err === "undefined" || err === null); - assert(existed === true); - }) - .call(done); - }); - - it('can link to a node with an encoded name', function(done) { - var encodedNodeNameSelector = '#root li[nodename="aTestNode"].opened li[nodename="täßt ?<>"]'; - var encodedNodeNameOpenSelector = encodedNodeNameSelector +' i.open-icon'; - client.url(homeURL).waitForExist('#last-element') - .click("#root li[nodename=\"aTestNode\"] i.jstree-ocl").waitForExist(encodedNodeNameOpenSelector) - .click(encodedNodeNameOpenSelector).waitForExist(encodedNodeNameSelector+' a.jstree-clicked', function(err, existed) { - assert(typeof err === "undefined" || err === null); - assert(existed === true); - }) - .call(done); - }); - - - it('can add a node with a specific node type ', function(done) { - client.url(homeURL); - client.waitForExist('#last-element').click("#root li[nodename=\"aTestNode\"] i.add-icon").waitForVisible('#addNodeDialog.add-node-finished .node_name_dd_container', 1000) - .setValue('.node_name_dd_container input', "aFolder") - .addValue('.node_name_dd_container input', 'Return') - .click(".form-group.node-type .select2-chosen").waitForExist('.node_type_dd_container input') - .setValue('.node_type_dd_container input', "sling:Folder") - .addValue('.node_type_dd_container input', 'Return') - .click('#addNodeDialog .btn.btn-primary.submit').waitForExist('#root li[nodename="aTestNode"] li[nodename="aFolder"][nodetype="sling:Folder"]', function(err, existed) { - assert(typeof err === "undefined" || err === null); - assert(existed === true); - }) - .call(done); - }); - - it('can add a node with a resource type on the second level ', function(done) { - var nodeName = "a node with a resource type"; - var resourceType = "test/resource-editor/resource-type"; - var resourceTypeSelector = '#root li[nodename="aTestNode"].opened li[nodename="a node with a resource type"] span.node-type'; - client.url(homeURL); - client.waitForExist('#last-element').click("#root li[nodename=\"aTestNode\"] i.add-icon") - .waitForVisible('#addNodeDialog.add-node-finished .node_name_dd_container', 1000) - .setValue('.node_name_dd_container input', nodeName) - .addValue('.node_name_dd_container input', 'Return') // presses the 'return' key - .click(".form-group.resource-type .select2-chosen").waitForExist('.resource_type_dd_container input', 1000) - .setValue('.resource_type_dd_container input', resourceType) - .addValue('.resource_type_dd_container input', 'Return') // presses the 'return' key - .click('#addNodeDialog .btn.btn-primary.submit').waitForExist(resourceTypeSelector, 1000) - .getText(resourceTypeSelector, function(err, text) { - assert(typeof err === "undefined" || err === null); - assert(text === resourceType); - }) - .call(done); - }); - - it('can rename a node with an encoded name', function(done) { - client = client.url(homeURL); - client.waitForExist('#last-element', function(err) { - client.click("#root li[nodename=\"aTestNode\"] i.jstree-ocl", function(err, res) { - var encodedNodeNameSelector = '#root li[nodename="aTestNode"].opened li[nodename="täßt ?<>"]'; - var encodedNodeNameAnchorSelector = encodedNodeNameSelector +' a .node-type'; -// The open node animation will take longer than 500ms thus setting 2000ms as max. - client.waitForExist(encodedNodeNameAnchorSelector, 2000, function(err, existed) { - client.doubleClick(encodedNodeNameAnchorSelector, function(err) { - var encodedNodeNameInputSelector = encodedNodeNameSelector+' input.jstree-rename-input'; - client.waitForExist(encodedNodeNameInputSelector, function(err, existed) { - client.setValue(encodedNodeNameInputSelector, 'täßt2& <>'); - client.execute(function() { - $('#root li[nodename="aTestNode"] li[nodename="täßt ?<>"] input.jstree-rename-input').blur(); - $('#root li[nodename="aTestNode"] li[nodename="täßt ?<>"] input.jstree-rename-input').blur(); - }); - client.waitForExist('#root li[nodename="aTestNode"].opened li[nodename="täßt2& <>"]', 2000, function(err, existed) { - assert(typeof err === "undefined" || err === null); - assert(true === existed); - + + describe('can manage nodes as he', function() { + + describe('can open the add node dialog with', function() { + it('the icon', function(done) { + client.url(homeURL).waitForExist('#last-element'). + click('#root_anchor i.add-icon').waitForVisible('#addNodeDialog', function(err, visible) { + assert(typeof err === "undefined" || err === null); + assert(true === visible); + }) + .call(done); + }); + + it('the shortcut', function(done) { + client.url(homeURL).waitForExist('#last-element') + .click('#root_anchor i.jstree-themeicon') + .keys("c").waitForVisible('#addNodeDialog', function(err, visible) { + assert(typeof err === "undefined" || err === null); + assert(true === visible); + }) + .call(done); + }); + }); + + + it('can add an unstructured node to the root node', function(done) { + client.url(homeURL).waitForExist('#last-element') + .click("#root i.add-icon").waitForVisible('#addNodeDialog.add-node-finished .node_name_dd_container input', 1000) + .setValue('.node_name_dd_container input', "aTestNode") + .addValue('.node_name_dd_container input', 'Return') // presses the 'return' key + .click('#addNodeDialog .btn.btn-primary.submit').waitForExist("#root li[nodename=\"aTestNode\"]", function(err, existed) { + assert(typeof err === "undefined" || err === null); + assert(existed === true); + }) + .call(done); + }); + + it('can add a node with an encoded name on the second level ', function(done) { + client.url(homeURL).waitForExist('#last-element') + .click("#root li[nodename=\"aTestNode\"] i.add-icon").waitForVisible('#addNodeDialog.add-node-finished .node_name_dd_container input', 1000) + .setValue('.node_name_dd_container input', "täßt ?<>") + .addValue('.node_name_dd_container input', 'Return') // presses the 'return' key + .click('#addNodeDialog .btn.btn-primary.submit').waitForExist("#root li[nodename=\"aTestNode\"].opened li[nodename=\"täßt ?<>\"]", function(err, existed) { + assert(typeof err === "undefined" || err === null); + assert(existed === true); + }) + .call(done); + }); + + it('can link to a node with an encoded name', function(done) { + var encodedNodeNameSelector = '#root li[nodename="aTestNode"].opened li[nodename="täßt ?<>"]'; + var encodedNodeNameOpenSelector = encodedNodeNameSelector +' i.open-icon'; + client.url(homeURL).waitForExist('#last-element') + .click("#root li[nodename=\"aTestNode\"] i.jstree-ocl").waitForExist(encodedNodeNameOpenSelector) + .click(encodedNodeNameOpenSelector).waitForExist(encodedNodeNameSelector+' a.jstree-clicked', function(err, existed) { + assert(typeof err === "undefined" || err === null); + assert(existed === true); + }) + .call(done); + }); + + + it('can add a node with a specific node type ', function(done) { + client.url(homeURL); + client.waitForExist('#last-element').click("#root li[nodename=\"aTestNode\"] i.add-icon").waitForVisible('#addNodeDialog.add-node-finished .node_name_dd_container', 1000) + .setValue('.node_name_dd_container input', "aFolder") + .addValue('.node_name_dd_container input', 'Return') + .click(".form-group.node-type .select2-chosen").waitForExist('.node_type_dd_container input') + .setValue('.node_type_dd_container input', "sling:Folder") + .addValue('.node_type_dd_container input', 'Return') + .click('#addNodeDialog .btn.btn-primary.submit').waitForExist('#root li[nodename="aTestNode"] li[nodename="aFolder"][nodetype="sling:Folder"]', function(err, existed) { + assert(typeof err === "undefined" || err === null); + assert(existed === true); + }) + .call(done); + }); + + it('can add a node with a resource type on the second level ', function(done) { + var nodeName = "a node with a resource type"; + var resourceType = "test/resource-editor/resource-type"; + var resourceTypeSelector = '#root li[nodename="aTestNode"].opened li[nodename="a node with a resource type"] span.node-type'; + client.url(homeURL); + client.waitForExist('#last-element').click("#root li[nodename=\"aTestNode\"] i.add-icon") + .waitForVisible('#addNodeDialog.add-node-finished .node_name_dd_container', 1000) + .setValue('.node_name_dd_container input', nodeName) + .addValue('.node_name_dd_container input', 'Return') // presses the 'return' key + .click(".form-group.resource-type .select2-chosen").waitForExist('.resource_type_dd_container input', 1000) + .setValue('.resource_type_dd_container input', resourceType) + .addValue('.resource_type_dd_container input', 'Return') // presses the 'return' key + .click('#addNodeDialog .btn.btn-primary.submit').waitForExist(resourceTypeSelector, 1000) + .getText(resourceTypeSelector, function(err, text) { + assert(typeof err === "undefined" || err === null); + assert(text === resourceType); + }) + .call(done); + }); + + it('can rename a node with an encoded name', function(done) { + client = client.url(homeURL); + client.waitForExist('#last-element', function(err) { + client.click("#root li[nodename=\"aTestNode\"] i.jstree-ocl", function(err, res) { + var encodedNodeNameSelector = '#root li[nodename="aTestNode"].opened li[nodename="täßt ?<>"]'; + var encodedNodeNameAnchorSelector = encodedNodeNameSelector +' a .node-type'; + // The open node animation will take longer than 500ms thus setting 2000ms as max. + client.waitForExist(encodedNodeNameAnchorSelector, 2000, function(err, existed) { + client.doubleClick(encodedNodeNameAnchorSelector, function(err) { + var encodedNodeNameInputSelector = encodedNodeNameSelector+' input.jstree-rename-input'; + client.waitForExist(encodedNodeNameInputSelector, function(err, existed) { + client.setValue(encodedNodeNameInputSelector, 'täßt2& <>'); + client.execute(function() { + $('#root li[nodename="aTestNode"] li[nodename="täßt ?<>"] input.jstree-rename-input').blur(); + $('#root li[nodename="aTestNode"] li[nodename="täßt ?<>"] input.jstree-rename-input').blur(); + }); + client.waitForExist('#root li[nodename="aTestNode"].opened li[nodename="täßt2& <>"]', 2000, function(err, existed) { + assert(typeof err === "undefined" || err === null); + assert(true === existed); + + }); }); }); - }); + }) }) - }) - }) - .call(done); + }) + .call(done); + }); + it('can delete nodes via multi selection and shortcut', function(done) { + client = client.url(homeURL); + client + .waitForExist('#last-element').click("#root li[nodename=\"aTestNode\"] i.add-icon") + .waitForVisible('#addNodeDialog.add-node-finished', 1000).click('#addNodeDialog .btn.btn-primary.submit') + // The open node animation will take longer than 500ms thus setting 2000ms as max. + .waitForExist('#root li[nodename="aTestNode"].opened', 2000).elements('#root li[nodename="aTestNode"].opened li a .jstree-themeicon', function(err, res) { + client + .moveTo(res.value[0].ELEMENT, 0, 0) + .buttonPress('left') + .keys('Shift') + .moveTo(res.value[1].ELEMENT, 0, 0) + .buttonPress('left'); + + client.keys('Shift'); // release the Shift key + + // On Mac the ctrl key opens the context menu in the browser + // this is why the Cmd key should be used in this case. + if ('darwin' === process.platform){ + client.keys('Command'); + } else { + client.keys('Control'); + } + + client.moveTo(res.value[3].ELEMENT, 0, 0) + .buttonPress('left'); + client.keys('NULL'); // release all keys + }); + var confirmationOkBtn = 'div.bootbox-confirm div.modal-footer button[data-bb-handler="confirm"]'; + var openTestNodeIcon = '#root li[nodename=\"aTestNode\"] i.open-icon'; + client.keys('Delete') + .waitForVisible(confirmationOkBtn) + .click(confirmationOkBtn) + .waitForVisible(openTestNodeIcon) + .click(openTestNodeIcon) + .waitForExist('#last-element').elements('#root li[nodename="aTestNode"] li a .jstree-themeicon', function(err, res) { + assert(typeof err === "undefined" || err === null); + assert(1 === res.value.length); + }); + client.call(done); + + }); + }); + + describe('can add a', function(done){ + it('String property', function(done) { + addProperty("String", "textarea"); + client.call(done); + }); + + it('Path property', function(done) { + addProperty("Path", "input"); + client.call(done); + }); + + function addProperty(type, editorTagName){ + var encodedNodeNameSelector = '#root li[nodename="aTestNode"].opened li[nodename="a node with a resource type"]'; + var encodedNodeNameOpenSelector = encodedNodeNameSelector +' i.open-icon'; + var addPropertyMenuItemSelector = "#properties .add-property-menu [data-property-type='"+type+"']"; + var key="a"+type+"Key"; + var value="a "+type+" value"; + var propValueEditorSelector = "#addPropertyDialog div[data-property-type='"+type+"'] "+editorTagName; + client.url(homeURL).waitForExist('#last-element') + .click("#root li[nodename=\"aTestNode\"] i.open-icon").waitForExist("#properties .add-property-menu-item", 1000) + .click("#properties .add-property-menu-item").waitForExist(addPropertyMenuItemSelector, 1000) + .click(addPropertyMenuItemSelector).waitForVisible('#new-property-key', 1000) + /* + * The value is not always set completely the first time for some strange reason so I set it twice. + * ToDo: Find a way to reproduce that and file a bug report. + * A simple test that fills some input fields on the facebook registration page did not show the problem. + * See: https://groups.google.com/forum/#!topic/webdriverio/b7ohm7tkG7Q + */ + .setValue('#new-property-key ', key) + .setValue('#new-property-key ', key) + .setValue(propValueEditorSelector, value) + .click("#addPropertyDialog .btn-primary.submit").waitForExist("label.proplabel[for='"+key+"']", 1000, function(err, existed) { + assert(typeof err === "undefined" || err === null); + assert(existed === true); + client.getValue("#properties div[data-property-name='"+key+"'].row .propinput.property-value", function(err, resultingValue) { + assert(typeof err === "undefined" || err === null); + assert(value === resultingValue); + }); + + }); + } }); - it('can delete nodes via multi selection and shortcut', function(done) { - client = client.url(homeURL); - client - .waitForExist('#last-element').click("#root li[nodename=\"aTestNode\"] i.add-icon") - .waitForVisible('#addNodeDialog.add-node-finished', 1000).click('#addNodeDialog .btn.btn-primary.submit') - // The open node animation will take longer than 500ms thus setting 2000ms as max. - .waitForExist('#root li[nodename="aTestNode"].opened', 2000).elements('#root li[nodename="aTestNode"].opened li a .jstree-themeicon', function(err, res) { - client - .moveTo(res.value[0].ELEMENT, 0, 0) - .buttonPress('left') - .keys('Shift') - .moveTo(res.value[1].ELEMENT, 0, 0) - .buttonPress('left'); - client.keys('Shift'); // release the Shift key - - // On Mac the ctrl key opens the context menu in the browser - // this is why the Cmd key should be used in this case. - if ('darwin' === process.platform){ - client.keys('Command'); - } else { - client.keys('Control'); - } - - client.moveTo(res.value[3].ELEMENT, 0, 0) - .buttonPress('left'); - client.keys('NULL'); // release all keys + describe('can save a String property', function(){ + var inputElementSelector = "#properties div[data-property-name='aStringKey'].row .propinput.property-value"; + + it('with the icon', function(done) { + var stringValue = "new String value"; + setStringFieldValue(client, stringValue); + client.click("#properties div[data-property-name='aStringKey'].row .property-icon.glyphicon-save").waitForExist("div.alert-success.growl-notify", 1000).refresh(); + testStringFieldValue(client, stringValue); + client.call(done); }); - var confirmationOkBtn = 'div.bootbox-confirm div.modal-footer button[data-bb-handler="confirm"]'; - var openTestNodeIcon = '#root li[nodename=\"aTestNode\"] i.open-icon'; - client.keys('Delete') - .waitForVisible(confirmationOkBtn) - .click(confirmationOkBtn) - .waitForVisible(openTestNodeIcon) - .click(openTestNodeIcon) - .waitForExist('#last-element').elements('#root li[nodename="aTestNode"] li a .jstree-themeicon', function(err, res) { - assert(typeof err === "undefined" || err === null); - assert(1 === res.value.length); + + it('with the shortcut', function(done) { + var stringValue = "3rd String value"; + setStringFieldValue(client, stringValue); + // On Mac the ctrl key opens the context menu in the browser + // this is why the Cmd key should be used in this case. + if ('darwin' === process.platform){ + client.keys('Command'); + client.keys("s") + client.keys('Command'); // releases the key + } else { + client.keys('Control'); + client.keys("s") + client.keys('Control'); // releases the key + }; + testStringFieldValue(client, stringValue); + client.call(done); }); - client.call(done); + function setStringFieldValue(client, value){ + client.url(homeURL).waitForExist('#last-element') + .click("#root li[nodename=\"aTestNode\"] i.open-icon").waitForExist("#properties label.proplabel[for='aStringKey']", 1000) + .setValue("#properties div[data-property-name='aStringKey'].row .propinput.property-value", value); + } + + function testStringFieldValue(client, value){ + client + .waitForExist("div.alert-success.growl-notify", 1000).refresh() + .waitForExist(inputElementSelector, 1000).getValue(inputElementSelector, function(err, resultingValue){ + assert(typeof err === "undefined" || err === null); + assert(value === resultingValue); + }) + } }); + + describe('can delete a property', function(){ + it('with the icon', function(done) { + var value= "aStringKey"; + focusInputField(client, value); + client + .click("#properties div[data-property-name='"+value+"'].row .property-icon.glyphicon-remove"); + testRemoval(client, value); + client.call(done); + }); + + it('with the shortcut', function(done) { + var value= "aPathKey"; + focusInputField(client, value); - it('can delete a node with the icon', function(done) { - client = client.url(homeURL); - client.waitForExist('#last-element', function(err) { - client.click('li[nodetype="rep:root"] li[nodename="aTestNode"] a i.remove-icon', function(err, res) { - client.waitForText('div.bootbox-confirm div.bootbox-body', function(err, result, third, fourth){ - client.click('div.bootbox-confirm div.modal-footer button[data-bb-handler="confirm"]', function(err, res) { - client.waitForExist('li[nodetype="rep:root"] li[nodename="aTestNode"]', true/*reverse*/, function(err, existed) { - assert(typeof err === "undefined" || err === null); - assert(existed === false); + // On Mac the ctrl key opens the context menu in the browser + // this is why the Cmd key should be used in this case. + if ('darwin' === process.platform){ + client.keys('Command'); + client.keys("Delete") + client.keys('Command'); // releases the key + } else { + client.keys('Control'); + client.keys("Delete") + client.keys('Control'); // releases the key + }; + testRemoval(client, value); + client.call(done); + }); + + function focusInputField(client, value){ + var stringPropertyInputFieldSelector = "#properties div[data-property-name='"+value+"'].row .propinput.property-value"; + client.url(homeURL).waitForExist('#last-element') + .click("#root li[nodename=\"aTestNode\"] i.open-icon").waitForExist(stringPropertyInputFieldSelector, 1000) + .click(stringPropertyInputFieldSelector); + } + + function testRemoval(client, value){ + client + .waitForVisible("div.bootbox-confirm .btn-primary", 1000) + .click("div.bootbox-confirm .btn-primary").waitForExist("div.alert-success.growl-notify", 1000).refresh() + .waitForExist("#properties div[data-property-name='"+value+"'].row", true/*reverse*/, function(err, existed) { + assert(typeof err === "undefined" || err === null); + assert(existed === false); + }) + } + }); + + // specs ("it" functions) are always executed before describe blocks + // that's why this spec is wrapped within a describe block as + // it has to be executed last + describe('can delete the (test) node', function(done){ + it('with the icon', function(done) { + client = client.url(homeURL); + client.waitForExist('#last-element', function(err) { + client.click('li[nodetype="rep:root"] li[nodename="aTestNode"] a i.remove-icon', function(err, res) { + client.waitForText('div.bootbox-confirm div.bootbox-body', function(err, result, third, fourth){ + client.click('div.bootbox-confirm div.modal-footer button[data-bb-handler="confirm"]', function(err, res) { + client.waitForExist('li[nodetype="rep:root"] li[nodename="aTestNode"]', true/*reverse*/, function(err, existed) { + assert(typeof err === "undefined" || err === null); + assert(existed === false); + }); }); }); - }); - }) - }) - .call(done); + }) + }) + .call(done); + }); }); - }); \ No newline at end of file -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
