I am opening a popover on a specific control inside a FlexColumnLayout. Depending on the screensize it will not be rendered once the mid column is expanded.
I'd like to check if the cotrol is rendered before opening the popover, but i cant seem to find a property that allows that.
Already tried the solutions of this post:
Check if a control is currently rendered and visible
If you want to test around in actuall code just use the demo App of the FlexColumnLayout and try to open a popup on one of the hidden buttons when the begin column is expanded.
Thanks for your help, Eric
Edit 27.08.18 (code in question):
Controller:
/**
* Listner. Triggered when help is canceled.
* Closes popover.
* #author WN00096217 (Eric Schuster)
* #memberof xxxxxxxxxxxx
* #function onHelpCancel
*/
onHelpCancel: function () {
var iHelp = this._oHelpModel.getProperty("/counter");
this._oHelpModel.setProperty("/counter", 0);
this._oHelpModel.getProperty("/p" + iHelp).close();
},
/**
* Listner. Triggered when help is continue.
* Closes popover, opens next popover.
* #author WN00096217 (Eric Schuster)
* #memberof xxxxxxxxxxxxxxxxxxx
* #function onHelpNext
*/
onHelpNext: function () {
var iHelp = this._oHelpModel.getProperty("/counter");
this._oHelpModel.setProperty("/counter", iHelp + 1);
this._oHelpModel.getProperty("/p" + iHelp).close();
this._oHelpModel.getProperty("/p" + (iHelp + 1)).openBy(this._oHelpModel.getProperty("/c" + (iHelp + 1)));
},
controller (part of innit):
this._oHelpModel.setProperty("/c0", this._oView.byId("xxxxx"));
this._oHelpModel.setProperty("/c1", this._oView.byId("xxxx"));
this._oHelpModel.setProperty("/c2", this._oView.byId("xxxxx"));
this._oHelpModel.setProperty("/c3", this._oView.byId("xxxxxx"));
this._oHelpModel.setProperty("/c4", this._oView.byId("xxxxxx"));
this._oHelpModel.setProperty("/c5", this._oView.byId("xxxxx"));
this._oHelpModel.setProperty("/c6", this._oView.byId("xxxxx"));
this._oHelpModel.setProperty("/c7", this._oView.byId("xxxxxx"));
this._oHelpModel.setProperty("/c8", this._oView.byId("xxxxx"));
Component (part of innit):
//p eq popover c eq controll
var oHelpModel = new JSONModel({
counter: 0,
p0: null,
c0: null,
p1: null,
c1: null,
p2: null,
c2: null,
p3: null,
c3: null,
p4: null,
c4: null,
p5: null,
c5: null,
p6: null,
c6: null,
p7: null,
c7: null,
p8: null,
c8: null
});
this.setModel(oHelpModel, "helpModel");
What i d like the controler to look like:
/**
* Listner. Triggered when help is continue.
* Closes popover, opens next popover.
* #author WN00096217 (Eric Schuster)
* #memberof xxxxxxxxxxxxxxxxx
* #function onHelpNext
*/
onHelpNext: function () {
var iHelp = this._oHelpModel.getProperty("/counter");
if("control is rendered"){
this._oHelpModel.setProperty("/counter", iHelp + 1);
this._oHelpModel.getProperty("/p" + iHelp).close();
this._oHelpModel.getProperty("/p" + (iHelp + 1)).openBy(this._oHelpModel.getProperty("/c" + (iHelp + 1)));
} else {
this._oHelpModel.setProperty("/counter", iHelp + 1);
this.onHelpNext();
return;
}
},
use the control's onAfterRendering event. in your corresponding view's controller:
var oControl = this.byId("yourControl");
oControl.addEventDelegate({
onAfterRendering: function() {
// your confirmation that the control is rendered
}
}
UPDATE after op's clarification:
give the control an id if it doesn't have an id yet. get the control by its id. check if the control is active. in your corresponding view's controller:
var oControl = this.byId("yourControl");
oControl.isActive(); // true if the control is visible
Related
I have a smart form and it has a container for fetching a RichTextEditor in:
<smartForm:GroupElement label="{/#Report/Detail/#sap:label}" visible="{= (${appView>/appMode} === 'edit') }">
<VBox id="RichTextEditorContainer" visible="{= (${appView>/appMode} !== 'review') }" app:objectId="{ path:'Id', events: { change: '.onBindingObjectChange'}}" width="100%">
<!-- Insert RichTextEditor by JS-->
</VBox>
</smartForm:GroupElement>
As it has been said here that:
Make sure you destroy the RichTextEditor instance instead of hiding it and create a new one when you show it again.
I don't make the editor in xml and by an event inject it:
onBindingObjectChange: function (oEvent) {
if (this._oRTXE) {
this._oRTXE.destroy();
}
var oBox = this.getView().byId("RichTextEditorContainer");
oBox.removeAllItems();
this._oRTXE = new RichTextEditor({
value: "{Detail}",
editable: true,
height: "120px",
width: "100%",
wrapping: false,
editorType: "TinyMCE4",
showGroupClipboard: false,
showGroupFontStyle: false,
showGroupStructure: false,
showGroupTextAlign: false
});
oBox.insertItem(this._oRTXE);
}
The problem is, when user tries to type fast, it shows <p>xyz</p> for a second and then the text editor will be disappeared. Please look at the following picture:
As a work around if I remove value: "{Detail}" (that makes binding) then this problem will not happen. Also, if I change its binding to a JSON model also this error won't happen.
The problem is the change event of the RichTextEditor. While it has been written in the documentation that change event happen after leaving focus or press enter but it will also happen when user starts for typing after first enter focus. Here is my work around. Bind the RichTextEditor to JSON model, and update oData by a customized event.
<smartForm:GroupElement label="{/#Report/Detail/#sap:label}" visible="{= (${appView>/appMode} === 'edit') }">
<VBox id="RichTextEditorContainer" visible="{= (${appView>/appMode} !== 'review') }" app:objectId="{ path:'Id', events: { change: '.onBindingObjectChange'}}"
app:detail="{ path:'Detail', events: { change: '.onBindingTextChange'} }"
width="100%">
<!-- Insert RichTextEditor by JS-->
</VBox>
</smartForm:GroupElement>
/**
* Event is fired when the data binding happen on RichTextEditor for object id
* #public
* #param {sap.ui.base.Event} oEvent pattern match event of data changed
*/
onBindingObjectChange: function (oEvent) {
var oBox = this.getView().byId("RichTextEditorContainer");
if (this._oRTXE && this._oRTXE.data("objectId") === oBox.data("objectId")) {
return;
} else if(this._oRTXE) {
this._oRTXE.destroy();
}
oBox.removeAllItems();
this._oRTXE = new RichTextEditor({
value: "{viewModel>/Detail}",
editable: true,
height: "120px",
width: "100%",
wrapping: false,
editorType: "TinyMCE4",
showGroupClipboard: false,
showGroupFontStyle: false,
showGroupStructure: false,
showGroupTextAlign: false,
change: this.onTextChanged.bind(this)
}).attachBrowserEvent("focusin", () => {this._oRTXE.bHasFocus = true;})
.attachBrowserEvent("focusout", () => {this._oRTXE.bHasFocus = false; this._checkWaitingChanges();});
this._oRTXE.data("objectId", oBox.data("objectId"));
oBox.insertItem(this._oRTXE);
},
/**
* Event is fired when the data binding happen on RichTextEditor for text
* #public
* #param {sap.ui.base.Event} oEvent pattern match event of data changed
*/
onBindingTextChange: function (oEvent) {
var oBox = this.getView().byId("RichTextEditorContainer");
this.getModel("viewModel").setProperty("/Detail", oBox.data("detail"));
},
/**
* Event is fired when the text changed on RichTextEditor by user
* #public
* #param {sap.ui.base.Event} oEvent pattern match event of text changed
*/
onTextChanged: function (oEvent) {
this.getModel("viewModel").setProperty("/LastDetail", oEvent.getParameter("newValue"));
this._oRTXE.bWaitingChanges = true;
if(this._oRTXE.bHasFocus === false){
this._oRTXE.bWaitingChanges = false;
var sNewValue = oEvent.getParameter("newValue"),
oBox = this.getView().byId("RichTextEditorContainer"),
oContext = oBox.getBindingContext(),
oModel = oContext.getModel(),
sBindingPath = oContext.getPath() + "/Detail";
oModel.setProperty(sBindingPath, sNewValue);
}
},
// Just checks if there is any changes that has not yet been written in the odata model
_checkWaitingChanges: function(){
if(this._oRTXE.bWaitingChanges === true){
this._oRTXE.bWaitingChanges = false;
var sNewValue = this.getModel("viewModel").getProperty("/LastDetail"),
oBox = this.getView().byId("RichTextEditorContainer"),
oContext = oBox.getBindingContext(),
oModel = oContext.getModel(),
sBindingPath = oContext.getPath() + "/Detail";
oModel.setProperty(sBindingPath, sNewValue);
}
}
We have a target area (target and targetparsys) with component which is a container (parsys) with some components. The problem is that when this container is situated in target area, parsys of container becomes a targetparsys inside of which there isn't a possibility to edit inside components. Does this issue has the solution?
I've tried to overlay the logic of creating the overlay in overlayManager.js. It has solved a little bit the problem, but there isn't a proper work of reordering components inside of it.
I overlayed the following file - /cq/gui/components/authoring/editors/clientlibs/core/js/overlayManager.js by changing and adding the following code:
self.create = function (editable) {
if (!editable.overlay) {
var parent = ns.editables.getParent(editable);
// going up recursively until we reach the root container
if (parent && !parent.overlay) {
self.create(parent);
}
// we check again because a child overlay might also be created by the parent constructor
if (!editable.overlay) {
editable.overlay = new overlayConstructor(editable, parent ? parent.overlay.dom : container);
}
// get children sorted by depth of their paths
var sortedChildren = getSortedChildren(editable, true);
//and going down recursively until we reach the deepest editable
sortedChildren.forEach(function (child) {
self.create(child);
});
}
};
/**
* Returns the sorted {Array} of child {#link Granite.author.Editable}s by depth of their paths for the given {#link Granite.author.Editable}
*
* #param {Granite.author.Editable} editable - {#link Granite.author.Editable} for which to find child editables to be sorted by depth of their paths
* #param {Boolean} all - All the children or only the direct descendant
*
* WARNING! Not OTB function! See the description on {self.create = function(editable)}
*/
function getSortedChildren(editable, all){
var children = ns.editables.getChildren(editable, all);
//key - editable, value - number of slashes
var childrenMap = new Map();
//going through all children
for (let i = 0; i < children.length; i++){
var path = children[i].path;
var numberOfSlashes = 1;
var isFirstSlashChecked = false;
//searching slashes
for (let j = 0; j < path.length; j++){
var letter = path[j];
var SLASH = '/';
if (letter == SLASH){
if (isFirstSlashChecked){
childrenMap.set(children[i], ++numberOfSlashes);
} else {
childrenMap.set(children[i], numberOfSlashes);
isFirstSlashChecked = true;
}
}
}
//if there are not slashes in editable path
if (!isFirstSlashChecked){
childrenMap.set(children[i], 0);
}
}
//sort map by depth (number of slashes)
var sortedChildrenMapByPaths = new Map([...childrenMap.entries()].sort((a, b) => a[1] - b[1]));
//return sorted editables
return Array.from(sortedChildrenMapByPaths.keys());
}
And also added checking null statement here:
function repositionOverlay(editable) {
var parent;
// if there is no overlay in place, don't bother we ignore it (most likely timing issue)
if (editable.overlay) {
parent = ns.editables.getParent(editable);
//the place which was overlaid
// don't rely on order of editables in the store...
if (parent && parent.overlay != null && !parent.overlay.currentPos) {
repositionOverlay(parent);
}
editable.overlay.position(editable, parent);
}
}
Trying to improve my Coffeescript skills, so thought I'd covert this jcrop demo. However it's not working as expect. Specifically, the redraw function does not appear to be being called.
This works fine.
// Create a new Selection object extended from Selection
var CircleSel = function(){ };
// Set the custom selection's prototype object to be an instance
// of the built-in Selection object
CircleSel.prototype = new $.Jcrop.component.Selection();
// Then we can continue extending it
$.extend(CircleSel.prototype,{
zoomscale: 1,
attach: function(){
this.frame.css({
background: 'url(' + $('#target')[0].src.replace('750','750') + ')'
});
},
positionBg: function(b){
var midx = ( b.x + b.x2 ) / 2;
var midy = ( b.y + b.y2 ) / 2;
var ox = (-midx*this.zoomscale)+(b.w/2);
var oy = (-midy*this.zoomscale)+(b.h/2);
//this.frame.css({ backgroundPosition: ox+'px '+oy+'px' });
this.frame.css({ backgroundPosition: -(b.x+1)+'px '+(-b.y-1)+'px' });
},
redraw: function(b){
// Call original update() method first, with arguments
$.Jcrop.component.Selection.prototype.redraw.call(this,b);
this.positionBg(this.last);
return this;
},
prototype: $.Jcrop.component.Selection.prototype
});
But when I try to write this in Coffescript it fails
CircleSel.prototype = new ($.Jcrop.component.Selection)
$.extend CircleSel.prototype,
zoomscale: 1
attach: ->
#frame.css background: 'url(' + $('#target')[0].src.replace('750', '750') + ')'
return
positionBg: (b) ->
midx = (b.x + b.x2) / 2
midy = (b.y + b.y2) / 2
ox = -midx * #zoomscale + b.w / 2
oy = -midy * #zoomscale + b.h / 2
#frame.css backgroundPosition: -(b.x + 1) + 'px ' + -b.y - 1 + 'px'
return
# this redraw function is not being called, everything else appears to work fine
redraw: (b) ->
$.Jcrop.component.Selection::redraw.call this, b
#positionBg #last
this
prototype: $.Jcrop.component.Selection.prototype
What have I done wrong?
I added "required" as "true" but it is not working. "required" as "true" only works for text field.
As per below document, I do not see any option to add mandatory field from dropdown.
http://docs.adobe.com/docs/en/aem/6-0/author/assets/managing-assets-touch-ui/managing-asset-schema-forms.html
How is it possible to achieve this?
Use $.validator.register to register custom validators.
I have written a detailed blog post on writing custom validators: http://www.nateyolles.com/blog/2016/02/aem-touch-ui-custom-validation.
I have made a comprehensive Touch UI validation library available on GitHub that fixes the issue you described where the "required" property doesn't work for several Granite UI fields as well as other functionality. See https://github.com/nateyolles/aem-touch-ui-validation.
Essentially, you need to modify the field's HTML to include an HTML input that can be validated by either overlaying the foundation component or using JavaScript to modify the DOM when the dialog opens. A hidden input is not eligible for validation, so you need to add a text input hidden by CSS. Use JavaScript to update the "hidden" field when the component action is updated. For example, a color is chosen in the color picker.
Then you register the custom validator against the non-visible text input. Pass in the selector of the non-visible text field and the function that does the actual validation. Also pass in functions for show and clear that show and hide the error message/icon.
The following example is for the color picker taken from the library I linked to above:
/**
* Validation for Granite Touch UI colorpicker.
*
* Additional properties for granite/ui/components/foundation/form/colorpicker
* are:
*
* {Boolean}required
* Is field required
* defaults to false
*
* <myColorPicker
* jcr:primaryType="nt:unstructured"
* sling:resourceType="granite/ui/components/foundation/form/colorpicker"
* fieldLabel="My colorpicker"
* name="./myColorPicker"
* required="{Boolean}true"/>
*/
var COLORPICKER_SELECTOR = '.coral-ColorPicker',
$.validator.register({
selector: '.marker-colorpicker',
validate: function(el) {
var field,
value,
required;
field = el.closest(".coral-Form-field");
value = el.val();
required = field.data('required');
if (required && !value) {
return Granite.I18n.get('Please fill out this field.');
} else {
el.setCustomValidity(null);
el.updateErrorUI();
}
},
show: function (el, message) {
var fieldErrorEl,
field,
error,
arrow;
fieldErrorEl = $("<span class='coral-Form-fielderror coral-Icon coral-Icon--alert coral-Icon--sizeS' data-init='quicktip' data-quicktip-type='error' />");
field = el.closest('.coral-Form-field');
el.add(field)
.attr('aria-invalid', 'true')
.toggleClass('is-invalid', true);
field.nextAll('.coral-Form-fieldinfo')
.addClass('u-coral-screenReaderOnly');
error = field.nextAll('.coral-Form-fielderror');
if (error.length === 0) {
arrow = field.closest('form').hasClass('coral-Form--vertical') ? 'right' : 'top';
fieldErrorEl.clone()
.attr('data-quicktip-arrow', arrow)
.attr('data-quicktip-content', message)
.insertAfter(field);
} else {
error.data('quicktipContent', message);
}
},
clear: function(el) {
var field = el.closest('.coral-Form-field');
el.add(field)
.removeAttr('aria-invalid')
.removeClass('is-invalid');
field.nextAll('.coral-Form-fielderror').tooltip('hide').remove();
field.nextAll('.coral-Form-fieldinfo').removeClass('u-coral-screenReaderOnly');
}
});
/**
* Create hidden field to validate against and click event handler when a
* Granite UI dialog loads.
*/
$(document).on('foundation-contentloaded', function(e) {
var $dialog,
$radioGroups;
$dialog = $(e.target);
$radioGroups = $dialog.find(COLORPICKER_SELECTOR);
$radioGroups.each(function() {
var $radioGroup,
required,
$marker,
$button;
$radioGroup = $(this);
required = $radioGroup.data('required');
if (required) {
$marker = $radioGroup.find('input[type="hidden"]');
$button = $radioGroup.find('.coral-ColorPicker-button')
/* Change to text as hidden is not validated */
$marker.attr('type', 'text');
$marker.addClass('marker-colorpicker');
$marker.attr('aria-required', 'true');
/* revalidate once the button color has changed */
$button.on('stylechange', function(){
$marker.trigger('change');
});
}
});
});
AFAIK, In touch ui dialogs you can apply such validation via jquery. One thing you can try. Create a clientlib folder under component with categories cq.authoring.dialog . Then add the below js snippet as per normal process :
(function (document, $, ns) {
"use strict";
$(document).on("click", ".cq-dialog-submit", function (e) {
e.stopPropagation();
e.preventDefault();
var $form = $(this).closest("form.foundation-form"),
title = $form.find("[name='authoringMode']").val(),
message, clazz = "coral-Button ";
if(!title){
ns.ui.helpers.prompt({
title: Granite.I18n.get("Invalid Input"),
message: "Please Check Values",
actions: [{
id: "CANCEL",
text: "CANCEL",
className: "coral-Button"
}
],
callback: function (actionId) {
if (actionId === "CANCEL") {
}
}
});
}else{
$form.submit();
}
});
})(document, Granite.$, Granite.author);
One thing here you need to change is $form.find("[name='authoringMode']") here name is the property and authoringMode is the value of select box in my dialog. as shown.
Here it will check at dialog submit time whether there is value in drop down and will not let author to submit the dialog till drop-down is blank.
Here is the reference.
http://experience-aem.blogspot.in/2015/02/aem-6-sp2-touch-ui-dialog-before-submit.html
this is my code
Info.newInfoPopUp = function (obj) {
var table = createInfo(obj);
popup = new OpenLayers.Popup.FramedCloud(
obj.name,
obj.lonlat,
new OpenLayers.Size(),
'<table class="info-table popup">' + table.html() + '</table>',
null,
true,
obj.closeCallback
);
popup.minSize =new OpenLayers.Size(300,155);
popup.maxSize = new OpenLayers.Size(800,200);
return popup;
};
the popup appears sometimes whith a vertical scroll-bar
any ideas?
The OpenLayers.Size class constructor should have parameters width and height.
Try this:
var popup = new OpenLayers.Popup.FramedCloud(
obj.name,
obj.lonlat,
new OpenLayers.Size(100,100),
tablHtml,
null,
true,
obj.closeCallback
);