Thursday, June 30, 2016

AEM6 | Customize create page wizard

To customize OOTB create page wizard you should perform following step(s) depending on the type of customization you are looking for-

1. Customize the template fields shown in wizard step-2

First, you need to override the cq:dialog of page component to configure what fields should be shown in create page wizard. In this dialog set the following property to true which you want to display in page create wizard-


cq:showOnCreate
Boolean
true



2. Customize the template selection step in wizard step-1

You need to write a clientlib file with following code to handle the template selection step-

This example explains how to auto select the template if only one template is available for selection-
$(document).on("foundation-contentloaded", function(e) {
    if (($(".foundation-collection-item").length==1) && ($(".foundation-collection-item.selected").length == 0)) {
        $('.foundation-collection-item').click();
        $(':button.coral-Wizard-nextButton').click(); 
    }
});

Category of the clientlibs can be any of this depending on your requirement- 
app.myproject.createpagewizard, cq.gui.siteadmin.admin.publishwizard, dam.gui.admin.publishassetwizard

In order to attach this new clientlibs to create page wizard, you need to overlay the wizard's head component and add your custom category i.e. "app.myproject.createpagewizard" as shown in below diagram-



3. Customize the confirmation Step-3

Once you have submitted the create page form, there comes a popup window for confirmation, if you want to modify the form submit behavior then you can do following customization-

(function(window, document, Granite, $) {
    "use strict";
    var ui = $(window).adaptTo("foundation-ui");

    function createPage(wizard) {
        $.ajax({
            type: wizard.prop("method"),
            url: wizard.prop("action"),
            contentType: wizard.prop("enctype"),
            data: wizard.serialize()
        }).done(function(html) {
            var $html = $(html);
            var edit = $html.find("a.cq-siteadmin-admin-createpage-edit");
            window.location = edit.prop("href");
        }).fail(function(xhr, error, errorThrown) {
            if (error === "error") {
                var $html = $(xhr.responseText);
                if ($html.find(".foundation-form-response-status-code").length > 0) {
                    var title = $html.find(".foundation-form-response-title").next().html();
                    var message = $html.find(".foundation-form-response-description").next().html();
                    ui.alert(title, message, "error");
                    return;
                }
            }
            ui.alert(error, errorThrown, "error");
        });
    }

    function submit(wizard) {
        var flag = true;
        if (flag == true && (wizard.prop("template").value.indexOf("/apps/myproject/templates/testTemplate") >= 0)) {
            $.ajax({
                url: "/bin/someservletpath/submit",
                method: "GET",
                data: {
                    url: wizard.prop("parentPath").value,
                    pageTemplate: wizard.prop("template").value
                },
                dataType: "html"
            }).done(function(res) {
                wizard.prop("parentPath").value = res;
                createPage(wizard);
            }).fail(function(res) {
                ui.alert(res, "error");
            });
        } else {
            createPage(wizard);
        }
    }

    $(document).on("submit", ".cq-siteadmin-admin-createpage", function(e) {
        e.preventDefault();
        submit($(this));
    });

})(window, document, Granite, Granite.$);

As mentioned in #2 above the category of clientlibs should be "app.myproject.createpagewizard" and associated to create page wizard via head component overlaying.

Sunday, June 19, 2016

Control component rendering in Touch UI Dialogs

RenderCondition comes very handy when you want to control rendering of your dialog. You can restrict display of field, button, tab etc. based on RenderConditions. I am going to take an  example of Carousel component where I will hide a tab panel from dialog to certain users. Consider the component dialog has two tabs- Carousel and List. Now to restrict display of one tab i.e. List to certain users who do not have "Create" access a certain path, perform following steps-

1. Go to cq:dialog component which you want to restrict for rendering, in this case go to - "/apps/myproject/components/carousel/cq:dialog/content/items/list" node

2. Create a new node "granite:rendercondition" of type "nt:unstructured"

3. Add following properties to this new node-
path- Type: String, Value: /content/mysite/en/homepage
privileges- Type: String, Value: jcr:addChildNodes
sling:resourceType-Type: String,Value: granite/ui/components/foundation/renderconditions/privilege

Another resourceType is - granite/ui/components/coral/foundation/renderconditions/simple which supports a expression property like this- 
expression="${param.item == '/content/mysite/en' || param.item == '/content/mysite/es'}" 
OR
expression="${granite:relativeParent(param.item, 1)== '/content/mysite'}" To ensure that just first level pages e.g. /content/mysite/en match the render condition.

Refer the screenshot below-


4. Save your changes. And go to useradmin console and revoke "Create" access on the following path- "/content/mysite/en/homepage". You can use regex also for the path value for pattern based matching as per your specific need.

5. Now go back to the page i.e. /content/mysite/en/homepage.html, Open the carousel component dialog, You should observe that the list tab does not appear to the logged in user as  the RenderCondition rule evaluates to false.

For more rendercondition options refer the link here-
https://developer.adobe.com/experience-manager/reference-materials/6-5/granite-ui/api/jcr_root/libs/granite/ui/docs/server/rendercondition.html

Changing Default Favicon of AEM Admin Interface

To change the default favicon of the touch UI admin interface, you need to overlay the /libs/granite/ui/components/foundation/page/favicon/favicon.jsp. Just change this path in your overlayed file and remove the Adobe copyright comment text.

Config cfg = cmp.getConfig();
AttrBuilder attrs = new AttrBuilder(request, xssAPI);
attrs.add("rel", "shortcut icon");
attrs.addHref("href", cfg.get("href", "/libs/granite/core/content/login/favicon.ico"));
// https://en.wikipedia.org/wiki/Favicon#How_to_use
%><link <%= attrs.build() %>>

Change the favicon path highlighted in red above.

