Theme privatemsg a la Gmail, add classes, etc

mr.andrey - February 19, 2009 - 19:16
Project:Privatemsg
Version:6.x-1.x-dev
Component:Code
Category:feature request
Priority:normal
Assigned:Unassigned
Status:needs work
Description

Hello,

So after digging around the thread and API for a little while, I'm still a bit puzzled at figuring out a "clean" way of theming PrivateMsg. It seems there are no classes for the message listing table (?)

So far I got it to look like gmail (screenshot attached), but I had to modify three lines in the module core.

Here's the basic idea:

#1. Add message body through API (custom module or phptemplate)

<?php
function helper123_privatemsg_sql_list_alter(&$fragments, $account) {
   
$fragments['select'][] = 'pm.body'; // load 'body' into $row in theme_privatemsg_message_row()
}
function
helper123_privatemsg_sql_list_sent_alter(&$fragments, $account) {
   
$fragments['select'][] = 'pm.body'; // load 'body' into $row in theme_privatemsg_message_row()
}
?>

#2. Customize the message listing output (phptemplate)

<?php
function phptemplate_privatemsg_message_row($row) {
 
$themed_row = array();

 
$unread = '';
  if (isset(
$row['is_new']) && $row['is_new'] ) {
   
$unread = ' privatemsg-unread';
  }
  if (isset(
$row['author']) && $row['author']) {
   
$authors = _privatemsg_generate_user_array($row['author']);
   
$themed_row[t('Authors')] = array('data' => '<span class="privatemsg-list-from'$unread .'">'. _privatemsg_format_participants($authors, 3, TRUE) .'</span>', 'class' => 'Authors');
  }
  if (
array_key_exists('recipient', $row)) {
   
$themed_row[t('Recipients')] = '';
    if (!empty(
$row['recipient'])) {
     
$recipients = _privatemsg_generate_user_array($row['recipient']);
     
$themed_row[t('Recipients')] = array('data' => '<span class="privatemsg-list-to'$unread .'">'. _privatemsg_format_participants($recipients, 3, TRUE) .'</span>', 'class' => 'Recipients');
    }
  }
  if (
array_key_exists('body', $row)) {
   
$body = ' - <span class="privatemsg-list-body">'. substr($row['body'], 0, 150) .'</span>';
  }
 
$themed_row[t('Subject')] = array('data' => '<span class="privatemsg-list-subject'. $unread .'">'. l($row['subject'], 'messages/view/'. $row['thread_id']) .'</span>'.$body, 'class' => 'Subject');

  if (
$row['last_updated']) {
   
//$themed_row[t('Last updated')] = '<span class="privatemsg-list-date'.  $unread .'">'. format_date($row['last_updated'], 'small') .'</span>';
   
$time_ago = time() - $row['last_updated'];
   
$themed_row[t('Last updated')] = array('data' => '<span class="privatemsg-list-date'$unread .'">'. format_interval($time_ago,1) .' ago</span>', 'class' => 'Last-updated');
   
  }
 
//drupal_set_message(var_export($themed_row,true));
  //return $themed_row;
 
return array('data' => $themed_row, 'class' => $unread);
}
?>

#3. Modify module core (I'd like to avoid this somehow)

Since we're converting simple rows into complex rows, we need to read the 'data' in the array.
find this line:

foreach (array_keys($rows[0]) as $index) {

... and replace with this:
foreach (array_keys($rows[0]['data']) as $index) {

Add some table heading classes:
find this line:

$head[$index] =  array('data' => $index);

... and replace with this:
$head[$index] =  array('data' => $index, 'class' => preg_replace('/[^a-z0-9_-]/i','-',$index));

Question: Is there a better way to add syntax-safe classes here than preg_replace?

Add a table class
find this line:

$content = theme('table', $head, $rows);

... and replace it with this:
$content = theme('table', $head, $rows, array('class' => 'privatemsg-list'));

The message listing table is now fully themeable.

I have a deadline on this particular project, so I didn't have time to look too deeply into alternatives, but I'd love to hear some ideas about improvements on this and on how to avoid modifying the module core to achieve the same.

Best,
Andrey.

AttachmentSize
privatemsg-gmail.jpg99.01 KB

#1

Berdir - February 19, 2009 - 19:37

This looks great!

I'm sure we can find a way to allow this without making changes to the core, I am working on restructuring the list display, which should already eliminate some of your problems. I'll explain it later in #348907: Per thread/Multiple thread actions in detail.

A very short feedback, will come back on you later when the mentioned issue above is working.

#1: This might not work on more strict sql servers like postgresql, I need to check that..

#2: I changed from a simple string to an array just a few hours ago. I did more or less the same as you and added the unread class to the tr tag and moved the span classes to td. I'll explain it in detail later.

#3: I'll add default css classes to the table and the headers...

Would it be possible for you to share your theme modifications so that we could include it as a selectable theme?

#2

mr.andrey - February 19, 2009 - 19:57

Great. Looking forward to hearing about your progress.

Sure, here is the CSS and images are attached:

/* listing */
#privatemsg-filter-dropdown fieldset {
padding-bottom:0;
}
span.privatemsg-unread {
font-weight: bold;
}
.privatemsg-list-body {
color: #777;
}
.privatemsg-list {
width:100%;
margin:0;
}
.privatemsg-list th {
padding-left:5px;
}
.privatemsg-list td {
white-space:nowrap;
overflow:hidden;
max-width:200px;
padding-left:5px;
}
.privatemsg-list th.Authors {
width: 150px;
}
.privatemsg-list th.Subject {
/* keep flexible */
}
.privatemsg-list th.Last-updated {
width:100px;
}
/* end listing */
/* bubble */
.privatemsg-list {
border:0;
}
.privatemsg-list th.Authors,
.privatemsg-list th.Recipients {
background: url('images/block_bubble_white_top.gif') no-repeat top left;
padding-left:10px;
}
.privatemsg-list td.Authors,
.privatemsg-list td.Recipients{
background: url('images/block_bubble_white_bg.gif') repeat-y left;
padding-left:10px;
}
.privatemsg-list .privatemsg-unread td.Authors,
.privatemsg-list .privatemsg-unread td.Recipients {
background: url('images/block_bubble_yellow_bg.gif') repeat-y left;
padding-left:10px;
}
.privatemsg-list th {
background: url('images/block_bubble_white_top.gif') no-repeat top center;
border-bottom: 1px solid #ccd9e6;
}
.privatemsg-list .privatemsg-unread td {
background: url('images/block_bubble_yellow_bg.gif') repeat-y center;
}
.privatemsg-list th.Last-updated {
background: url('images/block_bubble_white_top.gif') no-repeat top right;
padding-right:10px;
}
.privatemsg-list td.Last-updated {
background: url('images/block_bubble_white_bg.gif') repeat-y right;
padding-right:10px;
}
.privatemsg-list .privatemsg-unread td.Last-updated {
background: url('images/block_bubble_yellow_bg.gif') repeat-y right;
padding-right:10px;
}
/* end bubble */

I assume you don't need the custom tabs, but if you do, here's the thread with template and all:
#372958: Solution for tabs/QuickTabs that works in IE6, IE7, Firefox 3 and Safari 3 identically

Best,
Andrey.

#3

mr.andrey - February 19, 2009 - 19:59

For some reason the file didn't attach. Trying again.

AttachmentSize
images.zip 2.12 KB

#4

mr.andrey - February 19, 2009 - 20:29

Here's a more IE6&7-friendly version (apparently IE needs a div for the gmail-style body overflow to work correctly without breaking lines):

<?php
function phptemplate_privatemsg_message_row($row) {
 
$themed_row = array();

 
$unread = '';
  if (isset(
$row['is_new']) && $row['is_new'] ) {
   
$unread = ' privatemsg-unread';
  }
  if (isset(
$row['author']) && $row['author']) {
   
$authors = _privatemsg_generate_user_array($row['author']);
   
$themed_row[t('Authors')] = array('data' => '<span class="privatemsg-list-from'$unread .'">'. _privatemsg_format_participants($authors, 3, TRUE) .'</span>', 'class' => 'Authors');
  }
  if (
array_key_exists('recipient', $row)) {
   
$themed_row[t('Recipients')] = '';
    if (!empty(
$row['recipient'])) {
     
$recipients = _privatemsg_generate_user_array($row['recipient']);
     
$themed_row[t('Recipients')] = array('data' => '<span class="privatemsg-list-to'$unread .'">'. _privatemsg_format_participants($recipients, 3, TRUE) .'</span>', 'class' => 'Recipients');
    }
  }
  if (
array_key_exists('body', $row)) {
   
$body = '<span class="privatemsg-list-body"> - '. substr($row['body'], 0, 250) .'</span>';
  }
 
$themed_row[t('Subject')] = array('data' => '<div class="privatemsg-list-subject-body"><span class="privatemsg-list-subject'. $unread .'">'. l($row['subject'], 'messages/view/'. $row['thread_id']) .'</span>'.$body.'</div>', 'class' => 'Subject');

  if (
$row['last_updated']) {
   
//$themed_row[t('Last updated')] = '<span class="privatemsg-list-date'.  $unread .'">'. format_date($row['last_updated'], 'small') .'</span>';
   
$time_ago = time() - $row['last_updated'];
   
$themed_row[t('Last updated')] = array('data' => '<span class="privatemsg-list-date'$unread .'">'. format_interval($time_ago,1) .' ago</span>', 'class' => 'Last-updated');
   
  }
 
//drupal_set_message(var_export($themed_row,true));
  //return $themed_row;
 
return array('data' => $themed_row, 'class' => $unread);
}
?>

/* listing */
#privatemsg-filter-dropdown fieldset {
padding-bottom:5px;
}
span.privatemsg-unread {
font-weight: bold;
}
.privatemsg-list-body {
color: #777;
}
.privatemsg-list {
margin:0;
}
.privatemsg-list th {
padding-left:5px;
}
.privatemsg-list td {
white-space:nowrap;
overflow:hidden;
padding-left:5px;
width:480px;
}
.privatemsg-list td div.privatemsg-list-subject-body {
overflow:hidden;
white-space:nowrap;
width:480px;
}
.privatemsg-list th.Authors {
width: 150px;
}
.privatemsg-list th.Subject {
/* keep flexible */
}
.privatemsg-list th.Last-updated {
width:100px;
}
/* end listing */
/* bubble */
.privatemsg-list {
border:0;
}
.privatemsg-list th.Authors,
.privatemsg-list th.Recipients {
background: url('images/block_bubble_white_top.gif') no-repeat top left;
padding-left:10px;
}
.privatemsg-list td.Authors,
.privatemsg-list td.Recipients{
background: url('images/block_bubble_white_bg.gif') repeat-y left;
padding-left:10px;
}
.privatemsg-list .privatemsg-unread td.Authors,
.privatemsg-list .privatemsg-unread td.Recipients {
background: url('images/block_bubble_yellow_bg.gif') repeat-y left;
padding-left:10px;
}
.privatemsg-list th {
background: url('images/block_bubble_white_top.gif') no-repeat top center;
border-bottom: 1px solid #ccd9e6;
}
.privatemsg-list .privatemsg-unread td {
background: url('images/block_bubble_yellow_bg.gif') repeat-y center;
}
.privatemsg-list th.Last-updated {
background: url('images/block_bubble_white_top.gif') no-repeat top right;
padding-right:10px;
}
.privatemsg-list td.Last-updated {
background: url('images/block_bubble_white_bg.gif') repeat-y right;
padding-right:10px;
}
.privatemsg-list .privatemsg-unread td.Last-updated {
background: url('images/block_bubble_yellow_bg.gif') repeat-y right;
padding-right:10px;
}
/* end bubble */

