How can I reflect to attribute but not trigger a rerender in lit-element? - web-component

I am converting a custom element dropdown over to lit-element. The way the existing element shows the dropdown options is by setting an expanded boolean attribute on the element, and the options are shown/hidden via css:
my-element:not([expanded]) .options-container {
display: none;
}
my-element[expanded] .options-container {
display: block;
}
The component doesn't need to do any rerenders because the logic is all in the css.
How can I achieve this behavior with lit-element, and not rerender the component? Rerendering can be costly if there are a lot of dropdown options.
I have tried implementing a shouldUpdate that returns false if only expanded has changed - but this causes lit-element not to reflect expanded to the attribute when set via a property, which is necessary in order to show/hide via css.
This is what I have, which doesn't work:
class MyDropdown extends LitElement {
static get properties() {
return {
expanded: { type: Boolean, reflect: true },
...
};
}
shouldUpdate(changedProperties) {
if (changedProperties.has('expanded') && changedProperties.size === 1) {
return false;
}
return true;
}
// disable shadow-dom
createRenderRoot() {
return this;
}
}
Note that I am not using shadow dom yet, not sure if that would change the solution. I'm on lit-element 2.2.1.

The idea is to not use LitElement's static properties or #Property decorator. Write your own property implementation like this:
class MyDropdown extends LitElement {
_expanded = false;
get expanded() {
return this._expanded;
}
set expanded(val) {
this._expanded = val;
// Manually setting the property and reflecting attribute.
if (val) {
this.setAttribute('expanded', '');
} else {
this.removeAttribute('expanded');
}
}
// disable shadow-dom
createRenderRoot() {
return this;
}
}
Similarly, you can listen for attributeChangedCallback lifecycle event and adjust _expanded property whenever user changes the attribute and not property.

Related

Conditionally enable/disable fields in AEM 6.1 (granite.ui) TouchUI dialogs

Does anyone have any experience with conditionally disabling fields based on value of a previous field in an AEM6.1 TouchUI dialog?
To give some context I have a checkbox in my TouchUI dialog used to enable/disable (hide/show) a Call To Action button within a component. I'd like to disable the CTA buttonText and href fields in the dialog itself where the author has disabled the CTA via the checkbox. Adversely I'd like to enable these fields where the CTA checkbox is checked enabling CTA.
I have investigated /libs/cq/gui/components/authoring/dialog/dropdownshowhide/clientlibs/dropdownshowhide.js but it's not really fit for purpose given that this is specifically designed for hiding or showing fields based on value of dropdown list and my attempts to modify it to allow similar funationality on a checkbox have been less than fruitful. I want to enable/disabled fields rather than hide of show them.
After a bit of messing around I got this working by adding class="cq-dialog-checkbox-enabledisable" to my sling:resourceType="granite/ui/components/foundation/form/checkbox" and class="cq-dialog-checkbox-enabledisable-target" to the sling:resourceType="granite/ui/components/foundation/form/textarea" that I wanted to disable in my cq:dialog.xml.
I then created my own clientLib that has dependencies on granite.jquery and categories cq.authoring.dialog.
UPDATE: turns out the disabled property can't be set programatically on pathbrowser field types at the top level, so you neeed to disable the child fields contained inside it (js-coral-pathbrowser-input and js-coral-pathbrowser-button) code snippet below updated to reflect this.
/**
* Extension to the standard checkbox component. It enables/disables other components based on the
* selection made in the checkbox.
*
* How to use:
*
* - add the class cq-dialog-checkbox-enabledisable to the checkbox element
* - add the class cq-dialog-checkbox-enabledisable-target to each target component that can be enabled/disabled
*/
(function(document, $) {
"use strict";
// when dialog gets injected
$(document).on("foundation-contentloaded", function(e) {
// if there is already an inital value make sure the according target element becomes visible
enableDisable($(".cq-dialog-checkbox-enabledisable", e.target));
});
$(document).on("change", ".cq-dialog-checkbox-enabledisable", function(e) {
enableDisable($(this));
});
function enableDisable(el){
el.each(function(i, element) {
if ($(element).attr("type") === "checkbox"){
if ($(element).prop('checked')){
$('.cq-dialog-checkbox-enabledisable-target').enable();
} else {
$('.cq-dialog-checkbox-enabledisable-target').disable();
}
}
})
}
//recurse all pathbrowser children and grandchildren etc
function iteratePathBrowserDescendants (node, enable) {
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
if ((child.className.indexOf('js-coral-pathbrowser-input') > -1 ) || (child.className.indexOf('js-coral-pathbrowser-button') > -1 )) {
enablePathBrowser(child, enable);
} else {
iteratePathBrowserDescendants(child, enable);
}
}
}
function enablePathBrowser(node, enable) {
node.disabled = enable;
}
//iterate class cq-dialog-checkbox-enabledisable-target's and enable
$.prototype.enable = function () {
$.each(this, function (index, el) {
//special treatment for pathBrowser as it is made up of multiple fields and cannot be disabled at the top level
if (el.hasAttribute('data-init')) {
if (el.getAttribute('data-init') == 'pathbrowser'){
iteratePathBrowserDescendants(el, false);
};
} else {
el.disabled = false;
}
});
}
//iterate class cq-dialog-checkbox-enabledisable-target's and disable
$.prototype.disable = function () {
$.each(this, function (index, el) {
//special treatment for pathBrowser as it is made up of multiple fields and cannot be disabled at the top level
if (el.hasAttribute('data-init')) {
if (el.getAttribute('data-init') == 'pathbrowser'){
iteratePathBrowserDescendants(el, true);
};
} else {
el.disabled = true;
}
});
}
})(document,Granite.$);

