Rendering <style> inside Component with Renderer (Angular 7) - dom

I have issue with rendering inside component. I would like to emit data from another component and send to another component, data was emitted, but the problem is when I create the element with Renderer2, sometimes it's working, but sometimes not. Probably it's a problem with rendering style element in a component?
toolbar.state.service.ts
My service method for emitting data
private globalStyles = new BehaviorSubject<string>('');
formDesign(data: any) {
this.globalStyles.next(data);
}
aside.component.ts
Here I emit data from Reactive Form control and send to another component.
// Height
this.formGlobal.controls['height'].valueChanges
.pipe(debounceTime(500))
.pipe(distinctUntilChanged())
.pipe(takeUntil(this.destroy$))
.subscribe(height => {
this.formGlobal.controls['height'].setValue(height);
this.formGlobal.updateValueAndValidity();
this.showDataCriteria = {
width: this.formGlobal.controls['width'].value + 'px',
height: height + 'px'
};
this.toolbarStatus.formDesign(this.showDataCriteria);
});
builder.component.ts
Here I'm getting data from aside.component.ts and it received!
/**
* Generate CSS
*/
generateCss() {
let basicStyles = ' ';
let newStyle: HTMLElement;
let style: HTMLElement = this.document.getElementById('custom-class');
style
? (newStyle = style)
: (newStyle = this.renderer.createElement('style'));
this.renderer.setAttribute(newStyle, 'id', 'custom-class');
let completeStyleFields = '';
this.customStyle.global
? (basicStyles += `#${this.projectInfo.id} {${this.customStyle.global}}`)
: (basicStyles += '');
console.log(basicStyles);
this.customStyle.sections.forEach(element => {
completeStyleFields += `#${element.id} {${element.textProps}}`;
});
basicStyles += completeStyleFields;
const text = (this.document.textContent = basicStyles);
newStyle.innerText = text;
this.renderer.appendChild(this.dndComponent.nativeElement, newStyle);
}
The Main problem is after style element was created, and I'm seeing the element in the DOM, styles not accepting! Sometimes accepting, and sometimes not. What should I do? How manipulate reload page probably to inject component and styles element?
Short UPD:
After all, I'm seeing #4152ae54-8a9d-49d5-a33d-62dfbbd35890 {height:600px; width:812px; }
But styles not accepted to the elements!

CSS can't render if the first numeric letter (#4152ae54-8a9d-49d5-a33d-62dfbbd35890). That’s because even though HTML5 is quite happy for an ID to start with a number, CSS is not. CSS simply doesn’t allow selectors to begin with a number. The relevant part of the specification states.

Related

Can I set an ag-grid full-width row to have autoHeight?

I am trying to render a set of footnotes at the end of my data set. Each footnote should be a full-width row. On the docs page for row height, it says that you can set an autoHeight property for the column you want to use to set the height. Full-width rows, however, aren't tied to any column, so I don't think there's a place to set that autoHeight property.
For reference, here is my cell renderer, which gets invoked if a flag in the data object is true.
import { Component } from '#angular/core';
import { ICellRendererComp, ICellRendererParams } from '#ag-grid-community/core';
#Component({
template: '',
})
export class FootnoteRendererComponent implements ICellRendererComp {
cellContent: HTMLElement;
init?(params: ICellRendererParams): void {
this.cellContent = document.createElement('div');
this.cellContent.innerHTML = params.data.title;
this.cellContent.setAttribute('class', 'footnote');
}
getGui(): HTMLElement {
return this.cellContent;
}
refresh(): boolean {
return false;
}
}
The footnote (the "title" property above) could be one line or several depending on its length and the browser's window size. There may also be several footnotes. Is there a way to set autoHeight for each footnote row? Thanks for any help!
Not sure of CSS autoHeight can be use, but here is some example for calculating height dynamically. Take a look to getRowHeight function, it's works for any rows (full-width too):
public getRowHeight: (
params: RowHeightParams
) => number | undefined | null = function (params) {
if (params.node && params.node.detail) {
var offset = 80;
var allDetailRowHeight =
params.data.callRecords.length *
params.api.getSizesForCurrentTheme().rowHeight;
var gridSizes = params.api.getSizesForCurrentTheme();
return (
allDetailRowHeight +
((gridSizes && gridSizes.headerHeight) || 0) +
offset
);
}
};
Here is the solution I ended up with, though I like #LennyLip's answer as well. It uses some ideas from Text Wrapping in ag-Grid Column Headers & Cells.
There were two parts to the problem - 1) calculating the height, and 2) knowing when to calculate the height.
1) Calculating the Height
I updated the footnote's Cell Renderer to add an ID to each footnote text node, and used it in the function below.
const footnoteRowHeightSetter = function(params): void {
const footnoteCells = document.querySelectorAll('.footnote .footnote-text');
const footnoteRowNodes = [];
params.api.forEachNode(row => {
if (row.data.dataType === 'footnote') { // Test to see if it's a footnote
footnoteRowNodes.push(row);
}
});
if (footnoteCells.length > 0 && footnoteRowNodes.length > 0) {
footnoteRowNodes.forEach(rowNode => {
const cellId = 'footnote_' + rowNode.data.id;
const cell = _.find(footnoteCells, node => node.id === cellId);
const height = cell.clientHeight;
rowNode.setRowHeight(height);
});
params.api.onRowHeightChanged();
}
};
To summarize, the function gets all HTML nodes in the DOM that are footnote text nodes. It then gets all of the table's row nodes that are footnotes. It goes through those row nodes, matching each up with its DOM text. It uses the clientHeight property of the text node and sets the row node height to that value. Finally, it calls the api.onRowHeightChanged() function to let the table know it should reposition and draw the rows.
Knowing when to calculate the height
When I set the gridOptions.getRowHeight property to the function above, it didn't work. When the function fires, the footnote rows hadn't yet been rendered, so it was unable to get the clientHeight for the text nodes since they didn't exist.
Instead, I triggered the function using these event handlers in gridOptions.
onFirstDataRendered: footnoteRowHeightSetter,
onBodyScrollEnd: footnoteRowHeightSetter,
onGridSizeChanged: footnoteRowHeightSetter,
onFirstDataRendered covers the case where footnotes are on screen when the grid first renders (short table).
onBodyScrollEnd covers the case where footnotes aren't on screen at first but the user scrolls to see them.
onGridSizeChanged covers the case of grid resizing that alters the wrapping and height of the footnote text.
This is what worked for me. I like #LennyLip's answer and looking more into it before I select an answer.