#5

Sansui - July 2, 2009 - 13:50

Looks great, but I'm unable to find the lines to change in the current dev code. Perhaps the module has changed significantly since this posting

#6

litwol - July 2, 2009 - 16:51
Status:active» needs work

*alot* have changed.

#7

Berdir - July 9, 2009 - 08:15

If we want this in privatemsg, we need some sort of styles support, similiar to advanced forum or panels. advanced forum styles are explained at http://drupal.org/node/512116.

Note that this is definitly post 1.0 but of course, if someone is interested to implement this, feel free to do so.

#8

Berdir - October 30, 2009 - 08:36
Category:support request» feature request

Setting the correct category, this is a new feature, not a support request.

#9

trupal218 - November 8, 2009 - 09:08

subscribing - I am very interested in being able to have full control over privatemsg as discussed in this thread.

#10

Berdir - November 8, 2009 - 09:34

You already have, see http://blog.worldempire.ch/de/api/group/theming/1 for more information.

This issue is just about providing different themes with privatemsg.module and the ability to choose between them in the admin UI.

#11

trupal218 - November 8, 2009 - 10:07

my apologies Berdir -

I am trying to change 'Participants' to 'From' and use field_firstname from content profile, and customize the 'Write new message' page but read about problems with page-messages-new.tpl.php

I will study the link you provided above

Thanks

#12

Berdir - November 8, 2009 - 10:18

field_firstname from content profile

Privatemsg should always use theme_username(). However, you might not always have full user objects, especially when displaying a thread (for performance reasons). You can either use user_load() to load the full user or extend the participants query so that it does directly load and return that field (a bit more complicated but better performance). See:

- http://blog.worldempire.ch/api/group/sql/1
- http://blog.worldempire.ch/api/function/privatemsg_sql_participants/1

I am trying to change 'Participants' to 'From'

On the view page? That is a bit tricky because that comes from a function, not a theme: http://blog.worldempire.ch/api/function/_privatemsg_format_participants/1. However, since it's a translated string, you can use string overrides to do that: http://drupal.org/project/stringoverrides

page-messages-new.tpl.php

That is a completely different thing, that would be a template suggestion for the *whole* page at messages/new. But you can't use that to change parts of the page.

#13

trupal218 - November 8, 2009 - 10:47

Thank you - I was able to successfully change 'Participants' to 'From' using stringoverrides!

I'm currently using email registration module so the username field is hidden at registration and auto set to everything before the @example.com
Therefore, I would like to use field_firstname from cck/content profile (username isn't a variable I have been using on the website)

I have used the below code in .tpl.php files before. Is there something similar I can use to have the 'Participants' column show this field instead of username for each message thread?

<?php
  $a
= $content_profile->get_variable('profile', 'field_first_name');
  print
l($a[0]['safe'],'user/'.$user->uid);
?>

Your help is much appreciated!

#14

Berdir - November 9, 2009 - 16:22

You should be able to create a theme_username function (http://api.drupal.org/api/function/theme_username/6) for your theme and it should use that everywhere.

 
 

Drupal is a registered trademark of Dries Buytaert.