~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/app/javascript/lazr/effects/effects-async.js

[r=deryck][bug=803954] Bring lazr-js source into lp tree and package
        yui as a dependency

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
    Copyright (c) 2009, Canonical Ltd.  All rights reserved.
 
3
 
 
4
    This program is free software: you can redistribute it and/or modify
 
5
    it under the terms of the GNU Affero General Public License as published by
 
6
    the Free Software Foundation, either version 3 of the License, or
 
7
    (at your option) any later version.
 
8
 
 
9
    This program is distributed in the hope that it will be useful,
 
10
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
    GNU Affero General Public License for more details.
 
13
 
 
14
    You should have received a copy of the GNU Affero General Public License
 
15
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
16
*/
 
17
 
 
18
YUI.add('lazr.effects-async', function(Y) {
 
19
 
 
20
/**
 
21
 * A quick and simple effect for revealing blocks of text when you click
 
22
 * on them.  The first click fetches the content using an AJAX request,
 
23
 * after which the widget acts like a regular sliding-reveal.
 
24
 *
 
25
 * @module lazr.effects
 
26
 * @submodule async
 
27
 * @namespace lazr.effects
 
28
 */
 
29
 
 
30
Y.namespace('lazr.effects');
 
31
 
 
32
var effects = Y.lazr.effects;
 
33
var ui      = Y.lazr.ui;
 
34
var FOLDED  = 'lazr-folded';
 
35
 
 
36
 
 
37
/**
 
38
 * A quick and simple effect for revealing blocks of asynchronously loaded
 
39
 * content when you click on them.  The first click fetches the content using
 
40
 * an AJAX request, after which the widget acts like a regular sliding-reveal.
 
41
 *
 
42
 * The function tracks the state of the initial content load by setting the
 
43
 * <code>content_loaded</code> attribute on the container object.  The
 
44
 * attribute will be set to <code>true</code> after the initial load
 
45
 * completes.
 
46
 *
 
47
 * The trigger recieves the 'lazr-trigger' class, and the content
 
48
 * receives 'lazr-content'.
 
49
 *
 
50
 * Both the trigger and content nodes receive the 'lazr-folded' class whenever
 
51
 * the content is closed.
 
52
 *
 
53
 * The container may also obtain the 'lazr-waiting' and 'lazr-io-error'
 
54
 * classes during the asynchronous data fetch.
 
55
 *
 
56
 * @method async_slideout
 
57
 * @public
 
58
 * @param slider {Node} The node that will slide open and closed, and hold the
 
59
 *  asynchronous content.
 
60
 * @param trigger {Node} The node that we will clicked on to open and close
 
61
*   the slider.
 
62
 * @param uri {String} The URI to fetch the content from.
 
63
 * @param container {Node} <i>Optional</i> A child of the sliding
 
64
 *  container node that will hold the asynchronous content.
 
65
 */
 
66
Y.lazr.effects.async_slideout = function(slider, trigger, uri, container) {
 
67
    // The slider is busted in IE 7 :(
 
68
    if (Y.UA.ie) {
 
69
        return;
 
70
    }
 
71
 
 
72
    // Prepare our object state.
 
73
    slider = Y.one(slider);
 
74
    if (typeof slider.content_loaded == 'undefined') {
 
75
        slider.content_loaded = false;
 
76
    }
 
77
 
 
78
    if (typeof container == 'undefined' || container === null) {
 
79
        // The user didn't give us an explict target container for the new
 
80
        // content, so we'll reuse the sliding container node.
 
81
        container = slider;
 
82
    }
 
83
 
 
84
    trigger.addClass(FOLDED);
 
85
    trigger.addClass('lazr-trigger');
 
86
    slider.addClass(FOLDED);
 
87
    slider.addClass('lazr-content');
 
88
 
 
89
    trigger.on('click', function(e) {
 
90
        e.halt();
 
91
 
 
92
        trigger.toggleClass(FOLDED);
 
93
        container.toggleClass(FOLDED);
 
94
 
 
95
        if (!container.content_loaded) {
 
96
            fetch_and_reveal_content(slider, container, uri);
 
97
            container.content_loaded = true;
 
98
        } else {
 
99
            animate_drawer(slider);
 
100
        }
 
101
    });
 
102
};
 
103
 
 
104
/*
 
105
 * Slide the content in or out by reversing the slider.fx animation object.
 
106
 */
 
107
function animate_drawer(slider) {
 
108
    slider.fx.stop();
 
109
    slider.fx.set('reverse', !slider.fx.get("reverse"));
 
110
    slider.fx.run();
 
111
}
 
112
 
 
113
/*
 
114
 * Fetch the slide-out drawer's data asynchronously, unset the waiting state,
 
115
 * and fill the container with either the new content or an appropriate error
 
116
 * message.  Finally, slide the drawer to fit its new contents.
 
117
 */
 
118
function fetch_and_reveal_content(slider, container, uri) {
 
119
 
 
120
    var cfg = {
 
121
        on: {
 
122
            complete: function() {
 
123
                ui.clear_waiting(container);
 
124
            },
 
125
            success: function(id, response) {
 
126
                container.set('innerHTML', response.responseText);
 
127
                slider.fx.stop();
 
128
                slider.fx = effects.slide_out(slider);
 
129
                slider.fx.run();
 
130
            },
 
131
            failure: function(id, response, args) {
 
132
                // Undo the slide animation's changes to the container style.
 
133
                slider.setStyles({
 
134
                    height:   'auto',
 
135
                    overflow: 'visible'
 
136
                });
 
137
                show_nice_error(id, response, args, container, run_io);
 
138
                Y.lazr.anim.red_flash({ node: slider }).run();
 
139
 
 
140
                // If the user clicks the collapse trigger, we want to slide
 
141
                // the drawer back in.  But doing so first reverses the
 
142
                // animation, then runs it (because it assumes that slider.fx
 
143
                // is a effects.slide_out() object), so we need to reverse
 
144
                // our effects.slide_in() animation, so its state is the same
 
145
                // as if it were an open effects.slide_out().
 
146
                slider.fx.stop();
 
147
                slider.fx = effects.slide_in(slider);
 
148
                slider.fx.set('reverse', !slider.fx.get('reverse'));
 
149
            }
 
150
        }
 
151
    };
 
152
 
 
153
    // Wrap this in a closure, so we can retry it if there is an error.
 
154
    function run_io() {
 
155
        ui.waiting(container);
 
156
        container.set('innerHTML', '');
 
157
        // Slide out enough to fully show the spinner.
 
158
        slider.fx = effects.slide_out(slider, { to: { height: '20px' } });
 
159
        slider.fx.run();
 
160
 
 
161
        Y.io(uri, cfg);
 
162
    }
 
163
    run_io();
 
164
}
 
165
 
 
166
/*
 
167
 * Display a nice error message in the specified container if the asynchronous
 
168
 * data request failed.
 
169
 *
 
170
 * XXX mars 2009-04-21 bug=364612
 
171
 *
 
172
 * Need to move this to lazr.io.
 
173
 */
 
174
function show_nice_error(id, response, args, message_container,
 
175
    retry_callback) {
 
176
    var status_msg = '<span class="io-status">' +
 
177
        response.status + ' ' +
 
178
        response.statusText +
 
179
        '</span>';
 
180
    var msg_html =
 
181
        ['<div class="lazr-io-error">',
 
182
         '<p>Communication with the server failed</p>',
 
183
         '<p>The server\'s response was: ' + status_msg + '</p>',
 
184
         '<button title="Try to contact the server again">Retry</button>',
 
185
         '</div>'].join('');
 
186
 
 
187
    message_container.set('innerHTML', msg_html);
 
188
 
 
189
    // Hook up our Retry function.
 
190
    message_container.one('button').on('click', function(e) {
 
191
        e.halt();
 
192
        retry_callback();
 
193
    });
 
194
}
 
195
 
 
196
 
 
197
}, null, { "requires":["node", "event", "io-base", "lazr.base", "lazr.effects",
 
198
                       "lazr.anim"]});