Getting around list<> properties being readonly

I have two custom components written in QML
//XOption.qml
Container {
id: xOption
property string title;
property bool active: false;
function makeActive() {active=true}
onActiveChanged {
//alter appearance of option to reflect whether active/not
}
onTouch {
if (touchEvent reflects a tap) {
//notify underlying c++ engine that I was tapped
//engine will notify parent control that I was tapped by setting the flag var
}
}
//label to display title and some other visual components
}
//XParent.qml
Container {
id: XParent;
property list<XOption> options;
property int selectedOption: 0;
property string flag: cppengine.flag;
onCreationCompleted {
for (var k = 0; k < children.length; ++k) {
if (k==selectedOption)
options[k].makeActive()
}
cppengine.declareParentage(options);
}
onFlagChanged {
if (flag indicates that one of my child options was tapped) {
//determine from flag which option was tapped
tappedOption.makeActive()
//make other options inactive
}
}
}
But now I want to use XParent in another QML document and assign any number of different XOptions to it like so:
Container {
XParent {
options: [
XOption {
title: "title1";
},
XOption {
title: "title2";
}
]
}
}
However, when doing so, I get the error:
Invalid property assignment: "options" is a read-only property
Is there any way I could get around this? I've tried making options a string array type variant, that would contain the title of every child option to create, and then adding a ComponentDefinition for XOption to XParent and creating one for every title that was specified, but if I do that I am unable to call XOption's makeActive(), which is absolutely necessary.

How to override signal handler of superclass component

I have a base class item something like this:
Base.qml:
Item {
Thing {
id: theThing;
onMySignal: { console.log("The signal"); }
}
}
And I'm trying to make a derived item - Derived.qml.
How can I override the onMySignal handler of theThing? I have tried stuff like this...
Derived.qml:
Base {
theThing.onMySignal: { console.log("Do different things with theThing in Derived") }
}
but I can't find anything to tell me how to express this syntactically correctly, or whether/how to actually go about it!
You can define the code of the signal handler as a property in superclass and override it in the derived item:
Item {
property var handlerCode: function () { console.log("In Superclass") };
Thing {
id: theThing;
onMySignal: handlerCode()
}
}
Overriding :
Base {
handlerCode: function () { console.log("In Derived Item!") };
}

Android webview ':active' stays on when elem is hidden while it is active

