Index: Dev/trunk/src/client/qed-client/model/widgets/TabbedQuestionBrowser.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/widgets/TabbedQuestionBrowser.js	(revision 494)
+++ Dev/trunk/src/client/qed-client/model/widgets/TabbedQuestionBrowser.js	(revision 495)
@@ -1,129 +1,44 @@
 define([
-    "../../widgets/Selector",
+    "../../widgets/TabbedBrowser",
     "../classes/categories",
     "../classes/questions",
     "../classes/topics",
-    "dijit/layout/ContentPane",
-    "dijit/layout/TabContainer",
+    "dojo/_base/array",
     "dojo/_base/declare",
-    "dojo/_base/lang",
-    "dojo/_base/window",
-    "dojox/widget/Standby"
-], function(Selector, categories, questions, topics, ContentPane, TabContainer, declare, lang, win, Standby) {
-    return declare([TabContainer],{
+    "dojo/_base/lang"
+], function(TabbedBrowser, categories, questions, topics, array, declare, lang) {
+    return declare([TabbedBrowser],{
         tabPosition: 'left-h',
-        include: 'all',
-
-        selectedActions: null,
-        itemActions: null,
+        include: null,
         dndType: "question",
-
-        _dataMap: null,
-        _busyCount: 0,
-        constructor: function(){
-            this.inherited(arguments);
-            this._dataMap = {};
+        getCategories: function(){
+            return categories.query();
         },
-        startup: function() {
-            if ( this._started ){ return; }
-            this.inherited(arguments);
-            this._busyWidget = new Standby({
-                target: this.domNode,
-                duration: 200
-            }).placeAt(win.body());
-            this._busyWidget.startup();
-            this.watch("selectedChildWidget",lang.hitch(this,function(name,oldTab,newTab){
-                this._fillCategoryTab(newTab.__category);
-            }));
-            categories.query()
-            .forEach(lang.hitch(this,function(cat){
-                this._createCategoryTab(cat.name,cat.count);
+        getCategoryName: function(category){ return category.name; },
+        getCategoryCount: function(category){ return category.count; },
+        getTopics: function(category) {
+            return topics.query({category:category.name});
+        },
+        getTopicName: function(topic){ return topic.name; },
+        getTopicCount: function(topic){ return topic.count; },
+        getItems: function(category,topic) {
+            return questions.query({category:category.name,topic:topic.name})
+            .then(lang.hitch(this,function(items){
+                return array.filter(items, function(item){
+                    return (this.include === 'published' && item.publicationDate) ||
+                           (this.include === 'drafts' && !item.publicationDate) ||
+                           true;
+                }, this);
             }));
         },
-        _createCategoryTab: function(category,count) {
-            if (this._dataMap[category] === undefined) {
-                var categoryTab = new ContentPane({
-                    __category: category,
-                    title: (category || '[No category]')+" ("+count+")"
-                });
-                categoryTab.startup();
-                this._dataMap[category] = {
-                    _widget: categoryTab
-                };
-                this.addChild(categoryTab);
-            }
-        },
-        _fillCategoryTab: function(category) {
-            var categoryMap = this._dataMap[category];
-            if (!categoryMap._filled) {
-                this._busy();
-                categoryMap._filled = true;
-                topics.query({category:category})
-                .forEach(lang.hitch(this,function(topic){
-                    this._createTopicSelector(topic.name,category,topic.count);
-                })).then(lang.hitch(this,function(){
-                    this._done();
-                }));
-            }
-        },
-        _createTopicSelector: function(topic,category,count){
-            var categoryMap = this._dataMap[category];
-            if (categoryMap[topic] === undefined) {
-                var w = new Selector({
-                    __category: category,
-                    __topic: topic,
-                    dndType: this.dndType,
-                    title: (topic || '[No topic]')+" ("+count+")",
-                    selectedActions: this.selectedActions,
-                    itemActions: this.itemActions,
-                    itemTitle: this._itemTitle
-                }).placeAt(categoryMap._widget.containerNode);
-                w.startup();
-                categoryMap[topic] = {
-                    _widget: w
-                };
-                this._fillTopicSelector(topic,category);
-            }
-        },
-        _itemTitle: function(item) {
+        getItemName: function(item) {
             var title = '['+item.code+']'+item.title;
-            if ( this.include === 'all' ) {
-                title += (item.publicationDate?' (published on '+item.publicationDate+')':' (unpublished)');
+            if ( !this.include ) {
+                title += (item.publicationDate ?' (published on '+item.publicationDate+')':' (unpublished)');
             }
             return title;
         },
-        _fillTopicSelector: function(topic,category) {
-            var categoryMap = this._dataMap[category];
-            var topicMap = categoryMap[topic];
-            if (!topicMap._filled) {
-                topicMap._filled = true;
-                this._busy();
-                questions.query({category:category,topic:topic})
-                .forEach(lang.hitch(this,function(value){
-                    topicMap._widget.addItem(value);
-                })).then(lang.hitch(this,function(){
-                    this._done();
-                }));
-            }
-        },
-        _busy: function() {
-            if ( this._busyCount === 0 ) {
-                this._busyWidget.show();
-            }
-            this._busyCount++;
-        },
-        _done: function() {
-            if ( this._busyCount > 0 ) {
-                this._busyCount--;
-                if ( this._busyCount === 0 ) {
-                    this._busyWidget.hide();
-                }
-            } else {
-                console.warn('_done() was called more times than _busy().');
-            }
-        },
-        destroy: function() {
-            this._busyWidget.destroyRecursive();
-            this.inherited(arguments);
+        areItemsEqual: function(i1,i2) {
+            return questions.getId(i1) === questions.getId(i2);
         }
     });
Index: Dev/trunk/src/client/qed-client/model/widgets/questions/MultipleChoiceInputWidget.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/widgets/questions/MultipleChoiceInputWidget.js	(revision 494)
+++ Dev/trunk/src/client/qed-client/model/widgets/questions/MultipleChoiceInputWidget.js	(revision 495)
@@ -35,5 +35,5 @@
                 return value;
             } else {
-                return value.choice;
+                return value.choice.length > 0;
             }
         },
@@ -43,5 +43,5 @@
                 inherited.call(this,value);
             } else {
-                inherited.call(this,{choice:value});
+                inherited.call(this,{choice:value ? ["on"] : []});
             }
         }
Index: Dev/trunk/src/client/qed-client/pages/questions.js
===================================================================
--- Dev/trunk/src/client/qed-client/pages/questions.js	(revision 494)
+++ Dev/trunk/src/client/qed-client/pages/questions.js	(revision 495)
@@ -19,21 +19,28 @@
                 'class': 'orange',
                 dndType: null,
-                itemActions: {
-                    Delete: {
-                        callback: lang.hitch(this,"onDeleteQuestion"),
-                        icon: 'Delete',
-                        description: 'Delete question'
-                    },
-                    Edit: {
-                        callback: lang.hitch(this,"onEditQuestion"),
-                        icon: 'Edit',
-                        description: 'Edit question'
-                    },
-                    Publish: {
-                        callback: lang.hitch(this,"onPublishQuestion"),
-                        icon: 'Publish',
-                        description: 'Publish question'
+                getItemActions: lang.hitch(this,function(item) {
+                    var actions = {
+                        Edit: {
+                            callback: lang.hitch(this,"onEditQuestion"),
+                            icon: 'Edit',
+                            description: 'Edit question'
+                        }
+                    };
+                    if ( !item.publicationDate ) {
+                        lang.mixin(actions,{
+                            Delete: {
+                                callback: lang.hitch(this,"onDeleteQuestion"),
+                                icon: 'Delete',
+                                description: 'Delete question'
+                            },
+                            Publish: {
+                                callback: lang.hitch(this,"onPublishQuestion"),
+                                icon: 'Publish',
+                                description: 'Publish question'
+                            }
+                        });
                     }
-                }
+                    return actions;
+                })
             },this.questionBrowser);
             this.questionBrowser.startup();
@@ -45,4 +52,5 @@
             questions.remove(question)
             .then(lang.hitch(this,function(){
+                this.questionBrowser.removeItem(question);
                 this.notify("Question deleted.");
             }),lang.hitch(this,function(err){
Index: Dev/trunk/src/client/qed-client/pages/survey.js
===================================================================
--- Dev/trunk/src/client/qed-client/pages/survey.js	(revision 494)
+++ Dev/trunk/src/client/qed-client/pages/survey.js	(revision 495)
@@ -31,20 +31,24 @@
                 'class': 'blue',
                 include: 'published',
-                selectedActions: {
-                    "Include": {
-                        callback: lang.hitch(this,this._includeQuestion),
-                        icon: "Accept",
-                        description: "Include in survey"
-                    }
-                },
-                itemActions: {
-                    "Info": {
-                        callback: function(item){
-                            if ( item.description ) { alert(item.description); }
-                        },
-                        icon: "Inspect",
-                        description: "Show item description"
-                    }
-                }
+                getSelectedItemActions: lang.hitch(this,function() {
+                    return {
+                        "Include": {
+                            callback: lang.hitch(this,this._includeQuestion),
+                            icon: "Accept",
+                            description: "Include in survey"
+                        }
+                    };
+                }),
+                getItemActions: lang.hitch(this,function(item) {
+                    return {
+                        "Info": {
+                            callback: lang.hitch(this,function(item){
+                                if ( item.description ) { alert(item.description); }
+                            }),
+                            icon: "Inspect",
+                            description: "Show item description"
+                        }
+                    };
+                })
             },this.questionBrowser);
             this.questionBrowser.startup();
Index: Dev/trunk/src/client/qed-client/widgets/Selector.js
===================================================================
--- Dev/trunk/src/client/qed-client/widgets/Selector.js	(revision 494)
+++ Dev/trunk/src/client/qed-client/widgets/Selector.js	(revision 495)
@@ -64,12 +64,16 @@
             var actions = {};
             var action = null;
-            if ( this.selectedActions !== null ) {
-                for (var actionName in this.selectedActions) {
-                    if ( this.selectedActions.hasOwnProperty(actionName) ) {
-                        action = this.selectedActions[actionName];
+            var userActions =
+                    lang.isFunction(this.selectedActions) ?
+                    this.selectedActions() :
+                    this.selectedActions;
+            if ( userActions ) {
+                for (var actionName in userActions) {
+                    if ( userActions.hasOwnProperty(actionName) ) {
+                        action = userActions[actionName];
                         actions[actionName] = {
                             callback: action.callback &&
-                                    lang.hitch(this,this._onSelectedAction,
-                                            action.callback),
+                                      lang.hitch(this,this._onSelectedAction,
+                                                 action.callback),
                             properties: {
                                 blockButton: true,
@@ -135,5 +139,5 @@
         _onSelectedAction: function(callback) {
             if (this._selectedItem && callback) {
-                callback(this._selectedItem);
+                callback(this._selectedItem,this);
             }
         },
@@ -171,4 +175,18 @@
             }
         },
+        removeItem: function(item) {
+            var nodes = this._source ?
+                        this._source.getAllNodes() :
+                        this.optionsNode.childNodes;
+            baseArray.forEach(nodes, function(node) {
+                var widget = registry.byNode(node);
+                if ( widget && this.itemEquals ?
+                               this.itemEquals(widget.__item,item) :
+                               widget.__item === item ) {
+                    widget.destroyRecursive();
+                    if ( this._source ) { this._source.delItem(widget.id); }
+                }
+            }, this);
+        },
         _createNode: function(item, hint) {
             var node = hint === "avatar" ?
@@ -188,11 +206,15 @@
         _createLineNode: function(item) {
             var actions = {};
-            var action;
-            if (this.itemActions) {
-                for (var actionName in this.itemActions) {
-                    if ( this.itemActions.hasOwnProperty(actionName) ) {
-                        action = this.itemActions[actionName];
+            var userActions =
+                    lang.isFunction(this.itemActions) ?
+                    this.itemActions(item) :
+                    this.itemActions;
+            if (userActions) {
+                for (var actionName in userActions) {
+                    if ( userActions.hasOwnProperty(actionName) ) {
+                        var action = userActions[actionName];
                         actions[actionName] = {
-                            callback: action.callback && lang.partial(action.callback,item),
+                            callback: action.callback &&
+                                      lang.partial(action.callback,item,this),
                             properties: {
                                 blockButton: false,
@@ -207,5 +229,6 @@
             var w = new LineWithActionsWidget({
                 title: get(this.itemTitle,item),
-                actions: actions
+                actions: actions,
+                __item: item
             });
             w.startup();
@@ -214,5 +237,10 @@
         },
 
-        onSelect: function(/*item*/) {}
+        onSelect: function(/*item*/) {},
+
+        destroy: function() {
+            if ( this._source ) { this._source.destroy(); }
+            this.inherited(arguments);
+        }
     });
 });
Index: Dev/trunk/src/client/qed-client/widgets/TabbedBrowser.js
===================================================================
--- Dev/trunk/src/client/qed-client/widgets/TabbedBrowser.js	(revision 495)
+++ Dev/trunk/src/client/qed-client/widgets/TabbedBrowser.js	(revision 495)
@@ -0,0 +1,156 @@
+define([
+    "./Selector",
+    "dijit/layout/ContentPane",
+    "dijit/layout/TabContainer",
+    "dojo/_base/array",
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "dojo/_base/window",
+    "dojo/when",
+    "dojox/widget/Standby"
+], function(Selector, ContentPane, TabContainer, array, declare, lang, win, when, Standby) {
+    return declare([TabContainer],{
+        tabPosition: 'left-h',
+
+        // interface the client must implement
+        getCategories: function(){ return []; },
+        getCategoryName: function(category){ return ""; },
+        getCategoryCount: function(category){ return 0; },
+        getTopics: function(category){ return []; },
+        getTopicName: function(topic){ return "";},
+        getTopicCount: function(topic){ return 0; },
+        getItems: function(category,topic){ return []; },
+        getItemName: function(item){ return ""; },
+        getItemActions: function(item){ return {}; }, // {name:{'callback'(item,sel),'icon','description'}}
+        getSelectedItemActions: function(){ return {}; }, // {name:{'callback'(item,sel),'icon','description'}}
+        areItemsEqual: function(i1,i2){ return i1 === i2; },
+
+        dndType: "",
+
+        _dataMap: null,
+        _busyCount: 0,
+        constructor: function() {
+            this._dataMap = {};
+        },
+        startup: function() {
+            if ( this._started ){ return; }
+            this.inherited(arguments);
+            this._busyWidget = new Standby({
+                target: this.domNode,
+                duration: 200
+            }).placeAt(win.body());
+            this._busyWidget.startup();
+            this.watch("selectedChildWidget",lang.hitch(this,'_handleChangeTab'));
+            when(this.getCategories())
+            .then(lang.hitch(this,'_addCategories'));
+        },
+        _addCategories: function(categories) {
+            array.forEach(categories, function(category) {
+                this._addCategory(category);
+            }, this);
+        },
+        _handleChangeTab: function(name,oldTab,newTab) {
+            this._fillCategory(newTab.__category);
+        },
+        _addCategory: function(category) {
+            var name = this.getCategoryName(category);
+            var count = this.getCategoryCount(category);
+            if (this._dataMap[name] === undefined) {
+                var tab = new ContentPane({
+                    __category: category,
+                    title: (name||'[No category]')+(count&&' ('+count+')')
+                });
+                tab.startup();
+                this._dataMap[name] = {
+                    _widget: tab
+                };
+                this.addChild(tab);
+            }
+        },
+        _fillCategory: function(category) {
+            var name = this.getCategoryName(category);
+            var map = this._dataMap[name];
+            if (!map._filled) {
+                this._busy();
+                map._filled = true;
+                when(this.getTopics(category))
+                .then(lang.hitch(this,'_addTopicsToCategory',category))
+                .always(lang.hitch(this,'_done'));
+            }
+        },
+        _addTopicsToCategory: function(category,topics) {
+            array.forEach(topics, function(topic) {
+                var cname = this.getCategoryName(category);
+                var tname = this.getTopicName(topic);
+                this._addTopicToCategory(category,topic);
+            }, this);
+        },
+        _addTopicToCategory: function(category,topic) {
+            var map = this._dataMap[this.getCategoryName(category)];
+            var name = this.getTopicName(topic);
+            var count = this.getTopicCount(topic);
+            if (map[name] === undefined) {
+                var w = new Selector({
+                    __category: category,
+                    __topic: topic,
+                    dndType: this.dndType,
+                    title: (name||'[No topic]')+(count&&" ("+count+")"),
+                    selectedActions: lang.hitch(this,'getSelectedItemActions'),
+                    itemActions: lang.hitch(this,'getItemActions'),
+                    itemTitle: lang.hitch(this,'getItemName'),
+                    itemEquals: lang.hitch(this,'areItemsEqual')
+                }).placeAt(map._widget.containerNode);
+                w.startup();
+                map[name] = {
+                    _widget: w
+                };
+                this._fillTopicInCategory(category,topic);
+            }
+        },
+        _fillTopicInCategory: function(category,topic) {
+            var map = this._dataMap[this.getCategoryName(category)][this.getTopicName(topic)];
+            if (!map._filled) {
+                map._filled = true;
+                this._busy();
+                when(this.getItems(category,topic))
+                .then(lang.hitch(this,function(items){
+                    array.forEach(items,map._widget.addItem,map._widget);
+                })).always(lang.hitch(this,'_done'));
+            }
+        },
+        removeItem: function(item) {
+            var cmap = this._dataMap;
+            for ( var cat in cmap ) {
+                if ( cmap.hasOwnProperty(cat) ) {
+                    var tmap = cmap[cat];
+                    for ( var top in tmap ) {
+                        if ( tmap.hasOwnProperty(top) ) {
+                            var widget = tmap[top]._widget;
+                            if ( widget ) { widget.removeItem(item); }
+                        }
+                    }
+                }
+            }
+        },
+        _busy: function() {
+            if ( this._busyCount === 0 ) {
+                this._busyWidget.show();
+            }
+            this._busyCount++;
+        },
+        _done: function() {
+            if ( this._busyCount > 0 ) {
+                this._busyCount--;
+                if ( this._busyCount === 0 ) {
+                    this._busyWidget.hide();
+                }
+            } else {
+                console.warn('_done() was called more times than _busy().');
+            }
+        },
+        destroy: function() {
+            this._busyWidget.destroyRecursive();
+            this.inherited(arguments);
+        }
+    });
+});
Index: Dev/trunk/src/server/app.js
===================================================================
--- Dev/trunk/src/server/app.js	(revision 494)
+++ Dev/trunk/src/server/app.js	(revision 495)
@@ -148,5 +148,5 @@
 
     function identity(obj) { return obj; }
-    function handleUnknownResponse(status,error){
+    function handleUnknownResponse(status,error) {
         return new HTTPResult(500,{error: error.reason});
     }
@@ -444,4 +444,15 @@
 
     // Categories and topics
+    function getTopicsWithCategory(category) {
+        var url = '_design/questions/_view/all';
+        return HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true,group_level:2,startkey:[category],endkey:[category,{}]}}).response,
+                                       handleUnknownError)
+        .handle({
+            200: function(result) {
+                return _.map(result.rows, function(item) { return { name:item.key[1], count:item.value }; });
+            },
+            default: handleUnknownResponse
+        });
+    }
     app.get('/api/categories',
         ensureAuthenticated,
@@ -466,13 +477,5 @@
         function(req,res) {
             var category = req.params.category;
-            var url = '_design/questions/_view/all';
-            HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true,group_level:2,startkey:[category],endkey:[category,{}]}}).response,
-                                           handleUnknownError)
-            .handle({
-                200: function(result) {
-                    return _.map(result.rows, function(item) { return { name:item.key[1], count:item.value }; });
-                },
-                default: handleUnknownResponse
-            })
+            getTopicsWithCategory(category)
             .handle(res.send.bind(res));
         });
@@ -482,13 +485,18 @@
         function(req,res) {
             var url = '_design/questions/_view/all_topics';
-            HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true}}).response,
-                                           handleUnknownError)
-            .handle({
-                200: function(result) {
-                    return _.map(result.rows, function(item) { return {name:item.key, count:item.value}; });
-                },
-                default: handleUnknownResponse
-            })
-            .handle(res.send.bind(res));
+            var hr;
+            if ( 'category' in req.query ) {
+                hr = getTopicsWithCategory(req.query.category);
+            } else {
+                hr = HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true}}).response,
+                                                    handleUnknownError)
+                .handle({
+                    200: function(result) {
+                        return _.map(result.rows, function(item) { return {name:item.key, count:item.value}; });
+                    },
+                    default: handleUnknownResponse
+                });
+            }
+            hr.handle(res.send.bind(res));
         });
 
