diff --git a/to.etc.domui/src/main/java/to/etc/domui/dom/Animations.java b/to.etc.domui/src/main/java/to/etc/domui/dom/Animations.java index f5a71d27c..bfef5fe34 100644 --- a/to.etc.domui/src/main/java/to/etc/domui/dom/Animations.java +++ b/to.etc.domui/src/main/java/to/etc/domui/dom/Animations.java @@ -48,14 +48,14 @@ public NodeFixer(NodeBase node, DisplayType newDisplay) { * Since after the slideDown the node in the browser will have its default display this is also the state * in DomUI's node. * - * @param node + * @param node target node */ static public void slideDown(NodeBase node) { node.getPage().addAfterRenderListener(new NodeFixer(node, node.getDisplay())); node.setDisplay(DisplayType.NONE); node.appendStatement() .select(node) - .method("slideDown").end() + .method("doSlideDown").end() .endmethod() .next(); } @@ -75,7 +75,7 @@ static public void slideUp(NodeBase node, String jsCallback) { node.getPage().addAfterRenderListener(new NodeFixer(node, DisplayType.NONE)); node.appendStatement() .select(node) - .append(".slideUp(function() { " + jsCallback + " })") + .append(".doSlideUp(function() { " + jsCallback + " })") .next(); } @@ -104,6 +104,7 @@ static public void bounce(NodeBase node) { /** * Does pulsate effect. + * * @param node target node * @param times if 0 it uses default behavior for pulsate. */ diff --git a/to.etc.domui/src/main/java/to/etc/domui/dom/HtmlTagRenderer.java b/to.etc.domui/src/main/java/to/etc/domui/dom/HtmlTagRenderer.java index 78da7064d..75a3b9ff2 100644 --- a/to.etc.domui/src/main/java/to/etc/domui/dom/HtmlTagRenderer.java +++ b/to.etc.domui/src/main/java/to/etc/domui/dom/HtmlTagRenderer.java @@ -81,7 +81,12 @@ import to.etc.util.StringTool; import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static to.etc.util.StringTool.isBlank; /** * Basic, mostly standard-compliant handler for rendering HTML tags. @@ -90,6 +95,15 @@ * Created on Aug 17, 2007 */ public class HtmlTagRenderer implements INodeVisitor { + + /** Replacing specific style attributes with build-in classes -> optimization when we render styles attributes as jquery js code for CSP support */ + private static final Map STYLE_ATTRIBUTES_TO_CLASSES = Map.of( + "display:none", "ui-display-none", // + "display:inline", "ui-display-inline", // + "display:block", "ui-display-block", // + "display:flex", "ui-display-flex" // + ); + /** Scratch stringbuffer. */ private StringBuilder m_sb; @@ -615,7 +629,7 @@ static private String border(final StringBuilder a, final int w, final String s, } static private void renderBorderIf(@NonNull Appendable a, @Nullable String border) throws IOException { - if(StringTool.isBlank(border)) + if(isBlank(border)) return; a.append("border:").append(border).append(";"); } @@ -678,8 +692,26 @@ public void basicNodeRender(final NodeBase b, final IBrowserOutput o, boolean in } String s = getStyleFor(b); // Get/recalculate style + + //collect replacing classes for specified style attributes + List classesFromStyleAttributes = new ArrayList<>(); if(!s.isEmpty() || b.isStyleRendered()) { - o.attr("style", s); + StringBuilder styleSb = new StringBuilder(); + String[] styles = s.split(";"); + for(int i = 0; i < styles.length; i++) { + String stylePart = styles[i]; + if(!isBlank(stylePart)) { + String aClass = STYLE_ATTRIBUTES_TO_CLASSES.get(stylePart); + if(null != aClass) { + classesFromStyleAttributes.add(aClass); + } else { + styleSb.append(stylePart).append(";"); + } + } + } + if(styleSb.length() > 0) { + o.attr("style", styleSb.toString()); + } if(s.isEmpty()) { b.clearStyleRendered(); // if we rendered the empty style we can remove it next time } else { @@ -721,8 +753,20 @@ public void basicNodeRender(final NodeBase b, final IBrowserOutput o, boolean in if(b.isStretchHeight()) o.attr("stretch", "true"); - if(b.getCssClass() != null) - o.attr("class", b.getCssClass()); + + //append collected replacing classes too to existing classes of an element + String cssClass = b.getCssClass(); + if(cssClass != null || !classesFromStyleAttributes.isEmpty()) { + if(!classesFromStyleAttributes.isEmpty()) { + String appendedClasses = classesFromStyleAttributes.stream().collect(Collectors.joining(" ")); + if(null == cssClass) { + cssClass = appendedClasses; + }else { + cssClass = cssClass + " " + appendedClasses; + } + } + o.attr("class", cssClass); + } List sal = b.getSpecialAttributeList(); if(sal != null) { @@ -1076,11 +1120,11 @@ public void visitInput(final Input n) throws Exception { o().attr("onblur", sb().append(transformScript).append("WebUI.hideLookupTypingPopup('").append(n.getActualID()).append("')").toString()); } else { //-- Attach normal onKeyPress handling. - if(!StringTool.isBlank(n.getOnKeyPressJS())) { + if(!isBlank(n.getOnKeyPressJS())) { o().attr("onkeypress", "return " + n.getOnKeyPressJS()); } - if(!StringTool.isBlank(transformScript)) { + if(!isBlank(transformScript)) { o().attr("onblur", sb().append(transformScript).toString()); } } diff --git a/to.etc.domui/src/main/resources/resources/themes/scss/winter/_core.scss b/to.etc.domui/src/main/resources/resources/themes/scss/winter/_core.scss index d97bc6879..3e798ad42 100644 --- a/to.etc.domui/src/main/resources/resources/themes/scss/winter/_core.scss +++ b/to.etc.domui/src/main/resources/resources/themes/scss/winter/_core.scss @@ -209,3 +209,19 @@ i, em { text-align: left; white-space: nowrap; } + +.ui-display-none { + display: none !important; +} + +.ui-display-inline { + display: inline !important; +} + +.ui-display-block { + display: block !important; +} + +.ui-display-flex { + display: flex !important; +} diff --git a/to.etc.domui/src/main/resources/resources/ts/domui.comp.ts b/to.etc.domui/src/main/resources/resources/ts/domui.comp.ts index a1f4761f9..b0638026d 100644 --- a/to.etc.domui/src/main/resources/resources/ts/domui.comp.ts +++ b/to.etc.domui/src/main/resources/resources/ts/domui.comp.ts @@ -393,8 +393,54 @@ namespace WebUI { WebUI.valuechanged('eh', id); } + export function fixDisplayClass(elem): void { + let style = $(elem).attr("style"); + if(null == style) { + return; + } + if(style.includes("display: none;")) { + style = style + .replace("display: none;", "") + $(elem).attr("style", style); + $(elem).removeClass("ui-display-block"); + $(elem).removeClass("ui-display-flex"); + $(elem).removeClass("ui-display-inline"); + $(elem).addClass("ui-display-none"); + } + if(style.includes("display: block;")) { + style = style + .replace("display: block;", "") + $(elem).attr("style", style); + $(elem).removeClass("ui-display-none"); + $(elem).removeClass("ui-display-flex"); + $(elem).removeClass("ui-display-inline"); + $(elem).addClass("ui-display-block"); + } + if(style.includes("display: inline;")) { + style = style + .replace("display: inline;", "") + $(elem).attr("style", style); + $(elem).removeClass("ui-display-none"); + $(elem).removeClass("ui-display-block"); + $(elem).removeClass("ui-display-flex"); + $(elem).addClass("ui-display-inline"); + } + if(style.includes("display: flex;")) { + style = style + .replace("display: flex;", "") + $(elem).attr("style", style); + $(elem).removeClass("ui-display-none"); + $(elem).removeClass("ui-display-block"); + $(elem).removeClass("ui-display-inline"); + $(elem).addClass("ui-display-flex"); + } + } + export function flare(id): void { + $('#' + id).css('display', 'none'); + $('#' + id).removeClass("ui-display-none"); $('#' + id).fadeIn('fast', function() { + WebUI.fixDisplayClass($('#' + id)); $('#' + id).delay(500).fadeOut(1000, function() { $('#' + id).remove(); }); @@ -402,7 +448,10 @@ namespace WebUI { } export function flareStay(id): void { + $('#' + id).css('display', 'none'); + $('#' + id).removeClass("ui-display-none"); $('#' + id).fadeIn('fast', function() { + WebUI.fixDisplayClass($('#' + id)); $('body,html').bind('mousemove.' + id, function(e) { $('body,html').unbind('mousemove.' + id); $('#' + id).delay(500).fadeOut(1000, function() { @@ -413,7 +462,10 @@ namespace WebUI { } export function flareStayCustom(id, delay, fadeOut): void { + $('#' + id).css('display', 'none'); + $('#' + id).removeClass("ui-display-none"); $('#' + id).fadeIn('fast', function() { + WebUI.fixDisplayClass($('#' + id)); $('body,html').bind('mousemove.' + id, function(e) { $('body,html').unbind('mousemove.' + id); $('#' + id).delay(delay).fadeOut(fadeOut, function() { diff --git a/to.etc.domui/src/main/resources/resources/ts/domui.jquery.ts b/to.etc.domui/src/main/resources/resources/ts/domui.jquery.ts index 85e04c338..21476b485 100644 --- a/to.etc.domui/src/main/resources/resources/ts/domui.jquery.ts +++ b/to.etc.domui/src/main/resources/resources/ts/domui.jquery.ts @@ -153,6 +153,37 @@ $.fn.extend({ }); $(this).markerTransformed = true; }); + }, + + doSlideUp: function(callback) { + let fixDisplayCallback = function(elem) { + WebUI.fixDisplayClass(elem); + } + + let myOnSlideUp = fixDisplayCallback; + if(null != callback) { + myOnSlideUp = function(elem) { + callback(); + fixDisplayCallback(elem); + } + } + this.slideUp(myOnSlideUp(this)); + }, + + doSlideDown: function(callback) { + let fixDisplayCallback = function(elem) { + WebUI.fixDisplayClass(elem); + } + + let myOnSlideDown = fixDisplayCallback; + if(null != callback) { + myOnSlideDown = function(elem) { + callback(); + fixDisplayCallback(elem); + } + } + this.slideDown(myOnSlideDown(this)); } + });