Index: Dev/trunk/src/client/qed-client/model/widgets/TabbedQuestionBrowser.js
===================================================================
--- Dev/trunk/src/client/qed-client/model/widgets/TabbedQuestionBrowser.js	(revision 500)
+++ Dev/trunk/src/client/qed-client/model/widgets/TabbedQuestionBrowser.js	(revision 501)
@@ -13,22 +13,21 @@
         dndType: "question",
         getCategories: function(){
-            return categories.query();
+            var opts = {};
+            if ( this.include ) { opts[this.include] = true; }
+            return categories.query(opts);
         },
         getCategoryName: function(category){ return category.name; },
         getCategoryCount: function(category){ return category.count; },
         getTopics: function(category) {
-            return topics.query({category:category.name});
+            var opts = {category:category.name};
+            if ( this.include ) { opts[this.include] = true; }
+            return topics.query(opts);
         },
         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);
-            }));
+            var opts = {category:category.name,topic:topic.name};
+            if ( this.include ) { opts[this.include] = true; }
+            return questions.query(opts);
         },
         getItemName: function(item) {
Index: Dev/trunk/src/client/qed-client/widgets/ListWidget.js
===================================================================
--- Dev/trunk/src/client/qed-client/widgets/ListWidget.js	(revision 500)
+++ Dev/trunk/src/client/qed-client/widgets/ListWidget.js	(revision 501)
@@ -9,6 +9,7 @@
     "dojo/dnd/common",
     "dojo/dom-construct",
+    "dojo/_base/event",
     "dojo/on"
-], function(_Container, _WidgetBase, registry, array, declare, lang, Source, dnd, domConstruct, on) {
+], function(_Container, _WidgetBase, registry, array, declare, lang, Source, dnd, domConstruct, event, on) {
     return declare([_WidgetBase,_Container],{
         name: "",
@@ -58,4 +59,8 @@
                         lang.hitch(this,'_handleChange')));
         },
+        create: function() {
+            this.inherited(arguments);
+            this._onChangeActive = true;
+        },
         creator: function(item, hint) {
             var id = dnd.getUniqueId();
@@ -74,5 +79,5 @@
                 }
             }
-            this.own(on(nodeOrWidget,'change',lang.hitch(this,'_handleChange')));
+            //this.own(on(nodeOrWidget,'change',lang.hitch(this,'_handleChange')));
             var node = nodeOrWidget.domNode ? nodeOrWidget.domNode : nodeOrWidget;
             if ( hint !== "avatar" && node.id !== id ) {
@@ -122,9 +127,9 @@
         appendItems: function(items,forceEvent) {
             this.source.insertNodes(false,items);
-            if ( forceEvent ) { this._handleChange(); }
+            if ( forceEvent ) { this._handleDrop(); }
         },
         appendItem: function(item,forceEvent) {
             this.source.insertNodes(false,[item]);
-            if ( forceEvent ) { this._handleChange(); }
+            if ( forceEvent ) { this._handleDrop(); }
         },
         removeItem: function(key,forceEvent) {
@@ -133,5 +138,5 @@
             }), lang.hitch(this, "_destroyNodeOrWidget"));
             this.source.delItem(key);
-            if ( forceEvent ) { this._handleChange(); }
+            if ( forceEvent ) { this._handleDrop(); }
         },
         getChildren: function() {
@@ -144,5 +149,5 @@
                           lang.hitch(this, "_destroyNodeOrWidget"));
             this.source.clearItems();
-            if ( forceEvent ) { this._handleChange(); }
+            if ( forceEvent ) { this._handleDrop(); }
         },
         _destroyNodeOrWidget: function(node) {
Index: Dev/trunk/src/server/app.js
===================================================================
--- Dev/trunk/src/server/app.js	(revision 500)
+++ Dev/trunk/src/server/app.js	(revision 501)
@@ -136,4 +136,5 @@
     }
     
+    function identity(obj) { return obj; }
     function stripAndReturnPrivates(obj) {
         var priv = {};
@@ -146,6 +147,17 @@
         return priv;
     }
