Changeset 508 for Dev


Ignore:
Timestamp:
03/12/14 02:23:11 (11 years ago)
Author:
hendrikvanantwerpen
Message:
  • Server handles the new flat response format correctly.
  • Client widgets and survey rendering creates a flat structure.
  • Fixed logic error in checking if questions in survey are published.
  • Restrict accepted properties in answers and reject empty strings as properties.
Location:
Dev/trunk/src
Files:
2 added
18 edited

Legend:

Unmodified
Added
Removed
  • Dev/trunk/src/client/qed-client/model/widgets/QuestionEditorPreviewItem.js

    r504 r508  
    103103        },
    104104        _showViewWidget: function() {
    105             var newWidget = this._factory.createViewWidget( this.value );
     105            // we pass an empty code, just in case the widget depends
     106            // on it,but we don't know the actual code here.
     107            var newWidget = this._factory.createViewWidget( lang.mixin({code:""},this.value) );
    106108            if ( newWidget !== null ) {
    107109                this._destroyInnerWidget();
    108110                this.innerWidget = newWidget;
     111                this.innerWidget.set('readOnly',true);
    109112                this.addChild(this.innerWidget);
    110                 this.innerWidget.set('readOnly',true);
    111113                this.titleNode.innerHTML = this.value.type+" [preview]";
    112114                domClass.replace(this.editButton.iconNode, "rftIconEdit", "rftIconAccept");
     
    120122                this._destroyInnerWidget();
    121123                this.innerWidget = newWidget;
    122                 this.addChild(this.innerWidget);
    123124                this.innerWidget.set('readOnly',this.readOnly);
    124125                this.innerWidget.set('disabled',this.disabled);
     126                this.addChild(this.innerWidget);
    125127                this.titleNode.innerHTML = this.value.type+" [editing]";
    126128                domClass.replace(this.editButton.iconNode, "rftIconAccept", "rftIconEdit");
  • Dev/trunk/src/client/qed-client/model/widgets/SurveyRenderWidget.js

    r487 r508  
    2121            this.survey = survey;
    2222            var f = new QuestionWidgetFactory();
    23             array.forEach(this.survey.questions,function(question,question_index){
    24                 array.forEach(question.content || [], function(item,item_index){
    25                     // The dot causes values to be grouped in an object!
     23            array.forEach(this.survey.questions,function(question){
     24                array.forEach(question.content || [], function(item){
     25                    item.code = question.code;
    2626                    var w = f.createViewWidget(item);
    2727                    if ( w !== null ) {
    28                         w.name = question_index.toString()+'/'+question.code.toString()+'.'+item_index.toString();
    2928                        w.placeAt(this.domNode);
     29                        w.startup();
    3030                    }
    3131                },this);
  • Dev/trunk/src/client/qed-client/model/widgets/questions/MultipleChoiceInputWidget.js

    r500 r508  
    11define([
    2     "../../../widgets/_ComplexValueWidget",
     2    "dijit/_Container",
     3    "dijit/_TemplatedMixin",
     4    "dijit/_WidgetBase",
     5    "dijit/_WidgetsInTemplateMixin",
    36    "dijit/form/CheckBox",
    47    "dijit/form/RadioButton",
     
    710    "dojo/dom-construct",
    811    "dojo/text!./templates/MultipleChoiceInputWidget.html"
    9 ], function(_ComplexValueWidget, CheckBox, RadioButton, array, declare, domConstruct, template) {
    10     return declare([_ComplexValueWidget],{
     12], function(_Container, _TemplatedMixin, _WidgetBase, _WidgetsInTemplateMixin, CheckBox, RadioButton, array, declare, domConstruct, template) {
     13    return declare([_WidgetBase,_TemplatedMixin,_WidgetsInTemplateMixin,_Container],{
    1114        templateString: template,
    12         allowMultiple: false,
    1315        startup: function() {
    1416            if ( this._started ) { return; }
     
    1719            domConstruct.empty(this.domNode);
    1820            var Ctor = this.allowMultiple === true ? CheckBox : RadioButton;
    19             array.forEach(this.items, function(item, index){
     21            array.forEach(this.items, function(item){
    2022                var div = domConstruct.create("div", {
    2123                }, this.domNode, "last");
    2224                var input = new Ctor({
    23                     name: this.allowMultiple === true ? index.toString() : 'choice',
    24                     value: item.text
     25                    name: this.code + (this.allowMultiple === true ? item.subcode : ''),
     26                    value: this.allowMultiple === true ? null : item.subcode
    2527                }).placeAt(div);
    2628                var label = domConstruct.create("label",{
     
    2931                }, div);
    3032            }, this);
    31         },
    32         _getValueAttr: function() {
    33             var value = this.inherited(arguments);
    34             /*if ( this.allowMultiple === true ) {
    35                 return value;
    36             } else {
    37                 return value.choice; //.length > 0;
    38             }*/
    39         },
    40         _setValueAttr: function(value) {
    41             var inherited = this.getInherited(arguments);
    42             if ( this.allowMultiple === true ) {
    43                 inherited.call(this,value);
    44             } else {
    45                 inherited.call(this,{choice:value ? ["on"] : []});
    46             }
    4733        }
    4834    });
  • Dev/trunk/src/client/qed-client/model/widgets/questions/NumberInputWidget.js

    r443 r508  
    11define([
    2     "../../../widgets/_ComplexValueWidget",
     2    "dijit/_Container",
     3    "dijit/_TemplatedMixin",
     4    "dijit/_WidgetBase",
     5    "dijit/_WidgetsInTemplateMixin",
    36    "dojo/_base/declare",
    47    "dojo/text!./templates/NumberInputWidget.html"
    5 ], function(_ComplexValueWidget, declare, template) {
    6     return declare([_ComplexValueWidget],{
     8], function(_Container, _TemplatedMixin, _WidgetBase, _WidgetsInTemplateMixin, declare, template) {
     9    return declare([_WidgetBase,_TemplatedMixin,_WidgetsInTemplateMixin,_Container],{
     10        templateString: template,
    711        text: '',
    8         maxLength: null,
    9         templateString: template,
    1012        startup: function() {
     13            if ( this._started ) { return; }
     14            this.inherited(arguments);
     15
    1116            var constraints = {};
    12             if ( this.min !== null && !isNaN(this.min) ) {
     17            if ( this.min && !isNaN(this.min) ) {
    1318                constraints.min = this.min;
    1419            }
    15             if ( this.max !== null && !isNaN(this.max) ) {
     20            if ( this.max && !isNaN(this.max) ) {
    1621                constraints.max = this.max;
    1722            }
    18             if ( this.places !== null && !isNaN(this.places) ) {
     23            if ( this.places && !isNaN(this.places) ) {
    1924                constraints.places = this.places;
    2025            }
    2126            this.numberBox.set('constraints', constraints);
    22         },
    23         _getValueAttr: function() {
    24             return this.numberBox.get('value');
    25         },
    26         _setValueAttr: function(value) {
    27             return this.numberBox.set('value', value);
    2827        }
    2928    });
  • Dev/trunk/src/client/qed-client/model/widgets/questions/ScaleInputWidget.js

    r443 r508  
    11define([
    2     "../../../widgets/_ComplexValueWidget",
     2    "dijit/_Container",
     3    "dijit/_TemplatedMixin",
     4    "dijit/_WidgetBase",
     5    "dijit/_WidgetsInTemplateMixin",
    36    "dijit/form/RadioButton",
    47    "dojo/_base/array",
     
    811    "dojo/dom-construct",
    912    "dojo/text!./templates/ScaleInputWidget.html"
    10 ], function(_ComplexValueWidget, RadioButton, array, declare, lang, domAttr, domConstruct, template) {
    11     return declare([_ComplexValueWidget],{
     13], function(_Container, _TemplatedMixin, _WidgetBase, _WidgetsInTemplateMixin, RadioButton, array, declare, lang, domAttr, domConstruct, template) {
     14    return declare([_WidgetBase,_TemplatedMixin,_WidgetsInTemplateMixin,_Container],{
    1215        templateString: template,
    1316        baseClass: "qedScaleWidget",
    14         min: 0,
    15         max: 0,
    16         minLabel: "",
    17         maxLabel: "",
    18         naLabel: null,
    19         items: null,
    20         value: null,
    21         constuctor: function() {
    22             this.items = [];
    23             this.value = {};
    24         },
    2517        startup: function() {
    2618            if ( this._started ) { return; }
     
    4234        },
    4335        _renderItems: function() {
    44             array.forEach(this.items, function(item,index) {
     36            array.forEach(this.items, function(item) {
    4537                var tr = domConstruct.create("tr", {}, this.itemsNode);
    4638                var td;
     
    5749                    td = domConstruct.create("td", {}, tr);
    5850                    radio = new RadioButton({
    59                         name: index.toString(),
     51                        name: this.code+item.subcode,
    6052                        value: i.toString()
    6153                    });
     
    6961                    td = domConstruct.create("td", {}, tr);
    7062                    radio = new RadioButton({
    71                         name: index.toString(),
     63                        name: this.code+item.subcode,
    7264                        value: "n/a"
    7365                    });
  • Dev/trunk/src/client/qed-client/model/widgets/questions/StringInputWidget.js

    r443 r508  
    11define([
    2     "../../../widgets/_ComplexValueWidget",
     2    "dijit/_Container",
     3    "dijit/_TemplatedMixin",
     4    "dijit/_WidgetBase",
     5    "dijit/_WidgetsInTemplateMixin",
    36    "dojo/_base/declare",
    47    "dojo/text!./templates/StringInputWidget.html"
    5 ], function(_ComplexValueWidget, declare, template) {
    6     return declare([_ComplexValueWidget],{
    7         text: '',
     8], function(_Container, _TemplatedMixin, _WidgetBase, _WidgetsInTemplateMixin, declare, template) {
     9    return declare([_WidgetBase,_TemplatedMixin,_WidgetsInTemplateMixin,_Container],{
    810        templateString: template,
    9         _getValueAttr: function() {
    10             return this.textBox.get('value');
    11         },
    12         _setValueAttr: function(value) {
    13             return this.textBox.set('value', value);
    14         }
     11        text: ''
    1512    });
    1613});
  • Dev/trunk/src/client/qed-client/model/widgets/questions/TextInputWidget.js

    r461 r508  
    11define([
    2     "../../../widgets/_ComplexValueWidget",
     2    "dijit/_Container",
     3    "dijit/_TemplatedMixin",
     4    "dijit/_WidgetBase",
     5    "dijit/_WidgetsInTemplateMixin",
    36    "dojo/_base/declare",
    47    "dojo/text!./templates/TextInputWidget.html"
    5 ], function(_ComplexValueWidget, declare, template) {
    6     return declare([_ComplexValueWidget],{
     8], function(_Container, _TemplatedMixin, _WidgetBase, _WidgetsInTemplateMixin, declare, template) {
     9    return declare([_WidgetBase,_TemplatedMixin,_WidgetsInTemplateMixin,_Container],{
     10        templateString: template,
    711        text: '',
    8         maxLength: null,
    9         templateString: template,
    1012        startup: function() {
     13            if ( this._started ) { return; }
     14            this.inherited(arguments);
     15
    1116            if ( this.maxLength ) {
    1217                this.textArea.set('maxLength', this.maxLength);
    1318            }
    14         },
    15         _getValueAttr: function() {
    16             return this.textArea.get('value');
    17         },
    18         _setValueAttr: function(value) {
    19             return this.textArea.set('value', value);
    2019        }
    2120    });
  • Dev/trunk/src/client/qed-client/model/widgets/questions/templates/MultipleChoiceInputWidget.html

    r461 r508  
    1 <form>
    2 </form>
     1<div>
     2</div>
  • Dev/trunk/src/client/qed-client/model/widgets/questions/templates/NumberInputWidget.html

    r461 r508  
    1 <form>
     1<div>
    22  <p>${text}</p>
    3   <div class="qedField" data-dojo-attach-point="numberBox" data-dojo-type="dijit/form/NumberTextBox" name="text"></div>
    4 </form>
     3  <div class="qedField"
     4       data-dojo-attach-point="numberBox"
     5       data-dojo-type="dijit/form/NumberTextBox"
     6       name="${code}${subcode}"></div>
     7</div>
  • Dev/trunk/src/client/qed-client/model/widgets/questions/templates/ScaleInputWidget.html

    r461 r508  
    1 <form class="${baseClass}">
     1<div class="${baseClass}">
    22  <table>
    33    <thead>
     
    1212    </tbody>
    1313  </table>
    14 </form>
     14</div>
  • Dev/trunk/src/client/qed-client/model/widgets/questions/templates/StringInputWidget.html

    r461 r508  
    1 <form>
     1<div>
    22  <p>${text}</p>
    3   <div data-dojo-attach-point="textBox" data-dojo-type="dijit/form/TextBox" name="text"></div>
    4 </form>
     3  <div data-dojo-type="dijit/form/TextBox"
     4       name="${code}${subcode}"></div>
     5</div>
  • Dev/trunk/src/client/qed-client/model/widgets/questions/templates/TextInputWidget.html

    r492 r508  
    1 <form>
     1<div>
    22  <p>${text}</p>
    3   <textarea class="qedField" data-dojo-attach-point="textArea" data-dojo-type="dijit/form/Textarea" name="text"></textarea>
    4 </form>
     3  <textarea class="qedField"
     4            data-dojo-attach-point="textArea"
     5            data-dojo-type="dijit/form/Textarea"
     6            name="${code}${subcode}"></textarea>
     7</div>
  • Dev/trunk/src/client/qed-client/model/widgets/templates/SurveyRunWidget.html

    r493 r508  
    11<form class="${baseClass}">
     2
     3    <div>
     4        <label for="mode" class="qedLabel">Title</label>
     5        <textarea name="title" class="qedField"
     6                  data-dojo-props="required: true"
     7                  data-dojo-type="dijit/form/ValidationTextBox"></textarea>
     8    </div>
    29
    310    <div>
  • Dev/trunk/src/client/qed-client/pages/response.js

    r503 r508  
    4242            var canDelete = (this.response &&
    4343                             this.response._surveyRun.respondentCanDeleteOwnResponse);
    44             this.cancelButton.set('disabled',canDelete||false);
     44            this.cancelButton.set('disabled',canDelete?false:true);
    4545            this.surveyWidget.set('readOnly', false);
    4646        },
  • Dev/trunk/src/client/qed-client/pages/surveys.js

    r497 r508  
    4949        _onRunSurvey:function(survey){
    5050            var surveyRun = surveyRuns.create();
     51            surveyRun.title = 'Run of "' + survey.title + '" of '+(new Date().toString());
    5152            surveyRun.survey = survey;
    5253            surveyRuns.save(surveyRun)
  • Dev/trunk/src/server/app.js

    r507 r508  
    154154    }
    155155    function areDocsPublished(docs) {
    156         return _.every(docs,isDocPublished);
     156        return _.every(docs, isDocPublished);
    157157    }
    158158    function isDocPublished(doc) {
     
    592592            if ( !areDocsUnique(doc.questions) ) {
    593593                hr = new HTTPResult(400,{error:"Survey contains duplicate questions."});
    594             } else if ( !areDocsPublished(doc.questions) || isDocPublished(doc) ) {
     594            } else if ( isDocPublished(doc) && !areDocsPublished(doc.questions) ) {
    595595                hr = new HTTPResult(400,{error:"Cannot publish Survey with unpublished questions."});
    596596            } else {
     
    610610            var doc = req.body;
    611611            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
    612             var hr;
    613612            if ( !areDocsUnique(doc.questions) ) {
    614613                new HTTPResult(400,{error:"Survey contains duplicate questions."})
    615614                .handle(res.send.bind(res));
    616             } else if ( !areDocsPublished(doc.questions) || isDocPublished(doc) ) {
    617                 hr = new HTTPResult(400,{error:"Cannot publish Survey with unpublished questions."});
     615            } else if ( isDocPublished(doc) && !areDocsPublished(doc.questions) ) {
     616                new HTTPResult(400,{error:"Cannot publish Survey with unpublished questions."})
     617                .handle(res.send.bind(res));
    618618            } else {
    619619                putDocument(id,rev,'Survey',doc)
     
    718718            .handle({
    719719                200: function(responses) {
    720                     var flatResponses = responsesToVariables(responses);
     720                    var answers = _.map(responses,function(response){
     721                        return response.answers;
     722                    });
    721723                    res.set({
    722724                        'Content-Disposition': 'attachment; filename=surveyRun-'+id+'-responses.csv'
    723725                    });
    724726                    res.status(200);
    725                     writeObjectsToCSVStream(flatResponses, res);
     727                    writeObjectsToCSVStream(answers, res);
    726728                    res.end();
    727729                },
     
    953955}
    954956
    955 function responsesToVariables(responses) {
    956     return _.map(responses, responseToVariables);
    957 }
    958 
    959 function responseToVariables(response) {
    960     var result = flattenObject(response.answers);
    961     return result;
    962 }
    963 
    964 function flattenObject(value) {
    965     var result = {};
    966     (function agg(val,key,res){
    967         if ( _.isObject(val) ) {
    968             var keys = _.keys(val);
    969             // FIXME : dirty hack for questions with only one input
    970             if ( keys.length === 1 ) {
    971                 agg(val[keys[0]],key,res);
    972             } else {
    973                 _.forEach(val, function(v,k){
    974                     agg(v,(key ? key+'.' : '')+k,res);
    975                 });
    976             }
    977         } else if ( _.isArray(val) ) {
    978             // FIXME : dirty hack for questions with only one input
    979             if ( val.length === 1 ) {
    980                 agg(val[0],key,res);
    981             } else {
    982                 _.forEach(val, function(v,i){
    983                     agg(v,(key ? key+'.' : '')+i,res);
    984                 });
    985             }
    986         } else {
    987             res[key] = val;
    988         }
    989     })(value,null,result);
    990     return result;
    991 }
    992 
    993957function writeObjectsToCSVStream(objects, stream, prelude) {
    994958    var keys = _.chain(objects)
  • Dev/trunk/src/server/config/couchdb-design-docs.js

    r506 r508  
    1111
    1212    "qed/schemaInfo": {
    13         version: "3"
     13        version: "4"
    1414    },
    1515
  • Dev/trunk/src/server/config/couchdb-schema.json

    r506 r508  
    22  "$schema": "http://json-schema.org/draft-04/schema#",
    33  "title": "QED Object Schema",
    4   "version": "3",
     4  "version": "4",
    55  "type": "object",
    66  "oneOf": [
     
    1010  "definitions": {
    1111    "nonEmptyString": { "type": "string", "minLength": 1 },
     12    "codeString": { "type": "string", "pattern": "^[A-Za-z0-9]+$" },
    1213    "schemaInfo": {
    1314      "type": "object",
     
    3738          "_rev": { "$ref": "#/definitions/nonEmptyString" },
    3839          "categories": { "type": "array", "items": { "$ref": "#/definitions/nonEmptyString" } },
    39           "code": { "$ref": "#/definitions/nonEmptyString" },
     40          "code": { "$ref": "#/definitions/codeString" },
    4041          "content": { "type": "array", "items": { "$ref": "#/definitions/content/any" } },
    4142          "description": { "$ref": "#/definitions/nonEmptyString" },
     
    8687          "_id": { "$ref": "#/definitions/nonEmptyString" },
    8788          "_rev": { "$ref": "#/definitions/nonEmptyString" },
    88           "answers": { "type": "object" },
     89          "answers": {
     90              "type": "object",
     91              "patternProperties": {
     92                  "^[A-Za-z0-9]+$": {}
     93              },
     94              "additionalProperties": false
     95          },
    8996          "email": { "type": "string", "format": "email" },
    9097          "publicationDate": { "type": "string", "format": "datetime" },
     
    139146        "properties": {
    140147          "type": { "type": "string", "pattern": "^StringInput$" },
    141           "subcode": { "$ref": "#/definitions/nonEmptyString" },
     148          "subcode": { "$ref": "#/definitions/codeString" },
    142149          "text": { "$ref": "#/definitions/nonEmptyString" }
    143150        },
     
    150157          "type": { "type": "string", "pattern": "^TextInput$" },
    151158          "maxLength": { "type": "integer" },
    152           "subcode": { "$ref": "#/definitions/nonEmptyString" },
     159          "subcode": { "$ref": "#/definitions/codeString" },
    153160          "text": { "$ref": "#/definitions/nonEmptyString" }
    154161        },
     
    163170          "max": { "type": "integer" },
    164171          "places": { "type": "integer" },
    165           "subcode": { "$ref": "#/definitions/nonEmptyString" },
     172          "subcode": { "$ref": "#/definitions/codeString" },
    166173          "text": { "$ref": "#/definitions/nonEmptyString" }
    167174        },
     
    183190              "minLabel": { "$ref": "#/definitions/nonEmptyString" },
    184191              "maxLabel": { "$ref": "#/definitions/nonEmptyString" },
    185               "subcode": { "$ref": "#/definitions/nonEmptyString" },
     192              "subcode": { "$ref": "#/definitions/codeString" },
    186193              "text": { "$ref": "#/definitions/nonEmptyString" }
    187194            },
     
    201208              "type": "object",
    202209              "properties": {
    203                   "subcode": { "$ref": "#/definitions/nonEmptyString" },
     210                  "subcode": { "$ref": "#/definitions/codeString" },
    204211                  "text": { "$ref": "#/definitions/nonEmptyString" }
    205212              },
     
    210217              "type": "object",
    211218              "properties": {
    212                   "subcode": { "$ref": "#/definitions/nonEmptyString" },
     219                  "subcode": { "$ref": "#/definitions/codeString" },
    213220                  "text": { "$ref": "#/definitions/nonEmptyString" }
    214221              },
Note: See TracChangeset for help on using the changeset viewer.