How add mandatory dropdown field in Touch UI

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

How to create a Multinline textbox growing dynamically without scrollbars?

I need to use a text-box (multiline) in my application. It need to grow as the user inputs text into it. I want that textbox to grow without any scrollbars. I tried allow=resize:null but it just prevents textbox from being stretched.All i want is a textbox as we see in facebook comment /share area which grows simply without any scrollbars on sides. I hope i have neatly explained it.
You can use something like this: (Source: Expandable or Auto-Resize TextBox Height by Colt Kwong)
<asp:TextBox ID="txtMsg" runat="server" TextMode="MultiLine"
style="overflow:hidden" onkeyup="AutoExpand(this, event)" Rows="2" />
using this javascript:
function AutoExpand(txtBox, event)
{
if (event.keyCode == "13" || event.keyCode == "8") {
var therows = 0
var thetext = document.getElementById(txtBox.id).value;
var newtext = thetext.split("\n");
therows += newtext.length
document.getElementById(txtBox.id).rows = therows;
return false;
}
}
Or if you like jquery you should take a look at the jQuery autoResize Plugin.
A plugin for jQuery which changes the dimensions of input elements to
suit the amount of data entered. It operates on textarea,
input[type=text] and input[type=password] elements.
Usage is as follows:
$('textarea#foo').autoResize();
You can pass options:
$('textarea#foo').autoResize({
maxHeight: 200,
minHeight: 100
});

Drag from Tree to div