-
-    function identity(obj) { return obj; }
+    function areDocsUnique(docs) {
+        return _.chain(docs)
+                .map(function(doc){ return doc._id; })
+                .uniq()
+                .value().length === docs.length;
+    }
+    function areDocsPublished(docs) {
+        return _.every(docs,isDocPublished);
+    }
+    function isDocPublished(doc) {
+        return doc.publicationDate;
+    }
+
     function handleUnknownResponse(status,error) {
         return new HTTPResult(500,{error: error.reason});
@@ -342,7 +354,17 @@
 
     // Questions
-    function getQuestionsWithCode(code) {
-        var url = '_design/questions/_view/by_code';
+    function getQuestionsWithCode(code,sub) {
+        var url = '_design/questions/_view/'+(sub||'all')+'_by_code';
         var query = {include_docs:true,key:code};
+        return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response,
+                                              handleUnknownError)
+        .handle({
+            200: handleRowDocs,
+            default: handleUnknownResponse
+        });
+    }
+    function getQuestions(sub) {
+        var url = '_design/questions/_view/'+(sub||'all');
+        var query = {include_docs:true};
         return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response,
                                               handleUnknownError)
@@ -362,6 +384,6 @@
         });
     }
-    function getQuestionsWithTopic(topic) {
-        var url = '_design/questions/_view/all_topics';
+    function getQuestionsWithTopic(topic,sub) {
+        var url = '_design/questions/_view/'+(sub||'all')+'_topics';
         var query = {reduce:false,include_docs:true,key:topic};
         return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response,
@@ -372,7 +394,7 @@
         });
     }
-    function getQuestionsWithCategoryAndTopic(category,topic) {
+    function getQuestionsWithCategoryAndTopic(category,topic,sub) {
         var hasTopic = typeof topic !== 'undefined';
-        var url = '_design/questions/_view/all';
+        var url = '_design/questions/_view/'+(sub||'all');
         var query = {reduce:false,include_docs:true,
                      startkey:hasTopic?[category,topic]:[category],
@@ -389,11 +411,15 @@
         ensureMIME(JSON_MIME),
         function(req,res) {
+            var sub = 'published' in req.query && 'published';
             var hr;
             if ( 'category' in req.query ) {
-                hr = getQuestionsWithCategoryAndTopic(req.query.category,req.query.topic);
+                hr = getQuestionsWithCategoryAndTopic(req.query.category,
+                                                      req.query.topic,sub);
             } else if ( 'topic' in req.query ) {
-                hr = getQuestionsWithTopic(req.query.topic);
+                hr = getQuestionsWithTopic(req.query.topic,sub);
             } else if ( 'code' in req.query ) {
-                hr = getQuestionsWithCode(req.query.code);
+                hr = getQuestionsWithCode(req.query.code,sub);
+            } else {
+                hr = getQuestions(sub);
             }
             hr.handle(res.send.bind(res));
@@ -444,8 +470,31 @@
         makeDocDel_id('Question'));
 
-
     // Categories and topics
-    function getTopicsWithCategory(category) {
-        var url = '_design/questions/_view/all';
+    function getTopics(sub) {
+        var url = '_design/questions/_view/'+(sub||'all')+'_topics';
+        return 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
+        });
+    }
+    function getCategories(sub) {
+        var url = '_design/questions/_view/'+(sub||'all');
+        return HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true,group_level:1}}).response,
+                                       handleUnknownError)
+        .handle({
+            200: function(result) {
+                return _.map(result.rows, function(item) {
+                    return { name:item.key[0], count:item.value };
+                });
+            },
+            default: handleUnknownResponse
+        });
+    }
+    function getTopicsWithCategory(category,sub) {
+        var url = '_design/questions/_view/'+(sub||'all');
         return HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true,group_level:2,startkey:[category],endkey:[category,{}]}}).response,
                                        handleUnknownError)
@@ -461,15 +510,6 @@
         ensureMIME(JSON_MIME),
         function(req,res) {
-            var url = '_design/questions/_view/all';
-            HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true,group_level:1}}).response,
-                                           handleUnknownError)
-            .handle({
-                200: function(result) {
-                    return _.map(result.rows, function(item) {
-                        return { name:item.key[0], count:item.value };
-                    });
-                },
-                default: handleUnknownResponse
-            })
+            var sub = 'published' in req.query && 'published';
+            getCategories(sub)
             .handle(res.send.bind(res));
         });
