diff --git a/client_side_only_impl/vsaq_main.js b/client_side_only_impl/vsaq_main.js index 42456fa..c7afbf5 100644 --- a/client_side_only_impl/vsaq_main.js +++ b/client_side_only_impl/vsaq_main.js @@ -159,7 +159,7 @@ vsaq.Qpage.prototype.updateStorage_ = function(data) { var newStorageData = null; var storageData = this.readStorage_(); if (storageData) { - storageData = JSON.parse(storageData); + storageData = /** @type {Object|null} */ (JSON.parse(storageData)); goog.object.extend(storageData, data); newStorageData = goog.json.serialize(storageData); } else { @@ -259,7 +259,7 @@ vsaq.Qpage.prototype.loadExtensionThenQuestionnaire = function( text = vsaq.utils.vsaqonToJson(text); var extension = {}; try { - extension = JSON.parse(text); + extension = /** @type {Object|null} */ (JSON.parse(text)); } catch (err) { alert('Loading the extension failed. It does not appear to be ' + 'valid json'); @@ -297,7 +297,7 @@ vsaq.Qpage.prototype.loadQuestionnaire = function(opt_path, opt_extension) { text = vsaq.utils.vsaqonToJson(text); var template = {}; try { - template = JSON.parse(text); + template = /** @type {Object|null} */ (JSON.parse(text)); } catch (err) { alert('Loading the template failed. It does not appear to be ' + 'valid json'); @@ -328,7 +328,7 @@ vsaq.Qpage.prototype.loadQuestionnaire = function(opt_path, opt_extension) { this.questionnaire.listen( goog.events.EventType.CHANGE, goog.bind(function(e) { - goog.structs.forEach(e.changedValues, function(val, key) { + goog.object.forEach(e.changedValues, function(val, key) { this.changes[key] = val; }, this); if (goog.structs.getCount(this.changes) > 0) { diff --git a/compiler.flags b/compiler.flags index c0e0a05..1b5e751 100644 --- a/compiler.flags +++ b/compiler.flags @@ -7,7 +7,7 @@ --jscomp_error=checkVars --jscomp_error=const --jscomp_error=constantProperty ---jscomp_error=deprecated +--jscomp_warning=deprecated --jscomp_error=duplicate --jscomp_error=duplicateMessage --jscomp_error=es5Strict @@ -24,8 +24,6 @@ --jscomp_error=unknownDefines --jscomp_error=uselessCode --jscomp_error=visibility ---externs=third_party/closure-compiler/contrib/externs/chrome_extensions.js --only_closure_dependencies --manage_closure_dependencies ---js third_party/closure-library/closure/goog/deps.js --js build/deps.js diff --git a/do.sh b/do.sh index 613442a..9ba20f9 100755 --- a/do.sh +++ b/do.sh @@ -17,7 +17,7 @@ # PYTHON_CMD="python" -JSCOMPILE_CMD="java -jar third_party/closure-compiler/build/compiler.jar --flagfile=compiler.flags" +JSCOMPILE_CMD="java -jar third_party/closure-compiler/target/closure-compiler-1.0-SNAPSHOT.jar --flagfile=compiler.flags" CKSUM_CMD="cksum" # chosen because it's available on most Linux/OS X installations BUILD_DIR="build" BUILD_TPL_DIR="$BUILD_DIR/templates" @@ -35,10 +35,9 @@ vsaq_assert_dependencies() { fi # Check if required files are present. files=(third_party/closure-library \ - third_party/closure-templates-compiler \ - third_party/closure-stylesheets/build/closure-stylesheets.jar \ - third_party/closure-compiler/build/compiler.jar \ - third_party/closure-compiler/contrib/externs/chrome_extensions.js \ + third_party/closure-templates/target \ + third_party/closure-stylesheets/target/closure-stylesheets-1.5.0-SNAPSHOT-jar-with-dependencies.jar \ + third_party/closure-compiler/target/closure-compiler-1.0-SNAPSHOT.jar \ ) for var in "${files[@]}" do @@ -62,13 +61,16 @@ vsaq_build_templates() { set -e mkdir -p "$BUILD_TPL_DIR" rm -rf "$BUILD_TPL_DIR/*" + mkdir "$BUILD_TPL_DIR/proto" + # Compile safe html type proto to JS + third_party/protoc/bin/protoc --js_out $BUILD_TPL_DIR/proto \ + ./third_party/safe-html-types/proto/src/main/protobuf/webutil/html/types/html.proto # Compile soy templates echo "Compiling Soy templates..." rm -f "$BUILD_TPL_DIR/cksum" vsaq_get_file_cksum '*.soy' > "$BUILD_TPL_DIR/cksum" - find "vsaq" -name '*.soy' -exec java -jar third_party/closure-templates-compiler/SoyToJsSrcCompiler.jar \ - --shouldProvideRequireSoyNamespaces --shouldGenerateJsdoc --shouldDeclareTopLevelNamespaces --srcs {} \ - --outputPathFormat "$BUILD_TPL_DIR/{INPUT_DIRECTORY}{INPUT_FILE_NAME}.js" \; + find "vsaq" -name '*.soy' -exec java -jar third_party/closure-templates/target/soy-2018-03-14-SoyToJsSrcCompiler.jar \ + --srcs {} --outputPathFormat "$BUILD_TPL_DIR/{INPUT_DIRECTORY}{INPUT_FILE_NAME}.js" \; echo "Done." } @@ -110,9 +112,10 @@ vsaq_build_closure_lib_() { SRC_DIRS=( \ vsaq \ client_side_only_impl \ + third_party/closure-templates/target \ third_party/closure-library/closure/goog \ third_party/closure-library/third_party/closure/goog \ - third_party/closure-templates-compiler ) + third_party/protoc/protobuf-3.5.1/js/binary ) if [ -d "$3" ]; then SRC_DIRS+=("$3") fi @@ -122,13 +125,16 @@ vsaq_build_closure_lib_() { jscompile_vsaq+=" --js='$var/**.js' --js='!$var/**_test.js' --js='!$var/**_perf.js'" done jscompile_vsaq+=" --js='!third_party/closure-library/closure/goog/demos/**.js'" + jscompile_vsaq+=" --js='!third_party/closure-templates/javascript/examples/**.js'" if [ "$4" == "debug" ]; then jscompile_vsaq+=" --debug --formatting=PRETTY_PRINT -O WHITESPACE_ONLY" elif [ "$4" == "optimized" ]; then jscompile_vsaq+=" -O ADVANCED" fi + cmd="$jscompile_vsaq --closure_entry_point "$ENTRY_POINT" --js_output_file "$FNAME"" + echo $cmd echo -n "." - $jscompile_vsaq --closure_entry_point "$ENTRY_POINT" --js_output_file "$FNAME" + $cmd } vsaq_build_jsmodule() { @@ -161,7 +167,7 @@ vsaq_build() { BUILD_DIR_STATIC="$BUILD_DIR/static" mkdir -p "$BUILD_DIR_STATIC" - csscompile_vsaq="java -jar third_party/closure-stylesheets/build/closure-stylesheets.jar --allowed-non-standard-function color-stop" + csscompile_vsaq="java -jar third_party/closure-stylesheets/target/closure-stylesheets-1.5.0-SNAPSHOT-jar-with-dependencies.jar --allowed-non-standard-function color-stop" echo "Compiling CSS files..." $csscompile_vsaq "vsaq/static/vsaq_base.css" "vsaq/static/vsaq.css" > "$BUILD_DIR_STATIC/vsaq.css" echo "Copying remaining static files..." @@ -203,7 +209,7 @@ vsaq_generate_jsdeps() { $PYTHON_CMD third_party/closure-library/closure/bin/build/depswriter.py \ --root_with_prefix="build/templates/ build/templates/" \ --root_with_prefix="vsaq/ vsaq/" \ - --root_with_prefix="third_party/closure-templates-compiler/ third_party/closure-templates-compiler/" \ + --root_with_prefix="third_party/closure-templates/javascript third_party/closure-templates/javascript/" \ > "$BUILD_DIR/deps.js" } @@ -215,7 +221,7 @@ vsaq_run() { $PYTHON_CMD third_party/closure-library/closure/bin/build/depswriter.py \ --root_with_prefix="build/templates/ ../../../build/templates/" \ --root_with_prefix="vsaq/ ../vsaq/" \ - --root_with_prefix="third_party/closure-templates-compiler/ ../../../../third_party/closure-templates-compiler/" \ + --root_with_prefix="third_party/closure-templates/javascript/ ../../../../third_party/closure-templates/javascript/" \ > "$BUILD_DIR/deps-runfiles.js" rm -f "$BUILD_DIR/all_tests.js" diff --git a/download-libs.sh b/download-libs.sh index 9394c19..97c6aaf 100755 --- a/download-libs.sh +++ b/download-libs.sh @@ -19,10 +19,24 @@ export JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8 THIRD_PARTY_DIRECTORY="third_party" + + +type unzip >/dev/null 2>&1 || { + echo >&2 "Unzip is required to build VSAQ dependencies." + exit 1 +} +type wget >/dev/null 2>&1 || { + echo >&2 "Wget is required to build VSAQ dependencies." + exit 1 +} type ant >/dev/null 2>&1 || { echo >&2 "Ant is required to build VSAQ dependencies." exit 1 } +type mvn >/dev/null 2>&1 || { + echo >&2 "Apache Maven is required to build VSAQ dependencies." + exit 1 +} type javac >/dev/null 2>&1 || { echo >&2 "Java compiler is required to build VSAQ dependencies." exit 1 @@ -52,6 +66,7 @@ if [ ! -d .git ]; then rm -rf $THIRD_PARTY_DIRECTORY/closure-library rm -rf $THIRD_PARTY_DIRECTORY/closure-stylesheets rm -rf $THIRD_PARTY_DIRECTORY/js-dossier + rm -rf $THIRD_PARTY_DIRECTORY/closure-templates fi if [ ! -d $THIRD_PARTY_DIRECTORY ]; then @@ -62,41 +77,50 @@ cd $THIRD_PARTY_DIRECTORY git submodule add -f https://github.com/google/closure-compiler closure-compiler git submodule add -f https://github.com/google/closure-library closure-library git submodule add -f https://github.com/google/closure-stylesheets closure-stylesheets +git submodule add -f https://github.com/google/closure-templates closure-templates git submodule add -f https://github.com/jleyba/js-dossier js-dossier +git submodule add -f https://github.com/google/safe-html-types safe-html-types git submodule init git submodule update # Pin submodules to particular commits cd closure-compiler -git checkout -b 59b42c9fc8fc752b3ff3aabe04ad89a96f9a7bf7 59b42c9fc8fc752b3ff3aabe04ad89a96f9a7bf7 +git checkout -b 0441c526dc7ed322034d4f708062c00802184e8f 0441c526dc7ed322034d4f708062c00802184e8f cd .. cd closure-library -git checkout -b dc369cde87d7ef6dfb46d3b873f872ebee7d07cd dc369cde87d7ef6dfb46d3b873f872ebee7d07cd +git checkout -b 26de3253e443d36f64c2ea380faee879dfcf1c54 26de3253e443d36f64c2ea380faee879dfcf1c54 cd .. cd js-dossier -git checkout -b 6f2d09ee26925b7417f9f6bd1547dffe700ab60f 6f2d09ee26925b7417f9f6bd1547dffe700ab60f +git checkout -b e6e55806ea97a4fcf4157661ee809eb8b48fe848 e6e55806ea97a4fcf4157661ee809eb8b48fe848 +cd .. +cd closure-templates +git checkout -b 17dad0f13db94ca43a2e4c436658682a0403ced1 17dad0f13db94ca43a2e4c436658682a0403ced1 +cd .. +cd safe-html-types +git checkout -b 8507735457ea41a37dfa027fb176d49d5783c4ba 8507735457ea41a37dfa027fb176d49d5783c4ba cd .. # build closure compiler if [ ! -f closure-compiler/build/compiler.jar ] && [ -d closure-compiler ]; then cd closure-compiler - ant clean - ant jar +# ant clean +# ant jar + mvn -DskipTests -pl externs/pom.xml,pom-main.xml,pom-main-shaded.xml cd .. fi -# checkout closure templates compiler -if [ ! -d closure-templates-compiler ]; then - curl https://dl.google.com/closure-templates/closure-templates-for-javascript-latest.zip -O - unzip closure-templates-for-javascript-latest.zip -d closure-templates-compiler - rm closure-templates-for-javascript-latest.zip +# build closure templates compiler +if [ -d closure-templates ] && [ ! -d closure-templates/target ]; then + cd closure-templates + mvn -DskipTests package + cd .. fi # build css compiler -if [ ! -f closure-stylesheets/build/closure-stylesheets.jar ]; then +if [ ! -f closure-stylesheets/target/closure-stylesheets-1.5.0-SNAPSHOT-jar-with-dependencies.jar ]; then cd closure-stylesheets - ant + mvn compile assembly:single cd .. fi @@ -104,12 +128,21 @@ if [ -f chrome_extensions.js ]; then rm -f chrome_extensions.js fi +mkdir protoc; cd protoc +wget https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip +unzip protoc-3.5.1-linux-x86_64.zip +rm protoc-3.5.1-linux-x86_64.zip +wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-js-3.5.1.zip +unzip protobuf-js-3.5.1.zip +rm protobuf-js-3.5.1.zip +cd .. + # Temporary fix # Soy file bundled with the compiler does not compile with strict settings: # lib/closure-templates-compiler/soyutils_usegoog.js:1762: ERROR - element JS_STR_CHARS does not exist on this enum -cd closure-templates-compiler -echo $PWD -curl https://raw.githubusercontent.com/google/closure-templates/0cbc8543c34d3f7727dd83a2d1938672f16d5c20/javascript/soyutils_usegoog.js -O -cd .. +#cd closure-templates/javascript +#echo $PWD +#curl https://raw.githubusercontent.com/google/closure-templates/0cbc8543c34d3f7727dd83a2d1938672f16d5c20/javascript/soyutils_usegoog.js -O +#cd ../.. cd .. diff --git a/vsaq/static/qpage_base.js b/vsaq/static/qpage_base.js index 6ea551f..9b8bdf6 100644 --- a/vsaq/static/qpage_base.js +++ b/vsaq/static/qpage_base.js @@ -28,7 +28,6 @@ goog.require('goog.events.EventType'); goog.require('goog.structs'); goog.require('goog.ui.Tooltip'); goog.require('vsaq.Questionnaire'); -goog.require('vsaq.utils'); @@ -52,10 +51,6 @@ vsaq.QpageBase = function() { goog.dom.createDom(goog.dom.TagName.SPAN); this.questionnaire.setReadOnlyMode(this.isReadOnly); - vsaq.utils.initClickables({ - 'eh-edit': goog.bind(this.makeEditable, this) - }); - goog.events.listen(window, [goog.events.EventType.BEFOREUNLOAD], function() { if (vsaq.qpageObject_ && vsaq.qpageObject_.unsavedChanges()) @@ -122,16 +117,6 @@ vsaq.QpageBase.prototype.isReadOnly; vsaq.QpageBase.prototype.statusIndicator; -/** - * Make questionnaire editable. - */ -vsaq.QpageBase.prototype.makeEditable = function() { - this.isReadOnly = false; - this.questionnaire.setReadOnlyMode(this.isReadOnly); - this.questionnaire.render(); -}; - - /** * Attempts to keep track of updates that were done to the current * questionnaire. diff --git a/vsaq/static/questionnaire/blockitems.js b/vsaq/static/questionnaire/blockitems.js index 092e773..5e6a94f 100644 --- a/vsaq/static/questionnaire/blockitems.js +++ b/vsaq/static/questionnaire/blockitems.js @@ -43,7 +43,7 @@ goog.require('vsaq.questionnaire.templates'); * for the item to be visible to the user. * @param {?string} caption The caption of the block. * @param {?string=} opt_auth The needed authorization to get an item displayed. - * The auth param on {@code vsaq.questionnaire.items.BlockItem} only + * The auth param on `vsaq.questionnaire.items.BlockItem` only * prevents that items are displayed to the user (hidden by display=none). * @param {?string=} opt_className Name of a CSS class to add to the block. * @extends {vsaq.questionnaire.items.ContainerItem} diff --git a/vsaq/static/questionnaire/boxitem.js b/vsaq/static/questionnaire/boxitem.js index 4753133..506fb0f 100644 --- a/vsaq/static/questionnaire/boxitem.js +++ b/vsaq/static/questionnaire/boxitem.js @@ -49,14 +49,15 @@ goog.require('vsaq.questionnaire.utils'); * @param {number=} opt_maxlength HTML maxlength attribute value for the input * field. See {@link * https://html.spec.whatwg.org/multipage/forms.html#attr-fe-maxlength} + * @param {string=} opt_auth If "readonly", this ValueItem cannot be modified. * @extends {vsaq.questionnaire.items.ValueItem} * @constructor */ vsaq.questionnaire.items.BoxItem = function(id, conditions, caption, opt_placeholder, opt_inputPattern, opt_inputTitle, opt_isRequired, - opt_maxlength) { + opt_maxlength, opt_auth) { goog.base(this, id, conditions, caption, opt_placeholder, opt_inputPattern, - opt_inputTitle, opt_isRequired, opt_maxlength); + opt_inputTitle, opt_isRequired, opt_maxlength, opt_auth); /** * The text area where the user can provide an answer. @@ -122,13 +123,14 @@ vsaq.questionnaire.items.BoxItem.parse = function(questionStack) { return new vsaq.questionnaire.items.BoxItem(item.id, item.cond, item.text, item.placeholder, item.inputPattern, item.inputTitle, item.required, - item.maxlength); + item.maxlength, item.auth); }; /** @inheritDoc */ vsaq.questionnaire.items.BoxItem.prototype.setReadOnly = function(readOnly) { - this.textArea_.readOnly = readOnly; + // if item marked readonly, always keep it readonly + this.textArea_.readOnly = this.auth == 'readonly' ? true : readOnly; }; diff --git a/vsaq/static/questionnaire/boxitem_test.js b/vsaq/static/questionnaire/boxitem_test.js index f7129fc..02352ee 100644 --- a/vsaq/static/questionnaire/boxitem_test.js +++ b/vsaq/static/questionnaire/boxitem_test.js @@ -88,4 +88,22 @@ function testBoxItemParse() { assertEquals('placeholder', box.placeholder); assertTrue(box.required); assertEquals(0, testStack.length); + assertTrue(box.auth != 'readonly'); + + testStack = [{ + 'type': 'box', + 'text': CAPTION, + 'id': ID, + 'required' : true, + 'placeholder': 'placeholder', + 'auth': 'readonly' + }]; + box = vsaq.questionnaire.items.BoxItem.parse(testStack); + assert(box instanceof vsaq.questionnaire.items.BoxItem); + assertEquals(ID, box.id); + assertEquals(CAPTION, box.text); + assertEquals('placeholder', box.placeholder); + assertTrue(box.required); + assertEquals(0, testStack.length); + assertEquals('readonly', box.auth); } diff --git a/vsaq/static/questionnaire/checkitem.js b/vsaq/static/questionnaire/checkitem.js index 19ace81..0b4a9b1 100644 --- a/vsaq/static/questionnaire/checkitem.js +++ b/vsaq/static/questionnaire/checkitem.js @@ -37,12 +37,13 @@ goog.require('vsaq.questionnaire.utils'); * @param {?string} conditions A string containing conditions which must be met * for the item to be visible to the user. * @param {string} caption The caption to show next to the checkbox. + * @param {string=} opt_auth If "readonly", this ValueItem cannot be modified. * @extends {vsaq.questionnaire.items.ValueItem} * @constructor */ -vsaq.questionnaire.items.CheckItem = function(id, conditions, caption) { - goog.base(this, id, conditions, caption); - +vsaq.questionnaire.items.CheckItem = function(id, conditions, caption, opt_auth) { + goog.base(this, id, conditions, caption, undefined, undefined, undefined, + undefined, undefined, opt_auth); /** * The checkbox that is the actual control behind this question. * @type {!HTMLInputElement} @@ -104,13 +105,15 @@ vsaq.questionnaire.items.CheckItem.parse = function(questionStack) { if (item.type != vsaq.questionnaire.items.CheckItem.TYPE) throw new vsaq.questionnaire.items.ParseError('Wrong parser chosen.'); - return new vsaq.questionnaire.items.CheckItem(item.id, item.cond, item.text); + return new vsaq.questionnaire.items.CheckItem(item.id, item.cond, item.text, + item.auth); }; /** @inheritDoc */ vsaq.questionnaire.items.CheckItem.prototype.setReadOnly = function(readOnly) { - this.checkBox_.disabled = readOnly; + // if item marked readonly, always keep it readonly + this.checkBox_.disabled = this.auth == 'readonly' ? true : readOnly; }; diff --git a/vsaq/static/questionnaire/groupitem.js b/vsaq/static/questionnaire/groupitem.js index 06c5235..5663c07 100644 --- a/vsaq/static/questionnaire/groupitem.js +++ b/vsaq/static/questionnaire/groupitem.js @@ -51,11 +51,12 @@ goog.require('vsaq.questionnaire.templates'); * choices in form of dictionaries. * @param {?Array.>} choicesConds An array that * contains all possible conditions in form of dictionaries. + * @param {string=} opt_auth If "readonly", this ValueItem cannot be modified. * @extends {vsaq.questionnaire.items.ContainerItem} * @constructor */ vsaq.questionnaire.items.GroupItem = function(id, conditions, caption, - defaultChoice, choices, choicesConds) { + defaultChoice, choices, choicesConds, opt_auth) { goog.base(this, id, conditions); /** @@ -96,6 +97,10 @@ vsaq.questionnaire.items.GroupItem = function(id, conditions, caption, */ this.defaultChoiceItem = null; + // items of group obtain readonly status from their parents + if (opt_auth == 'readonly') + this.auth = opt_auth; + // Iterate over all choices and create valid questionnaire items for them. goog.array.forEach(choices, function(choice) { // choice is supposed to be a dictionary with exactly one entry. @@ -118,7 +123,8 @@ vsaq.questionnaire.items.GroupItem = function(id, conditions, caption, } var appendNewItem = - this.createSingleItem(choiceId, choiceCondition, choiceText); + this.createSingleItem(choiceId, choiceCondition, choiceText, + this.auth); appendNewItem.parentItemSet(this); goog.events.listen(appendNewItem.eventDispatcher, @@ -141,7 +147,7 @@ vsaq.questionnaire.items.GroupItem.prototype.setDefaultChoice = function() { if (this.defaultChoice && !this.defaultChoiceItem) { var defaultChoiceId = goog.string.createUniqueString(); this.defaultChoiceItem = this.createSingleItem(defaultChoiceId, null, - vsaq.questionnaire.items.GroupItem.DEFAULT_CHOICE); + vsaq.questionnaire.items.GroupItem.DEFAULT_CHOICE, this.auth); this.defaultChoiceItem.parentItemSet(this); goog.events.listen(this.defaultChoiceItem.eventDispatcher, vsaq.questionnaire.items.Item.CHANGED, @@ -214,6 +220,8 @@ vsaq.questionnaire.items.GroupItem.prototype.answerChanged_ = function(ev) { * @param {string} choiceId The choice id * @param {?string} choiceCondition The choice condition * @param {string} choiceText The choice text + * @param {string=} opt_auth If 'readonly', GroupItem's individual ValueItems + * are immutable. * @return {vsaq.questionnaire.items.Item} The choice item */ vsaq.questionnaire.items.GroupItem.prototype.createSingleItem = @@ -255,9 +263,9 @@ vsaq.questionnaire.items.GroupItem.prototype.exportItem = function() { * @constructor * */ vsaq.questionnaire.items.RadiogroupItem = function(id, conditions, caption, - defaultChoice, choices, choicesConds) { + defaultChoice, choices, choicesConds, opt_auth) { goog.base(this, id, conditions, caption, defaultChoice, choices, - choicesConds); + choicesConds, opt_auth); /** @inheritDoc */ this.groupItemType = vsaq.questionnaire.items.RadioItem.TYPE; @@ -268,10 +276,10 @@ goog.inherits(vsaq.questionnaire.items.RadiogroupItem, /** @inheritDoc */ vsaq.questionnaire.items.RadiogroupItem.prototype.createSingleItem = function( - choiceId, choiceCondition, choiceText) { + choiceId, choiceCondition, choiceText, auth) { var newRadioItem = new vsaq.questionnaire.items.RadioItem(choiceId, choiceCondition, - choiceText); + choiceText, auth); // GroupItem passes readonly to ValueItem newRadioItem.type = vsaq.questionnaire.items.RadioItem.TYPE; return newRadioItem; }; @@ -292,7 +300,7 @@ vsaq.questionnaire.items.RadiogroupItem.parse = function(questionStack) { return new vsaq.questionnaire.items.RadiogroupItem( item.id, item.cond, item.text, item.defaultChoice, item.choices, - item.choicesConds); + item.choicesConds, item.auth); }; @@ -312,9 +320,9 @@ vsaq.questionnaire.items.RadiogroupItem.TYPE = 'radiogroup'; * @constructor * */ vsaq.questionnaire.items.CheckgroupItem = function(id, conditions, caption, - defaultChoice, choices, choicesConds) { + defaultChoice, choices, choicesConds, opt_auth) { goog.base(this, id, conditions, caption, defaultChoice, choices, - choicesConds); + choicesConds, opt_auth); /** @inheritDoc */ this.groupItemType = vsaq.questionnaire.items.CheckItem.TYPE; @@ -325,10 +333,11 @@ goog.inherits(vsaq.questionnaire.items.CheckgroupItem, /** @inheritDoc */ vsaq.questionnaire.items.CheckgroupItem.prototype.createSingleItem = function( - choiceId, choiceCondition, choiceText) { + choiceId, choiceCondition, choiceText, opt_auth) { var newCheckItem = new vsaq.questionnaire.items.CheckItem(choiceId, choiceCondition, - choiceText); + choiceText, opt_auth); // GroupItem passes readonly to ValueItem + newCheckItem.type = vsaq.questionnaire.items.CheckItem.TYPE; return newCheckItem; }; @@ -358,5 +367,5 @@ vsaq.questionnaire.items.CheckgroupItem.parse = function(questionStack) { return new vsaq.questionnaire.items.CheckgroupItem( item.id, item.cond, item.text, item.defaultChoice, item.choices, - item.choicesConds); + item.choicesConds, item.auth); }; diff --git a/vsaq/static/questionnaire/groupitem_test.js b/vsaq/static/questionnaire/groupitem_test.js index d614cf5..97d584d 100644 --- a/vsaq/static/questionnaire/groupitem_test.js +++ b/vsaq/static/questionnaire/groupitem_test.js @@ -124,10 +124,53 @@ function testGroupItemParse() { var testStack = [TEST_CHECKGROUP]; checkgroupItem = vsaq.questionnaire.items.CheckgroupItem.parse(testStack); assert(checkgroupItem instanceof vsaq.questionnaire.items.CheckgroupItem); + for (var i = 0; i < checkgroupItem.containerItems.length; ++i) + assertFalse('readonly' == checkgroupItem.containerItems[i].auth); testStack = [TEST_RADIOGROUP]; radiogroupItem = vsaq.questionnaire.items.RadiogroupItem.parse(testStack); assert(radiogroupItem instanceof vsaq.questionnaire.items.RadiogroupItem); + for (var i = 0; i < radiogroupItem.containerItems.length; ++i) + assertFalse('readonly' == radiogroupItem.containerItems[i].auth); + + // Verify all items once again + testGroupItem(); + + // testing readonly functionality + var TEST_CHECKGROUP2 = { + "type": "checkgroup", + "defaultChoice": true, + "text": "caption", + "choices": [ + {"choice_id_1": "Text 1"}, + {"choice_id_2": "Text 2"} + ], + "choicesConds": [], + "auth": "readonly" + }; + + var TEST_RADIOGROUP2 = { + 'type': 'radiogroup', + 'text': 'caption', + 'defaultChoice': false, + 'choices': [ + {'choice_id_1': 'Text 1'}, + {'choice_id_2': 'Text 2'} + ], + 'choicesConds': [], + 'auth': 'readonly' + }; + testStack = [TEST_CHECKGROUP2]; + checkgroupItem = vsaq.questionnaire.items.CheckgroupItem.parse(testStack); + assert(checkgroupItem instanceof vsaq.questionnaire.items.CheckgroupItem); + for (var i = 0; i < checkgroupItem.containerItems.length; ++i) + assertEquals('readonly', checkgroupItem.containerItems[i].auth); + + testStack = [TEST_RADIOGROUP2]; + radiogroupItem = vsaq.questionnaire.items.RadiogroupItem.parse(testStack); + assert(radiogroupItem instanceof vsaq.questionnaire.items.RadiogroupItem); + for (var i = 0; i < radiogroupItem.containerItems.length; ++i) + assertEquals('readonly', radiogroupItem.containerItems[i].auth); // Verify all items once again testGroupItem(); diff --git a/vsaq/static/questionnaire/items.js b/vsaq/static/questionnaire/items.js index 8c9d782..c1e96de 100644 --- a/vsaq/static/questionnaire/items.js +++ b/vsaq/static/questionnaire/items.js @@ -64,7 +64,7 @@ goog.inherits(vsaq.questionnaire.items.ParseError, goog.debug.Error); /** * Base class for all questionnaire items. *
All items derived from this base class need to have a property - * {@code TYPE}, which holds the identifier that is used for the particular item + * `TYPE`, which holds the identifier that is used for the particular item * kind in the serialized format.
* @param {?string} id An ID uniquely identifying the question. * @param {?string} conditions A string containing conditions which must be met @@ -358,7 +358,7 @@ vsaq.questionnaire.items.Item.TYPE; * a Json structure. This function takes the first element from the passed * array of serialized questionnaire items, and returns the parsed item. * If an error is encountered parsing the top item, the function will throw a - * {@code vsaq.questionnaire.items.ParseError}. + * `vsaq.questionnaire.items.ParseError`. * @param {!Array.After instantiation, the questions and structure of the questionnaire need - * to be provided by setting an Array of {@code vsaq.questionnaire.items.Item}s - * with {@code setTemplate}. If previous answers have been recorded, those can - * be loaded with {@code setValues}.
+ * to be provided by setting an Array of `vsaq.questionnaire.items.Item`s + * with `setTemplate`. If previous answers have been recorded, those can + * be loaded with `setValues`. * - *Calling {@code render} will display the questionnaire to the user. It + *
Calling `render` will display the questionnaire to the user. It * should be called only after setting a template.
* *Any changes the user makes to the questionnaire cause an {@code * goog.events.EventType.CHANGE} event to be raised. At any given time, the - * currently selected answers can be exported through {@code getValuesAsJson} + * currently selected answers can be exported through `getValuesAsJson` * in JSON format.
* * @constructor @@ -119,7 +118,7 @@ vsaq.Questionnaire = function(rootElement) { /** * A dictionary of all items, where their ID is the key. - * @type {!Object.{$knownPropertiesKeys[$knownPropertyIndex]} | @@ -121,7 +121,7 @@ {/if} |
Based on your selections, we have compiled the following todo list for you:
Great news, you're all set. There is no todo.
diff --git a/vsaq/static/questionnaire/tipitem.js b/vsaq/static/questionnaire/tipitem.js index 00ad65d..0471a56 100644 --- a/vsaq/static/questionnaire/tipitem.js +++ b/vsaq/static/questionnaire/tipitem.js @@ -33,7 +33,7 @@ goog.require('vsaq.questionnaire.utils'); /** - * A tip that provides advice to the user. If {@code clarification} is set to + * A tip that provides advice to the user. If `clarification` is set to * true, additionally a text area is shown to the user where they can provide * additional information. * @param {string} id An ID uniquely identifying the tip. @@ -48,12 +48,15 @@ goog.require('vsaq.questionnaire.utils'); * @param {string=} opt_name Name of the issue. * @param {string=} opt_todo Todo list entry associated with the tip. * @param {string=} opt_customTitle Title of bubble defined in JSON. + * @param {string=} opt_auth If "readonly", this ValueItem cannot be modified. * @extends {vsaq.questionnaire.items.ValueItem} * @constructor */ vsaq.questionnaire.items.TipItem = function(id, conditions, text, warn, - opt_severity, opt_clarification, opt_name, opt_todo, opt_customTitle) { - goog.base(this, id, conditions, text); + opt_severity, opt_clarification, opt_name, opt_todo, opt_customTitle, + opt_auth) { + goog.base(this, id, conditions, text, undefined, undefined, undefined, + undefined, undefined, opt_auth); /** * Indicating whether the tip is a warning or only informational. @@ -192,14 +195,16 @@ vsaq.questionnaire.items.TipItem.parse = function(questionStack) { var isWarn = goog.string.makeSafe(item.warn) == 'yes'; return new vsaq.questionnaire.items.TipItem(item.id, item.cond, item.text, - isWarn, item.severity, item.why, item.name, item.todo, item.customTitle); + isWarn, item.severity, item.why, item.name, item.todo, item.customTitle, + item.auth); }; /** @inheritDoc */ vsaq.questionnaire.items.TipItem.prototype.setReadOnly = function(readOnly) { if (this.textArea_) - this.textArea_.readOnly = readOnly; + // if item marked readonly, always keep it readonly + this.textArea_.readOnly = this.readonly ? true : readOnly; }; diff --git a/vsaq/static/questionnaire/tipitem_test.js b/vsaq/static/questionnaire/tipitem_test.js index cea2e10..37fac04 100644 --- a/vsaq/static/questionnaire/tipitem_test.js +++ b/vsaq/static/questionnaire/tipitem_test.js @@ -133,5 +133,27 @@ function testTipItemParse() { assertEquals(0, testStack.length); assertEquals(TODO, tip.todo); assertEquals(CUSTOMTITLE_TEXT, tip.customTitle); + assertTrue(tip.auth != 'readonly'); + + testStack = [{ + 'type': 'tip', + 'text': CAPTION, + 'id': TIP_ID, + 'warn': 'yes', + 'why': WHY_TEXT, + 'todo': TODO, + 'customTitle' : CUSTOMTITLE_TEXT, + 'auth': 'readonly', + }]; + tip = vsaq.questionnaire.items.TipItem.parse(testStack); + assert(tip instanceof vsaq.questionnaire.items.TipItem); + assertEquals(TIP_ID, tip.id); + assertEquals(CAPTION, tip.text); + assertEquals(true, tip.warn); + assertEquals(WHY_TEXT, tip.clarification); + assertEquals(0, testStack.length); + assertEquals(TODO, tip.todo); + assertEquals(CUSTOMTITLE_TEXT, tip.customTitle); + assertEquals('readonly', tip.auth); } diff --git a/vsaq/static/questionnaire/uploaditem.js b/vsaq/static/questionnaire/uploaditem.js index 8f78e0b..dca947e 100644 --- a/vsaq/static/questionnaire/uploaditem.js +++ b/vsaq/static/questionnaire/uploaditem.js @@ -44,11 +44,13 @@ goog.require('vsaq.questionnaire.utils'); * @param {?string} conditions A string containing conditions which must be met * for the item to be visible to the user. * @param {string} caption The caption to show above the file upload. + * @param {string=} opt_auth If "readonly", this ValueItem cannot be modified. * @extends {vsaq.questionnaire.items.ValueItem} * @constructor */ -vsaq.questionnaire.items.UploadItem = function(id, conditions, caption) { - goog.base(this, id, conditions, caption); +vsaq.questionnaire.items.UploadItem = function(id, conditions, caption, opt_auth) { + goog.base(this, id, conditions, caption, undefined, undefined, undefined, + undefined, undefined, opt_auth); /** * The form through which the file is uploaded. @@ -258,13 +260,14 @@ vsaq.questionnaire.items.UploadItem.parse = function(questionStack) { if (item.type != vsaq.questionnaire.items.UploadItem.TYPE) throw new vsaq.questionnaire.items.ParseError('Wrong parser chosen.'); - return new vsaq.questionnaire.items.UploadItem(item.id, item.cond, item.text); + return new vsaq.questionnaire.items.UploadItem(item.id, item.cond, item.text, item.auth); }; /** @inheritDoc */ vsaq.questionnaire.items.UploadItem.prototype.setReadOnly = function(readOnly) { - this.fileInput_.readOnly = readOnly; + // if item marked readonly, always keep it readonly + this.fileInput_.readOnly = this.auth == 'readonly' ? true : readOnly; }; diff --git a/vsaq/static/questionnaire/uploaditem_test.js b/vsaq/static/questionnaire/uploaditem_test.js index fc9f461..02dbfec 100644 --- a/vsaq/static/questionnaire/uploaditem_test.js +++ b/vsaq/static/questionnaire/uploaditem_test.js @@ -132,6 +132,20 @@ function testUploadItemParse() { assertEquals(ID, upload.id); assertEquals(CAPTION, upload.text); assertEquals(0, testStack.length); + assertTrue(upload.auth != 'readonly'); + + testStack = [{ + 'type': 'upload', + 'text': CAPTION, + 'id': ID, + 'auth': 'readonly' + }]; + upload = vsaq.questionnaire.items.UploadItem.parse(testStack); + assert(upload instanceof vsaq.questionnaire.items.UploadItem); + assertEquals(ID, upload.id); + assertEquals(CAPTION, upload.text); + assertEquals(0, testStack.length); + assertEquals('readonly', upload.auth); } diff --git a/vsaq/static/questionnaire/yesnoitem.js b/vsaq/static/questionnaire/yesnoitem.js index a0b644a..30d4c5f 100644 --- a/vsaq/static/questionnaire/yesnoitem.js +++ b/vsaq/static/questionnaire/yesnoitem.js @@ -40,12 +40,14 @@ goog.require('vsaq.questionnaire.utils'); * @param {string} caption The caption to show next to the radio button. * @param {string} yes String shown as label for the first option. * @param {string} no String shown as label for the second option. + * @param {string=} opt_auth If "readonly", this ValueItem cannot be modified. * @extends {vsaq.questionnaire.items.ValueItem} * @constructor */ vsaq.questionnaire.items.YesNoItem = function(id, conditions, caption, yes, - no) { - goog.base(this, id, conditions, caption); + no, opt_auth) { + goog.base(this, id, conditions, caption, undefined, undefined, undefined, + undefined, undefined, opt_auth); /** * The text for the yes choice. @@ -151,14 +153,15 @@ vsaq.questionnaire.items.YesNoItem.parse = function(questionStack) { throw new vsaq.questionnaire.items.ParseError('Wrong parser chosen.'); return new vsaq.questionnaire.items.YesNoItem(item.id, item.cond, item.text, - item.yes, item.no); + item.yes, item.no, item.auth); }; /** @inheritDoc */ vsaq.questionnaire.items.YesNoItem.prototype.setReadOnly = function(readOnly) { - this.yesRadio_.disabled = readOnly; - this.noRadio_.disabled = readOnly; + // if item marked readonly, always keep it readonly + this.yesRadio_.disabled = this.auth == 'readonly' ? true : readOnly; + this.noRadio_.disabled = this.auth == 'readonly' ? true : readOnly; }; diff --git a/vsaq/static/questionnaire/yesnoitem_test.js b/vsaq/static/questionnaire/yesnoitem_test.js index d911232..6d21c9c 100644 --- a/vsaq/static/questionnaire/yesnoitem_test.js +++ b/vsaq/static/questionnaire/yesnoitem_test.js @@ -100,6 +100,27 @@ function testYesNoItemParse() { assertEquals(YES, yesno.yes); assertEquals(NO, yesno.no); assertEquals(0, testStack.length); + assertTrue(yesno.auth != 'readonly'); + + assert(yesno.yesRadio_ instanceof HTMLInputElement); + assert(yesno.noRadio_ instanceof HTMLInputElement); + + var testStack2 = [{ + 'type': 'yesno', + 'text': CAPTION, + 'id': ID, + 'yes': YES, + 'no': NO, + 'auth': 'readonly' + }]; + yesno = vsaq.questionnaire.items.YesNoItem.parse(testStack2); + assert(yesno instanceof vsaq.questionnaire.items.YesNoItem); + assertEquals(ID, yesno.id); + assertEquals(CAPTION, yesno.text); + assertEquals(YES, yesno.yes); + assertEquals(NO, yesno.no); + assertEquals(0, testStack.length); + assertEquals('readonly', yesno.auth); assert(yesno.yesRadio_ instanceof HTMLInputElement); assert(yesno.noRadio_ instanceof HTMLInputElement); diff --git a/vsaq/static/utils.js b/vsaq/static/utils.js index 4098d5b..1f0b604 100644 --- a/vsaq/static/utils.js +++ b/vsaq/static/utils.js @@ -29,7 +29,6 @@ goog.require('goog.events.EventType'); goog.require('goog.object'); goog.require('goog.string'); goog.require('goog.string.format'); -goog.require('goog.structs'); /** @@ -62,7 +61,7 @@ vsaq.utils.addClickHandler = function(elementId, callback) { vsaq.utils.initClickables = function(handlers) { goog.events.listen(goog.dom.getDocument(), [goog.events.EventType.CLICK], function(e) { - goog.structs.forEach(handlers, function(handler, className) { + goog.object.forEach(handlers, function(handler, className) { var clickable = goog.dom.getAncestorByClass(e.target, className); if (clickable && !goog.dom.classlist.contains(clickable, 'maia-button-disabled')) {