summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorbus Iff2011-10-06 15:16:37 (GMT)
committer Morbus Iff2011-10-06 15:16:37 (GMT)
commit4491b0578475f75e7e1600abea5adc7a73a7b4e5 (patch)
tree965908d3ae15a6a1604b6a33af4ff17d76ef91b4
parent4ac0e5d32eadcc1b7543bf0c18e6824ab30b8d2a (diff)
Achievement leaderboards can be altered via hook_achievements_leaderboard().
-rw-r--r--CHANGELOG.txt11
-rw-r--r--README.txt8
-rw-r--r--achievements.api.php36
-rw-r--r--achievements.css8
-rw-r--r--achievements.pages.inc94
5 files changed, 111 insertions, 46 deletions
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index fc634c6..cbe0ba9 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,4 +1,15 @@
+Achievements 7.x-1.4, 2011-XX-XX
+--------------------------------
+ * (todo) Write up a "Getting started" part of the README?
+ * (todo) Add some links for good achievement design?
+
+ * Achievement leaderboards can be altered via hook_achievements_leaderboard().
+ * Lets you add new table columns or heavily modify leaderboard display.
+ * Context (type of leaderboard, block, etc.) and raw data is passed.
+ * See achievements.api.php for full details about the new hook.
+
+
Achievements 7.x-1.3, 2011-09-29
--------------------------------
* The following hooks have been added. See achievements.api.php for details:
diff --git a/README.txt b/README.txt
index 660986d..39a2419 100644
--- a/README.txt
+++ b/README.txt
@@ -12,7 +12,7 @@ INTRODUCTION
Current Maintainer: Morbus Iff <morbus@disobey.com>
The Achievements module offers the ability to create achievements and
-badges similar to systems seen on the Xbox 360, Playstation 3, Foursquare,
+badges similar to systems seen on Xbox 360, Playstation 3, Foursquare,
Gowalla, GetGlue, and more. For a Drupal site, this could mean commenting a
certain number of times, starting a forum topic, visiting the site every day
of the week, or anything else that can be tracked and coded.
@@ -47,8 +47,10 @@ Current features and design:
* Achievements can be grouped into categories and tabbed with jQuery UI.
* An adminterface allows you to manually grant or remove achievements. If
- the user is offline at the time, any unlocked achievements will display
- the next time the user visits the site.
+ the user is offline, unlocked achievements will display the next time
+ the user visits the site.
+
+ * Achievement unlocks fade-in and out at the window's bottom right corner.
* Your code decides whether achievements are retroactively applied or not.
diff --git a/achievements.api.php b/achievements.api.php
index 973ea14..ab6a548 100644
--- a/achievements.api.php
+++ b/achievements.api.php
@@ -158,13 +158,13 @@ function example_node_insert($node) {
* hesitate to create an issue asking for them.
*
* achievement_totals:
- * Leaderboard: find the totals of all users in ranking order.
+ * Find the totals of all users in ranking order.
*
* achievement_totals_user:
- * Leaderboard: find the totals of the passed user.
+ * Find the totals of the passed user.
*
* achievement_totals_user_nearby:
- * Leaderboard: find users nearby the ranking of the passed user.
+ * Find users nearby the ranking of the passed user.
*/
function example_query_alter() {
// futz with morbus' logic. insert explosions and singularities.
@@ -214,3 +214,33 @@ function example_achievements_unlocked($achievement, $uid) {
function example_achievements_locked($achievement, $uid) {
// react to achievement removal. bad user, BaAaDdd UUserrRR!
}
+
+/**
+ * Implements hook_achievements_leaderboard().
+ *
+ * Allows you to tweak or even recreate the leaderboard as required. The
+ * default implementation creates leaderboards as HTML tables and this hook
+ * allows you to modify that table (new columns, tweaked values, etc.) or
+ * replace it entirely with a new render element.
+ *
+ * @param &$leaderboard
+ * An array of information about the leaderboard. Available keys are:
+ * - achievers: The database results from the leaderboard queries.
+ * Results are keyed by leaderboard type (top, relative, first, and
+ * recent) and then by user ID, sorted in proper ranking order.
+ * - block: A boolean indicating whether this is a block-based leaderboard.
+ * - type: The type of leaderboard being displayed. One of: top (the overall
+ * leaderboard displayed on achievements/leaderboard), relative (the
+ * current-user-centric version with nearby ranks), first (the first users
+ * who unlocked a particular achievement), and recent (the most recent
+ * users who unlocked a particular achievement).
+ * - render: A render array for use with drupal_render(). Default rendering
+ * is with #theme => table, and you'll receive all the keys necessary
+ * for that implementation. You're welcome to insert your own unique
+ * render, bypassing the default entirely.
+ */
+function example_achievements_leaderboard(&$leaderboard) {
+ if ($leaderboard['type'] == 'first') {
+ $leaderboard['render']['#caption'] = t('Congratulations to our first 10!');
+ }
+} \ No newline at end of file
diff --git a/achievements.css b/achievements.css
index eb6d019..3867283 100644
--- a/achievements.css
+++ b/achievements.css
@@ -141,18 +141,18 @@
/**
* Stats on the per-achievement leaderboards.
*/
-.achievement-stats-first {
+.achievement-leaderboard-first {
float: left;
width: 49%;
}
-.achievement-stats-recent {
+.achievement-leaderboard-recent {
float: right;
width: 49%;
}
-.achievement-stats-first caption,
-.achievement-stats-recent caption {
+.achievement-leaderboard-first caption,
+.achievement-leaderboard-recent caption {
font-size: 150%;
margin-bottom: 0.6em;
}
diff --git a/achievements.pages.inc b/achievements.pages.inc
index 46e8407..8e7277e 100644
--- a/achievements.pages.inc
+++ b/achievements.pages.inc
@@ -46,11 +46,11 @@ function achievements_leaderboard_totals() {
$variables = array('achievement' => achievements_load($achiever->achievement_id), 'unlock' => array());
achievements_template_shared_variables($variables); // generate image, munge for hidden, etc.
- $rows[$type][] = array( // give a special class if its the current user.
+ $rows[$type][$achiever->uid] = array( // give a special class if its the current user.
'class' => ($achiever->uid == $GLOBALS['user']->uid) ? array('achievement-leaderboard-current-user') : array(),
'data' => array(
- array(
- 'data' => isset($achiever->rank) ? $achiever->rank : $rank++,
+ array( // increment our internal rank counter, and apply it to the dataset too.
+ 'data' => isset($achiever->rank) ? $achiever->rank : ($achiever->rank = $rank++),
'class' => array('achievement-leaderboard-rank')
),
array(
@@ -78,14 +78,22 @@ function achievements_leaderboard_totals() {
}
foreach (array('top', 'relative') as $type) {
- $build['achievements']['leaderboard'][$type] = array(
- '#attributes' => array('class' => array('achievement-leaderboard', 'achievement-leaderboard-' . $type)),
- '#header' => $type == 'top' ? $header : NULL, // relative piggybacks off of top.
- '#empty' => $type == 'top' ? t('No one has been ranked yet.') : NULL,
- '#rows' => $rows[$type],
- '#theme' => 'table',
+ $leaderboard = array(
+ 'achievers' => $achievers,
+ 'block' => FALSE,
+ 'type' => $type,
+ 'render' => array(
+ '#attributes' => array('class' => array('achievement-leaderboard', 'achievement-leaderboard-' . $type)),
+ '#header' => $type == 'top' ? $header : NULL, // relative piggybacks off of top.
+ '#empty' => $type == 'top' ? t('No one has been ranked yet.') : NULL,
+ '#rows' => $rows[$type],
+ '#theme' => 'table',
+ ),
);
+ drupal_alter('achievements_leaderboard', $leaderboard);
+ $build['achievements']['leaderboard'][$type] = $leaderboard['render'];
}
+
$build['achievements']['leaderboard_pager'] = array(
'#theme' => 'pager',
);
@@ -121,8 +129,8 @@ function achievements_leaderboard_block() {
$rows[$type][] = array( // give a special class if its the current user.
'class' => ($achiever->uid == $GLOBALS['user']->uid) ? array('achievement-leaderboard-current-user') : array(),
'data' => array(
- array(
- 'data' => isset($achiever->rank) ? $achiever->rank : $rank++,
+ array( // increment our internal rank counter, and apply it to the dataset too.
+ 'data' => isset($achiever->rank) ? $achiever->rank : ($achiever->rank = $rank++),
'class' => array('achievement-leaderboard-rank')
),
array(
@@ -139,18 +147,26 @@ function achievements_leaderboard_block() {
}
foreach (array('top', 'relative') as $type) {
- $build['achievements']['leaderboard'][$type] = array(
- '#attributes' => array('class' => array('achievement-leaderboard', 'achievement-leaderboard-' . $type)),
- '#header' => $type == 'top' ? $header : NULL, // relative piggybacks off of top.
- '#empty' => $type == 'top' ? t('No one has been ranked yet.') : NULL,
- '#rows' => $rows[$type],
- '#theme' => 'table',
+ $leaderboard = array(
+ 'achievers' => $achievers,
+ 'block' => TRUE,
+ 'type' => $type,
+ 'render' => array(
+ '#attributes' => array('class' => array('achievement-leaderboard', 'achievement-leaderboard-' . $type)),
+ '#header' => $type == 'top' ? $header : NULL, // relative piggybacks off of top.
+ '#empty' => $type == 'top' ? t('No one has been ranked yet.') : NULL,
+ '#rows' => $rows[$type],
+ '#theme' => 'table',
+ ),
);
+ drupal_alter('achievements_leaderboard', $leaderboard);
+ $build['achievements']['leaderboard'][$type] = $leaderboard['render'];
}
+
$build['achievements']['see-more'] = array(
- '#prefix' => '<div class="achievement-see-more">',
- '#markup' => l(t('View full leaderboard »'), 'achievements/leaderboard'),
- '#suffix' => '</div>',
+ '#prefix' => '<div class="achievement-see-more">',
+ '#markup' => l(t('View full leaderboard »'), 'achievements/leaderboard'),
+ '#suffix' => '</div>',
);
return $build;
@@ -175,41 +191,47 @@ function achievements_leaderboard_for($achievement) {
$query->condition('au.achievement_id', $achievement['id']); // ... with a slight tweak.
$query->fields('au', array('uid', 'rank', 'timestamp'))->fields('at', array('points', 'unlocks'))->fields('u', array('name'));
$query2 = clone $query; // allows us to save a few lines of duplicate query building. never used clone before. awesome.
- $stats['first'] = $query->orderBy('rank')->range(0, 10)->execute()->fetchAllAssoc('rank'); // FI... sigh.
- $stats['recent'] = $query2->orderBy('timestamp', 'DESC')->range(0, 10)->execute()->fetchAllAssoc('rank');
+ $achievers['first'] = $query->orderBy('rank')->range(0, 10)->execute()->fetchAllAssoc('uid'); // FI... sigh.
+ $achievers['recent'] = $query2->orderBy('timestamp', 'DESC')->range(0, 10)->execute()->fetchAllAssoc('uid');
- // both stat tables are displayed similarly.
foreach (array('first', 'recent') as $type) {
$rows = array(); // clear previous run.
- foreach ($stats[$type] as $stat) {
- $rows[] = array(
+ foreach ($achievers[$type] as $achiever) {
+ $rows[$achiever->uid] = array(
array(
- 'data' => $stat->rank,
+ 'data' => $achiever->rank,
'class' => array('achievement-leaderboard-rank')
),
array(
- 'data' => theme('username', array('account' => $stat)),
+ 'data' => theme('username', array('account' => $achiever)),
'class' => array('achievement-leaderboard-username')
),
array(
- 'data' => l($stat->points, 'user/' . $stat->uid . '/achievements'),
+ 'data' => l($achiever->points, 'user/' . $achiever->uid . '/achievements'),
'class' => array('achievement-leaderboard-points')
),
array(
- 'data' => format_date($stat->timestamp, 'short'),
+ 'data' => format_date($achiever->timestamp, 'short'),
'class' => array('achievement-leaderboard-when')
),
);
}
- $build['achievements']['stats'][$type] = array(
- '#attributes' => array('class' => array('achievement-stats-' . $type)),
- '#caption' => t('@type achievement unlocks', array('@type' => drupal_ucfirst($type))),
- '#header' => array(t('#'), t('Who'), t('Points'), t('When')),
- '#empty' => t('No one has unlocked this yet. Keep trying!'),
- '#rows' => $rows,
- '#theme' => 'table',
+ $leaderboard = array(
+ 'achievers' => $achievers,
+ 'block' => FALSE,
+ 'type' => $type,
+ 'render' => array(
+ '#attributes' => array('class' => array('achievement-leaderboard-' . $type)),
+ '#caption' => t('@type achievement unlocks', array('@type' => drupal_ucfirst($type))),
+ '#header' => array(t('#'), t('Who'), t('Points'), t('When')),
+ '#empty' => t('No one has unlocked this yet. Keep trying!'),
+ '#rows' => $rows,
+ '#theme' => 'table',
+ ),
);
+ drupal_alter('achievements_leaderboard', $leaderboard);
+ $build['achievements']['leaderboard'][$type] = $leaderboard['render'];
}
return $build;