Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EAK-386] Implementation of the CodeEditor widget #392

Open
wants to merge 12 commits into
base: epic/new-components
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class ResourceTypes {
public static final String BUTTON = "granite/ui/components/coral/foundation/button";
public static final String BUTTON_GROUP = "granite/ui/components/coral/foundation/form/buttongroup";
public static final String CHECKBOX = "granite/ui/components/coral/foundation/form/checkbox";
public static final String CODE_EDITOR = "etoolbox-authoring-kit/components/authoring/codeeditor";
public static final String COLORFIELD = "granite/ui/components/coral/foundation/form/colorfield";
public static final String CONTAINER = "granite/ui/components/coral/foundation/container";
public static final String CORAL_FILEUPLOAD = "granite/ui/components/coral/foundation/form/fileupload";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Licensed 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.
*/
package com.exadel.aem.toolkit.api.annotations.widgets.codeeditor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.exadel.aem.toolkit.api.annotations.meta.ResourceType;
import com.exadel.aem.toolkit.api.annotations.meta.ResourceTypes;
import com.exadel.aem.toolkit.api.annotations.meta.ValueRestriction;
import com.exadel.aem.toolkit.api.annotations.meta.ValueRestrictions;

/**
* Used to set up a syntax-highlighting code editor withing a Touch UI dialog or page. Default implementation is based
* on the open-source <a href="https://ace.c9.io/">Ace Editor</a>
*/

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ResourceType(ResourceTypes.CODE_EDITOR)
public @interface CodeEditor {

/**
* Specifies a particular link to the editor's {@code js} file in an external repository or CDN. Can be used to
* require a specific version or a custom build
* @return Optional string value
*/
@ValueRestriction(ValueRestrictions.NOT_BLANK_OR_DEFAULT)
String source() default "";

/**
* Defines the code highlighting completion mode of the editor (the "language" or markup format it works with). Must
* match one of the built-in modes enumerated in the <a href="https://github.com/ajaxorg/ace-builds/tree/master/src-noconflict">project's
* repository</a> (unless a custom mode is supplied). By default, the {@code json} mode is used
* @return Optional string value
*/
@ValueRestriction(ValueRestrictions.NOT_BLANK_OR_DEFAULT)
String mode() default "";

/**
* Defines the visual and code highlighting theme of the editor. Must match one of the built-in themes enumerated in
* the <a href="https://github.com/ajaxorg/ace-builds/tree/master/src-noconflict">project's repository</a> (unless a
* custom theme is supplied). By default, the {@code crimson_editor} theme is used
* @return Optional string value
*/
@ValueRestriction(ValueRestrictions.NOT_BLANK_OR_DEFAULT)
String theme() default "";

/**
* Declares options that will be passed to the {@code CodeEditor} upon initialization. Every option is a key-value
* pair. The option keys originate from the <a href="https://ajaxorg.github.io/ace-api-docs/index.html">editor's
* API</a>
* @return Optional array of {@link CodeEditorOption} objects
*/
CodeEditorOption[] options() default {};

/**
* When set, specifies the string marker prepended to the data stored in JCR for the current field. E.g., if the
* visible value of the field is {@code "Hello World"} and the {@code dataPrefix = "text:"} is specified, to the JCR
* goes {@code "text:Hello World"}. This can be used to distinguish between snippets written in different languages,
* or else to prevent AEM from falsely processing the field value as some Granite content (e.g. when storing JSON
* strings in a multifield)
* @return Optional string value
*/
String dataPrefix() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Licensed 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.
*/
package com.exadel.aem.toolkit.api.annotations.widgets.codeeditor;

/**
* Represents a configuration option passed to a {@code CodeEditor} upon initialization
*/
public @interface CodeEditorOption {

/**
* Defines the name of the option
* @return String value, non-blank
*/
String name();

/**
* Defines the value of the option
* @return String value
*/
String value();

/**
* Defines the type of the option that will be used with the initialization script. Expected values are {@code
* String} (default), {@code Boolean}, and {@code Integer}
* @return Class reference
*/
Class<?> type() default String.class;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@ public class CoreConstants {
public static final String NN_LIST = "list";

public static final String PN_APPEND = "append";
public static final String PN_DISABLED = "disabled";
public static final String PN_ITEM_RESOURCE_TYPE = "itemResourceType";
public static final String PN_LIMIT = "limit";
public static final String PN_LIST_ITEM = "listItem";
public static final String PN_OFFSET = "offset";
public static final String PN_PATH = "path";
public static final String PN_PREPEND = "prepend";
public static final String PN_REQUIRED = "required";
public static final String PN_SELECTED = "selected";
public static final String PN_TEXT = "text";
public static final String PN_UPDATE_COMPONENT_LIST = "updatecomponentlist";
public static final String PN_VALIDATION = "validation";
public static final String PN_VALUE = "value";

public static final String PARAMETER_ID = "@id";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package com.exadel.aem.toolkit.core.authoring.models;

import java.util.Map;
import javax.annotation.PostConstruct;
import javax.script.SimpleBindings;

import org.apache.commons.lang3.StringUtils;
Expand All @@ -25,11 +26,18 @@
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import com.adobe.granite.ui.components.AttrBuilder;
import com.adobe.granite.ui.components.Config;
import com.adobe.granite.ui.components.Value;
import com.adobe.granite.ui.components.htl.ComponentHelper;

import com.exadel.aem.toolkit.api.annotations.main.AemComponent;
import com.exadel.aem.toolkit.core.CoreConstants;

/**
* Presents the basic logic for rendering Granite UI components with the use of Sling models and HTL markup. This class
* manages only the common component properties and is expected to be extended with component-specific Sling models for
* all cases but the most basic ones
*/
@Model(adaptables = SlingHttpServletRequest.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@AemComponent(
path = "components/authoring/base",
Expand All @@ -38,6 +46,9 @@
)
public class BaseModel {

private static final String ATTRIBUTE_ARIA_REQUIRED = "aria-required";
private static final String ATTRIBUTE_FOUNDATION_VALIDATION = "data-foundation-validation";

@SlingObject
private SlingHttpServletRequest request;

Expand All @@ -46,69 +57,126 @@ public class BaseModel {

private LocalComponentHelper componentHelper;

/**
* Performs post-inject model initialization
*/
@PostConstruct
private void init() {
componentHelper = new LocalComponentHelper(request);
}

/**
* Retrieves common attributes of a Granit UI component such as {@code required}, {@code disabled}, etc. These
* attributes are typically rendered to the main container of the component's HTML markup
* @return Map of string values
*/
public Map<String, String> getCommonAttributes() {
return getComponentHelper().getCommonAttributes();
return componentHelper.getCommonAttributes();
}

/**
* Retrieves the value of the {@code name} attribute of the editable component
* @return String value; a non-blank string is expected
*/
public String getName() {
return name;
}

/**
* Retrieves the value of the editable component
* @return A nullable string value
*/
public Object getValue() {
if (request == null
|| request.getRequestPathInfo().getSuffixResource() == null
|| StringUtils.isBlank(name)) {
return getDefaultValue();
}

Resource suffixResource = request.getRequestPathInfo().getSuffixResource();
String relativePath = name.contains(CoreConstants.SEPARATOR_SLASH)
? StringUtils.substringBeforeLast(name, CoreConstants.SEPARATOR_SLASH)
: StringUtils.EMPTY;
Resource dataResource = getDataResource();
Config config = new Config(dataResource);
Value value = new Value(request, config);

String propertyName = name.contains(CoreConstants.SEPARATOR_SLASH)
? StringUtils.substringAfterLast(name, CoreConstants.SEPARATOR_SLASH)
: name;

Resource endResource = StringUtils.isNotBlank(relativePath)
? request.getResourceResolver().getResource(suffixResource, relativePath)
: suffixResource;
if (endResource == null) {
return getDefaultValue();
}

return endResource.getValueMap().get(propertyName);
return value.get(CoreConstants.RELATIVE_PATH_PREFIX + propertyName);
}


/**
* Retrieves the value which is rendered when no user-defined value is set for this editable component
* @return A nullable string value
*/
public Object getDefaultValue() {
return null;
}

private LocalComponentHelper getComponentHelper() {
if (componentHelper == null) {
componentHelper = new LocalComponentHelper(request);
}
return componentHelper;
/**
* Retrieves the {@link Resource} that contains data for the current component
* @return {@code Resource} object
*/
private Resource getDataResource() {
Resource suffixResource = request.getRequestPathInfo().getSuffixResource();
String relativePath = name.contains(CoreConstants.SEPARATOR_SLASH)
? StringUtils.substringBeforeLast(name, CoreConstants.SEPARATOR_SLASH)
: StringUtils.EMPTY;

return StringUtils.isNotBlank(relativePath)
? request.getResourceResolver().getResource(suffixResource, relativePath)
: suffixResource;
}

/**
* Implements {@link ComponentHelper} to provide common HTML attributes for the Granite UI components rendered with
* Sling models and HTL
*/
private static class LocalComponentHelper extends ComponentHelper {
private SlingHttpServletRequest request;

/**
* Creates a new {@link ComponentHelper} instance
* @param request {@code SlingHttpServletRequest} used to construct the current component
*/
public LocalComponentHelper(SlingHttpServletRequest request) {
if (request == null) {
return;
}
this.request = request;
init(new SimpleBindings((SlingBindings) request.getAttribute(SlingBindings.class.getName())));
}

/**
* Retrieves common HTML attributes for the Granite UI components
* @return A non-null {@code Map}
*/
public Map<String, String> getCommonAttributes() {
AttrBuilder attrBuilder = getInheritedAttrs();
populateCommonAttrs(attrBuilder);
return attrBuilder.getData();
Map<String, String> result = attrBuilder.getData();
if (request == null) {
return result;
}
if (request.getResource().getValueMap().get(CoreConstants.PN_DISABLED, false)) {
result.put(CoreConstants.PN_DISABLED, Boolean.TRUE.toString());
}
if (request.getResource().getValueMap().get(CoreConstants.PN_REQUIRED, false)) {
result.put(ATTRIBUTE_ARIA_REQUIRED, Boolean.TRUE.toString());
}
String validation = StringUtils.join(
CoreConstants.SEPARATOR_COMMA,
request.getResource().getValueMap().get(CoreConstants.PN_VALIDATION, String[].class));
if (StringUtils.isNotBlank(validation)) {
result.put(ATTRIBUTE_FOUNDATION_VALIDATION, validation);
}
return result;
}

/**
* A required stub method according the {@code ComponentHelper} contract
*/
@Override
protected void activate() {
// Not implemented
// Not Implemented
}
}
}
Loading