Refer the below scrrenshot to overlay the OOTB favicon.jsp-


Wednesday, June 15, 2016

AEM6 | RTE validator plugin

You can use the belowJS for sample validator plugin for Rich text editor-

CUI.rte.plugins.ValidatorPlugin = new Class({

    toString: "ValidatorPlugin",

    extend: CUI.rte.plugins.Plugin,

    /**
     * RTE plugin for maximun length check. If maxlength is set to 0 the validation will not happen. Set a number 
     * greater than 0 for maxlength property.
     */

    /**
     * @private
     */
    alertShown: false,
    errorEl: null,

    /**
     * @private
     */
    _init: function(editorKernel) {
        this.inherited(arguments);
        editorKernel.addPluginListener("beforekeydown", this.handleKeyDown, this, this,
            false);
        editorKernel.addPluginListener("keyup", this.handleKeyUp, this, this,
            false);
        editorKernel.addPluginListener("paste", this.handlePaste, this, this.editContext,
            false);
    },
    handlePaste: function(e, ctx) {
        var k = e.nativeEvent;
        var newtext, existingText, isIE;
        var clp = (k.originalEvent || k).clipboardData;
        var ek = this.editorKernel;
        existingText = $(e.editContext.root).context.textContent;
        if (clp === undefined || clp === null) {
            newtext = window.clipboardData.getData('Text');
            isIE = true;
        } else {
            newtext = clp.getData('text/plain');
            isIE = false;
        }

        var fulltext = existingText + newtext;
        //console.log("hi");
        //console.log("Existing: " + existingText);
        console.log("value: " + fulltext);
        var len = fulltext.length;
        if (len > this.config.maxlength && this.config.maxlength != 0) {

            this.showError(ek, "Maximum character limit: '" + this.config.maxlength + "' reached!");
            $(e.editContext.root).context.textContent = fulltext.substr(0, this.config.maxlength);
            k.preventDefault();
        }
    },

    /**
     * Handles key strokes.
     * @param {Object} e The plugin event
     * @private
     */
    handleKeyDown: function(e, k) {
        //Don't handle delete, backspace and arrow clicks
        if (e.isBackSpace() || e.isDelete() || e.isShift() || e.ctrlKeyPressed ||
            (e.charCode > 32 && e.charCode < 41)) {
            return;
        }
        var context = e.editContext;
        var dpr = CUI.rte.DomProcessor;
        var sel = CUI.rte.Selection;
        var com = CUI.rte.Common;
        var lut = CUI.rte.ListUtils;
        var ek = this.editorKernel;
        /* if(e.charCode==13)
         {
             debugger;

         }*/
        if (!this.isValidMaxLength(e, context)) {
            this.showError(ek, "Maximum character limit: '" + this.config.maxlength + "' reached!");
            e.preventDefault();
            return;
        }
    },
    showError: function(ek, message) {
        if (!this.alertShown) {
            ek.getDialogManager().alert(
                CUI.rte.Utils.i18n("Error"),
                CUI.rte.Utils.i18n(message),
                CUI.rte.Utils.scope(this.focus, this));
            this.alertShown = true;
        } else {}

    },

    /**
     * Handles post-processing required for all browsers. The method is called whenever a
     * key has been pressed.
     * @param {Object} e The plugin event
     * @private
     */
    handleKeyUp: function(e) {},
    isValidMaxLength: function(e, context) {
        if (this.config.maxlength == 0) {
            return true;
        }

        var length = context.root.textContent.length;
        var $contentEditable = $('#ContentFrame').contents().find('[contenteditable="true"]').length ? $('#ContentFrame').contents().find('[contenteditable="true"] p>br') : $('[contenteditable="true"] p>br');
        length += $contentEditable.length;
        if (length >= this.config.maxlength) {
            return false;
        }
        return true;
    },
    notifyPluginConfig: function(pluginConfig) {
        pluginConfig = pluginConfig || {};
        CUI.rte.Utils.applyDefaults(pluginConfig, {
            "maxlength": 0,

        });
        this.config = pluginConfig;
    }

});


//register plugin
CUI.rte.plugins.PluginRegistry.register("customvalidate", CUI.rte.plugins.ValidatorPlugin);


Put this js in a clientlibrary folder with category "cq.authoring.dialog".

After that in your component dialog, use the validator as shown below:



AEM6 | Prefill text field with http, mailto

You can use the below sample code to prefill an input text field with URL/ EMail prefixes based on user input. It prefixes mailto syntax if user input forms an email pattern.

(function($) {
    "use strict";
    $(document).on("change", ".js-coral-pathbrowser-input", function(e) {
        // If it's a relative path - do nothing
        if (this.value.indexOf('/') != 0) {
            // It's not a relative path - treat it as either a mail address or webb address
            if ((this.value.indexOf('@') > -1) && (this.value.indexOf('mailto:') == -1)) {
                // It's a mail address
                $(this).prop("value", 'mailto:' + this.value);
            } else if ((this.value.indexOf('http://') == -1) && (this.value.indexOf('https://') == -1) && (this.value != "") && (this.value.indexOf('mailto:') == -1) && ($(this).parent().parent().hasClass('js-cq-TagsPickerField') == false)) {
                // It's a webb address
                $(this).prop("value", 'http://' + this.value);
            }
        }
        if (this.value.indexOf(' ') >= 0) {
            $(this).prop("value", $.trim(this.value));
            $(this).prop("value", this.value.replace(new RegExp(' ', 'g'), '%20'));
        }
    });
})(Granite.$);

You need to put the above code in a clientlibs JS file with category "cq.authoring.dialog".

CDN | Clearing Cloudflare cache

In order to clear Cloudflare cache automatically via code, follow below steps: 1. Develop Custom TransportHandler Develop a custom Trans...