summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/WebService/Bug.pm9
-rw-r--r--extensions/MyDashboard/lib/Queries.pm4
-rw-r--r--extensions/MyDashboard/lib/WebService.pm27
-rw-r--r--extensions/MyDashboard/template/en/default/pages/mydashboard.html.tmpl43
-rw-r--r--extensions/MyDashboard/web/js/mydashboard_yui2.js159
-rw-r--r--extensions/MyDashboard/web/js/query.js43
-rw-r--r--extensions/MyDashboard/web/styles/mydashboard.css11
-rw-r--r--js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/closed.pngbin0 -> 218 bytes
-rw-r--r--js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/gallery-datatable-row-expansion-bmo.css1
-rw-r--r--js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/open.pngbin0 -> 203 bytes
-rw-r--r--js/yui3/gallery-datatable-row-expansion-bmo/gallery-datatable-row-expansion-bmo-min.js383
-rw-r--r--js/yui3/gallery-funcprog/gallery-funcprog-min.js1
-rw-r--r--js/yui3/gallery-math/gallery-math-min.js1
-rw-r--r--js/yui3/gallery-node-optimizations/gallery-node-optimizations-min.js1
-rw-r--r--js/yui3/gallery-object-extras/gallery-object-extras-min.js1
15 files changed, 514 insertions, 170 deletions
diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm
index 4018cfa6e..c0615b169 100644
--- a/Bugzilla/WebService/Bug.pm
+++ b/Bugzilla/WebService/Bug.pm
@@ -373,13 +373,13 @@ sub history {
$bug_id = $bug->id;
$item{id} = $self->type('int', $bug_id);
- my ($activity) = Bugzilla::Bug::GetBugActivity($bug_id);
+ my ($activity) = Bugzilla::Bug::GetBugActivity($bug_id, undef, $params->{start_time});
my @history;
foreach my $changeset (@$activity) {
my %bug_history;
$bug_history{when} = $self->type('dateTime', $changeset->{when});
- $bug_history{who} = $self->type('string', $changeset->{who});
+ $bug_history{who} = $self->type('email', $changeset->{who});
$bug_history{changes} = [];
foreach my $change (@{ $changeset->{changes} }) {
my $api_field = $api_name{$change->{fieldname}} || $change->{fieldname};
@@ -2121,6 +2121,11 @@ Note that it's possible for aliases to be disabled in Bugzilla, in which
case you will be told that you have specified an invalid bug_id if you
try to specify an alias. (It will be error 100.)
+=item C<start_time>
+
+An optional C<datetime> string that only shows changes at and after a specific
+time.
+
=back
=item B<Returns>
diff --git a/extensions/MyDashboard/lib/Queries.pm b/extensions/MyDashboard/lib/Queries.pm
index 79247bf73..e81e7f73f 100644
--- a/extensions/MyDashboard/lib/Queries.pm
+++ b/extensions/MyDashboard/lib/Queries.pm
@@ -10,6 +10,7 @@ package Bugzilla::Extension::MyDashboard::Queries;
use strict;
use Bugzilla;
+use Bugzilla::Bug;
use Bugzilla::CGI;
use Bugzilla::Search;
use Bugzilla::Util qw(format_time);
@@ -17,6 +18,8 @@ use Bugzilla::Util qw(format_time);
use Bugzilla::Extension::MyDashboard::Util qw(open_states quoted_open_states);
use Bugzilla::Extension::MyDashboard::TimeAgo qw(time_ago);
+use Data::Dumper;
+
use base qw(Exporter);
our @EXPORT = qw(
QUERY_ORDER
@@ -242,5 +245,4 @@ sub query_flags {
return undef;
}
-
1;
diff --git a/extensions/MyDashboard/lib/WebService.pm b/extensions/MyDashboard/lib/WebService.pm
index 853ef2baf..07619e705 100644
--- a/extensions/MyDashboard/lib/WebService.pm
+++ b/extensions/MyDashboard/lib/WebService.pm
@@ -9,7 +9,7 @@ package Bugzilla::Extension::MyDashboard::WebService;
use strict;
use warnings;
-use base qw(Bugzilla::WebService);
+use base qw(Bugzilla::WebService Bugzilla::WebService::Bug);
use Bugzilla::Constants;
use Bugzilla::Error;
@@ -92,6 +92,7 @@ sub prod_comp_search {
sub run_bug_query {
my($self, $params) = @_;
+ my $dbh = Bugzilla->dbh;
my $user = Bugzilla->login(LOGIN_REQUIRED);
defined $params->{query}
@@ -103,6 +104,30 @@ sub run_bug_query {
foreach my $qdef (QUERY_DEFS) {
next if $qdef->{name} ne $params->{query};
my ($bugs, $query_string) = query_bugs($qdef);
+
+ # Add last changes to each bug
+ foreach my $b (@$bugs) {
+ my $last_changes = {};
+ my $activity = $self->history({ ids => [ $b->{bug_id} ],
+ start_time => $b->{changeddate} });
+ if (@{$activity->{bugs}[0]{history}}) {
+ $last_changes->{activity} = $activity->{bugs}[0]{history}[0]{changes};
+ $last_changes->{email} = $activity->{bugs}[0]{history}[0]{who};
+ $last_changes->{when} = $activity->{bugs}[0]{history}[0]{when};
+ }
+ my $last_comment_id = $dbh->selectrow_array("
+ SELECT comment_id FROM longdescs
+ WHERE bug_id = ? AND bug_when >= ?",
+ undef, $b->{bug_id}, $b->{changeddate});
+ if ($last_comment_id) {
+ my $comments = $self->comments({ comment_ids => [ $last_comment_id ] });
+ $last_changes->{comment} = $comments->{comments}{$last_comment_id}{text};
+ $last_changes->{email} = $comments->{comments}{$last_comment_id}{creator} if !$last_changes->{email};
+ $last_changes->{when} = $comments->{comments}{$last_comment_id}{creation_time} if !$last_changes->{when};
+ }
+ $b->{last_changes} = $last_changes;
+ }
+
$query_string =~ s/^POSTDATA=&//;
$qdef->{bugs} = $bugs;
$qdef->{buffer} = $query_string;
diff --git a/extensions/MyDashboard/template/en/default/pages/mydashboard.html.tmpl b/extensions/MyDashboard/template/en/default/pages/mydashboard.html.tmpl
index dcaad0441..e5cd64399 100644
--- a/extensions/MyDashboard/template/en/default/pages/mydashboard.html.tmpl
+++ b/extensions/MyDashboard/template/en/default/pages/mydashboard.html.tmpl
@@ -24,6 +24,49 @@
[% saved_queries.push(q) IF q.saved %]
[% END %]
+<script id="last-changes-template" type="text/x-handlebars-template">
+ {{#if email}}
+ <div id="last_changes">
+ <div id="last_changes_header">
+ Last Changes :: {{email}} :: {{when}}
+ </div>
+ {{#if activity}}
+ <table id="activity">
+ {{#each activity}}
+ <tr>
+ <td class="field_label">{{field_name}}:</td>
+ <td class="field_data">
+ {{#if removed}}
+ {{#unless added}}
+ Removed:
+ {{/unless}}
+ {{removed}}
+ {{/if}}
+ {{#if added}}
+ {{#if removed}}
+ &rarr;
+ {{/if}}
+ {{/if}}
+ {{#if added}}
+ {{#unless removed}}
+ Added:
+ {{/unless}}
+ {{added}}
+ {{/if}}
+ </td>
+ </tr>
+ {{/each}}
+ </table>
+ {{/if}}
+ {{#if comment}}
+ <pre class='bz_comment_text'>{{comment}}</pre>
+ {{/if}}
+ {{else}}
+ This is a new [% terms.bug %] and no changes have been made yet.
+ {{/if}}
+ </div>
+</script>
+
<div id="mydashboard">
<div class="yui3-skin-sam">
<div id="left">
diff --git a/extensions/MyDashboard/web/js/mydashboard_yui2.js b/extensions/MyDashboard/web/js/mydashboard_yui2.js
deleted file mode 100644
index 25529d8c8..000000000
--- a/extensions/MyDashboard/web/js/mydashboard_yui2.js
+++ /dev/null
@@ -1,159 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/.
- *
- * This Source Code Form is "Incompatible With Secondary Licenses", as
- * defined by the Mozilla Public License, v. 2.0.
- */
-
-YAHOO.namespace('MyDashboard');
-
-var MD = YAHOO.MyDashboard;
-
-MD.showQuerySection = function () {
- var query_select = YAHOO.util.Dom.get('query');
- var selected_value = '';
- for (var i = 0, l = query_select.options.length; i < l; i++) {
- if (query_select.options[i].selected) {
- selected_value = query_select.options[i].value;
- }
- }
- for (var i = 0, l = MD.full_query_list.length; i < l; i++) {
- var query = MD.full_query_list[i];
- if (selected_value == MD.full_query_list[i]) {
- YAHOO.util.Dom.removeClass(query + '_container', 'bz_default_hidden');
- }
- else {
- YAHOO.util.Dom.addClass(query + '_container', 'bz_default_hidden');
- }
- }
-}
-
-MD.query_column_defs = [
- { key:"id", label:"ID", sortable:true, sortOptions:{ sortFunction: MD.sortBugIdLinks } },
- { key:"updated", label:"Updated", sortable:true },
- { key:"bug_status", label:"Status", sortable:true },
- { key:"summary", label:"Summary", sortable:true },
-];
-
-MD.query_fields = [
- { key:"id" },
- { key:"updated" },
- { key:"bug_status" },
- { key:"summary" }
-];
-
-MD.requestee_column_defs = [
- { key:"requester", label:"Requester", sortable:true },
- { key:"flag", label:"Flag", sortable:true },
- { key:"bug", label:"Bug", sortable:true },
- { key:"created", label:"Created", sortable:true }
-];
-
-MD.requestee_fields = [
- { key:"requester" },
- { key:"flag" },
- { key:"bug" },
- { key:"created" }
-];
-
-MD.requester_column_defs = [
- { key:"requestee", label:"Requestee", sortable:true },
- { key:"flag", label:"Flag", sortable:true },
- { key:"bug", label:"Bug", sortable:true },
- { key:"created", label:"Created", sortable:true }
-];
-
-MD.requester_fields = [
- { key:"requestee" },
- { key:"flag" },
- { key:"bug" },
- { key:"created" }
-];
-
-MD.addStatListener = function (div_name, table_name, column_defs, fields, options) {
- YAHOO.util.Event.addListener(window, "load", function() {
- YAHOO.example.StatsFromMarkup = new function() {
- this.myDataSource = new YAHOO.util.DataSource(YAHOO.util.Dom.get(table_name));
- this.myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
- this.myDataSource.responseSchema = { fields:fields };
- this.myDataTable = new YAHOO.widget.DataTable(div_name, column_defs, this.myDataSource, options);
- this.myDataTable.subscribe("rowMouseoverEvent", this.myDataTable.onEventHighlightRow);
- this.myDataTable.subscribe("rowMouseoutEvent", this.myDataTable.onEventUnhighlightRow);
- };
- });
-}
-
-// Custom sort handler to sort by bug id inside an anchor tag
-MD.sortBugIdLinks = function (a, b, desc) {
- // Deal with empty values
- if (!YAHOO.lang.isValue(a)) {
- return (!YAHOO.lang.isValue(b)) ? 0 : 1;
- }
- else if(!YAHOO.lang.isValue(b)) {
- return -1;
- }
- // Now we need to pull out the ID text and convert to Numbers
- // First we do 'a'
- var container = document.createElement("bug_id_link");
- container.innerHTML = a.getData("id");
- var anchors = container.getElementsByTagName("a");
- var text = anchors[0].textContent;
- if (text === undefined) text = anchors[0].innerText;
- var new_a = new Number(text);
- // Then we do 'b'
- container.innerHTML = b.getData("id");
- anchors = container.getElementsByTagName("a");
- text = anchors[0].textContent;
- if (text == undefined) text = anchors[0].innerText;
- var new_b = new Number(text);
-
- if (!desc) {
- return YAHOO.util.Sort.compare(new_a, new_b);
- }
- else {
- return YAHOO.util.Sort.compare(new_b, new_a);
- }
-}
-
-// Custom sort handler for bug severities
-MD.sortBugSeverity = function (a, b, desc) {
- // Deal with empty values
- if (!YAHOO.lang.isValue(a)) {
- return (!YAHOO.lang.isValue(b)) ? 0 : 1;
- }
- else if(!YAHOO.lang.isValue(b)) {
- return -1;
- }
-
- var new_a = new Number(MD.severities[YAHOO.lang.trim(a.getData('bug_severity'))]);
- var new_b = new Number(MD.severities[YAHOO.lang.trim(b.getData('bug_severity'))]);
-
- if (!desc) {
- return YAHOO.util.Sort.compare(new_a, new_b);
- }
- else {
- return YAHOO.util.Sort.compare(new_b, new_a);
- }
-}
-
-// Custom sort handler for bug priorities
-MD.sortBugPriority = function (a, b, desc) {
- // Deal with empty values
- if (!YAHOO.lang.isValue(a)) {
- return (!YAHOO.lang.isValue(b)) ? 0 : 1;
- }
- else if(!YAHOO.lang.isValue(b)) {
- return -1;
- }
-
- var new_a = new Number(MD.priorities[YAHOO.lang.trim(a.getData('priority'))]);
- var new_b = new Number(MD.priorities[YAHOO.lang.trim(b.getData('priority'))]);
-
- if (!desc) {
- return YAHOO.util.Sort.compare(new_a, new_b);
- }
- else {
- return YAHOO.util.Sort.compare(new_b, new_a);
- }
-}
diff --git a/extensions/MyDashboard/web/js/query.js b/extensions/MyDashboard/web/js/query.js
index 746717e75..865d255a6 100644
--- a/extensions/MyDashboard/web/js/query.js
+++ b/extensions/MyDashboard/web/js/query.js
@@ -9,9 +9,17 @@
// Main query code
YUI({
base: 'js/yui3/',
- combine: false
+ combine: false,
+ groups: {
+ gallery: {
+ combine: false,
+ base: 'js/yui3/',
+ patterns: { 'gallery-': {} }
+ }
+ }
}).use("node", "datatable", "datatable-sort", "datatable-message", "json-stringify",
- "datatable-datasource", "datasource-io", "datasource-jsonschema", "cookie", function (Y) {
+ "datatable-datasource", "datasource-io", "datasource-jsonschema", "cookie",
+ "gallery-datatable-row-expansion-bmo", "handlebars", function (Y) {
var counter = 0,
dataSource = null,
dataTable = null,
@@ -83,7 +91,8 @@ YUI({
dataSource.plug(Y.Plugin.DataSourceJSONSchema, {
schema: {
resultListLocator: "result.result.bugs",
- resultFields: ["bug_id", "changeddate", "bug_status", "short_desc"],
+ resultFields: ["bug_id", "changeddate", "bug_status",
+ "short_desc", "last_changes"],
metaFields: {
description: "result.result.description",
heading: "result.result.heading",
@@ -94,14 +103,34 @@ YUI({
dataTable = new Y.DataTable({
columns: [
- { key:"bug_id", label:"Bug", sortable:true,
+ { key: Y.Plugin.DataTableRowExpansion.column_key, label: ' ' },
+ { key: "bug_id", label: "Bug", sortable: true,
formatter: '<a href="show_bug.cgi?id={value}" target="_blank">{value}</a>', allowHTML: true },
- { key:"changeddate", label:"Updated", sortable:true },
- { key:"bug_status", label:"Status", sortable:true },
- { key:"short_desc", label:"Summary", sortable:true },
+ { key: "changeddate", label: "Updated", sortable: true },
+ { key: "bug_status", label: "Status", sortable: true },
+ { key: "short_desc", label: "Summary", sortable: true },
],
});
+ var last_changes_source = Y.one('#last-changes-template').getHTML(),
+ last_changes_template = Y.Handlebars.compile(last_changes_source);
+
+ dataTable.plug(Y.Plugin.DataTableRowExpansion, {
+ uniqueIdKey: 'bug_id',
+ template: function(data) {
+ var last_changes = {};
+ if (data.last_changes.email) {
+ last_changes = {
+ activity: data.last_changes.activity,
+ email: data.last_changes.email,
+ when: data.last_changes.when,
+ comment: data.last_changes.comment,
+ };
+ }
+ return last_changes_template(last_changes);
+ }
+ });
+
dataTable.plug(Y.Plugin.DataTableSort);
dataTable.plug(Y.Plugin.DataTableDataSource, {
diff --git a/extensions/MyDashboard/web/styles/mydashboard.css b/extensions/MyDashboard/web/styles/mydashboard.css
index dc253c0d1..253e60d42 100644
--- a/extensions/MyDashboard/web/styles/mydashboard.css
+++ b/extensions/MyDashboard/web/styles/mydashboard.css
@@ -51,3 +51,14 @@
padding: 20px !important;
height: 40px;
}
+
+#last_changes_header {
+ font-size: 12px;
+ font-weight: bold;
+ padding-bottom: 5px;
+ border-bottom: 1px solid rgb(200, 200, 186);
+}
+
+#last_changes .field_label {
+ text-align: left;
+}
diff --git a/js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/closed.png b/js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/closed.png
new file mode 100644
index 000000000..019c18e14
--- /dev/null
+++ b/js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/closed.png
Binary files differ
diff --git a/js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/gallery-datatable-row-expansion-bmo.css b/js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/gallery-datatable-row-expansion-bmo.css
new file mode 100644
index 000000000..7ea55b74d
--- /dev/null
+++ b/js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/gallery-datatable-row-expansion-bmo.css
@@ -0,0 +1 @@
+.yui3-skin-sam .yui3-datatable tr.row-expansion td.post-row-expansion{border-top:1px solid #cbcbcb}.yui3-skin-sam .yui3-datatable .row-toggle a.row-expand-nub{padding:0 8px;height:14px;margin-left:2px;*display:inline-block}.yui3-skin-sam .yui3-datatable .row-closed a.row-expand-nub{background:url(closed.png) no-repeat}.yui3-skin-sam .yui3-datatable .row-open a.row-expand-nub{background:url(open.png) no-repeat}#yui3-css-stamp.skin-sam-gallery-datatable-row-expansion{display:none}
diff --git a/js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/open.png b/js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/open.png
new file mode 100644
index 000000000..dc7805017
--- /dev/null
+++ b/js/yui3/gallery-datatable-row-expansion-bmo/assets/skins/sam/open.png
Binary files differ
diff --git a/js/yui3/gallery-datatable-row-expansion-bmo/gallery-datatable-row-expansion-bmo-min.js b/js/yui3/gallery-datatable-row-expansion-bmo/gallery-datatable-row-expansion-bmo-min.js
new file mode 100644
index 000000000..e3f87804c
--- /dev/null
+++ b/js/yui3/gallery-datatable-row-expansion-bmo/gallery-datatable-row-expansion-bmo-min.js
@@ -0,0 +1,383 @@
+YUI.add('gallery-datatable-row-expansion-bmo', function (Y, NAME) {
+
+"use strict";
+
+/**
+ * @module gallery-datatable-row-expansion
+ */
+
+/**********************************************************************
+ * <p>Plugin for DataTable to show additional information for each row via
+ * a twistdown. The result of the template is displayed spanning all the
+ * columns beyond the twistdown column.</p>
+ *
+ * <p>This class patches `getCell` and `getRow` to ignore the additional
+ * rows created by this plugin.</p>
+ *
+ * @main gallery-datatable-row-expansion
+ * @class DataTableRowExpansion
+ * @namespace Plugin
+ * @extends Plugin.Base
+ * @constructor
+ * @param config {Object} configuration
+ */
+function RowExpansion(
+ /* object */ config)
+{
+ RowExpansion.superclass.constructor.call(this, config);
+}
+
+RowExpansion.NAME = "DataTableRowExpansionPlugin";
+RowExpansion.NS = "rowexpander";
+
+RowExpansion.ATTRS =
+{
+ /**
+ * String template or function that returns a string.
+ *
+ * @attribute template
+ * @type {String|Function}
+ * @required
+ */
+ template:
+ {
+ value: '',
+ validator: function(value)
+ {
+ return (Y.Lang.isString(value) || Y.Lang.isFunction(value));
+ }
+ },
+
+ /**
+ * Id of a column (usually not displayed) that yields a
+ * unique value for each record. Used to maintain the twistdown state
+ * when paginating.
+ *
+ * @attribute uniqueIdKey
+ * @type {String}
+ * @required
+ */
+ uniqueIdKey:
+ {
+ value: '',
+ validator: Y.Lang.isString
+ }
+};
+
+/**
+ * The key used to indicate which column contains the twistdown.
+ *
+ * @property Y.RowExpansion.column_key
+ * @type {String}
+ * @value "row-expander"
+ */
+RowExpansion.column_key = 'row-expander';
+
+/**
+ * The class added to rows created by this plugin.
+ *
+ * @property Y.RowExpansion.row_class
+ * @type {String}
+ * @value "row-expansion"
+ */
+RowExpansion.row_class = 'row-expansion';
+
+function insertRow(o)
+{
+ var plugin = this.rowexpander;
+
+ var pre_cells = '';
+ for (var i=0; i<=plugin.col_count.pre; i++)
+ {
+ pre_cells += '<td class="yui3-datatable-cell pre-row-expansion">&nbsp;</td>';
+ }
+
+ var tmpl = plugin.get('template');
+ if (Y.Lang.isFunction(tmpl))
+ {
+ var s = tmpl.call(this, o.data);
+ }
+ else
+ {
+ var s = Y.Lang.sub(tmpl, o.data);
+ }
+
+ var row = o.cell.ancestor();
+ var extra_row = Y.Lang.sub(
+ '<tr class="{c}">' +
+ '{pre}' +
+ '<td colspan="{post}" class="yui3-datatable-cell post-row-expansion">{tmpl}</td>' +
+ '</tr>',
+ {
+ c: row.get('className') + ' ' + RowExpansion.row_class,
+ pre: pre_cells,
+ post: plugin.col_count.post,
+ tmpl: s
+ });
+
+ row.insert(extra_row, 'after');
+}
+
+function formatTwistdown(o)
+{
+ var plugin = this.rowexpander,
+ row_id = o.data[ plugin.get('uniqueIdKey') ],
+ open = plugin.open_rows[ row_id ];
+
+ o.td.addClass('row-toggle');
+ o.td.replaceClass('row-(open|closed)', open ? 'row-open' : 'row-closed');
+
+ o.td.on('click', function()
+ {
+ var open = plugin.open_rows[ row_id ] = ! plugin.open_rows[ row_id ];
+
+ if (open)
+ {
+ insertRow.call(this, o);
+ o.td.replaceClass('row-(open|closed)', open ? 'row-open' : 'row-closed');
+ }
+ else
+ {
+ o.cell.ancestor().next().remove();
+ o.td.replaceClass('row-(open|closed)', open ? 'row-open' : 'row-closed');
+ }
+ },
+ this);
+
+ o.cell.set('innerHTML', '<a class="row-expand-nub" href="javascript:void(0);"></a>');
+
+ if (open)
+ {
+ insertRow.call(this, o);
+ }
+}
+
+function analyzeColumns()
+{
+ function countColumns(result, col)
+ {
+ if (col.key == RowExpansion.column_key)
+ {
+ col.nodeFormatter = formatTwistdown;
+ result.found = true;
+ }
+ else if (col.children)
+ {
+ result = Y.reduce(col.children, result, countColumns);
+ }
+ else
+ {
+ result[ result.found ? 'post' : 'pre' ]++;
+ }
+ return result;
+ }
+
+ this.col_count = Y.reduce(
+ this.get('host').get('columns'),
+ { pre:0, post:0, found:false },
+ countColumns);
+}
+
+var shift_map =
+{
+ above: [-1, 0],
+ below: [ 1, 0],
+ next: [ 0, 1],
+ prev: [ 0, -1],
+ previous: [ 0, -1]
+};
+
+/*
+Returns the `<td>` Node from the given row and column index. Alternately,
+the `seed` can be a Node. If so, the nearest ancestor cell is returned.
+If the `seed` is a cell, it is returned. If there is no cell at the given
+coordinates, `null` is returned.
+
+Optionally, include an offset array or string to return a cell near the
+cell identified by the `seed`. The offset can be an array containing the
+number of rows to shift followed by the number of columns to shift, or one
+of "above", "below", "next", or "previous".
+
+<pre><code>// Previous cell in the previous row
+var cell = table.getCell(e.target, [-1, -1]);
+
+// Next cell
+var cell = table.getCell(e.target, 'next');
+var cell = table.getCell(e.taregt, [0, 1];</pre></code>
+
+@method getCell
+@param {Number[]|Node} seed Array of row and column indexes, or a Node that
+ is either the cell itself or a descendant of one.
+@param {Number[]|String} [shift] Offset by which to identify the returned
+ cell Node
+@return {Node}
+@since 3.5.0
+*/
+function getCell(seed, shift)
+{
+ var tbody = this.tbodyNode,
+ row, cell;
+
+ if (seed && tbody)
+ {
+ if (Y.Lang.isString(shift))
+ {
+ if (shift_map[shift])
+ {
+ shift = shift_map[shift];
+ }
+ else
+ {
+ throw Error('unknown shift in getCell: ' + shift);
+ }
+ }
+
+ if (Y.Lang.isArray(seed))
+ {
+ row = tbody.get('children').item(0);
+ cell = row && row.get('children').item(seed[1]);
+ if (shift)
+ {
+ shift[0] += seed[0];
+ }
+ else
+ {
+ shift = [ seed[0], 0 ];
+ }
+ }
+ else if (seed._node)
+ {
+ cell = seed.ancestor('.' + this.getClassName('cell'), true);
+ if (cell.ancestor('tr.' + RowExpansion.row_class))
+ {
+ throw Error('getCell cannot be called with an element from an expansion row');
+ }
+ }
+
+ if (cell && shift)
+ {
+ var firstRowIndex = tbody.get('firstChild.rowIndex');
+ if (Y.Lang.isArray(shift))
+ {
+ row = cell.ancestor();
+ var delta = Math.sign(shift[0]);
+ if (delta !== 0)
+ {
+ var rows = tbody.get('children');
+ var index = row.get('rowIndex') - firstRowIndex;
+ var count = Math.abs(shift[0]);
+ for (var i=0; i<count && row; i++)
+ {
+ index += delta;
+ row = rows.item(index);
+ if (row && row.hasClass(RowExpansion.row_class))
+ {
+ index += delta;
+ row = rows.item(index);
+ }
+ }
+ }
+
+ index = cell.get('cellIndex') + shift[1];
+ cell = row && row.get('children').item(index);
+ }
+ }
+ }
+
+ return (cell || null);
+}
+
+/*
+Returns the `<tr>` Node from the given row index, Model, or Model's
+`clientId`. If the rows haven't been rendered yet, or if the row can't be
+found by the input, `null` is returned.
+
+@method getRow
+@param {Number|String|Model} id Row index, Model instance, or clientId
+@return {Node}
+@since 3.5.0
+*/
+function getRow(id)
+{
+ var tbody = this.tbodyNode,
+ row = null;
+
+ if (tbody)
+ {
+ if (id)
+ {
+ id = this._idMap[id.get ? id.get('clientId') : id] || id;
+ }
+
+ row = Y.one(Y.Lang.isNumber(id) ? this.getCell([id,0]).ancestor() : '#' + id);
+ }
+
+ return row;
+}
+
+function replaceGetters()
+{
+ var view = this.get('host').view;
+ if (view instanceof Y.DataTable.TableView &&
+ view.body instanceof Y.DataTable.BodyView)
+ {
+ var body = view.body;
+
+ this.orig_getCell = body.getCell;
+ this.orig_getRow = body.getRow;
+
+ body.getCell = getCell;
+ body.getRow = getRow;
+ }
+}
+
+function restoreGetters()
+{
+ var view = this.get('host').view;
+ if (view.body && this.orig_getCell)
+ {
+ view.body.getCell = this.orig_getCell;
+ }
+
+ if (view.body && this.orig_getRow)
+ {
+ view.body.getRow = this.orig_getRow;
+ }
+}
+
+Y.extend(RowExpansion, Y.Plugin.Base,
+{
+ initializer: function(config)
+ {
+ this.open_rows = {};
+ this.on('uniqueIdKeyChange', function()
+ {
+ this.open_rows = {};
+ });
+
+ analyzeColumns.call(this);
+ this.afterHostEvent('columnsChange', analyzeColumns);
+
+ this.afterHostEvent('table:renderTable', replaceGetters);
+ },
+
+ destructor: function()
+ {
+ restoreGetters.call(this);
+ }
+});
+
+Y.namespace("Plugin");
+Y.Plugin.DataTableRowExpansion = RowExpansion;
+
+
+}, '@VERSION@', {
+ "skinnable": "true",
+ "requires": [
+ "datatable",
+ "plugin",
+ "gallery-funcprog",
+ "gallery-node-optimizations",
+ "gallery-math"
+ ]
+});
diff --git a/js/yui3/gallery-funcprog/gallery-funcprog-min.js b/js/yui3/gallery-funcprog/gallery-funcprog-min.js
new file mode 100644
index 000000000..5354b068c
--- /dev/null
+++ b/js/yui3/gallery-funcprog/gallery-funcprog-min.js
@@ -0,0 +1 @@
+YUI.add("gallery-funcprog",function(e,t){"use strict";function n(t,n){var r=e.Array(arguments,1,!0);switch(e.Array.test(n)){case 1:return e.Array[t].apply(null,r);case 2:return r[0]=e.Array(n,0,!0),e.Array[t].apply(null,r);default:return n&&n[t]&&n!==e?(r.shift(),n[t].apply(n,r)):e.Object[t].apply(null,r)}}e.mix(e,{every:function(e,t,r,i){return n("every",e,t,r,i)},filter:function(e,t,r,i){return n("filter",e,t,r,i)},find:function(e,t,r,i){return n("find",e,t,r,i)},map:function(e,t,r,i){return n("map",e,t,r,i)},partition:function(e,t,r,i){return n("partition",e,t,r,i)},reduce:function(e,t,r,i,s){return n("reduce",e,t,r,i,s)},reduceRight:function(e,t,r,i,s){return n("reduceRight",e,t,r,i,s)},reject:function(e,t,r,i){return n("reject",e,t,r,i)}}),e.mix(e.Array,{findIndexOf:function(t,n,r){var i=-1;return e.Array.some(t,function(e,s){if(n.call(r,e,s,t))return i=s,!0}),i}}),e.Array.reduceRight=e.Lang._isNative(Array.prototype.reduceRight)?function(e,t,n,r){return Array.prototype.reduceRight.call(e,function(e,t,i,s){return n.call(r,e,t,i,s)},t)}:function(e,t,n,r){var i=t;for(var s=e.length-1;s>=0;s--)i=n.call(r,i,e[s],s,e);return i}},"@VERSION@",{requires:["oop","array-extras","gallery-object-extras"],optional:["gallery-nodelist-extras2"]});
diff --git a/js/yui3/gallery-math/gallery-math-min.js b/js/yui3/gallery-math/gallery-math-min.js
new file mode 100644
index 000000000..1d9348e48
--- /dev/null
+++ b/js/yui3/gallery-math/gallery-math-min.js
@@ -0,0 +1 @@
+YUI.add("gallery-math",function(e,t){"use strict";e.mix(Math,{sign:function(e){return e<0?-1:e>0?1:0},add:function(){return e.Array.reduce(e.Array(arguments),0,function(t,n){return e.Lang.isArray(n)&&(n=Math.add.apply(this,n)),t+n})},addReciprocals:function(){return e.Array.reduce(e.Array(arguments),0,function(t,n){return e.Lang.isArray(n)?t+Math.addReciprocals.apply(this,n):t+1/n})},parallel:function(){return 1/Math.addReciprocals.apply(this,arguments)},multiply:function(){return e.Array.reduce(e.Array(arguments),1,function(t,n){return e.Lang.isArray(n)&&(n=Math.multiply.apply(this,n)),t*n})},degreesToRadians:function(e){return e*Math.PI/180},radiansToDegrees:function(e){return e*180/Math.PI},acosh:function(e){return Math.log(e+Math.sqrt(e*e-1))},asinh:function(e){return Math.log(e+Math.sqrt(e*e+1))},atanh:function(e){return Math.log((1+e)/(1-e))/2},cosh:function(e){var t=Math.exp(e);return(t+1/t)/2},sinh:function(e){var t=Math.exp(e);return(t-1/t)/2},tanh:function(e){var t=Math.exp(2*e);return(t-1)/(t+1)}})},"@VERSION@",{requires:["array-extras"]});
diff --git a/js/yui3/gallery-node-optimizations/gallery-node-optimizations-min.js b/js/yui3/gallery-node-optimizations/gallery-node-optimizations-min.js
new file mode 100644
index 000000000..297b60ac6
--- /dev/null
+++ b/js/yui3/gallery-node-optimizations/gallery-node-optimizations-min.js
@@ -0,0 +1 @@
+YUI.add("gallery-node-optimizations",function(e,t){"use strict";var n=/^([a-z]*)\.([-_a-z0-9]+)$/i,r=/^\.([-_a-z0-9]+)$/i,i=/^[a-z]+$/i;e.Node.class_re_prefix="(?:^|\\s)(?:",e.Node.class_re_suffix=")(?:\\s|$)";var s=e.Node.prototype.ancestor;e.Node.prototype.ancestor=function(t,n){if(e.Lang.isString(t)){var o=r.exec(t);if(o&&o.length)return this.getAncestorByClassName(o[1],n);if(i.test(t))return this.getAncestorByTagName(t,n)}return s.apply(this,arguments)},e.Node.prototype.getAncestorByClassName=function(t,n){var r=this._node;n||(r=r.parentNode);while(r&&!e.DOM.hasClass(r,t)){r=r.parentNode;if(!r||!r.tagName)return null}return e.one(r)},e.Node.prototype.getAncestorByTagName=function(t,n){var r=this._node;n||(r=r.parentNode),t=t.toLowerCase();while(r&&r.tagName.toLowerCase()!=t){r=r.parentNode;if(!r||!r.tagName)return null}return e.one(r)},e.Node.prototype.getElementsByClassName=function(t,n){var r=e.Node.getDOMNode(this).getElementsByTagName(n||"*"),i=new e.NodeList;for(var s=0;s<r.length;s++){var o=r[s];e.DOM.hasClass(o,t)&&i.push(o)}return i},e.Node.prototype.getFirstElementByClassName=function(t,n){if(!n||n=="*"||n=="div"){var r=[e.Node.getDOMNode(this)],i=[];while(r.length){for(var s=0;s<r.length;s++){var o=r[s],u=o.children||o.childNodes;for(var a=0;a<u.length;a++){var f=u[a];if(e.DOM.hasClass(f,t))return e.one(f);i.push(f)}}r=i,i=[]}}else{var l=e.Node.getDOMNode(this).getElementsByTagName(n||"*");for(var s=0;s<l.length;s++){var f=l[s];if(e.DOM.hasClass(f,t))return e.one(f)}}return null}},"@VERSION@",{requires:["node-base"]});
diff --git a/js/yui3/gallery-object-extras/gallery-object-extras-min.js b/js/yui3/gallery-object-extras/gallery-object-extras-min.js
new file mode 100644
index 000000000..608ae8f57
--- /dev/null
+++ b/js/yui3/gallery-object-extras/gallery-object-extras-min.js
@@ -0,0 +1 @@
+YUI.add("gallery-object-extras",function(e,t){"use strict";e.mix(e.Object,{every:function(e,t,n,r){for(var i in e)if((r||e.hasOwnProperty(i))&&!t.call(n,e[i],i,e))return!1;return!0},filter:function(e,t,n,r){var i={};for(var s in e){var o=e[s];(r||e.hasOwnProperty(s))&&t.call(n,o,s,e)&&(i[s]=o)}return i},find:function(e,t,n,r){for(var i in e){var s=e[i];if((r||e.hasOwnProperty(i))&&t.call(n,s,i,e))return s}return null},keyOf:function(e,t,n){for(var r in e)if((n||e.hasOwnProperty(r))&&e[r]===t)return r;return null},invoke:function(t,n){var r=e.Array(arguments,2,!0),i={};for(var s in t){var o=t[s];t.hasOwnProperty(s)&&e.Lang.isFunction(o[n])&&(i[s]=o[n].apply(o,r))}return i},map:function(e,t,n,r){var i={};for(var s in e)if(r||e.hasOwnProperty(s))i[s]=t.call(n,e[s],s,e);return i},partition:function(e,t,n,r){var i={matches:{},rejects:{}};for(var s in e){var o=e[s];if(r||e.hasOwnProperty(s)){var u=t.call(n,o,s,e)?i.matches:i.rejects;u[s]=o}}return i},reduce:function(e,t,n,r,i){var s=t;for(var o in e)if(i||e.hasOwnProperty(o))s=n.call(r,s,e[o],o,e);return s},reject:function(t,n,r,i){return e.Object.filter(t,function(e,t,i){return!n.call(r,e,t,i)},r,i)},zip:function(t,n){var r={};return e.Array.each(t,function(e,t){r[e.toString()]=n[t]}),r}}),e.Object.reduceRight=e.Object.reduce,e.mix(e.Array,{toObject:function(t,n){var r={};return e.Array.each(t,function(e){r[e[n]]=e}),r}})},"@VERSION@",{requires:[""],optional:["gallery-funcprog"]});