Changeset 490 for Dev


Ignore:
Timestamp:
03/08/14 22:51:23 (11 years ago)
Author:
hendrikvanantwerpen
Message:
  • Mark content as dirty to prevent moving away from unsaved data.
  • Better change propagation from lists and our own widgets.
  • Generate notifications for errors and show correct message.
  • Moved all path/url generation to the class stores, not everywhere we use it.
  • Give user always a choice between Save and Save & Close.
  • Better refresh behaviour on form changes and saves.
  • Don't generate duplicate code error when existing object is the one you're storing.
Location:
Dev/trunk/src
Files:
32 edited

Legend:

Unmodified
Added
Removed
  • Dev/trunk/src/client/qed-client/app/Content.js

    r443 r490  
    88        _container: null,
    99        _previousContent: null,
     10        _dirty: false,
    1011        startup: function() {
    1112            if ( this._started ) { return; }
     
    3233            }
    3334            widget.region = 'center';
     35            this._previousContent = widget;
    3436            this._container.addChild(widget);
    35             this._previousContent = widget;
    3637        },
    3738        notify: function(text,type) {
     
    4041            }
    4142            this._toaster.setContent(text,type || 'message');
     43        },
     44        markDirty: function() {
     45            this._dirty = true;
     46        },
     47        markClean:function() {
     48            this._dirty = false;
     49        },
     50        isDirty: function() {
     51            return this._dirty;
    4252        }
    4353    });
  • Dev/trunk/src/client/qed-client/app/Page.js

    r443 r490  
    11define([
    2     'dojo/_base/declare',
    3     'dijit/_TemplatedMixin',
    4     'dijit/_WidgetsInTemplateMixin',
    5     'dijit/layout/BorderContainer'
    6 ],function(declare,_TemplatedMixin,_WidgetsInTemplateMixin,BorderContainer){
    7     return declare([BorderContainer,_TemplatedMixin,_WidgetsInTemplateMixin],{
    8         templateString: '<div>Empty page.</div>'
     2    "./Content",
     3    "./Path",
     4    "dijit/_TemplatedMixin",
     5    "dijit/_WidgetsInTemplateMixin",
     6    "dijit/layout/BorderContainer",
     7    "dojo/_base/declare",
     8    "dojo/_base/lang",
     9    "dojo/hash"
     10], function(Content, Path, _TemplatedMixin, _WidgetsInTemplateMixin, BorderContainer, declare, lang, hash) {
     11    var Page = declare([BorderContainer,_TemplatedMixin,_WidgetsInTemplateMixin],{
     12        templateString: '<div>Empty page.</div>',
     13        die: function(msg) {
     14            Content.set(new Page({templateString:'<div>Error: '+msg+'</div>'}));
     15        },
     16        notify: lang.hitch(Content,'notify'),
     17        setURL: function(url,opts,addToHistory) {
     18            hash(Path.format(url,opts),addToHistory !== true);
     19        },
     20        markDirty: lang.hitch(Content,'markDirty'),
     21        markClean: lang.hitch(Content,'markClean')
    922    });
     23    return Page;
    1024});
  • Dev/trunk/src/client/qed-client/app/Router.js

    r487 r490  
    11define([
    2     'dojo/_base/declare',
    3     'dojo/hash',
    4     'dojo/topic',
    5     './Content',
    6     './Page',
    7     './Path'
    8 ],function(declare,hash,topic,Content,Page,Path){
     2    "./Content",
     3    "./Page",
     4    "./Path",
     5    "dojo/_base/declare",
     6    "dojo/_base/event",
     7    "dojo/_base/lang",
     8    "dojo/_base/window",
     9    "dojo/hash",
     10    "dojo/on",
     11    "dojo/topic"
     12], function(Content, Page, Path, declare, event, lang, window, hash, on, topic) {
    913
    1014    var Router = declare(null,{
     
    1216        _routes: null,
    1317        _previousHash: null,
     18        _beforePreviousHash: null,
    1419
    1520        constructor: function() {
     
    1823        startup: function() {
    1924            if ( this._started ) { return; }
    20             if ( !Content._started ) {
    21                 Content.startup();
    22             }
     25            Content.startup();
    2326            this._started = true;
    24 
    25             var self = this;
    26 
    2727            if ( hash() === "" ) {
    2828                hash(Path.getDefault());
    2929            }
    30             this._handlePathChange(hash());
    31                         topic.subscribe("/dojo/hashchange", function(){
    32                 self._handlePathChange.apply(self,arguments);
    33             });
     30            this._handleHashChange(hash());
     31            window.onbeforeunload = function() {
     32                return Content.isDirty() && "Unsaved changes, leave anyway?";
     33            };
     34                        topic.subscribe("/dojo/hashchange",
     35                            lang.hitch(this,'_handleHashChange'));
    3436        },
    3537        register: function(route) {
     
    6870            return callback;
    6971        },
    70         _handlePathChange: function(newHash) {
    71             if ( this._previousHash === newHash ) { return; }
     72        _handleHashChange: function(newHash) {
     73            if ( this._previousHash === newHash ) {
     74                return false;
     75            }
     76            if ( Content.isDirty() ) {
     77                if ( !confirm("Unsaved changes, leave anyway?") ) {
     78                    var probablyBack = this._beforePreviousHash === newHash;
     79                    // if we think we go backwards, we re-add the history
     80                    // entry, otherwise we reset the current one,
     81                    // resulting in minor annoyance of double back
     82                    // behaviour.
     83                    hash(this._previousHash,!probablyBack);
     84                    return false;
     85                } else {
     86                    Content.markClean();
     87                }
     88            }
     89            this._beforePreviousHash = this._previousHash;
    7290            this._previousHash = newHash;
    7391            for (var i = this._routes.length-1; i >= 0; i--) {
     
    7896                        route.callback(params);
    7997                    } catch(err) {
    80                         console.error("Page change failed with",err,err.toString());
     98                        console.error("Page change failed with",err,err.stack,err.toString());
    8199                    }
    82                     return;
     100                    return true;
    83101                }
    84102            }
     
    88106                console.error("Default page failed.",err);
    89107            }
     108            return true;
    90109        },
    91110        go: function(path,args) {
    92111            if ( !this._started ) { return; }
    93112            var newHash = Path.format(path,args);
    94             this._handlePathChange(newHash);
    95113            hash(newHash);
    96114        },
  • Dev/trunk/src/client/qed-client/css/dijit/overrides.less

    r447 r490  
    122122    background-color: #333333;
    123123}
     124
     125.claro .dijitSelect {
     126    color: #000000;
     127}
  • Dev/trunk/src/client/qed-client/model/classes/_Class.js

    r487 r490  
    2727        },
    2828        load: function(id) {
    29             return this._store.get(id).then(lang.hitch(this,'_doDeserialize'));
     29            return this._store.get(id)
     30            .then(lang.hitch(this,'_doDeserialize'),
     31                  lang.hitch(this,'_deserializeError'));
    3032        },
    3133        save: function(obj) {
    32             return this._store.put(this._doSerialize(obj));
     34            return this._store.put(this._doSerialize(obj))
     35            .otherwise(lang.hitch(this,'_deserializeError'));
    3336        },
    3437        remove: function(objOrId,rev) {
     
    4043                rev = this.getRev(objOrId);
    4144            }
    42             return this._store.remove(id,{headers:{'If-Match':'"'+rev+'"'}});
     45            return this._store.remove(id,{headers:{'If-Match':'"'+rev+'"'}})
     46            .otherwise(lang.hitch(this,'_deserializeError'));
    4347        },
    4448        getRev: function(obj) {
    4549            return obj._rev;
     50        },
     51        getName: function() {
     52            return this._type;
     53        },
     54        getObjectPath: function(idOrObj) {
     55            return '/'+this._collection+'/'+(typeof idOrObj === "string" ?
     56                                             idOrObj :
     57                                             this.getId(idOrObj));
    4658        }
    4759    });
  • Dev/trunk/src/client/qed-client/model/classes/_View.js

    r487 r490  
    11define([
    22    "../../store/JsonRest",
     3    "dojo/Deferred",
    34    "dojo/_base/declare",
     5    "dojo/_base/json",
    46    "dojo/_base/lang",
    57    "dojo/store/util/QueryResults"
    6 ], function(JsonRest, declare, lang, queryResults) {
     8], function(JsonRest, Deferred, declare, json, lang, queryResults) {
    79
    810    var _View = declare([],{
     
    1820            });
    1921        },
     22        _deserializeError: function(err) {
     23            return new Deferred().reject(json.fromJson(err.responseText));
     24        },
    2025        _doDeserialize: function(obj) {
    2126            obj = lang.clone(obj);
     
    3035        getId: function(obj) {
    3136            return obj._id;
     37        },
     38        getCollectionPath: function() {
     39            return '/'+this._collection;
    3240        }
    3341    });
  • Dev/trunk/src/client/qed-client/model/classes/responses.js

    r487 r490  
    4343                handleAs: 'json',
    4444                contentType: false
    45             }).then(lang.hitch(this,'_doDeserialize'),function(err){
    46                 return new Deferred().reject(json.fromJson(err.responseText));
    47             });
     45            }).then(lang.hitch(this,'_doDeserialize'),
     46                    lang.hitch(this,'_deserializeError'));
    4847        },
    4948        postWithSecret: function(response,secret) {
     
    5554                contentType: 'application/json',
    5655                rawBody: body
    57             }).then(lang.hitch(this,'_doDeserialize'),function(err){
    58                 return new Deferred().reject(json.fromJson(err.responseText));
    59             });
     56            }).then(lang.hitch(this,'_doDeserialize'),
     57                    lang.hitch(this,'_deserializeError'));
    6058        },
    6159        putWithSecret: function(response,secret) {
     
    6765                contentType: 'application/json',
    6866                rawBody: body
    69             }).then(lang.hitch(this,'_doDeserialize'),function(err){
    70                 return new Deferred().reject(json.fromJson(err.responseText));
    71             });
     67            }).then(lang.hitch(this,'_doDeserialize'),
     68                    lang.hitch(this,'_deserializeError'));
    7269        },
    7370        removeWithSecret: function(response,secret) {
     
    8582                contentType: 'application/json',
    8683                rawBody: body
    87             });
     84            }).otherwise(lang.hitch(this,'_deserializeError'));
    8885        }
    8986    });
  • Dev/trunk/src/client/qed-client/model/classes/surveys.js

    r487 r490  
    2626                obj.publicationDate = stamp.toISOString(obj.publicationDate);
    2727            }
     28        },
     29        getPreviewPath: function(idOrObj) {
     30            return '/previewSurvey/'+(typeof idOrObj === "string"?
     31                                      idOrObj :
     32                                      this.getId(idOrObj));
    2833        }
    2934    });
  • Dev/trunk/src/client/qed-client/model/widgets/AccountListView.js

    r443 r490  
    2323                actions: {
    2424                    "Remove" : {
    25                         callback: lang.hitch(this, 'removeItem', item),
     25                        callback: lang.hitch(this, 'removeItem', item, true),
    2626                        properties: {
    2727                            blockButton: false,
  • Dev/trunk/src/client/qed-client/model/widgets/CategoryListView.js

    r443 r490  
    2525                actions: {
    2626                    "Remove" : {
    27                         callback: lang.hitch(this, 'removeItem', id),
     27                        callback: lang.hitch(this, 'removeItem', id, true),
    2828                        properties: {
    2929                            blockButton: false,
  • Dev/trunk/src/client/qed-client/model/widgets/QuestionEditorPreview.js

    r443 r490  
    2424            });
    2525            this.own(previewItem.on('destroy',
    26                                     lang.hitch(this,'removeItem',id)));
     26                                    lang.hitch(this,'removeItem',id,true)));
    2727            previewItem.startup();
    2828            return previewItem;
  • Dev/trunk/src/client/qed-client/model/widgets/QuestionListView.js

    r443 r490  
    2323                actions: {
    2424                    "Remove" : {
    25                         callback: lang.hitch(this, 'removeItem', id),
     25                        callback: lang.hitch(this, 'removeItem', id, true),
    2626                        properties: {
    2727                            blockButton: false,
  • Dev/trunk/src/client/qed-client/model/widgets/SurveySummary.js

    r487 r490  
    11define([
     2    "../classes/surveys",
    23    "dijit/_TemplatedMixin",
    34    "dijit/_WidgetBase",
     
    56    "dojo/dom-attr",
    67    "dojo/text!./templates/SurveySummary.html"
    7 ], function(_TemplatedMixin, _WidgetBase, declare, domAttr, template) {
     8], function(surveys, _TemplatedMixin, _WidgetBase, declare, domAttr, template) {
    89    return declare([_WidgetBase,_TemplatedMixin],{
    910        templateString: template,
     
    1718        _setValueAttr: function(survey) {
    1819            this.titleNode.innerHTML = survey.title || "";
    19             domAttr.set(this.titleNode, "href", survey._id && ("#!/survey/"+survey._id));
     20            domAttr.set(this.titleNode, "href", survey && surveys.getObjectPath(survey));
    2021            this.descriptionNode.innerHTML = survey.description;
    2122            this.questionsNode.innerHTML = survey.questions.length;
  • Dev/trunk/src/client/qed-client/model/widgets/TabbedQuestionBrowser.js

    r487 r490  
    2424            this.inherited(arguments);
    2525            this._dataMap = {};
    26         },
    27         postCreate: function() {
    28             this.inherited(arguments);
    29             this._query = '_design/questions/_view/'+this.include;
    3026        },
    3127        startup: function() {
  • Dev/trunk/src/client/qed-client/model/widgets/questions/MultipleChoiceInputConfigWidget.js

    r443 r490  
    3737                value: item,
    3838                onDestroy: lang.hitch(this,function(evt){
    39                     this.itemsWidget.removeItem(id);
    40                     event.stop(evt);
     39                    this.itemsWidget.removeItem(id,true);
     40                    if ( evt ) { event.stop(evt); }
    4141                    return false;
    4242                })
     
    6161        },
    6262        onAddItem: function(evt) {
    63             this.itemsWidget.appendItem({});
    64             event.stop(evt);
     63            this.itemsWidget.appendItem({},true);
     64            if ( evt ) { event.stop(evt); }
    6565            return false;
    6666        }
  • Dev/trunk/src/client/qed-client/model/widgets/questions/ScaleInputConfigWidget.js

    r443 r490  
    6161                id: id,
    6262                onDestroy: lang.hitch(this,function(evt){
    63                     this.itemsWidget.removeItem(id);
    64                     event.stop(evt);
     63                    this.itemsWidget.removeItem(id,true);
     64                    if ( evt ) { event.stop(evt); }
    6565                    return false;
    6666                })
     
    7575        },
    7676        onAddNewItem: function(e) {
    77             this.itemsWidget.appendItem({});
    78             event.stop(e);
     77            this.itemsWidget.appendItem({},true);
     78            if ( e ) { event.stop(e); }
    7979            return false;
    8080        },
  • Dev/trunk/src/client/qed-client/pages/index.js

    r443 r490  
    11define([
    2     'dojo/_base/declare',
    3     '../app/Router',
    4     '../app/Page',
    5     'dojo/text!./templates/index.html'
    6 ],function(declare,Router,Page,template){
     2    "../app/Page",
     3    "../app/Router",
     4    "../model/classes/questions",
     5    "../model/classes/surveys",
     6    "dojo/_base/declare",
     7    "dojo/text!./templates/index.html"
     8], function(Page, Router, questions, surveys, declare, template) {
    79    return declare([Page],{
    810        templateString: template,
     
    1113            if ( this._started ) { return; }
    1214            this.inherited(arguments);
    13             this.btnContentCreate.on("click",function(){ Router.go("/sessions"); });
    14             this.btnContentFacilitate.on("click",function(){ Router.go("/run"); });
    15             this.btnSurveys.on("click",function(){ Router.go("/surveys"); });
    16             this.btnQuestions.on("click",function(){ Router.go("/questions"); });
    17             this.btnApplications.on("click",function(){ Router.go("/applications"); });
    18             this.btnDashboards.on("click",function(){ Router.go("/dashboards"); });
    19             this.btnResults.on("click",function(){ Router.go("/results"); });
     15            //this.btnContentCreate.on("click",function(){ Router.go("/sessions"); });
     16            //this.btnContentFacilitate.on("click",function(){ Router.go("/run"); });
     17            this.btnSurveys.on("click",function(){
     18                Router.go(surveys.getCollectionPath());
     19            });
     20            this.btnQuestions.on("click",function(){
     21                Router.go(questions.getCollectionPath());
     22            });
     23            //this.btnApplications.on("click",function(){Router.go("/applications");});
     24            //this.btnDashboards.on("click",function(){ Router.go("/dashboards"); });
     25            //this.btnResults.on("click",function(){ Router.go("/results"); });
    2026        }
    2127    });
  • Dev/trunk/src/client/qed-client/pages/question.js

    r487 r490  
    11define([
    2     "../app/Content",
    3     "../app/Page",
    42    "../app/Router",
    53    "../model/classes/questions",
    6     "../model/widgets/QuestionEditorPreview",
    7     "../model/widgets/QuestionEditorToolkit",
    8     "../widgets/_ComplexValueMixin",
     4    "./_ObjectPage",
     5    "dojo/Deferred",
    96    "dojo/_base/declare",
    107    "dojo/_base/event",
    118    "dojo/_base/lang",
    12     "dojo/when",
     9    "require",
    1310    "dojo/text!./templates/question.html"
    14 ], function(Content, Page, Router, questions, QuestionEditorPreview, QuestionEditorToolkit, _ComplexValueMixin, declare, event, lang, when, template) {
    15     return declare([Page,_ComplexValueMixin], {
     11], function(Router, questions, _ObjectPage, Deferred, declare, event, lang, require, template) {
     12    return declare([_ObjectPage], {
     13        contextRequire: require,
    1614        templateString: template,
    1715        _toolkit: null,
    1816        _preview: null,
    19         value: null,
    20        
    21         buildRendering: function() {
    22             this.inherited(arguments);
    23 
    24             this._toolkit = new QuestionEditorToolkit({
    25             },this.QuestionEditorToolkitNode);
    26             this._toolkit.on('submit',lang.hitch(this,"_onSave"));
    27             this._toolkit.startup();
    28 
    29             this._preview = new QuestionEditorPreview({
    30                 name: 'content',
    31                 delay: 5,
    32                 region: 'center'
    33             });
    34             this._preview.startup();
    35             this.addChild(this._preview);
    36         },
     17        classStore: questions,
    3718        startup: function() {
    3819            if ( this._started ) { return; }
    3920            this.inherited(arguments);
    40             if ( !this.questionId ) {
    41                 throw new Error("Error: no reference to object set!");
     21            this.propertiesForm.on('change',lang.hitch(this,'_handlePropertiesChange'));
     22            this.contentList.on('change',lang.hitch(this,'_handleContentChange'));
     23            this._load();
     24        },
     25        _refresh: function() {
     26            this.titleNode.innerHTML = this.object.title || "";
     27            this.propertiesForm.set('value',this.object);
     28            this.contentList.set('value',this.object.content);
     29        },
     30        _handlePropertiesChange: function() {
     31            if ( this.propertiesForm.validate() ) {
     32                lang.mixin(this.object,this.propertiesForm.get('value'));
    4233            }
    43             if (this.questionId === "new") {
    44                 this.set('value', questions.create());
     34            this.markDirty();
     35        },
     36        _handleContentChange: function() {
     37            if ( this.contentList.validate() ) {
     38                this.object.content = this.contentList.get('value');
     39            }
     40            this.markDirty();
     41        },
     42        _save: function() {
     43            if ( this.propertiesForm.validate() && this.contentList.validate() ) {
     44                lang.mixin(this.object,this.propertiesForm.get('value'));
     45                this.object.content = this.contentList.get('value');
     46                return this.inherited(arguments);
    4547            } else {
    46                 when(questions.load(this.questionId))
    47                 .then(lang.hitch(this, function(value) {
    48                     this.set('value', value);
    49                 }));
     48                return new Deferred().reject();
    5049            }
    5150        },
    52         _setValueAttr: function(value) {
    53             this.value = value;
    54             this.inherited(arguments);
    55             this.titleNode.innerHTML = value.title || "";
    56         },
    57         _getValueAttr: function() {
    58             var value = this.inherited(arguments);
    59             lang.mixin(this.value, value);
    60             return this.value;
    61         },
    6251        _onSave: function(evt) {
    63             if ( this.validate() ) {
    64                 questions.save(this.get('value'))
    65                 .then(function() {
    66                     Router.go('/questions');
    67                 },function(err){
    68                     Content.notify(err,'error');
    69                 });
    70             }
     52            this._save();
    7153            if ( evt ) { event.stop( evt ); }
    7254            return false;
    7355        },
    74         _onDiscard: function() {
    75             Router.go('/questions');
    76             return true;
     56        _onSaveAndClose: function(evt) {
     57            this._save()
     58            .then(function(){
     59                Router.go(questions.getCollectionPath());
     60            });
     61            if ( evt ) { event.stop( evt ); }
     62            return false;
     63        },
     64        _onDiscard: function(evt) {
     65            this.markClean();
     66            Router.go(questions.getCollectionPath());
     67            if ( evt ) { event.stop( evt ); }
     68            return false;
     69        },
     70        _ignore: function(evt) {
     71            if ( evt ) { event.stop( evt ); }
     72            return false;
    7773        }
    7874    });
  • Dev/trunk/src/client/qed-client/pages/questions.js

    r487 r490  
    11define([
    2     "../app/Content",
    32    "../app/Page",
    43    "../app/Router",
     
    109    "dojo/_base/lang",
    1110    "dojo/text!./templates/questions.html"
    12 ], function(Content, Page, Router, questions, TabbedQuestionBrowser, Deferred, declare, event, lang, template) {
     11], function(Page, Router, questions, TabbedQuestionBrowser, Deferred, declare, event, lang, template) {
    1312    return declare([Page],{
    1413        templateString: template,
     
    4140        },
    4241        onNewQuestion: function() {
    43             Router.go("/question/new");
     42            Router.go(questions.getObjectPath('new'));
    4443        },
    4544        onDeleteQuestion: function(question) {
    4645            questions.remove(question)
    47             .then(function(){
    48                 Content.notify("Question deleted.");
    49             },function(err){
    50                 Content.notify(err,'error');
    51             });
     46            .then(lang.hitch(this,function(){
     47                this.notify("Question deleted.");
     48            }),lang.hitch(this,function(err){
     49                this.notify(err.error,'error');
     50            }));
    5251        },
    5352        onEditQuestion: function(question) {
    54             Router.go("/question/"+question._id);
     53            Router.go(questions.getObjectPath(question));
    5554        },
    5655        onPublishQuestion: function(question) {
    5756            question.publicationDate = new Date();
    5857            questions.save(question)
    59             .then(function(){
    60                 Content.notify("Question published.");
    61             },function(err){
    62                 Content.notify(err,'error');
    63             });
     58            .then(lang.hitch(this,function(){
     59                this.notify("Question published.");
     60            }),lang.hitch(this,function(err){
     61                this.notify(err.error,'error');
     62            }));
    6463        }
    6564    });
  • Dev/trunk/src/client/qed-client/pages/response.js

    r487 r490  
    11define([
    2     "../app/Content",
    32    "../app/Page",
    4     "../lib/async",
    53    "../model/classes/responses",
    64    "dojo/_base/declare",
     
    1311    "require",
    1412    "dojo/text!./templates/response.html"
    15 ], function(Content, Page, async, responses, declare, event, json, lang, all, request, when, require, template) {
     13], function(Page, responses, declare, event, json, lang, all, request, when, require, template) {
    1614    return declare([Page],{
    1715        contextRequire: require,
     
    6058            .then(lang.hitch(this,function(response){
    6159                this.response = response;
    62                 Content.notify("Your response is saved.");
    63             }), function(err){
    64                 Content.notify(err,'error');
    65             });
     60                this.notify("Your response is saved.");
     61            }), lang.hitch(this,function(err){
     62                this.notify(err.error,'error');
     63            }));
    6664        },
    6765        _onSubmit: function(e) {
     
    8987            .then(lang.hitch(this,function(res){
    9088                this._showInfo("<div>Your response has been deleted, no answers have been saved.</div>");
    91                 Content.notify("Your response is deleted.");
    92             }), function(err){
    93                 Content.notify(err,'error');
    94             });
     89                this.notify("Your response is deleted.");
     90            }), lang.hitch(this,function(err){
     91                this.notify(err.error,'error');
     92            }));
    9593            if ( e ) { event.stop(e); }
    9694            return false;
  • Dev/trunk/src/client/qed-client/pages/survey.js

    r487 r490  
    11define([
    2     "../app/Page",
    32    "../app/Router",
    43    "../model/classes/surveys",
    54    "../model/widgets/QuestionListView",
    65    "../model/widgets/TabbedQuestionBrowser",
     6    "./_ObjectPage",
    77    "dojo/_base/array",
    88    "dojo/_base/declare",
     
    1212    "require",
    1313    "dojo/text!./templates/survey.html"
    14 ], function(Page, Router, surveys, QuestionListView, TabbedQuestionBrowser, array, declare, event, lang, when, require, template) {
    15     return declare([Page],{
     14], function(Router, surveys, QuestionListView, TabbedQuestionBrowser, _ObjectPage, array, declare, event, lang, when, require, template) {
     15    return declare([_ObjectPage],{
    1616        contextRequire: require,
    1717        templateString: template,
    18         survey: null,
     18        classStore: surveys,
    1919        questionList: null,
    2020        startup: function() {
    2121            if ( this._started ) { return; }
    2222            this.inherited(arguments);
    23             if ( this.surveyId ) {
    24                 this._setupQuestionBrowser();
    25                 this._setupListView();
    26                 this._loadSurvey();
    27             } else {
    28                 throw "No valid uid or survey passed!";
    29             }
     23            this._setupQuestionBrowser();
     24            this._setupListView();
     25            this._load();
    3026        },
    3127        _setupQuestionBrowser: function() {
     
    6056            this.questionList.startup();
    6157        },
    62         _loadSurvey: function() {
    63             if ( this.surveyId === "new" ) {
    64                 this.survey = surveys.create();
    65                 this.refresh();
    66             } else {
    67                 when(surveys.load(this.surveyId))
    68                 .then(lang.hitch(this,function(survey){
    69                     this.survey = survey;
    70                     this.questionList.set('value',
    71                                           this.survey.questions);
    72                     this.refresh();
    73                 }));
    74             }
     58        _refresh: function() {
     59            this.titleNode.innerHTML = this.object.title || "(set title in properties)";
     60            this.propertiesDialog.set('value',this.object);
     61            this.questionList.set('value',
     62                                  this.object.questions);
     63        },
     64        _save: function() {
     65            this.object.questions = this.questionList.get('value');
     66            return this.inherited(arguments);
    7567        },
    7668        _includeQuestion: function(question) {
    7769            this.questionList.appendItem(question);
    7870        },
    79         refresh: function() {
    80             this.titleNode.innerHTML = this.survey.title || "(set title in properties)";
    81             this.propertiesDialog.set('value',this.survey);
    82         },
    8371        _onShowProperties: function(evt) {
    8472            this.propertiesDialog.show();
     73            if ( evt ) { event.stop(evt); }
     74            return false;
    8575        },
    8676        _onPropertiesOk: function(evt) {
    8777            this.propertiesDialog.hide();
    88             lang.mixin(this.survey, this.propertiesDialog.get('value'));
    89             this.refresh();
    90             event.stop(evt);
     78            lang.mixin(this.object, this.propertiesDialog.get('value'));
     79            this.markDirty();
     80            this._refresh();
     81            if ( evt ) { event.stop(evt); }
    9182            return false;
    9283        },
    9384        _onPropertiesCancel: function(evt) {
    9485            this.propertiesDialog.hide();
    95             this.propertiesDialog.reset('value',this.survey);
    96             event.stop(evt);
     86            this.propertiesDialog.set('value',this.object);
     87            if ( evt ) { event.stop(evt); }
    9788            return false;
    9889        },
    9990        _onSave: function(evt) {
    100             this.survey.questions = this.questionList.get('value');
    101             surveys.save(this.survey)
     91            this._save();
     92            if ( evt ) { event.stop(evt); }
     93            return false;
     94        },
     95        _onSaveAndClose: function(evt) {
     96            this._save()
    10297            .then(function() {
    103                 Router.go('/surveys');
     98                Router.go(surveys.getCollectionPath());
    10499            });
    105100            event.stop(evt);
    106101            return false;
    107102        },
    108         _onDiscard: function(evt) {
    109             Router.go('/surveys');
     103        _onDiscardAndClose: function(evt) {
     104            this.markClean();
     105            Router.go(surveys.getCollectionPath());
    110106        },
    111107        _onShowPreview: function() {
    112             Router.go('/previewSurvey/'+this.survey._id,{
     108            Router.go(surveys.getPreviewPath(this.object),{
    113109                preview: true
    114110            });
  • Dev/trunk/src/client/qed-client/pages/surveyRun.js

    r487 r490  
    11define([
    22    "../app/Content",
    3     "../app/Page",
     3    "../app/Path",
    44    "../app/Router",
    55    "../lib/func",
    66    "../model/classes/responses",
    77    "../model/classes/surveyRuns",
     8    "../model/classes/surveys",
    89    "../widgets/LineWithActionsWidget",
     10    "./_ObjectPage",
     11    "dojo/Deferred",
    912    "dojo/_base/array",
    1013    "dojo/_base/declare",
     
    1417    "require",
    1518    "dojo/text!./templates/surveyRun.html"
    16 ], function(Content, Page, Router, func, responses, surveyRuns, LineWithActionsWidget, array, declare, event, lang, when, require, template) {
    17     return declare([Page],{
     19], function(Content, Path, Router, func, responses, surveyRuns, surveys, LineWithActionsWidget, _ObjectPage, Deferred, array, declare, event, lang, when, require, template) {
     20    return declare([_ObjectPage],{
    1821        contextRequire: require,
    1922        templateString: template,
    20         surveyRun: null,
     23        classStore: surveyRuns,
    2124        startup: function() {
    2225            if ( this._started ) { return; }
    2326            this.inherited(arguments);
    24             this.surveyRunWidget.on("blur",lang.hitch(this,'_onPropChange'));
    25             if ( this.surveyRunId ) {
    26                 this._loadSurveyRun();
     27            this.surveyRunWidget.on("change",lang.hitch(this,'_onPropChange'));
     28            this._load();
     29        },
     30        _refresh: function() {
     31            this.titleNode.innerHTML = this.object.title || "";
     32            this.surveySummaryWidget.set('value',this.object.survey);
     33            this.surveyRunWidget.set('value',this.object);
     34            this._refreshURL();
     35            this._loadResponses();
     36        },
     37        _refreshURL: function() {
     38            if ( this.object.mode === "open" ) {
     39                this.runURLNode.innerHTML =
     40                    this._link(this._buildGeneralURL(this.object));
    2741            } else {
    28                 throw "No valid uid or survey passed!";
     42                this.runURLNode.innerHTML =
     43                    "No general URL. Add individual respondents below.";
    2944            }
    3045        },
    31         _loadSurveyRun: function() {
    32             when(surveyRuns.load(this.surveyRunId))
    33             .then(lang.hitch(this,function(surveyRun){
    34                 this.surveyRun = surveyRun;
    35                 this.refreshSurveyRun();
    36                 this._loadResponses();
    37             }));
    38         },
    39         refreshSurveyRun: function() {
    40             this.titleNode.innerHTML = this.surveyRun.title || "";
    41             this.surveySummaryWidget.set('value',this.surveyRun.survey);
    42             this.surveyRunWidget.set('value',this.surveyRun);
    43             this._onPropChange();
    44         },
    4546        _loadResponses: function() {
    46             responses.query({surveyRunId:surveyRuns.getId(this.surveyRun)})
     47            responses.query({surveyRunId:surveyRuns.getId(this.object)})
    4748            .then(lang.hitch(this,function(allResponses){
    4849                array.forEach(allResponses, function(response){
     
    7374        },
    7475        _onPropChange: function(e) {
    75             var surveyRun = this.surveyRunWidget.get('value');
    76             if ( surveyRun.mode === "open" ) {
    77                 this.runURLNode.innerHTML =
    78                     this._link(this._buildGeneralURL(this.surveyRun));
    79             } else {
    80                 this.runURLNode.innerHTML =
    81                     "No general URL. Add individual respondents below.";
     76            if ( this.surveyRunWidget.validate() ) {
     77                lang.mixin(this.object,this.surveyRunWidget.get('value'));
     78                this._refreshURL();
    8279            }
     80            this.markDirty();
    8381        },
    8482        _buildGeneralURL: function(surveyRun) {
    85             return 'response.html#!/surveyRuns/'+surveyRuns.getId(surveyRun)+'!secret='+surveyRun.secret;
     83            return 'response.html#'+Path.format(surveyRuns.getObjectPath(surveyRun),{secret:surveyRun.secret});
    8684        },
    8785        _buildResponseURL: function(response) {
    88             return 'response.html#!/responses/'+responses.getId(response)+'!secret='+response.secret;
     86            return 'response.html#'+Path.format(responses.getObjectPath(response),{secret:response.secret});
    8987        },
    9088        _link: function(url,label) {
    9189            return '<a target="_blank" href="'+url+'">'+(label || url)+'</a>';
    9290        },
     91        _save: function() {
     92            if ( this.surveyRunWidget.validate() ) {
     93                lang.mixin(this.object,this.surveyRunWidget.get('value'));
     94                return this.inherited(arguments);
     95            } else {
     96                return new Deferred.reject();
     97            }
     98        },
    9399        _onSave: function(evt) {
    94             if ( this.surveyRunWidget.validate() ) {
    95                 lang.mixin(this.surveyRun,this.surveyRunWidget.get('value'));
    96 
    97                 surveyRuns.save(this.surveyRun)
    98                 .then(function() {
    99                     Router.go('/surveys');
    100                 },function(err){
    101                     Content.notify(err);
    102                 });
    103             }
     100            this._save();
     101            if ( evt ) { event.stop(evt); }
     102            return false;
     103        },
     104        _onSaveAndClose: function(evt) {
     105            this._save()
     106            .then(function() {
     107                Router.go(surveys.getCollectionPath());
     108            });
    104109            if ( evt ) { event.stop(evt); }
    105110            return false;
    106111        },
    107112        _onDiscard: function(evt) {
    108             Router.go('/surveys');
     113            this.markClean();
     114            Router.go(surveys.getCollectionPath());
    109115            if ( evt ) { event.stop(evt); }
    110116            return false;
  • Dev/trunk/src/client/qed-client/pages/surveys.js

    r487 r490  
    11define([
    2     "../app/Content",
    32    "../app/Page",
    43    "../app/Router",
     4    "../model/classes/surveyRuns",
    55    "../model/classes/surveys",
    6     "../model/classes/surveyRuns",
    76    "../widgets/LineWithActionsWidget",
    87    "dojo/_base/array",
     
    1110    "dojo/when",
    1211    "dojo/text!./templates/surveys.html"
    13 ], function(Content, Page, Router, surveys, surveyRuns, LineWithActionsWidget, array, declare, lang, when, template) {
     12], function(Page, Router, surveyRuns, surveys, LineWithActionsWidget, array, declare, lang, when, template) {
    1413    return declare([Page],{
    1514        templateString: template,
     
    2019        },
    2120        _onNewSurvey: function(){
    22             Router.go('/survey/new');
     21            Router.go(surveys.getObjectPath('new'));
    2322        },
    2423        _onPublishSurvey:function(survey){
     
    2928                self.refreshDrafts();
    3029                self.refreshPublished();
    31             },function(err){
    32                 Content.notify(err,'error');
    33             });
     30            },lang.hitch(this,function(err){
     31                this.notify(err.error,'error');
     32            }));
    3433        },
    3534        _onDeleteSurvey:function(survey){
     
    3837            .then(function(){
    3938                self.refreshDrafts();
    40             },function(err){
    41                 Content.notify(err,'error');
    42             });
     39            },lang.hitch(this,function(err){
     40                this.notify(err.error,'error');
     41            }));
    4342        },
    4443        _onEditSurvey:function(survey){
    45             Router.go('/survey/'+survey._id);
     44            Router.go(surveys.getObjectPath(survey));
    4645        },
    4746        _onPreviewSurvey:function(survey){
    48             Router.go('/previewSurvey/'+survey._id);
     47            Router.go(surveys.getPreviewPath(survey));
    4948        },
    5049        _onRunSurvey:function(survey){
     
    5453            .then(lang.hitch(this,function(surveyRun){
    5554                this._onRunDetails(surveyRun);
    56             }),function(err){
    57                 Content.notify(err);
    58             });
     55            }),lang.hitch(this,function(err){
     56                this.notify(err.error,'error');
     57            }));
    5958        },
    6059        _onRunDetails: function(surveyRun) {
    61             Router.go('/surveyRun/'+surveyRun._id);
     60            Router.go(surveyRuns.getObjectPath(surveyRun));
    6261        },
    6362        refresh: function() {
  • Dev/trunk/src/client/qed-client/pages/templates/question.html

    r466 r490  
    1 <form class="orange">
     1<div class="orange">
    22    <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'top'">
    33        <h2>
     
    66        </h2>
    77    </div>
    8     <div data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="region:'left', design:'headline'" style="width: 300px;">
    9         <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'center'">
    10             <div data-dojo-attach-point="QuestionEditorToolkitNode"></div>
     8    <form data-dojo-type="dijit/form/Form"
     9         data-dojo-props="region:'left'"
     10         data-dojo-attach-point="propertiesForm"
     11         data-dojo-attach-event="onSubmit:_ignore">
     12        <div data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="design:'headline'" style="width: 300px;">
     13            <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'center'">
     14                <div data-dojo-type="../model/widgets/QuestionEditorToolkit"
     15                     data-dojo-attach-point="QuestionEditorToolkitNode"></div>
     16            </div>
     17            <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'top'">
     18                <button data-dojo-type="dijit/form/Button"
     19                        data-dojo-props="baseClass: 'rftLargeButton', iconClass: 'rftIcon rftIconAccept'"
     20                        data-dojo-attach-event="onClick:_onSave">
     21                  Save</button>
     22                <button data-dojo-type="dijit/form/Button"
     23                        data-dojo-props="baseClass: 'rftLargeButton', iconClass: 'rftIcon rftIconAccept'"
     24                        data-dojo-attach-event="onClick:_onSaveAndClose">
     25                  Save &amp; Close</button>
     26                <button data-dojo-type="dijit/form/Button"
     27                        data-dojo-props="baseClass: 'rftLargeButton', iconClass: 'rftIcon rftIconCancel'"
     28                        data-dojo-attach-event="onClick:_onDiscard">
     29                  Discard &amp; Close</button>
     30            </div>
    1131        </div>
    12         <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'top'">
    13             <button data-dojo-type="dijit/form/Button" data-dojo-props="baseClass: 'rftLargeButton', iconClass: 'rftIcon rftIconCancel'" data-dojo-attach-event="onClick:_onDiscard">Discard</button>
    14             <button data-dojo-type="dijit/form/Button" data-dojo-props="baseClass: 'rftLargeButton', iconClass: 'rftIcon rftIconAccept'" data-dojo-attach-event="onClick:_onSave">Save and exit</button>
    15         </div>
    16     </div>
    17 </form>
     32    </form>
     33    <div data-dojo-type="../model/widgets/QuestionEditorPreview"
     34         data-dojo-attach-point="contentList"
     35         data-dojo-props="name:'content',delay:5,region:'center'"></div>
     36</div>
  • Dev/trunk/src/client/qed-client/pages/templates/survey.html

    r457 r490  
    2424                    data-dojo-attach-event="onClick:_onSave"
    2525                    data-dojo-props="baseClass: 'rftLargeButton', iconClass: 'rftIcon rftIconAccept'">
    26                 Save Changes</button>
     26                Save</button>
    2727            <button data-dojo-type="dijit/form/Button"
    28                     data-dojo-attach-event="onClick:_onDiscard"
     28                    data-dojo-attach-event="onClick:_onSaveAndClose"
     29                    data-dojo-props="baseClass: 'rftLargeButton', iconClass: 'rftIcon rftIconAccept'">
     30                Save &amp; Close</button>
     31            <button data-dojo-type="dijit/form/Button"
     32                    data-dojo-attach-event="onClick:_onDiscardAndClose"
    2933                    data-dojo-props="baseClass: 'rftLargeButton', iconClass: 'rftIcon rftIconCancel'">
    30                 Discard changes</button>
     34                Discard &amp; Close</button>
    3135            <button data-dojo-type="dijit/form/Button"
    3236                    data-dojo-attach-event="onClick:_onShowPreview"
  • Dev/trunk/src/client/qed-client/pages/templates/surveyRun.html

    r467 r490  
    4545            <div>
    4646                <div class="qedLabel">Export results</div>
    47                 <a target="_blank" href="/api/surveyRuns/${surveyRunId}/responses.csv" class="qedField">To CSV</a>
     47                <a target="_blank" href="/api/surveyRuns/${objectId}/responses.csv" class="qedField">To CSV</a>
    4848            </div>
    4949        </fieldset>
     
    5555                class="blue"
    5656                data-dojo-props="baseClass: 'rftBlockButton', iconClass: 'rftIcon rftIconSave'"
    57                 data-dojo-attach-event="onClick:_onSave">Save &amp; Close</button>
     57                data-dojo-attach-event="onClick:_onSave">Save</button>
     58        <button data-dojo-type="dijit/form/Button"
     59                class="blue"
     60                data-dojo-props="baseClass: 'rftBlockButton', iconClass: 'rftIcon rftIconSave'"
     61                data-dojo-attach-event="onClick:_onSaveAndClose">Save &amp; Close</button>
    5862        <button data-dojo-type="dijit/form/Button"
    5963                class="blue"
  • Dev/trunk/src/client/qed-client/response.js

    r487 r490  
    4242
    4343    function setContent(response) {
    44         hash(Path.format("/responses/"+responses.getId(response),
     44        hash(Path.format(responses.getObjectPath(response),
    4545                         {secret:response.secret}));
    4646        Content.set(new ResponsePage({
  • Dev/trunk/src/client/qed-client/routes.js

    r487 r490  
    11define([
     2    "./model/classes/questions",
     3    "./model/classes/surveyRuns",
     4    "./model/classes/surveys",
    25    "./pages/index",
    36    "./pages/previewSurvey",
     
    710    "./pages/surveyRun",
    811    "./pages/surveys"
    9 ], function(index, previewSurvey, question, questions, survey, surveyRun, surveys) {
     12], function(questionsClass, surveyRunsClass, surveysClass, index, previewSurvey, question, questions, survey, surveyRun, surveys) {
    1013
    1114    return [
    1215        { path: "/", redirect: "/index" },
    1316        { path: "/index", constructor: index },
    14         { path: "/questions", constructor: questions },
    15         { path: "/question/:questionId", constructor: question },
    16         { path: "/surveys", constructor: surveys },
    17         { path: "/survey/:surveyId", constructor: survey },
    18         { path: "/surveyRun/:surveyRunId", constructor: surveyRun },
     17        { path: questionsClass.getCollectionPath(), constructor: questions },
     18        { path: questionsClass.getObjectPath(':objectId'), constructor: question },
     19        { path: surveysClass.getCollectionPath(), constructor: surveys },
     20        { path: surveysClass.getObjectPath(':objectId'), constructor: survey },
     21        { path: surveyRunsClass.getObjectPath(':objectId'), constructor: surveyRun },
    1922        //{ path: "/sessions", constructor: sessions },
    2023        //{ path: "/session/:sessionId", constructor: session },
    21         { path: "/previewSurvey/:surveyId", constructor: previewSurvey }
     24        { path: surveysClass.getPreviewPath(':surveyId'), constructor: previewSurvey }
    2225    ];
    2326
  • Dev/trunk/src/client/qed-client/stddeps.js

    r487 r490  
    2828    './app/Notifications',
    2929
     30    './model/widgets/QuestionEditorPreview',
     31    './model/widgets/QuestionEditorToolkit',
     32    './model/widgets/AccountListView',
    3033    './model/widgets/AccountListView',
    3134    './model/widgets/SurveyRenderWidget',
  • Dev/trunk/src/client/qed-client/widgets/LineWithActionsWidget.js

    r472 r490  
    4444                            onClick: lang.hitch(this, function(action, e){
    4545                                if ( action.callback ) { action.callback(e); }
    46                                 event.stop(e);
     46                                if ( e ) { event.stop(e); }
    4747                                return false;
    4848                            }, this.actions[action])
     
    5858                            onClick: lang.hitch(this, function(action, e){
    5959                                if ( action.callback ) { action.callback(e); }
    60                                 event.stop(e);
     60                                if ( e ) { event.stop(e); }
    6161                                return false;
    6262                            }, this.actions[action])
     
    7373            var preventDefault = this.onClick(e) === false;
    7474            if (preventDefault) {
    75                 event.stop(e);
     75                if ( e ) { event.stop(e); }
    7676            }
    7777            return !preventDefault;
  • Dev/trunk/src/client/qed-client/widgets/ListWidget.js

    r443 r490  
    88    "dojo/dnd/Source",
    99    "dojo/dnd/common",
    10     "dojo/dom-construct"
    11 ], function(_Container, _WidgetBase, registry, array, declare, lang, Source, dnd, domConstruct) {
     10    "dojo/dom-construct",
     11    "dojo/on"
     12], function(_Container, _WidgetBase, registry, array, declare, lang, Source, dnd, domConstruct, on) {
    1213    return declare([_WidgetBase,_Container],{
    1314        name: "",
     
    4950            });
    5051            this.source = new this.container(this.domNode,sourceParams);
     52            this.source.on('Drop',lang.hitch(this,'_handleChange'));
    5153        },
    5254        creator: function(item, hint) {
     
    6668                }
    6769            }
     70            on(nodeOrWidget,'change',lang.hitch(this,'_handleChange'));
    6871            var node = nodeOrWidget.domNode ? nodeOrWidget.domNode : nodeOrWidget;
    6972            if ( hint !== "avatar" && node.id !== id ) {
     
    7982        createListElement: null, /* function(id,item){},*/
    8083        _getValueAttr: function() {
    81             return array.map(this.source.getAllNodes(),function(node){
     84            this.value = array.map(this.source.getAllNodes(),function(node){
    8285                var widget = registry.byNode(node);
    8386                if ( widget && "value" in widget ) {
     
    8790                }
    8891            },this);
     92            return this.value;
    8993        },
    9094        _setValueAttr: function(value) {
     
    110114            }
    111115        },
    112         appendItems: function(items) {
     116        appendItems: function(items,forceEvent) {
    113117            this.source.insertNodes(false,items);
     118            if ( forceEvent ) { this._handleChange(); }
    114119        },
    115         appendItem: function(item) {
     120        appendItem: function(item,forceEvent) {
    116121            this.source.insertNodes(false,[item]);
     122            if ( forceEvent ) { this._handleChange(); }
    117123        },
    118         removeItem: function(key) {
     124        removeItem: function(key,forceEvent) {
    119125            array.forEach(array.filter(this.source.getAllNodes(), function(node){
    120126                return node.id === key;
    121127            }), lang.hitch(this, "_destroyNodeOrWidget"));
    122128            this.source.delItem(key);
     129            if ( forceEvent ) { this._handleChange(); }
    123130        },
    124131        getChildren: function() {
     
    127134            }),function(widget){ return widget !== null; });
    128135        },
    129         clear: function() {
     136        clear: function(forceEvent) {
    130137            array.forEach(this.source.getAllNodes(),
    131138                          lang.hitch(this, "_destroyNodeOrWidget"));
    132139            this.source.clearItems();
     140            if ( forceEvent ) { this._handleChange(); }
    133141        },
    134142        _destroyNodeOrWidget: function(node) {
     
    149157            this.source.destroy();
    150158            this.inherited(arguments);
    151         }
     159        },
     160        _handleChange: function() {
     161            this.value = this._getValueAttr();
     162            this.onChange(this.value);
     163        },
     164        onChange: function(value) {}
    152165    });
    153166});
  • Dev/trunk/src/server/app.js

    r487 r490  
    149149    function identity(obj) { return obj; }
    150150    function handleUnknownResponse(status,error){
    151         return new HTTPResult(500,{error: "Unexpected database response",
    152                                    innerStatus: status, innerResponse: error});
     151        return new HTTPResult(500,{error: error.reason});
    153152    }
    154153    function handleUnknownError(error){
     
    393392            .handle({
    394393                200: function(others) {
    395                     if ( others.length > 0 ) {
     394                    if ( others.length > 0 && _.some(others,function(other){ return other._id !== id; })  ) {
    396395                        return new HTTPResult(403,{error:"Other question with this code already exists."});
    397396                    } else {
Note: See TracChangeset for help on using the changeset viewer.