By default, tapping a row in an Enyo list will “select” it. Sometime you might want finer-grained control over when selection happens and this post will offer a means to accomplish just that.
Selection is managed by an instance of enyo.Selection which is owned by the List control. You can control how selection behaves via two passthrough properties on the List control: toggleSelected and multiSelect.
toggleSelected is false by default which means tapping the row will always select it. By setting toggleSelected to false, tapping the row will toggle the selection back and forth. multiSelect is also false by default which means only one row will be selected at a time. By setting it to true, you can select multiple row (surprising, I know).
When a row is selected or deselected, it is rerendered by the List and your onSetupItem handler is called. In addition to an index property, the event will also have a selected property that reflects the current selection state of the row. Alternatively, if you need to retrieve the selection state of the entire list, you can call getSelection() on the List to get a handle on its underlying Selection instance.
Perhaps you’ve decided to include interactive controls in your list item such as a checkbox or button and have decided that tapping those shouldn’t trigger row selection. Preventing the selection can use the same technique as triggering the selection: the tap method.
FlyweightRepeater does all the work for rendering list items using the flyweight pattern. It also implements a tap() method to catch any taps on child nodes to trigger selection. So, if you want to prevent selection, all you have to do is return true in your ontap handler to cancel bubbling thereby preventing the selection trigger.
What if you had several buttons and only wanted one control (like a checkbox) to control selection? Turns out that any subkind of Control can override tap() to handle ontap events (even without specifying an entry in your handlers hash!) because Control already declares the handler. So, if you create a custom kind which implements tap() to return true, selection will never be triggered. By adding a little exception logical to tap(), you can decide when selection occurs rather than on every tap.
Here’s a working example:
enyo.kind({
name:"SimpleRow",
kind:"Control",
classes:"simple-row",
published:{
title:""
},
components:[
{name:"cb", kind:"Button", content:"Select", ontap:"cbTapped"},
{name:"title"}
],
create:function() {
this.inherited(arguments);
this.titleChanged();
},
titleChanged:function() {
this.$.title.setContent(this.title);
},
cbTapped:function(source, event) {
// nothing magical about this property, just made it up
event.allowBubble = true;
},
tap:function(source, event) {
if(event.allowBubble) {
event.allowBubble = false;
} else {
// cancel bubble without my special flag
return true;
}
}
});
enyo.kind({
name:"ex.App",
components:[
{kind:"List", toggleSelected:true, count:25, onSetupItem:"setupItem", components:[
{name:"sr", kind:"SimpleRow"}
]}
],
setupItem:function(source, event) {
this.$.sr.setTitle("Item " + event.index);this.$.sr.addRemoveClass("selected", event.selected);
}
});
new ex.App().renderInto(document.body);
{kind: "extras.InputDecorator", alwaysLooksFocused:true, style:"width:100%;height:400px", components: [
{kind:"Scroller", style:"height:100%;width:100%", components:[
{name:"content", kind: "onyx.RichText", richContent:false, onchange: "inputChange", style:"min-height:100%"}
]}
]}
You have to set the height of the input decorator (either explicitly or as a fit:true child of a FittableRows instance) as well as the width (either by setting the width or changing it to display:block). By setting the min-height of the RichText control, you ensure that anywhere the user clicks in the area, the control will get focus but it will still expand if the user adds more content than the screen holds.
N.B. I’ve used my extras.InputDecorator for the alwaysLooksFocused property but it works the same with vanilla onyx.InputDecorator.
Here’s a quick way to adapt the Panels CarouselArranger to peek the previous panel. By setting peekWidth on the Panels instance, this modified arranger will offset the left by that amount. It isn’t a perfect implementation (e.g. you can see the prior panel in the background when you slide the current panel away) but it would probably work in many cases.
For simplicity, the kind inherits from CarouselArranger. I’ve only modified the arrangeNoWrap method so the contents are virtually copied verbatim. Look for peek to find the customizations.
enyo.kind({
name: "extras.CarouselArranger",
kind: "enyo.CarouselArranger",
arrangeNoWrap: function(inC, inName) {
var peek = this.container.peekWidth || 0;
var c$ = this.container.children;
var s = this.container.clamp(inName);
var nw = this.containerBounds.width;
// do we have enough content to fill the width?
for (var i=s, cw=0, c; c=c$[i]; i++) {
cw += c.width + c.marginWidth;
if (cw > nw) {
break;
}
}
// if content width is less than needed, adjust starting point index and offset
var n = nw - cw;
var o = 0;
if (n > 0) {
var s1 = s;
for (var i=s-1, aw=0, c; c=c$[i]; i--) {
aw += c.width + c.marginWidth;
if (n - aw <= 0) {
o = (n - aw);
s = i;
break;
}
}
}
// arrange starting from needed index with detected offset so we fill space
for (var i=0, e=this.containerPadding.left + o + peek, w, c; c=c$[i]; i++) {
w = c.width + c.marginWidth;
if (i === s-1) {
this.arrangeControl(c, {left: -w+peek});
} else if (i < s) {
this.arrangeControl(c, {left: -w});
} else {
this.arrangeControl(c, {left: Math.floor(e)});
e += w;
}
}
}
});This is very much beta code but wanted to share how to add history to the Enyo2 Panels control. For flavor, I even added html5 history support so if you’re using a Panels controls as the main view controller (like Pane in Enyo1), you get quick back button support.
Here’s the code as well as a quick example (full screen and in jsfiddle):
enyo.kind({
name:"extras.Panels",
kind:"enyo.Panels",
published:{
history:""
},
create:function() {
this.historyChanged();
this.inherited(arguments);
this.createComponent({kind:"Signals", onBack:"back", onpopstate:"statePopped"});
enyo.dispatcher.listen(window, "popstate");
},
historyChanged:function() {
this.history = this.history || [];
},
back:function() {
this.history.pop(); // current
if(this.history.length > 0) {
var last = this.history.pop();
this.setIndex(last);
}
},
indexChanged:function() {
this.inherited(arguments);
if(this.history[this.history.length-1] !== this.index) {
this.pushState();
}
},
pushState:function() {
if(window.history.pushState) {
var c = this.getControls();
window.history.pushState({index:this.index}, "", "#"+c[this.index].name);
}
this.history.push(this.index);
},
statePopped:function(source, event) {
if(event.state) {
this.back();
}
}
});
If you’re looking to support other browsers, you might check out history.js which appears to be a nice polyfill for the history API.
In enyo v1, making an input control appear focused was easy: simply set alwaysLooksFocused to true. In enyo v2, the styling is delegated to an InputDecorator but it doesn’t provide this same property. Fortunately, it’s easy to add to a custom subkind.
enyo.kind({
name:"extras.InputDecorator",
kind:"onyx.InputDecorator",
published:{
alwaysLooksFocused:false,
},
create:function() {
this.inherited(arguments);
this.alwaysLooksFocusedChanged();
},
alwaysLooksFocusedChanged:function() {
this.addRemoveClass("onyx-focused", this.alwaysLooksFocused);
},
receiveBlur:function() {
if(!this.alwaysLooksFocused) {
this.inherited(arguments);
}
}
});Had a random idea to implement a <style> tag in Enyo for global runtime CSS manipulation. I’m thinking it might be handy to resize a bunch of elements as a result of window resize, for example. I wrote a quick example in enyo v1 and ported the couple differences for v2.
You can either set the entire CSS via its published css property or set individual blocks via the set(classSpec, styleObject) method.
Thoughts?
Edit: Make a quick update to enable removing CSS from a selector by passing a null. See the example below for removing the border from the children of .app.
Here it is live:
CSS transitions are a really easy way to add basic animation to an enyo app. It works on (seemingly) any CSS property and takes JavaScript out of the picture. With Pirate’s Dice (which is still not fully functional …), I’m using them to zoom the background when starting a new game. I’m also trying to support multiple resolutions (phones in portrait and landscape, tablets, and desktop) using media queries.
There are two ways to create a scrim in enyo: globally through enyo.scrim or through the enyo.Scrim kind. In either case, the scrim “fades” the entire view (usually in order to show a modal dialog or a spinner). Another use case I’m currently experimenting with in Pirate’s Dice is a local scrim — one that only fades a particular portion of the app.
While it’s probably not fair to call this “daily” any longer, I’m going to stick with it anyway.
You may have heard that enyo has found its way onto the legacy devices via a new Maps application in the App Catalog. To test things out, I tried to deploy Score Keeper onto my Pre2. It deploys successfully but there are definitely some bugs to sort out. One way I intend to clean up the UI is by including a phone-specific stylesheet through a dynamic depends.js file.
I wouldn’t have expected to write so much about events in enyo but there’s a lot of depth to cover. I previously posted about an Animated Grid Layout control I created which I later used in Score Keeper for the board selection. I have a similar requirement for the main panes of InContact but also need to reorder the items.