Make zuul javascript app more modular

Refactor the zuul status app to be more modular. This allows the
controlling page (eg index.html) to load in only the components
it requires. This will make it easier to format just one patchset
or change where the layout controls are etc.

This is to work towards overlaying zuul results in gerrit.

Change-Id: Idfba9f08c25850c340cc962fa0f5f9652306b877
diff --git a/etc/status/public_html/app.js b/etc/status/public_html/app.js
index 8616201..d6f93c8 100644
--- a/etc/status/public_html/app.js
+++ b/etc/status/public_html/app.js
@@ -19,19 +19,6 @@
 'use strict';
 
 (function ($) {
-    var $container, $msg, $indicator, $queueInfo, $queueEventsNum,
-        $queueResultsNum, $pipelines, $jq;
-    var xhr, zuul,
-        zuul_graph_update_count = 0,
-        zuul_sparkline_urls = {},
-        current_filter = '',
-        demo = location.search.match(/[?&]demo=([^?&]*)/),
-        source_url = location.search.match(/[?&]source_url=([^?&]*)/),
-        source = demo ?
-            './status-' + (demo[1] || 'basic') + '.json-sample' :
-            'status.json';
-    source = source_url ? source_url[1] : source;
-
     function set_cookie(name, value) {
         document.cookie = name + '=' + value + '; path=/';
     }
@@ -51,96 +38,50 @@
         return default_value;
     }
 
+    $.zuul = function(options) {
+        var options = $.extend({
+            'enabled': true,
+            'graphite_url': '',
+            'source': 'status.json',
+            'msg_id': '#zuul_msg',
+            'pipelines_id': '#zuul_pipelines',
+            'queue_events_num': '#zuul_queue_events_num',
+            'queue_results_num': '#zuul_queue_results_num',
+        }, options);
 
-    zuul = {
-        enabled: true,
-        graphite_url: '',
-        collapsed_exceptions: [],
+        var collapsed_exceptions = [];
+        var current_filter = read_cookie('zuul_filter_string', current_filter);
+        var $jq;
 
-        schedule: function () {
-            if (!zuul.enabled) {
-                setTimeout(zuul.schedule, 5000);
-                return;
-            }
-            zuul.update().complete(function () {
-                setTimeout(zuul.schedule, 5000);
-            });
+        var xhr,
+            zuul_graph_update_count = 0,
+            zuul_sparkline_urls = {};
 
-            /* Only update graphs every minute */
-            if (zuul_graph_update_count > 11) {
-                zuul_graph_update_count = 0;
-                zuul.update_sparklines();
-            }
-        },
-
-        /** @return {jQuery.Promise} */
-        update: function () {
-            // Cancel the previous update if it hasn't completed yet.
-            if (xhr) {
-                xhr.abort();
-            }
-
-            zuul.emit('update-start');
-
-            xhr = $.getJSON(source)
-                .done(function (data) {
-                    if ('message' in data) {
-                        $msg.removeClass('alert-danger').addClass('alert-info');
-                        $msg.text(data.message);
-                        $msg.show();
-                    } else {
-                        $msg.empty();
-                        $msg.hide();
-                    }
-
-                    if ('zuul_version' in data) {
-                        $('#zuul-version-span').text(data.zuul_version);
-                    }
-                    if ('last_reconfigured' in data) {
-                        var last_reconfigured =
-                            new Date(data.last_reconfigured);
-                        $('#last-reconfigured-span').text(
-                            last_reconfigured.toString());
-                    }
-
-                    $pipelines.html('');
-                    $.each(data.pipelines, function (i, pipeline) {
-                        $pipelines.append(zuul.format.pipeline(pipeline));
+        function get_sparkline_url(pipeline_name) {
+            if (options.graphite_url !== '') {
+                if (!(pipeline_name in zuul_sparkline_urls)) {
+                    zuul_sparkline_urls[pipeline_name] = $.fn.graphite
+                        .geturl({
+                        url: options.graphite_url,
+                        from: "-8hours",
+                        width: 100,
+                        height: 26,
+                        margin: 0,
+                        hideLegend: true,
+                        hideAxes: true,
+                        hideGrid: true,
+                        target: [
+                            "color(stats.gauges.zuul.pipeline." + pipeline_name
+                            + ".current_changes, '6b8182')"
+                        ]
                     });
+                }
+                return zuul_sparkline_urls[pipeline_name];
+            }
+            return false;
+        }
 
-                    $queueEventsNum.text(
-                        data.trigger_event_queue ?
-                            data.trigger_event_queue.length : '0'
-                    );
-                    $queueResultsNum.text(
-                        data.result_event_queue ?
-                            data.result_event_queue.length : '0'
-                    );
-                })
-                .fail(function (err, jqXHR, errMsg) {
-                    $msg.text(source + ': ' + errMsg).show();
-                    $msg.removeClass('zuul-msg-wrap-off');
-                })
-                .complete(function () {
-                    xhr = undefined;
-                    zuul.emit('update-end');
-                });
-
-            return xhr;
-        },
-
-        update_sparklines: function() {
-            $.each(zuul_sparkline_urls, function(name, url) {
-                var newimg = new Image();
-                var parts = url.split('#');
-                newimg.src = parts[0] + '#' + new Date().getTime();
-                $(newimg).load(function (x) {
-                    zuul_sparkline_urls[name] = newimg.src;
-                });
-            });
-        },
-
-        format: {
+        var format = {
             job: function(job) {
                 var $job_line = $('<span />');
 
@@ -160,7 +101,7 @@
                     );
                 }
 
-                $job_line.append(zuul.format.job_status(job));
+                $job_line.append(this.job_status(job));
 
                 if (job.voting === false) {
                     $job_line.append(
@@ -180,11 +121,11 @@
                 }
 
                 if (result === 'in progress') {
-                    return zuul.format.job_progress_bar(job.elapsed_time,
+                    return this.job_progress_bar(job.elapsed_time,
                                                         job.remaining_time);
                 }
                 else {
-                    return zuul.format.status_label(result);
+                    return this.status_label(result);
                 }
             },
 
@@ -237,7 +178,7 @@
                 var now = Date.now();
                 var delta = now - ms;
                 var status = 'text-success';
-                var text = zuul.format.time(delta, true);
+                var text = this.time(delta, true);
                 if (delta > (4 * hours)) {
                     status = 'text-danger';
                 } else if (delta > (2 * hours)) {
@@ -338,7 +279,7 @@
                     .append($change_link);
                 var $change_progress_row_right = $('<div />')
                     .addClass('col-xs-9')
-                    .append(zuul.format.change_total_progress_bar(change));
+                    .append(this.change_total_progress_bar(change));
 
                 var $change_progress_row = $('<div />')
                     .addClass('row')
@@ -353,9 +294,9 @@
                     .addClass('col-xs-8')
                     .append($project_span, $change_progress_row);
 
-                var remaining_time = zuul.format.time(
+                var remaining_time = this.time(
                         change.remaining_time, true);
-                var enqueue_time = zuul.format.enqueue_time(
+                var enqueue_time = this.enqueue_time(
                         change.enqueue_time);
                 var $remaining_time = $('<small />').addClass('time')
                     .attr('title', 'Remaining Time').html(remaining_time);
@@ -373,6 +314,7 @@
             },
 
             change_list: function(jobs) {
+                var format = this;
                 var $list = $('<ul />')
                     .addClass('list-group zuul-patchset-body');
 
@@ -380,7 +322,7 @@
                     var $item = $('<li />')
                         .addClass('list-group-item')
                         .addClass('zuul-change-job')
-                        .append(zuul.format.job(job));
+                        .append(format.job(job));
                     $list.append($item);
                 });
 
@@ -390,7 +332,7 @@
             change_panel: function (change) {
                 var $header = $('<div />')
                     .addClass('panel-heading zuul-patchset-header')
-                    .append(zuul.format.change_header(change));
+                    .append(this.change_header(change));
 
                 var panel_id = change.id ? change.id.replace(',', '_')
                                          : change.project.replace('/', '_') +
@@ -399,9 +341,9 @@
                     .attr('id', panel_id)
                     .addClass('panel panel-default zuul-change')
                     .append($header)
-                    .append(zuul.format.change_list(change.jobs));
+                    .append(this.change_list(change.jobs));
 
-                $header.click(zuul.toggle_patchset);
+                $header.click(this.toggle_patchset);
                 return $panel;
             },
 
@@ -458,7 +400,7 @@
 
                     if (i === change._tree_index) {
                         $tree_cell.append(
-                            zuul.format.change_status_icon(change));
+                            this.change_status_icon(change));
                     }
                     if (change._tree_branches.indexOf(i) !== -1) {
                         var $image = $('<img />')
@@ -481,7 +423,7 @@
                 var $change_column = $('<td />')
                     .css('width', change_width + 'px')
                     .addClass('zuul-change-cell')
-                    .append(zuul.format.change_panel(change));
+                    .append(this.change_panel(change));
 
                 $change_row.append($change_column);
 
@@ -495,10 +437,10 @@
             },
 
             pipeline_sparkline: function(pipeline_name) {
-                if (zuul.graphite_url !== '') {
+                if (options.graphite_url !== '') {
                     var $sparkline = $('<img />')
                         .addClass('pull-right')
-                        .attr('src', zuul.get_sparkline_url(pipeline_name));
+                        .attr('src', get_sparkline_url(pipeline_name));
                     return $sparkline;
                 }
                 return false;
@@ -519,7 +461,7 @@
                             .css('margin-top', '0.5em')
                             .text(count)
                     )
-                    .append(zuul.format.pipeline_sparkline(pipeline.name));
+                    .append(this.pipeline_sparkline(pipeline.name));
 
                 $header_div.append($heading);
 
@@ -533,11 +475,11 @@
                 return $header_div;
             },
 
-            pipeline: function (pipeline) {
-                var count = zuul.create_tree(pipeline);
+            pipeline: function (pipeline, count) {
+                var format = this;
                 var $html = $('<div />')
                     .addClass('zuul-pipeline col-md-4')
-                    .append(zuul.format.pipeline_header(pipeline, count));
+                    .append(this.pipeline_header(pipeline, count));
 
                 $.each(pipeline.change_queues,
                        function (queue_i, change_queue) {
@@ -562,16 +504,215 @@
 
                         $.each(changes, function (change_i, change) {
                             var $change_box =
-                                zuul.format.change_with_status_tree(
+                                format.change_with_status_tree(
                                     change, change_queue);
                             $html.append($change_box);
-                            zuul.display_patchset($change_box);
+                            format.display_patchset($change_box);
                         });
                     });
                 });
                 return $html;
             },
 
+            toggle_patchset: function(e) {
+                // Toggle showing/hiding the patchset when the header is
+                // clicked.
+
+                // Grab the patchset panel
+                var $panel = $(e.target).parents('.zuul-change');
+                var $body = $panel.children('.zuul-patchset-body');
+                $body.toggle(200);
+                var collapsed_index = collapsed_exceptions.indexOf(
+                    $panel.attr('id'));
+                if (collapsed_index === -1 ) {
+                    // Currently not an exception, add it to list
+                    collapsed_exceptions.push($panel.attr('id'));
+                }
+                else {
+                    // Currently an except, remove from exceptions
+                    collapsed_exceptions.splice(collapsed_index, 1);
+                }
+            },
+
+            display_patchset: function($change_box, animate) {
+                // Determine if to show or hide the patchset and/or the results
+                // when loaded
+
+                // See if we should hide the body/results
+                var $panel = $change_box.find('.zuul-change');
+                var panel_change = $panel.attr('id');
+                var $body = $panel.children('.zuul-patchset-body');
+                var expand_by_default = $('#expand_by_default')
+                    .prop('checked');
+
+                var collapsed_index = collapsed_exceptions
+                    .indexOf(panel_change);
+
+                if (expand_by_default && collapsed_index === -1 ||
+                    !expand_by_default && collapsed_index !== -1) {
+                    // Expand by default, or is an exception
+                    $body.show(animate);
+                }
+                else {
+                    $body.hide(animate);
+                }
+
+                // Check if we should hide the whole panel
+                var panel_project = $panel.find('.change_project').text()
+                    .toLowerCase();
+
+
+                var panel_pipeline = $change_box
+                    .parents('.zuul-pipeline')
+                    .find('.zuul-pipeline-header > h3')
+                    .html()
+                    .toLowerCase();
+
+                if (current_filter !== '') {
+                    var show_panel = false;
+                    var filter = current_filter.trim().split(/[\s,]+/);
+                    $.each(filter, function(index, f_val) {
+                        if (f_val !== '') {
+                            f_val = f_val.toLowerCase();
+                            if (panel_project.indexOf(f_val) !== -1 ||
+                                panel_pipeline.indexOf(f_val) !== -1 ||
+                                panel_change.indexOf(f_val) !== -1) {
+                                show_panel = true;
+                            }
+                        }
+                    });
+                    if (show_panel === true) {
+                        $change_box.show(animate);
+                    }
+                    else {
+                        $change_box.hide(animate);
+                    }
+                }
+                else {
+                    $change_box.show(animate);
+                }
+            },
+        };
+
+        var app = {
+            schedule: function (app) {
+                var app = app || this;
+                if (!options.enabled) {
+                    setTimeout(function() {app.schedule(app);}, 5000);
+                    return;
+                }
+                app.update().complete(function () {
+                    setTimeout(function() {app.schedule(app);}, 5000);
+                });
+
+                /* Only update graphs every minute */
+                if (zuul_graph_update_count > 11) {
+                    zuul_graph_update_count = 0;
+                    zuul.update_sparklines();
+                }
+            },
+
+            /** @return {jQuery.Promise} */
+            update: function () {
+                // Cancel the previous update if it hasn't completed yet.
+                if (xhr) {
+                    xhr.abort();
+                }
+
+                this.emit('update-start');
+                var app = this;
+
+                var $msg = $(options.msg_id)
+                xhr = $.getJSON(options.source)
+                    .done(function (data) {
+                        if ('message' in data) {
+                            $msg.removeClass('alert-danger')
+                                .addClass('alert-info')
+                                .text(data.message)
+                                .show();
+                        } else {
+                            $msg.empty()
+                                .hide();
+                        }
+
+                        if ('zuul_version' in data) {
+                            $('#zuul-version-span').text(data.zuul_version);
+                        }
+                        if ('last_reconfigured' in data) {
+                            var last_reconfigured =
+                                new Date(data.last_reconfigured);
+                            $('#last-reconfigured-span').text(
+                                last_reconfigured.toString());
+                        }
+
+                        var $pipelines = $(options.pipelines_id);
+                        $pipelines.html('');
+                        $.each(data.pipelines, function (i, pipeline) {
+                            var count = app.create_tree(pipeline);
+                            $pipelines.append(
+                                format.pipeline(pipeline, count));
+                        });
+
+                        $(options.queue_events_num).text(
+                            data.trigger_event_queue ?
+                                data.trigger_event_queue.length : '0'
+                        );
+                        $(options.queue_results_num).text(
+                            data.result_event_queue ?
+                                data.result_event_queue.length : '0'
+                        );
+                    })
+                    .fail(function (err, jqXHR, errMsg) {
+                        $msg.text(source + ': ' + errMsg).show();
+                        $msg.removeClass('zuul-msg-wrap-off');
+                    })
+                    .complete(function () {
+                        xhr = undefined;
+                        app.emit('update-end');
+                    });
+
+                return xhr;
+            },
+
+            update_sparklines: function() {
+                $.each(zuul_sparkline_urls, function(name, url) {
+                    var newimg = new Image();
+                    var parts = url.split('#');
+                    newimg.src = parts[0] + '#' + new Date().getTime();
+                    $(newimg).load(function (x) {
+                        zuul_sparkline_urls[name] = newimg.src;
+                    });
+                });
+            },
+
+            emit: function () {
+                $jq.trigger.apply($jq, arguments);
+                return this;
+            },
+            on: function () {
+                $jq.on.apply($jq, arguments);
+                return this;
+            },
+            one: function () {
+                $jq.one.apply($jq, arguments);
+                return this;
+            },
+
+            control_form: function() {
+                // Build the filter form filling anything from cookies
+
+                var $control_form = $('<form />')
+                    .attr('role', 'form')
+                    .addClass('form-inline')
+                    .submit(this.handle_filter_change);
+
+                $control_form
+                    .append(this.filter_form_group())
+                    .append(this.expand_form_group());
+
+                return $control_form;
+            },
+
             filter_form_group: function() {
                 // Update the filter form with a clear button if required
 
@@ -590,7 +731,7 @@
                           'separated')
                     .attr('value', current_filter);
 
-                $input.change(zuul.handle_filter_change);
+                $input.change(this.handle_filter_change);
 
                 var $clear_icon = $('<span />')
                     .addClass('form-control-feedback')
@@ -621,7 +762,7 @@
                     .attr('type', 'checkbox')
                     .attr('id', 'expand_by_default')
                     .prop('checked', expand_by_default)
-                    .change(zuul.handle_expand_by_default);
+                    .change(this.handle_expand_by_default);
 
                 var $label = $('<label />')
                     .css('padding-left', '1em')
@@ -634,284 +775,103 @@
                 return $form_group;
             },
 
-            control_form: function() {
-                // Build the filter form filling anything from cookies
-
-                var $control_form = $('<form />')
-                    .attr('role', 'form')
-                    .addClass('form-inline')
-                    .submit(zuul.handle_filter_change);
-
-                $control_form
-                    .append(zuul.format.filter_form_group())
-                    .append(zuul.format.expand_form_group());
-
-                return $control_form;
-            },
-        },
-
-        emit: function () {
-            $jq.trigger.apply($jq, arguments);
-            return this;
-        },
-        on: function () {
-            $jq.on.apply($jq, arguments);
-            return this;
-        },
-        one: function () {
-            $jq.one.apply($jq, arguments);
-            return this;
-        },
-
-        toggle_patchset: function(e) {
-            // Toggle showing/hiding the patchset when the header is clicked
-            // Grab the patchset panel
-            var $panel = $(e.target).parents('.zuul-change');
-            var $body = $panel.children('.zuul-patchset-body');
-            $body.toggle(200);
-            var collapsed_index = zuul.collapsed_exceptions.indexOf(
-                $panel.attr('id'));
-            if (collapsed_index === -1 ) {
-                // Currently not an exception, add it to list
-                zuul.collapsed_exceptions.push($panel.attr('id'));
-            }
-            else {
-                // Currently an except, remove from exceptions
-                zuul.collapsed_exceptions.splice(collapsed_index, 1);
-            }
-        },
-
-        display_patchset: function($change_box, animate) {
-            // Determine if to show or hide the patchset and/or the results
-            // when loaded
-
-            // See if we should hide the body/results
-            var $panel = $change_box.find('.zuul-change');
-            var panel_change = $panel.attr('id');
-            var $body = $panel.children('.zuul-patchset-body');
-            var expand_by_default = $('#expand_by_default').prop('checked');
-
-            var collapsed_index = zuul.collapsed_exceptions.indexOf(panel_change);
-
-            if (expand_by_default && collapsed_index === -1 ||
-                !expand_by_default && collapsed_index !== -1) {
-                // Expand by default, or is an exception
-                $body.show(animate);
-            }
-            else {
-                $body.hide(animate);
-            }
-
-            // Check if we should hide the whole panel
-            var panel_project = $panel.find('.change_project').text()
-                .toLowerCase();
-
-
-            var panel_pipeline = $change_box
-                .parents('.zuul-pipeline')
-                .find('.zuul-pipeline-header > h3')
-                .html()
-                .toLowerCase();
-
-            if (current_filter !== '') {
-                var show_panel = false;
-                var filter = current_filter.trim().split(/[\s,]+/);
-                $.each(filter, function(index, f_val) {
-                    if (f_val !== '') {
-                        f_val = f_val.toLowerCase();
-                        if (panel_project.indexOf(f_val) !== -1 ||
-                            panel_pipeline.indexOf(f_val) !== -1 ||
-                            panel_change.indexOf(f_val) !== -1) {
-                            show_panel = true;
-                        }
-                    }
-                });
-                if (show_panel === true) {
-                    $change_box.show(animate);
+            handle_filter_change: function() {
+                // Update the filter and save it to a cookie
+                current_filter = $('#filter_string').val();
+                set_cookie('zuul_filter_string', current_filter);
+                if (current_filter === '') {
+                    $('#filter_form_clear_box').hide();
                 }
                 else {
-                    $change_box.hide(animate);
+                    $('#filter_form_clear_box').show();
                 }
-            }
-            else {
-                $change_box.show(animate);
-            }
-        },
 
-        handle_filter_change: function() {
-            // Update the filter and save it to a cookie
-            current_filter = $('#filter_string').val();
-            set_cookie('zuul_filter_string', current_filter);
-            if (current_filter === '') {
-                $('#filter_form_clear_box').hide();
-            }
-            else {
-                $('#filter_form_clear_box').show();
-            }
-
-            $('.zuul-change-box').each(function(index, obj) {
-                var $change_box = $(obj);
-                zuul.display_patchset($change_box, 200);
-            });
-            return false;
-        },
-
-        handle_expand_by_default: function(e) {
-            // Handle toggling expand by default
-            set_cookie('zuul_expand_by_default', e.target.checked);
-            zuul.collapsed_exceptions = [];
-            $('.zuul-change-box').each(function(index, obj) {
-                var $change_box = $(obj);
-                zuul.display_patchset($change_box, 200);
-            });
-        },
-
-        create_tree: function(pipeline) {
-            var count = 0;
-            var pipeline_max_tree_columns = 1;
-            $.each(pipeline.change_queues, function(change_queue_i,
-                                                       change_queue) {
-                var tree = [];
-                var max_tree_columns = 1;
-                var changes = [];
-                var last_tree_length = 0;
-                $.each(change_queue.heads, function(head_i, head) {
-                    $.each(head, function(change_i, change) {
-                        changes[change.id] = change;
-                        change._tree_position = change_i;
-                    });
+                $('.zuul-change-box').each(function(index, obj) {
+                    var $change_box = $(obj);
+                    format.display_patchset($change_box, 200);
                 });
-                $.each(change_queue.heads, function(head_i, head) {
-                    $.each(head, function(change_i, change) {
-                        count += 1;
-                        var idx = tree.indexOf(change.id);
-                        if (idx > -1) {
-                            change._tree_index = idx;
-                            // remove...
-                            tree[idx] = null;
-                            while (tree[tree.length - 1] === null) {
-                                tree.pop();
-                            }
-                        } else {
-                            change._tree_index = 0;
-                        }
-                        change._tree_branches = [];
-                        change._tree = [];
-                        if (typeof(change.items_behind) === 'undefined') {
-                            change.items_behind = [];
-                        }
-                        change.items_behind.sort(function(a, b) {
-                            return (changes[b]._tree_position -
-                                    changes[a]._tree_position);
-                        });
-                        $.each(change.items_behind, function(i, id) {
-                            tree.push(id);
-                            if (tree.length>last_tree_length &&
-                                last_tree_length > 0) {
-                                change._tree_branches.push(
-                                    tree.length - 1);
-                            }
-                        });
-                        if (tree.length > max_tree_columns) {
-                            max_tree_columns = tree.length;
-                        }
-                        if (tree.length > pipeline_max_tree_columns) {
-                            pipeline_max_tree_columns = tree.length;
-                        }
-                        change._tree = tree.slice(0);  // make a copy
-                        last_tree_length = tree.length;
-                    });
-                });
-                change_queue._tree_columns = max_tree_columns;
-            });
-            pipeline._tree_columns = pipeline_max_tree_columns;
-            return count;
-        },
-
-        get_sparkline_url: function(pipeline_name) {
-            if (zuul.graphite_url !== '') {
-                if (!(pipeline_name in zuul_sparkline_urls)) {
-                    zuul_sparkline_urls[pipeline_name] = $.fn.graphite.geturl({
-                        url: zuul.graphite_url,
-                        from: "-8hours",
-                        width: 100,
-                        height: 26,
-                        margin: 0,
-                        hideLegend: true,
-                        hideAxes: true,
-                        hideGrid: true,
-                        target: [
-                            "color(stats.gauges.zuul.pipeline."+pipeline_name+".current_changes, '6b8182')"
-                        ]
-                    });
-                }
-                return zuul_sparkline_urls[pipeline_name];
-            }
-            return false;
-        },
-    };
-
-    current_filter = read_cookie('zuul_filter_string', current_filter);
-
-    $jq = $(zuul);
-
-    $jq.on('update-start', function () {
-        $container.addClass('zuul-container-loading');
-        $indicator.addClass('zuul-spinner-on');
-    });
-
-    $jq.on('update-end', function () {
-        $container.removeClass('zuul-container-loading');
-        setTimeout(function () {
-            $indicator.removeClass('zuul-spinner-on');
-        }, 500);
-    });
-
-    $jq.one('update-end', function () {
-        // Do this asynchronous so that if the first update adds a message, it
-        // will not animate while we fade in the content. Instead it simply
-        // appears with the rest of the content.
-        setTimeout(function () {
-            // Fade in the content
-            $container.addClass('zuul-container-ready');
-        });
-    });
-
-    $(function ($) {
-        $msg = $('<div />').addClass('alert').hide();
-        $indicator = $('<button class="btn pull-right zuul-spinner">' +
-                       'updating ' +
-                       '<span class="glyphicon glyphicon-refresh"></span>' +
-                       '</button>');
-        $queueInfo = $('<p>Queue lengths: <span>0</span> events, ' +
-                       '<span>0</span> results.</p>');
-        $queueEventsNum = $queueInfo.find('span').eq(0);
-        $queueResultsNum = $queueEventsNum.next();
-
-        var $control_form = zuul.format.control_form();
-
-        $pipelines = $('<div class="row"></div>');
-        var $zuulVersion = $('<p>Zuul version: <span id="zuul-version-span">' +
-                         '</span></p>');
-        var $lastReconf = $('<p>Last reconfigured: ' +
-                        '<span id="last-reconfigured-span"></span></p>');
-
-        $container = $('#zuul-container').append($msg, $indicator,
-                                                 $queueInfo, $control_form,
-                                                 $pipelines, $zuulVersion,
-                                                 $lastReconf);
-
-        //zuul.graphite_url = 'http://graphite.openstack.org/render/'
-        zuul.schedule();
-
-        $(document).on({
-            'show.visibility': function () {
-                zuul.enabled = true;
-                zuul.update();
+                return false;
             },
-            'hide.visibility': function () {
-                zuul.enabled = false;
-            }
-        });
-    });
+
+            handle_expand_by_default: function(e) {
+                // Handle toggling expand by default
+                set_cookie('zuul_expand_by_default', e.target.checked);
+                collapsed_exceptions = [];
+                $('.zuul-change-box').each(function(index, obj) {
+                    var $change_box = $(obj);
+                    format.display_patchset($change_box, 200);
+                });
+            },
+
+            create_tree: function(pipeline) {
+                var count = 0;
+                var pipeline_max_tree_columns = 1;
+                $.each(pipeline.change_queues, function(change_queue_i,
+                                                           change_queue) {
+                    var tree = [];
+                    var max_tree_columns = 1;
+                    var changes = [];
+                    var last_tree_length = 0;
+                    $.each(change_queue.heads, function(head_i, head) {
+                        $.each(head, function(change_i, change) {
+                            changes[change.id] = change;
+                            change._tree_position = change_i;
+                        });
+                    });
+                    $.each(change_queue.heads, function(head_i, head) {
+                        $.each(head, function(change_i, change) {
+                            count += 1;
+                            var idx = tree.indexOf(change.id);
+                            if (idx > -1) {
+                                change._tree_index = idx;
+                                // remove...
+                                tree[idx] = null;
+                                while (tree[tree.length - 1] === null) {
+                                    tree.pop();
+                                }
+                            } else {
+                                change._tree_index = 0;
+                            }
+                            change._tree_branches = [];
+                            change._tree = [];
+                            if (typeof(change.items_behind) === 'undefined') {
+                                change.items_behind = [];
+                            }
+                            change.items_behind.sort(function(a, b) {
+                                return (changes[b]._tree_position -
+                                        changes[a]._tree_position);
+                            });
+                            $.each(change.items_behind, function(i, id) {
+                                tree.push(id);
+                                if (tree.length>last_tree_length &&
+                                    last_tree_length > 0) {
+                                    change._tree_branches.push(
+                                        tree.length - 1);
+                                }
+                            });
+                            if (tree.length > max_tree_columns) {
+                                max_tree_columns = tree.length;
+                            }
+                            if (tree.length > pipeline_max_tree_columns) {
+                                pipeline_max_tree_columns = tree.length;
+                            }
+                            change._tree = tree.slice(0);  // make a copy
+                            last_tree_length = tree.length;
+                        });
+                    });
+                    change_queue._tree_columns = max_tree_columns;
+                });
+                pipeline._tree_columns = pipeline_max_tree_columns;
+                return count;
+            },
+        };
+
+        $jq = $(app);
+        return {
+            options: options,
+            format: format,
+            app: app,
+            jq: $jq
+        };
+    }
 }(jQuery));