I'm building a html-app for Android and I have an issue with the :active css rule. It works like it should BUT when I hide an element that is ':active'. the state is never dismissed.
For example:
I have a button with this css:
.button:active { background-color:rgba(0,0,0,0.5); }
and this javascript:
$(".button").on("click",function(evt){
$(evt.originalEvent.target).css("display","none");
});
When I tap the button it is hidden. But when I un-hide it, it will still have the .button:active css rule applied.
Help?
Try the following
$(".button").on("click",function(evt){
$(evt.originalEvent.target).removeClass("active");/*Or whatever your class name is**/
$(evt.originalEvent.target).css("display","none");
});
I think I got it working with a MAJOR workaround (because event.target for touches returns the element the user tapped on which may very well be a childnode of the actual element that binds the events (see example below, it will return the [img] elem, not the [div]). Seufs.
PS: #Richa's answer did help me to do a workaround instead of hoping there would be a fix for :active
HTML (snippet)
<div class='button activatablel'><img src='someicon.png'></div>
CSS
.activatablel { /* nothing, just used to find the elements with jquery) */ }
.activatablel_active {
background:#f00;
}
JAVASCRIPT
elems = $(".activatablel");
for (var i in elems) {
var elem = elems[i];
elem.ontouchstart = function(evt) {
// Now we have to find the ACTUAL element that bound this event
// because somebody decided it's useful to not do this &$*((#^#))_
var foundTheActualTarget = false;
var thetarget = evt.target;
var whilenum = 0;
while (!foundTheActualTarget) {
if (thetarget.className) {
if (thetarget.className.indexOf("activatablel")>=0) {
foundTheActualTarget = true;
break;
}
}
thetarget = thetarget.parentNode;
whilenum++;
if (whilenum>256) { break; } // TODO: unless we intend to do this job in Reno, we're in Barney
}
if ($(thetarget).hasClass("activatablel_active")) { return; }
$(thetarget).addClass("activatablel_active");
}
elem.ontouchend = function(evt) {
$("*").removeClass("activatablel_active");
}
elem.ontouchcancel = elem.ontouchend;
}

How does one define a default style for a custom Flex component?

I'm creating a new Flex component (Flex 3). I'd like it to have a default style. Is there a naming convention or something for my .cs file to make it the default style? Am I missing something?
Christian's right about applying the CSS, but if you're planning on using the component in a library across projects, you're gonna want to write a default css file for that library. Here's how you do it:
Create a css file called "defaults.css" (Only this file name will work!) and put it at the top level under the "src" folder of your library. If the css file references any assets, they have to be under "src" as well.
(IMPORTANT!) Go to library project's Properties > Flex Library Build Path > Assets and include the css file and all assets.
That's how the Adobe team sets up all their default styles, now you can do it too. Just figured this out- huge
Two ways, generally. One's just by referencing the class name directly -- so for example, if you'd created a new component class MyComponent in ActionScript, or indirectly by making an MXML component extending another UIComponent called MyComponent, in both cases, the component would pick up the styles declared in your external stylesheet, provided that stylesheet's been imported into your application (e.g., via Style source):
MyComponent
{
backgroundColor: #FFFFFF;
}
Another way is by setting the UIComponent's styleName property (as a string):
public class MyComponent
{
// ...
this.styleName = "myStyle";
// ...
}
... and defining the style in the CSS file like so (note the dot notation):
.myStyle
{
backgroundColor: #FFFFFF;
}
Make sense?
In addition to what Christian Nunciato suggested, another option is to define a static initializer for your Flex component's styles. This allows you to set the default styles without requiring the developer to include a CSS file.
private static function initializeStyles():void
{
var styles:CSSStyleDeclaration = StyleManager.getStyleDeclaration("ExampleComponent");
if(!styles)
{
styles = new CSSStyleDeclaration();
}
styles.defaultFactory = function():void
{
this.exampleNumericStyle = 4;
this.exampleStringStyle = "word to your mother";
this.exampleClassStyle = DefaultItemRenderer //make sure to import it!
}
StyleManager.setStyleDeclaration("ExampleComponent", styles, false);
}
//call the static function immediately after the declaration
initializeStyles();
A refinement of what joshtynjala suggested:
public class CustomComponent extends UIComponent {
private static var classConstructed:Boolean = classConstruct();
private static function classConstruct():Boolean {
if (!StyleManager.getStyleDeclaration("CustomComponent")) {
var cssStyle:CSSStyleDeclaration = new CSSStyleDeclaration();
cssStyle.defaultFactory = function():void {
this.fontFamily = "Tahoma";
this.backgroundColor = 0xFF0000;
this.backgroundAlpha = 0.2;
}
StyleManager.setStyleDeclaration("CustomComponent", cssStyle, true);
}
return true;
}
}
I've read this in the docs somewhere; the classContruct method gets called automatically.
You may want to override default styles using the <fx:Style> tag or similar. If that's the case, a CSSStyleDeclaration may already exist by the time classConstructed is checked. Here's a solution:
private static var classConstructed:Boolean = getClassConstructed ();
private static function getClassConstructed ():Boolean {
var defaultCSSStyles:Object = {
backgroundColorGood: 0x87E224,
backgroundColorBad: 0xFF4B4B,
backgroundColorInactive: 0xCCCCCC,
borderColorGood: 0x333333,
borderColorBad: 0x333333,
borderColorInactive: 0x666666,
borderWeightGood: 2,
borderWeightBad: 2,
borderWeightInactive: 2
};
var cssStyleDeclaration:CSSStyleDeclaration = FlexGlobals.topLevelApplication.styleManager.getStyleDeclaration ("StatusIndicator");
if (!cssStyleDeclaration) {
cssStyleDeclaration = new CSSStyleDeclaration ("StatusIndicator", FlexGlobals.topLevelApplication.styleManager, true);
}
for (var i:String in defaultCSSStyles) {
if (cssStyleDeclaration.getStyle (i) == undefined) {
cssStyleDeclaration.setStyle (i, defaultCSSStyles [i]);
}
}
return (true);
}
To create a default style you can also have a property in your class and override the styleChanged() function in UIComponent, eg to only draw a background color across half the width of the component:
// this metadata helps flex builder to give you auto complete when writing
// css for your CustomComponent
[Style(name="customBackgroundColor", type="uint", format="color", inherit="no")]
public class CustomComponent extends UIComponent {
private static const DEFAULT_CUSTOM_COLOR:uint = 0x00FF00;
private var customBackgroundColor:uint = DEFAULT_CUSTOM_COLOR;
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
var allStyles:Boolean = (!styleProp || styleProp == "styleName");
if(allStyles || styleProp == "customBackgroundColor")
{
if(getStyle("customBackgroundColor") is uint);
{
customBackgroundColor = getStyle("customBackgroundColor");
}
else
{
customBackgroundColor = DEFAULT_CUSTOM_COLOR;
}
invalidateDisplayList();
}
// carry on setting any other properties you might like
// check out UIComponent.styleChanged() for more examples
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
graphics.clear();
graphics.beginFill(customBackgroundColor);
graphics.drawRect(0,0,unscaledWidth/2,unscaledHeight);
}
}
You could also create a setter for the customBackgroundColor that called invalidateDisplayList(), so you could also set the customBackgroundColor property programatically as well as through css.

Resources