~deryck/lazr-js/morphing-window-dialog

« back to all changes in this revision

Viewing changes to src-js/lazrjs/actions/actions.js

  • Committer: Launchpad Patch Queue Manager
  • Date: 2009-11-20 16:20:23 UTC
  • mfrom: (147.2.9 actions)
  • Revision ID: launchpad@pqm.canonical.com-20091120162023-99f2zdeb4ywc6qoq
[r=mars][ui=none] New widget for actions that take care of their own
        CSS styling and behavior regulation

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright (c) 2009, Canonical Ltd. All rights reserved. */
 
2
 
 
3
YUI.add('lazr.actions', function(Y) {
 
4
 
 
5
Y.namespace('lazr.actions');
 
6
 
 
7
var ACTION = "action",
 
8
    ACTIONCLASS = "Action",
 
9
    ACTIONS = "actions",
 
10
    ACTIONS_HELPER = "ActionsHelper",
 
11
    ACTIONS_ID = "actionsId",
 
12
    ITEM = "item",
 
13
    ITEMCLASSNAME = "itemClassName",
 
14
    LABEL = "label",
 
15
    LAZR_ACTION_DISABLED = 'lazr-action-disabled',
 
16
    LINK = "link",
 
17
    LINKCLASSNAME = "linkClassName",
 
18
    PERMISSION = "permission",
 
19
    RUNNING = "running",
 
20
    TITLE = "title";
 
21
 
 
22
/*
 
23
 * The Actions and ActionsHelper widgets allow for creating arbitrary collections of behavioral
 
24
 * links and situating them in DOM elements on the page. In the absence of a label attribute,
 
25
 * they can be presented with CSS sprites for graphical representation. When actions are running,
 
26
 * they have their primary linkClassName CSS class replaced with lazr-waiting, which can be
 
27
 * styled as needed (spinner, hidden, greyed-out, &c). Each action can be governed by a
 
28
 * permission, which will fire at the time of rendering and, if failing, decorate the action with
 
29
 * the lazr-action-disabled class, which can be styled as needed (hidden, greyed-out, &c).
 
30
*/
 
31
 
 
32
/*
 
33
 * The ActionsHelper widget collects and delegates Action
 
34
 * widgets associated with a common Node
 
35
 *
 
36
 * @class ActionsHelper
 
37
 */
 
38
var ActionsHelper = function(config) {
 
39
    ActionsHelper.superclass.constructor.apply(this, arguments);
 
40
};
 
41
 
 
42
ActionsHelper.NAME = ACTIONS_HELPER;
 
43
 
 
44
ActionsHelper.ATTRS = {
 
45
    actions: { valueFn: function() { return []; }},
 
46
    actionsId: { valueFn: function() { return Y.guid(); }},
 
47
};
 
48
 
 
49
Y.extend(ActionsHelper, Y.Base, {
 
50
    /**
 
51
     * Render actions
 
52
     * <p>
 
53
     * This method is called to render each of its Actions in turn, in the specified node.
 
54
     * </p>
 
55
     *
 
56
     * @method render
 
57
     * @param node {Node} The node that should contain the ActionsHelper
 
58
     */
 
59
    render: function(node) {
 
60
        var doc = Y.config.doc;
 
61
        var actions = this.get(ACTIONS);
 
62
        var actionsId = this.get(ACTIONS_ID);
 
63
 
 
64
        // Check if we already have an instance of the
 
65
        // container in the DOM.
 
66
        var actionsContainer = Y.one("#" + actionsId);
 
67
 
 
68
        if (actionsContainer) {
 
69
            // If the container already exists in the DOM,
 
70
            // unattach it so that it can be moved to a
 
71
            // new parent.
 
72
            actionsContainer.remove()
 
73
        } else {
 
74
            actionsContainer = new Y.Node.create(
 
75
                "<ul id='" + actionsId + "' />");
 
76
        }
 
77
        // If there are no icons to be displayed, don't bother
 
78
        // creating the container.
 
79
        if (actions.length) {
 
80
            // Render each action as a separate item inside
 
81
            // the container.
 
82
            for (var i=0; i<actions.length; i++){
 
83
                var action = actions[i];
 
84
                action.render(actionsContainer);
 
85
            }
 
86
        }
 
87
 
 
88
        // Finally, if a container was created or an existing
 
89
        // one was found, append it to the Document Fragment
 
90
        // for later attachment to the new destination node.
 
91
        if (actionsContainer !== null) {
 
92
            node.appendChild(Y.Node.getDOMNode(actionsContainer));
 
93
        }
 
94
    },
 
95
});
 
96
 
 
97
Y.lazr.actions.ActionsHelper = ActionsHelper;
 
98
 
 
99
/**
 
100
 * This class provides a self-protecting action, governed by permissions,  attached
 
101
 * to a link that can have its style updated when running.
 
102
 *
 
103
 * @class Action
 
104
 * @constructor
 
105
 */
 
106
var Action = function(config) {
 
107
    Action.superclass.constructor.apply(this, arguments);
 
108
};
 
109
 
 
110
/**
 
111
 * Dictionary of selectors to define subparts of the widget that we care about.
 
112
 * YUI calls ATTRS.set(foo) for each foo defined here
 
113
 *
 
114
 * @property Action.NAME
 
115
 * @type String
 
116
 * @static
 
117
 */
 
118
Action.NAME = ACTIONCLASS;
 
119
 
 
120
Action.ATTRS = {
 
121
    /**
 
122
     * A function representing the underlying behavior of this action.
 
123
     *
 
124
     * @attribute action
 
125
     * @type Function
 
126
     */
 
127
    action: {
 
128
        value: null
 
129
    },
 
130
 
 
131
    /**
 
132
     * A function which runs at render time, evaluating to true or fase, determining
 
133
     * whether or not the action should be disabled.
 
134
     *
 
135
     * @attribute permission
 
136
     * @type Function
 
137
     */
 
138
    permission: {
 
139
        value: null
 
140
    },
 
141
 
 
142
    /**
 
143
     * Optional text label for the Action. If present, it will be the text of the
 
144
     * anchor tag
 
145
     *
 
146
     * @attribute label
 
147
     * @type String
 
148
     */
 
149
    label: {
 
150
        value: null
 
151
    },
 
152
 
 
153
    /**
 
154
     * Title attribute of the inner anchor tag
 
155
     *
 
156
     * @attribute title
 
157
     * @type String
 
158
     */
 
159
    title: {
 
160
        value: null
 
161
    },
 
162
 
 
163
    /**
 
164
     * A special CSS class name for the list element of the Action
 
165
     *
 
166
     * @attribute itemClassName
 
167
     * @type String
 
168
     */
 
169
    itemClassName: {
 
170
        value: null
 
171
    },
 
172
 
 
173
    /**
 
174
     * A special CSS class name for the inner anchor element of the Action, will get
 
175
     * swapped out with Y.lazr.ui.CSS_WAITING when the action is running.
 
176
     *
 
177
     * @attribute linkClassName
 
178
     * @type String
 
179
     */
 
180
    linkClassName: {
 
181
        value: null
 
182
    },
 
183
 
 
184
    /**
 
185
     * A flag determining whether the current Action is engaged in its action
 
186
     *
 
187
     * @attribute running
 
188
     * @type Boolean
 
189
     */
 
190
    running: {
 
191
        value: false,
 
192
        setter: function(v) { return this._updateRunState(v); },
 
193
        getter: function(v) { return v; }
 
194
    },
 
195
 
 
196
    /**
 
197
     * A list element, decorated with our CSS class and with our link attached.
 
198
     *
 
199
     * @attribute item
 
200
     * @type Node
 
201
     */
 
202
    item: {
 
203
        valueFn: function() { return this._createItem(); }
 
204
    },
 
205
 
 
206
    /**
 
207
     * An anchor element, decorated with our CSS class and with our behavior attached.
 
208
     *
 
209
     * @attribute link
 
210
     * @type Node
 
211
     */
 
212
    link: {
 
213
        valueFn: function() { return this._createLink(); }
 
214
    }
 
215
 
 
216
};
 
217
 
 
218
Y.extend(Action, Y.Base, {
 
219
 
 
220
    /**
 
221
     * Helper method to toggle the CSS_WAITING class on the link element
 
222
     *
 
223
     * @method
 
224
     * @private
 
225
     */
 
226
    _updateRunState: function(isRunning) {
 
227
        // when we get set to true:
 
228
        //   - turn our icon to a spinner, if appropriate
 
229
        if (this.get(LINKCLASSNAME) !== null) {
 
230
            if (isRunning) {
 
231
                this.get(LINK).replaceClass(
 
232
                    this.get(LINKCLASSNAME),
 
233
                    Y.lazr.ui.CSS_WAITING);
 
234
            } else {
 
235
                this.get(LINK).replaceClass(
 
236
                    Y.lazr.ui.CSS_WAITING,
 
237
                    this.get(LINKCLASSNAME));
 
238
            }
 
239
        }
 
240
        return isRunning;
 
241
    },
 
242
 
 
243
    /**
 
244
     * Helper method to create the link element, and perform initial decoration
 
245
     *
 
246
     * @method
 
247
     * @private
 
248
     */
 
249
    _createLink: function() {
 
250
        var label = this.get(LABEL);
 
251
        var title = this.get(TITLE);
 
252
        var linkClassName = this.get(LINKCLASSNAME);
 
253
        var link = Y.Node.create(
 
254
        "<a href='#' alt='" + title +
 
255
            "' title='" + title + "'></a>");
 
256
        link.on("click", this.actionRunner, this);
 
257
 
 
258
        if (label !== null) {
 
259
            link.append(Y.Node.create(label));
 
260
        }
 
261
 
 
262
        if (linkClassName !== null) {
 
263
            link.addClass(linkClassName);
 
264
        }
 
265
 
 
266
        return link;
 
267
    },
 
268
 
 
269
    /**
 
270
     * Helper method to create the list item element, and perform initial decoration
 
271
     *
 
272
     * @method
 
273
     * @private
 
274
     */
 
275
    _createItem: function() {
 
276
        var itemClassName = this.get(ITEMCLASSNAME);
 
277
        var link = this.get(LINK);
 
278
        var item = Y.Node.create('<li/>'); 
 
279
 
 
280
        if (itemClassName !== null) {
 
281
            item.addClass(itemClassName);
 
282
        }
 
283
 
 
284
        item.append(link);
 
285
 
 
286
        return item;
 
287
    },
 
288
 
 
289
    /**
 
290
     * Render the action and attach it to a node
 
291
     *
 
292
     * @method render
 
293
     */
 
294
    render: function(node) {
 
295
        // Build the link, labeling it if necessary
 
296
        // Compose the item, decorating it if necessary
 
297
        var item = this.get(ITEM);
 
298
        var permission = this.get(PERMISSION);
 
299
 
 
300
        if (permission && !permission()) {
 
301
            item.addClass(LAZR_ACTION_DISABLED);
 
302
        } else {
 
303
            item.removeClass(LAZR_ACTION_DISABLED);
 
304
        }
 
305
 
 
306
        // Place the item
 
307
        node.append(item);
 
308
    },
 
309
 
 
310
    /**
 
311
     * Wrap the actual function so that it short-circuits if the action is currently
 
312
     * running
 
313
     *
 
314
     * @method actionRunner
 
315
     */
 
316
    actionRunner: function() {
 
317
        if (!this.get(RUNNING)) {
 
318
            // Not running, fire.
 
319
            this.get(ACTION)();
 
320
        }
 
321
    }
 
322
});
 
323
 
 
324
Y.lazr.actions.Action = Action;
 
325
 
 
326
}, "0.1.", {"requires": ["oop", "base", "node", "lazr.base"]});