I am trying to implement a drag and drop senario from an extJs TreePanel into a div in the body of the page. I have been following an example by Saki here.
So far I have the below code:
var contentAreas = new Array();
var tree = new Ext.tree.TreePanel({
title : 'Widgets',
useArrows: true,
autoScroll: true,
animate: true,
enableDrag: true,
border: false,
layout:'fit',
ddGroup:'t2div',
loader:new Ext.tree.TreeLoader(),
root:new Ext.tree.AsyncTreeNode({
expanded:true,
leaf:false,
text:'Tree Root',
children:children
}),
listeners:{
startdrag:function() {
$('.content-area').css("outline", "5px solid #FFE767");
},
enddrag:function() {
$('.content-area').css("outline", "0");
}
}
});
var areaDivs = Ext.select('.content-area', true);
Ext.each(areaDivs, function(el) {
var dd = new Ext.dd.DropTarget(el, {
ddGroup:'t2div',
notifyDrop:function(ddt, e, node) {
alert('Drop');
return true;
}
});
contentAreas[contentAreas.length] = dd;
});
The drag begins and the div highlights but when I get over the div it does not show as a valid drop target and the drop fails.
This is my first foray into extJS. I'm JQuery through and through and I am struggling at the moment.
Any help would be appreciated.
Ian
Edit
Furthermore if I create a panel with a drop target in it, this works fine. What is the difference between creating an element and selecting an existing element from the dom. This is obviously where I am going wrong but I'm none the wiser. I have to be able to select existing dom elements and make them into drop targets so the code below is not an option.
Here is the drop target that works
var target = new Ext.Panel({
renderTo: document.body
,layout:'fit'
,id:'target'
,bodyStyle:'font-size:13px'
,title:'Drop Target'
,html:'<div class="drop-target" '
+'style="border:1px silver solid;margin:20px;padding:8px;height:140px">'
+'Drop a node here. I\'m the DropTarget.</div>'
// setup drop target after we're rendered
,afterRender:function() {
Ext.Panel.prototype.afterRender.apply(this, arguments);
this.dropTarget = this.body.child('div.drop-target');
var dd = new Ext.dd.DropTarget(this.dropTarget, {
// must be same as for tree
ddGroup:'t2div'
// what to do when user drops a node here
,notifyDrop:function(dd, e, node) {
alert('drop');
return true;
} // eo function notifyDrop
});
}
});
See if adding true as the second param here makes any difference:
var areaDivs = Ext.select('.content-area', true);
As a cosmetic note, the param name e conventionally indicates an event object (as in the second arg of notifyDrop). For an element, el is more typical. Doesn't matter functionally, but looks weird to someone used to Ext code to see e passed into the DropTarget constructor.
If you are having problem duplicating a working example such as that, copy the entire thing, then modify it to your needs line-by-line - you can't go wrong.
As i know you can't set DropZone to any Ext element, just to Ext component. So this might be you problem. Try to use DropTarget instead of DropZone.

can I build a css class on the fly in tiny mce?

I'm using tiny mce, but I found it adds multiple spans with inline styles to the content for any applied style. Inline styles are not W3c Compliant, so must avoid inline css. Is it possible to create css class on the fly and apply to the selection, while editing content in tiny mce ?
Yes that is possible, but it took me some effort. What needs to be done is to write the class into the head of the editors iframe. Here is some example code which should work for IE,FF, Safari and point you into the right direction:
fonturl = "http://myfonts.com/arial.ttf"
csstext_to_add = '#font-face {font-family: "ownfont";src: url("'+fonturl+'");}'; // example
iframe_id = ed.id;
with(document.getElementById(iframe_id).contentWindow){
var h=document.getElementsByTagName("head");
if (!h.length) {
return;
}
var newStyleSheet=document.createElement("style");
newStyleSheet.type="text/css";
h[0].appendChild(newStyleSheet);
try{
if (typeof newStyleSheet.styleSheet !== "undefined") {
newStyleSheet.styleSheet.cssText = csstext_to_add;
}
else {
newStyleSheet.appendChild(document.createTextNode(csstext_to_add));
newStyleSheet.innerHTML=csstext_to_add;
}
}
catch(e){}
}
It is also possible to add that class as option into a dropdown (what takes some effort).
Thariama's answer was perfect. I'm using the tinyMCE jQuery connector for some of my pages and I have multiple instances of tinyMCE on the page. I made some modifications, but essentially its the same thing. I've created a text area field on the page that people can provide their own CSS. Also, I needed to change some CSS rules on the fly...
// function to change tinyMCE css on the fly
function checkCustomCSS() {
var $css = $('#page_css'),
newcss;
if ($css.val().length > 0) {
// since front end, we are wrapping their HTML in a wrapper and
// the tinyMCE edit iFrame is just using <body>, we need to change
// some rules so they can see the changes
newcss = $css.val().replace('#content_wrapper', 'body');
// loop through each tinyMCE editor and apply the code changes
// You could check the editor.id to make sure that the correct
// editor gets the appropriate changes.
$.each(tinyMCE.editors, function() {
var $this = $(this),
editorID = $this[0].id,
$ifrm = $('#' + editorID+ '_ifr'),
cwin, head, sheet;
if ($ifrm.length > 0 /* && editorID === 'OUR_EDITOR_ID_NAME' */) {
cwin = $ifrm[0].contentWindow;
head = cwin.document.getElementsByTagName("head");
if (!head.length) {
return;
}
sheet = cwin.document.createElement("style");
sheet.type = "text/css";
head[0].appendChild(sheet);
try {
if (typeof sheet.styleSheet !== "undefined") {
sheet.styleSheet.cssText = newcss;
} else {
sheet.appendChild(cwin.document.createTextNode(newcss));
sheet.innerHTML = newcss;
}
} catch (e) {}
}
});
}
}
Then in the tinyMCE init call I added and onInit call to setup changes to the #page_css , like this:
oninit: function() {
$('#page_css').on('change', function() {
checkCustomCSS();
});
}
Works like a charm.