Pseudo-Drawers In A List

From WebOS101

Jump to: navigation, search

Drawers in a list are nice for displaying additional content at the user's request; however, drawers in a list are very tricky (perhaps impossible) things to get operating correctly. You can fairly easily simulate a drawer using standard CSS and javascript.

First, let's set up the HTML for our list in the list's row template (see List for basic info on setting up a list Widget):

<div name='noteIcon' class='#{hasnoteFormatted}'></div>
<div name='taskTitle' class='tasktitle'>
#{title}
</div>
 
<div name="notesDrawer" class="note-container" style="display: none;">
<div class="palm-drawer-container">
<div class="palm-drawer-contents">
<div class='mynote'>#{-noteFormatted}</div>
</div>
</div>
</div>

In this case, our list model's items have a title field and a note field. We're using the formatters attribute in the list's setupWidget() call to create a formatted version of the note, and add a special class for the note icon. More on this later!

Notice that the notesDrawer div is set to display: none; it will not be displayed on initial setup of the list.

Next, we need some CSS to format our note. Most of the CSS is just using the basic Palm CSS classes: palm-drawer-container and palm-drawer-content. These make our note "pseudo-drawer" appear the same as Palm's default drawers. Any additional formatting you'd like to add can go in the mynote class. We'll also create a CSS class for a note icon:

.mynote {
color: #000;
font-size: 18px;
padding: 5px 5px 5px 5px;
margin-left: 8px;
margin-right: 8px;
}
 
.icon-note {
height: 32px;
width: 32px;
float: right;
padding: 5px 10px 5px 0px;
background: url(../images/note-32x32.png) center no-repeat;
}

The final step is setting up the list, creating a function to handle taps on the list, and creating formatter functions to handle displaying the icon and the note.

First up is the setupWidget() call in your scene's Assistant.prototype.setup() method:

this.controller.setupWidget('taskListing', 
{
itemTemplate: 'intro/taskRowTemplate',
listTemplate: 'intro/taskListTemplate',
swipeToDelete: false,
renderLimit: 30,
delay: 300,
formatters: {
note: this.formatNote.bind(this),
hasnote: this.formatHasNote.bind(this),
},
reorderable: false
},
this.taskListModel = {disabled: false}
);
 
// Setup a handler for the listTap event
this.listTapHandler = this.listTap.bindAsEventListener(this);
this.controller.listen('taskListing', Mojo.Event.listTap, this.listTapHandler);

Note that we didn't setup the list model's items array in setupWidget. We could have setup the items array here, but in the case of loading items from an Ajax request or from a SQLite database, it's often better to retrieve the items in the scene's activate() method, then update the list model in the onSuccess function. Updating the list model can be done something like this:

this.taskListModel.items = [
{title: "First list item", note: ""},
{title: "Second list item", note: "This item has a note!"},
{title: "Third list item", note: ""},
{title: "Fourth list item", note: "Another note \n Call 555-124-4567"}
];
this.controller.modelChanged(this.taskListModel);

The next step is creating the function to handle the listTap event:

SceneAssistant.prototype.listTap = function (event) {
var id = event.originalEvent.target.id,
className = event.originalEvent.target.className,
curDrawer, curDrawerNode;
//Mojo.Log.info("Classname:", className, "Id:", id);
if (className === 'done-icon-note' || className === 'mynote') {
 
//Mojo.Log.info("Note Icon or drawer tapped!", id, className);
 
curDrawerNode = this.controller.get('taskListing').mojo.getNodeByIndex(event.index);
curDrawer = curDrawerNode.getElementsByClassName('note-container')[0];
 
if (curDrawer.getStyle('display') === 'none') {
curDrawer.show();
}
else {
curDrawer.hide();
this.controller.get('taskListing').mojo.getList().mojo.revealItem(event.index);
}
 
return;
}
this.controller.stageController.pushScene('edittask',
this.taskListModel.items[event.index]);
};

This listTap function checks the CSS classname of the div that was tapped. If the tap is on the note icon, or on the note "pseudo-drawer", we toggle the display of the note. If the note is long, a scroll to the bottom of the note may push the original item off the display, so we use revealItem() to make sure the scene is scrolled back to the original item when the note is hidden.

If the tap was anywhere else on the list item, we're going to push a scene to edit the item, passing the selected item to the new scene.

The final code needed is setting up the formatter functions to display the note and the note icon:

IntroAssistant.prototype.formatHasNote = function (value, model) {
if (value) {
return "icon-note";
}
else {
return "";
}
};
 
IntroAssistant.prototype.formatNote = function (value, model) {
if (value) {
return Mojo.Format.runTextIndexer(value.replace(/\n/g, "<br />"));
}
else {
return "";
}
};

The first formatter function simply tests whether the current item has a note and if so, returns the CSS class for the note icon. If the item has no note, then an empty string is returned. Recall from above, in the HTML, we used this line:

<div name='noteIcon' class='#{hasnoteFormatted}'></div>

If the item has a note, we end up with


<div name='noteIcon' class='icon-note'></div>

and our note icon is displayed. If the item does not have a note, the class is empty and no icon is displayed!

The second formatter function uses the Mojo.Format.runTextIndexer along with a string replacement regexp. The string replacement replaces any occurrence of '\n' with '
' to include linebreaks in the display of our note. runTextIndexer is a magical piece of magic that replaces any url, email address or phone number with a clickable link.

Personal tools