~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/registry/javascript/distroseries/widgets.js

[r=allenap][bug=809786] Split out the non-DistroSeries-specific
        widgets from lp.registry.distroseries.widgets into a new
        lp.all.formwidgets module.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 * Copyright 2011 Canonical Ltd. This software is licensed under the
3
3
 * GNU Affero General Public License version 3 (see the file LICENSE).
4
4
 *
5
 
 * DistroSeries related stuff.
 
5
 * DistroSeries Widgets.
6
6
 *
7
 
 * @module registry
8
 
 * @submodule distroseries
 
7
 * @module lp.registry.distroseries
 
8
 * @submodule widgets
9
9
 */
10
10
 
11
11
YUI.add('lp.registry.distroseries.widgets', function(Y) {
14
14
 
15
15
var namespace = Y.namespace('lp.registry.distroseries.widgets');
16
16
 
17
 
 
18
 
/**
19
 
 * A form row matching that which LaunchpadForm presents, containing a
20
 
 * field (defined in a subclass), and an optional label and
21
 
 * description.
22
 
 *
23
 
 * @class FormRowWidget
24
 
 */
25
 
var FormRowWidget;
26
 
 
27
 
FormRowWidget = function() {
28
 
    FormRowWidget.superclass.constructor.apply(this, arguments);
29
 
};
30
 
 
31
 
Y.mix(FormRowWidget, {
32
 
 
33
 
    NAME: 'formRowWidget',
34
 
 
35
 
    ATTRS: {
36
 
 
37
 
        /**
38
 
         * The field name.
39
 
         *
40
 
         * @property name
41
 
         */
42
 
        name: {
43
 
            setter: function(value, name) {
44
 
                this.fieldNode.all("input, select").set("name", value);
45
 
            }
46
 
        },
47
 
 
48
 
        /**
49
 
         * The top label for the field.
50
 
         *
51
 
         * @property label
52
 
         */
53
 
        label: {
54
 
            getter: function() {
55
 
                return this.labelNode.get("text");
56
 
            },
57
 
            setter: function(value, name) {
58
 
                this.labelNode.set("text", value);
59
 
            }
60
 
        },
61
 
 
62
 
        /**
63
 
         * A dictionary {link:link, text:text} to populate
64
 
         * the pop-up help for the field.
65
 
         *
66
 
         * @property help
67
 
         */
68
 
        help: {
69
 
            getter: function() {
70
 
                return {link:this.helpNode.one('a')
71
 
                            .get("href"),
72
 
                        text:this.helpNode
73
 
                            .one('.invisible-link')
74
 
                            .get("text")};
75
 
            },
76
 
            setter: function(value, name) {
77
 
                if ((value.link !== undefined) &&
78
 
                    (value.text !== undefined)) {
79
 
                    this.helpNode.one('a').set("href", value.link);
80
 
                    this.helpNode.one('.invisible-link')
81
 
                        .set("text", value.text);
82
 
                    this.helpNode.removeClass('unseen');
83
 
                }
84
 
                else {
85
 
                    this.helpNode.addClass('unseen');
86
 
                }
87
 
            }
88
 
        },
89
 
 
90
 
        /**
91
 
         * A description shown near the field.
92
 
         *
93
 
         * @label description
94
 
         */
95
 
        description: {
96
 
            getter: function() {
97
 
                return this.descriptionNode.get("text");
98
 
            },
99
 
            setter: function(value, name) {
100
 
                this.descriptionNode.set("text", value);
101
 
            }
102
 
        }
103
 
    }
104
 
 
105
 
});
106
 
 
107
 
Y.extend(FormRowWidget, Y.Widget, {
108
 
 
109
 
    BOUNDING_TEMPLATE: "<tr></tr>",
110
 
 
111
 
    CONTENT_TEMPLATE: '<td colspan="2"></td>',
112
 
 
113
 
    initializer: function(config) {
114
 
        this.labelNode = Y.Node.create("<label />");
115
 
        this.helpNode = Y.Node.create(('<span class="helper unseen">'+
116
 
            '&nbsp;<a href=""' +
117
 
            'target="help" class="sprite maybe">&nbsp;' +
118
 
            '<span class="invisible-link"></span></a></span>'));
119
 
        this.fieldNode = Y.Node.create("<div></div>");
120
 
        this.descriptionNode = Y.Node.create('<p class="formHelp" />');
121
 
        this.spinnerNode = Y.Node.create(
122
 
            '<img src="/@@/spinner" alt="Loading..." />');
123
 
    },
124
 
 
125
 
    renderUI: function() {
126
 
        this.get("contentBox")
127
 
            .append(this.labelNode)
128
 
            .append(this.helpNode)
129
 
            .append(this.fieldNode)
130
 
            .append(this.descriptionNode);
131
 
    },
132
 
 
133
 
    /**
134
 
     * Show the spinner.
135
 
     *
136
 
     * @method showSpinner
137
 
     */
138
 
    showSpinner: function() {
139
 
        this.fieldNode.empty().append(this.spinnerNode);
140
 
    },
141
 
 
142
 
    /**
143
 
     * Hide the spinner.
144
 
     *
145
 
     * @method hideSpinner
146
 
     */
147
 
    hideSpinner: function() {
148
 
        this.spinnerNode.remove();
149
 
    },
150
 
 
151
 
    /**
152
 
     * Display an error.
153
 
     *
154
 
     * @method showError
155
 
     */
156
 
    showError: function(error) {
157
 
        var message = Y.Node.create('<p />').set("text", error);
158
 
        this.fieldNode.empty().append(message);
159
 
        Y.lazr.anim.red_flash({node: message}).run();
160
 
    }
161
 
 
162
 
});
163
 
 
164
 
namespace.FormRowWidget = FormRowWidget;
 
17
var formwidgets = Y.lp.app.formwidgets;
 
18
 
165
19
 
166
20
/**
167
21
 * A table to display, order, delete the selected parent series. Each parent
249
103
    }
250
104
});
251
105
 
252
 
Y.extend(ParentSeriesListWidget, FormRowWidget, {
 
106
Y.extend(ParentSeriesListWidget, formwidgets.FormRowWidget, {
253
107
 
254
108
    initializer: function() {
255
109
        ParentSeriesListWidget.superclass.initializer();
465
319
 
466
320
 
467
321
/**
468
 
 * A form row matching that which LaunchpadForm presents, containing a
469
 
 * list of checkboxes, and an optional label and description.
470
 
 *
471
 
 * @class ChoiceListWidget
472
 
 */
473
 
var ChoiceListWidget;
474
 
 
475
 
ChoiceListWidget = function() {
476
 
    ChoiceListWidget.superclass.constructor.apply(this, arguments);
477
 
};
478
 
 
479
 
Y.mix(ChoiceListWidget, {
480
 
 
481
 
    NAME: 'choiceListWidget',
482
 
 
483
 
    ATTRS: {
484
 
 
485
 
        /**
486
 
         * An array of strings from which to choose.
487
 
         *
488
 
         * @property choices
489
 
         */
490
 
        choices: {
491
 
            getter: function() {
492
 
                return this.fieldNode.all("li > input").get("value");
493
 
            },
494
 
            setter: function(value, name) {
495
 
                var choices = Y.Array.unique(value).sort();
496
 
                var list = Y.Node.create("<ul />");
497
 
                var self = this;
498
 
                choices.forEach(
499
 
                    function(choice) {
500
 
                       var item = self._createChoice(choice);
501
 
                       list.append(item);
502
 
                    }
503
 
                );
504
 
                this.fieldNode.empty().append(list);
505
 
            }
506
 
        },
507
 
 
508
 
        /**
509
 
         * The current selection.
510
 
         *
511
 
         * @property choice
512
 
         */
513
 
        choice: {
514
 
            setter: function(value, name) {
515
 
                if (!Y.Lang.isArray(value)) {
516
 
                    value = [value];
517
 
                }
518
 
                this.fieldNode.all("li > input").each(
519
 
                    function(node) {
520
 
                        node.set(
521
 
                            "checked",
522
 
                            value.indexOf(node.get("value")) >= 0);
523
 
                    }
524
 
                );
525
 
            },
526
 
            getter: function() {
527
 
                var choice = [];
528
 
                this.fieldNode.all("li > input").each(
529
 
                    function(node) {
530
 
                        if (node.get("checked")) {
531
 
                            choice.push(node.get("value"));
532
 
                        }
533
 
                    }
534
 
                );
535
 
                if (this.get("type") === "radio") {
536
 
                    if (choice.length === 0) {
537
 
                        choice = null;
538
 
                    }
539
 
                    else if (choice.length === 1) {
540
 
                        choice = choice[0];
541
 
                    }
542
 
                    else {
543
 
                        choice = undefined;
544
 
                    }
545
 
                }
546
 
                return choice;
547
 
            }
548
 
        },
549
 
 
550
 
        /**
551
 
         * The input type to display. Choose from "checkbox" or "radio".
552
 
         *
553
 
         * @property type
554
 
         */
555
 
        type: {
556
 
            value: "checkbox",
557
 
            setter: function(value, name) {
558
 
                this.fieldNode.all("li > input").set("type", value);
559
 
            }
560
 
        }
561
 
 
562
 
    }
563
 
 
564
 
});
565
 
 
566
 
Y.extend(ChoiceListWidget, FormRowWidget, {
567
 
 
568
 
    /**
569
 
     * Helper method to create an entry for the select widget.
570
 
     *
571
 
     * @method _createChoice
572
 
     */
573
 
    _createChoice: function(choice) {
574
 
         var field_name = this.get("name");
575
 
         var field_type = this.get("type");
576
 
         var item = Y.Node.create(
577
 
            "<li><input /> <label /></li>");
578
 
        item.one("input")
579
 
            .set("type", field_type)
580
 
            .set("name", field_name)
581
 
            .set("value", choice);
582
 
        item.one("label")
583
 
            .setAttribute(
584
 
                "for", item.one("input").generateID())
585
 
            .setStyle("font-weight", "normal")
586
 
            .set("text", choice);
587
 
        return item;
588
 
    },
589
 
 
590
 
   /**
591
 
     * Remove a list of choices from the possible widget's choices.
592
 
     *
593
 
     * @method remove_choices
594
 
     */
595
 
    remove_choices: function(choices) {
596
 
        choices.forEach(
597
 
            function(choice) {
598
 
                this.fieldNode.all("select > option").each(
599
 
                    function(option) { options.push(option); });
600
 
                this.fieldNode.all(
601
 
                    "li input[value=" + choice + "]").each(
602
 
                        function(li_input) {
603
 
                            li_input.get('parentNode').remove();
604
 
                        }
605
 
                );
606
 
            },
607
 
            this
608
 
        );
609
 
        Y.lazr.anim.green_flash({node: this.fieldNode}).run();
610
 
    },
611
 
 
612
 
    _sorted_position: function(choice) {
613
 
        var options = [];
614
 
        this.fieldNode.all("input").each(
615
 
            function(node) {
616
 
                options.push(node.get('value'));
617
 
            }
618
 
        );
619
 
        options.push(choice);
620
 
        return options.sort().indexOf(choice);
621
 
    },
622
 
 
623
 
   /**
624
 
     * Add new choices (if they are not already present).
625
 
     *
626
 
     * @method add_choices
627
 
     */
628
 
    add_choices: function(new_choices) {
629
 
        new_choices.forEach(
630
 
            function(choice) {
631
 
                if (this.fieldNode.all(
632
 
                    "li > input[value=" + choice + "]").isEmpty()) {
633
 
                    var list = this.fieldNode.one('ul');
634
 
                    if (list === null) {
635
 
                        list = Y.Node.create("<ul />");
636
 
                        this.fieldNode.empty().append(list);
637
 
                    }
638
 
                    var option = this._createChoice(choice);
639
 
                    var options = list.all('input');
640
 
                    if (options.isEmpty()) {
641
 
                        list.append(option);
642
 
                    }
643
 
                    else {
644
 
                        var pos = this._sorted_position(choice);
645
 
                        if (pos === 0) {
646
 
                            list.prepend(option);
647
 
                        }
648
 
                        else {
649
 
                            list.insertBefore(option, options.item(pos));
650
 
                        }
651
 
                    }
652
 
                }
653
 
            }, this
654
 
        );
655
 
        Y.lazr.anim.green_flash({node: this.fieldNode}).run();
656
 
    }
657
 
 
658
 
});
659
 
 
660
 
 
661
 
namespace.ChoiceListWidget = ChoiceListWidget;
662
 
 
663
 
 
664
 
/**
665
322
 * A special form of ChoiceListWidget for choosing architecture tags.
666
323
 *
667
324
 * @class ArchitecturesChoiceListWidget
682
339
 
683
340
});
684
341
 
685
 
Y.extend(ArchitecturesChoiceListWidget, ChoiceListWidget, {
 
342
Y.extend(ArchitecturesChoiceListWidget, formwidgets.ChoiceListWidget, {
686
343
 
687
344
    initializer: function(config) {
688
345
        this.client = new Y.lp.client.Launchpad();
787
444
 
788
445
 
789
446
/**
790
 
 * A special form of FormRowWidget, containing a select control.
791
 
 *
792
 
 * @class SelectWidget
793
 
 */
794
 
var SelectWidget;
795
 
 
796
 
SelectWidget = function() {
797
 
    SelectWidget.superclass.constructor.apply(this, arguments);
798
 
};
799
 
 
800
 
Y.mix(SelectWidget, {
801
 
 
802
 
    NAME: 'selectWidget',
803
 
 
804
 
    ATTRS: {
805
 
 
806
 
        /**
807
 
         * An array of objects from which to choose. Each object
808
 
         * should contain a value for "value", "text" and "data".
809
 
         *
810
 
         * @property choices
811
 
         */
812
 
        choices: {
813
 
            getter: function() {
814
 
                /* I think this is a YUI3 wart; I can't see any way to
815
 
                   map() over a NodeList, so I must push the elements
816
 
                   one by one into an array first. */
817
 
                var options = Y.Array([]);
818
 
                this.fieldNode.all("select > option").each(
819
 
                    function(option) { options.push(option); });
820
 
                return options.map(
821
 
                    function(option) {
822
 
                        return {
823
 
                            value: option.get("value"),
824
 
                            text: option.get("text"),
825
 
                            data: option.getData("data")
826
 
                        };
827
 
                    }
828
 
                );
829
 
            },
830
 
            setter: function(value, name) {
831
 
                var select = Y.Node.create("<select />");
832
 
                select.set("name", this.get("name"))
833
 
                      .set("size", this.get("size"));
834
 
                if (this.get("multiple")) {
835
 
                    select.set("multiple", "multiple");
836
 
                }
837
 
                var choices = Y.Array(value);
838
 
                choices.forEach(
839
 
                    function(choice) {
840
 
                        var option = Y.Node.create("<option />");
841
 
                        option.set("value", choice.value)
842
 
                              .set("text", choice.text)
843
 
                              .setData("data", choice.data);
844
 
                        select.append(option);
845
 
                    }
846
 
                );
847
 
                if (choices.length > 0) {
848
 
                    this.fieldNode.empty().append(select);
849
 
                }
850
 
                else {
851
 
                    this.fieldNode.empty();
852
 
                }
853
 
            }
854
 
        },
855
 
 
856
 
        /**
857
 
         * The current selection.
858
 
         *
859
 
         * @property choice
860
 
         */
861
 
        choice: {
862
 
            setter: function(value, name) {
863
 
                if (!Y.Lang.isArray(value)) {
864
 
                    value = [value];
865
 
                }
866
 
                this.fieldNode.all("select > option").each(
867
 
                    function(node) {
868
 
                        node.set(
869
 
                            "selected",
870
 
                            value.indexOf(node.get("value")) >= 0);
871
 
                    }
872
 
                );
873
 
            },
874
 
            getter: function() {
875
 
                var choice = [];
876
 
                this.fieldNode.all("select > option").each(
877
 
                    function(node) {
878
 
                        if (node.get("selected")) {
879
 
                            choice.push(node.get("value"));
880
 
                        }
881
 
                    }
882
 
                );
883
 
                return choice;
884
 
            }
885
 
        },
886
 
 
887
 
        /**
888
 
         * The number of rows to show in the select widget.
889
 
         *
890
 
         * @property size
891
 
         */
892
 
        size: {
893
 
            value: 1,
894
 
            setter: function(value, name) {
895
 
                this.fieldNode.all("select").set("size", value);
896
 
            }
897
 
        },
898
 
 
899
 
        /**
900
 
         * Whether multiple rows can be selected.
901
 
         *
902
 
         * @property multiple
903
 
         */
904
 
        multiple: {
905
 
            value: false,
906
 
            setter: function(value, name) {
907
 
                value = value ? true : false;
908
 
                this.fieldNode.all("select").set("multiple", value);
909
 
                return value;
910
 
            }
911
 
        }
912
 
 
913
 
    }
914
 
 
915
 
});
916
 
 
917
 
Y.extend(SelectWidget, FormRowWidget, {
918
 
 
919
 
    _sorted_position: function(choice) {
920
 
        var options = [];
921
 
        this.fieldNode.all("option").each(
922
 
            function(node) {
923
 
                options.push(node.get('text'));
924
 
            }
925
 
        );
926
 
        options.push(choice);
927
 
        return options.sort().indexOf(choice);
928
 
    },
929
 
 
930
 
    /**
931
 
     * Choose a size for the select control based on the number of
932
 
     * choices, up to an optional maximum size.
933
 
     *
934
 
     * @method autoSize
935
 
     */
936
 
    autoSize: function(maxSize) {
937
 
        var choiceCount = this.fieldNode.all("select > option").size();
938
 
        if (choiceCount === 0) {
939
 
            this.set("size", 1);
940
 
        }
941
 
        else if (maxSize === undefined) {
942
 
            this.set("size", choiceCount);
943
 
        }
944
 
        else if (choiceCount < maxSize) {
945
 
            this.set("size", choiceCount);
946
 
        }
947
 
        else {
948
 
            this.set("size", maxSize);
949
 
        }
950
 
        return this;
951
 
    }
952
 
 
953
 
});
954
 
 
955
 
namespace.SelectWidget = SelectWidget;
956
 
 
957
 
 
958
 
/**
959
447
 * A special form of SelectWidget for choosing packagesets.
960
448
 *
961
449
 * @class PackagesetPickerWidget
1001
489
});
1002
490
 
1003
491
 
1004
 
Y.extend(PackagesetPickerWidget, SelectWidget, {
 
492
Y.extend(PackagesetPickerWidget, formwidgets.SelectWidget, {
1005
493
 
1006
494
    /**
1007
495
     * Add a distroseries: add its packagesets to the packageset picker.
1152
640
namespace.PackagesetPickerWidget = PackagesetPickerWidget;
1153
641
 
1154
642
 
1155
 
/**
1156
 
 * A widget to encapsulate functionality around the form actions.
1157
 
 *
1158
 
 * @class FormActionsWidget
1159
 
 */
1160
 
var FormActionsWidget;
1161
 
 
1162
 
FormActionsWidget = function() {
1163
 
    FormActionsWidget
1164
 
        .superclass.constructor.apply(this, arguments);
1165
 
};
1166
 
 
1167
 
FormActionsWidget.ATTRS = {
1168
 
    duration: {
1169
 
        value: 1.0
1170
 
    },
1171
 
 
1172
 
    height: {
1173
 
        value: 0
1174
 
    },
1175
 
 
1176
 
    opacity: {
1177
 
        value: 0
1178
 
    }
1179
 
};
1180
 
 
1181
 
 
1182
 
Y.mix(FormActionsWidget, {
1183
 
 
1184
 
    NAME: 'formActionsWidget',
1185
 
 
1186
 
    HTML_PARSER: {
1187
 
        submitButtonNode: "input[type=submit]"
1188
 
    }
1189
 
 
1190
 
});
1191
 
 
1192
 
Y.extend(FormActionsWidget, Y.Widget, {
1193
 
 
1194
 
    initializer: function(config) {
1195
 
        this.client = new Y.lp.client.Launchpad();
1196
 
        this.error_handler = new Y.lp.client.ErrorHandler();
1197
 
        this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
1198
 
        this.error_handler.showError = Y.bind(this.showError, this);
1199
 
        this.submitButtonNode = config.submitButtonNode;
1200
 
        this.spinnerNode = Y.Node.create(
1201
 
            '<img src="/@@/spinner" alt="Loading..." />');
1202
 
    },
1203
 
 
1204
 
    /**
1205
 
     * Show the spinner, and hide the submit button.
1206
 
     *
1207
 
     * @method showSpinner
1208
 
     */
1209
 
    showSpinner: function() {
1210
 
        this.submitButtonNode.replace(this.spinnerNode);
1211
 
    },
1212
 
 
1213
 
    /**
1214
 
     * Hide the spinner, and show the submit button again.
1215
 
     *
1216
 
     * @method hideSpinner
1217
 
     */
1218
 
    hideSpinner: function() {
1219
 
        this.spinnerNode.replace(this.submitButtonNode);
1220
 
    },
1221
 
 
1222
 
    /**
1223
 
     * Display an error.
1224
 
     *
1225
 
     * @method showError
1226
 
     */
1227
 
    showError: function(error) {
1228
 
        Y.Node.create('<p class="error message" />')
1229
 
            .appendTo(this.get("contentBox"))
1230
 
            .set("text", error);
1231
 
    },
1232
 
 
1233
 
    /**
1234
 
     * Remove all errors that have been previously displayed by showError.
1235
 
     *
1236
 
     * @method hideErrors
1237
 
     */
1238
 
    hideErrors: function(error) {
1239
 
        this.get("contentBox").all("p.error.message").remove();
1240
 
    }
1241
 
 
1242
 
});
1243
 
 
1244
 
namespace.FormActionsWidget = FormActionsWidget;
1245
 
 
1246
 
}, "0.1", {"requires": ["node", "dom", "io", "widget", "lp.client",
1247
 
                        "lazr.anim", "array-extras", "transition"]});
 
643
}, "0.1", {"requires": [
 
644
               "node", "dom", "io", "widget", "lp.client",
 
645
               "lp.app.formwidgets", "lazr.anim", "array-extras",
 
646
               "transition"]});