@@ -479,5 +519,6 @@
         function(req,res) {
             var category = req.params.category;
-            getTopicsWithCategory(category)
+            var sub = 'published' in req.query && 'published';
+            getTopicsWithCategory(category,sub)
             .handle(res.send.bind(res));
         });
@@ -486,17 +527,10 @@
         ensureMIME(JSON_MIME),
         function(req,res) {
-            var url = '_design/questions/_view/all_topics';
+            var sub = 'published' in req.query && 'published';
             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 = getTopicsWithCategory(req.query.category,sub);
+            } else {
+                hr = getTopics(sub);
             }
             hr.handle(res.send.bind(res));
@@ -504,10 +538,4 @@
 
     // Surveys
-    function areSurveyQuestionsUnique(survey) {
-        return _.chain(survey.questions)
-                .map(function(q){ return q.code; })
-                .uniq()
-                .value().length === survey.questions.length;
-    }
     app.get('/api/surveys',
         ensureAuthenticated,
@@ -538,6 +566,8 @@
             var doc = req.body;
             var hr;
-            if ( !areSurveyQuestionsUnique(doc) ) {
+            if ( !areDocsUnique(doc.questions) ) {
                 hr = new HTTPResult(400,{error:"Survey contains duplicate questions."});
+            } else if ( !areDocsPublished(doc.questions) || isDocPublished(doc) ) {
+                hr = new HTTPResult(400,{error:"Cannot publish Survey with unpublished questions."});
             } else {
                 hr = postDocument('Survey',doc);
@@ -557,7 +587,9 @@
             var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
             var hr;
-            if ( !areSurveyQuestionsUnique(doc) ) {
+            if ( !areDocsUnique(doc.questions) ) {
                 new HTTPResult(400,{error:"Survey contains duplicate questions."})
                 .handle(res.send.bind(res));
+            } else if ( !areDocsPublished(doc.questions) || isDocPublished(doc) ) {
+                hr = new HTTPResult(400,{error:"Cannot publish Survey with unpublished questions."});
             } else {
                 putDocument(id,rev,'Survey',doc)
@@ -587,12 +619,17 @@
         function(req,res) {
             var doc = req.body;
-            randomToken()
-            .handle({
-                201: function(token) {
-                    doc.secret = token;
-                    return postDocument('SurveyRun',doc);
-                }
-            })
-            .handle(res.send.bind(res));
+            var hr;
+            if ( !isDocPublished(doc.survey) ) {
+                hr = new HTTPResult(400,{error:"Cannot run unpublished survey."});
+            } else {
+                hr = randomToken()
+                .handle({
+                    201: function(token) {
+                        doc.secret = token;
+                        return postDocument('SurveyRun',doc);
+                    }
+                });
+            }
+            hr.handle(res.send.bind(res));
         });
     app.get('/api/surveyRuns/:id',
@@ -608,5 +645,7 @@
             var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
             var hr;
-            if ( typeof doc.secret === 'undefined' ) {
+            if ( !isDocPublished(doc.survey) ) {
+                hr = new HTTPResult(400,{error:"Cannot run unpublished survey."});
+            } else if ( typeof doc.secret === 'undefined' ) {
                 hr = randomToken()
                 .handle({
Index: Dev/trunk/src/server/config/couchdb-design-docs.js
===================================================================
--- Dev/trunk/src/server/config/couchdb-design-docs.js	(revision 500)
+++ Dev/trunk/src/server/config/couchdb-design-docs.js	(revision 501)
@@ -96,7 +96,13 @@
                 reduce: function(key, values, rereduce) { return sum(values); }
             },
-            by_code: {
+            all_by_code: {
                 map: function(doc){
                     if ( doc.type !== 'Question' ) { return; }
+                    emit(doc.code,doc);
+                }
+            },
+            published_by_code: {
+                map: function(doc){
+                    if ( doc.type !== 'Question' || !doc.publicationDate ) { return; }
                     emit(doc.code,doc);
                 }
