thefragebogen / thefragebogen Goto Github PK
View Code? Open in Web Editor NEWA standalone questionnaire system that only requires a HTML5-capable web browser.
Home Page: http://www.TheFragebogen.de
License: MIT License
A standalone questionnaire system that only requires a HTML5-capable web browser.
Home Page: http://www.TheFragebogen.de
License: MIT License
Assigning className = undefined
is annoying when debugging CSS.
In addition, move QuestionnaireItem.className
to UIElement
.
Needs to implemented after ES6 upgrade.
TODOs:
let
instead of var
The issue for moving to ES6 class structure is #28.
In TheFragebogen parameter-based constructors are used: new QuestionnaireItemDefinedOne(undefined, "Are you ready?", true, ["Yes"])
.
This is, however, difficult to read for users - especially as optional parameters need to be defined.
It would be great to enable use of so called named parameters: new QuestionnaireItemDefinedOne({question: "Are you ready?", options: {"Yes}, required: true})
.
As alternative a factory pattern could be used.
See: http://2ality.com/2011/11/keyword-parameters.html
Requirement:
At the moment, TheFragebogen does not store it's progress persistently.
Thus, closing the web page will result in a loss of all so far gathered data.
This issue might be avoided to a certain degree by storing the data at a regular interval.
However, continuing a questionnaire is not easily possible using this method.
It would be nice to persistently store the current progress (incl. gathered data) in HTML5's local storage [1].
On start, TheFragebogen can check if their is stored data available and if it's fits to the questionnaire (ie. data is in line with the loaded configuration).
If so the data can be restored and TheFragebogen would continue from the last stored data point.
Support for serializing and de-serializing should be already implemented for nearly all components of TheFragebogen using the getData()-method and setData()-method after an object was created.
[1] https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API
Sometimes, the time how long a screen is shown needs to be measured.
For ScreenUIElements
this can be easily done using a questionnaireItem.
However, for other screens this functionality would need be implemented manually again.
A generic solution would be desired.
Code should be working, but it is not nice.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Observer</title>
</head>
<body>
<div id="content">
<fieldset>
<legend>Server Location</legend>
<div>
<label>URL:</label>
<input type="text" id="serverUrl" value="ws://localhost:8080">
<button id="connectButton">Open</button>
<button id="disconnectButton" style="display:none">Close</button>
</div>
<div>
<label>Status:</label>
<span id="connectionStatus">CLOSED</span>
</div>
</fieldset>
<fieldset id="requestArea">
<legend>Request</legend>
<div>
<textarea id="sendMessage"></textarea>
</div>
<div>
<button id="sendButton" disabled="disabled">Send</button>
[Shortcut] Ctr + Enter
</div>
</fieldset>
<fieldset id="messageArea">
<legend>Message Log <button id="clearButton">Clear</button></legend>
<div id="messages"></div>
</fieldset>
</div>
</body>
<script type="text/javascript">
//Variables
var ws = null;
var connected = false;
var serverUrl = $('#serverUrl');
var connectionStatus = $('#connectionStatus');
var sendMessage = $('#sendMessage');
var messages = $('#messages');
var connectButton = $('#connectButton');
var disconnectButton = $('#disconnectButton');
var sendButton = $('#sendButton');
var clearButton = $('#clearButton');
//Shortcut to send message
var isCtrl;
sendMessage.keyup(function (e) {
if (e.which == 17) isCtrl = false;
}).keydown(function (e) {
if (e.which == 17) isCtrl = true;
if (e.which == 13 && isCtrl == true) {
sendButton.click();
return false;
}
});
//Click events
connectButton.click(function (e) {
close();
open();
});
disconnectButton.click(function (e) {
close();
});
sendButton.click(function (e) {
var msg = $('#sendMessage').val();
var data = {type: "message", timeStamp: Date.now(), origin: "you", data: msg}
addMessage(data);
ws.send(msg);
});
clearButton.click(function (e) {
clearLog();
});
//Functions
var open = function () {
var url = serverUrl.val();
ws = new WebSocket(url);
ws.onopen = onOpen;
ws.onclose = onClose;
ws.onmessage = onMessage;
ws.onerror = onError;
connectionStatus.text('OPENING ...');
serverUrl.attr('disabled', 'disabled');
connectButton.hide();
disconnectButton.show();
}
var close = function () {
if (ws) {
console.log('CLOSING ...');
ws.close();
}
}
var addMessage = function (data) {
var text = typeof (data) == "object" ? ("(" + new Date(data.timeStamp).toLocaleTimeString() + ") " + data.origin + " sent '" + data.data + "'") : data;
messages.prepend($('<pre>').text(text));
}
var updateMessages = function (msgs) {
clearLog();
for (var i in msgs)
addMessage(msgs[i]);
console.log("Messages updated!");
}
var clearLog = function () {
messages.html('');
}
//Event listeners
var onOpen = function () {
console.log('OPENED: ' + serverUrl.val());
connected = true;
connectionStatus.text('OPENED');
sendMessage.removeAttr('disabled');
sendButton.removeAttr('disabled');
};
var onClose = function () {
console.log('CLOSED: ' + serverUrl.val());
ws = null;
connected = false;
connectionStatus.text('CLOSED');
serverUrl.removeAttr('disabled');
connectButton.show();
disconnectButton.hide();
sendMessage.attr('disabled', 'disabled');
sendButton.attr('disabled', 'disabled');
};
var onMessage = function (event) {
try {
var data = JSON.parse(event.data);
} catch (err) {
console.log("Try with an Object stringified");
var data = event
}
try {
if (JSON.parse(data.data) instanceof Array)
updateMessages(JSON.parse(data.data));
} catch (err) {
addMessage(data);
}
};
var onError = function (event) {
alert(event.data);
}
</script>
</html>
Using addEventListener
is easier to read and also easier to find.
Should be done after finishing ES6 upgrade (ie., after merging ES6class).
QuestionnaireItemQuality7pt
has fixed content for all labels already set in the SVG.
It would be great to have make these configurable.
Call parent method if needed.
For example: UIElement.releaseUI().
The same for Screen.
UIElements
(and their siblings) should in createUI()
set this.node
instead of var node
.
NPM already complains about potential security issues.
Should upgrade all version to most recent.
In addition, the copy of grunt-concat-in-order should be removed and the one from npm used.
We just need to wait for the next release.
The data exported so far, the concat
-operator is used implicitely in getDataCSV
.
For complex objects this should be changed to JSON.stringify()
.
Code is in ScreenController.prototype.requestDataArray = function() {
ES6 allows to set default values to function parameters: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters
This would make the code base easier to maintain.
UglifyJS does not support ES6.
As their is not easy out-of-the-box replacement, the a minified version will not be included in ES6+ releases.
In some instances the string(s) are concatenated by the calling function.
Example: https://github.com/TheFragebogen/TheFragebogen/blob/master/src/screen_data_preview.js#L33
Freely available services are:
Are there alternatives?
Pagination is already implemented (see PaginateUIButton
).
However, setting it up for a child of Screen
is quite annoying.
Solution: Implement a class ScreenPaginate
that provides this functionality and can be used for inheritance.
Also affects: ScreenUIElements
and ScreenDataPreview
thefragebogen.js
does not contain meta data about the version.
This is only added for thefragebogen.min.js
.
This might lead to confusion if somebody uses the .js
file instead of min.js
.
For development and debugging this might be done quite frequently.
A pull request to grunt's concat-in-order is submitted adding the banner option.
miensol/grunt-concat-in-order#7
Not sure if I'm doing something wrong here, I followed the "Getting Started" guide and everything else worked fine.
PS C:\Users\gabriel\Documents\Fragebogen\TheFragebogen> grunt doc
Running "includereplace:dist" (includereplace) task
Processed 43 files
Running "revision" task
HEAD at revision b883a790163947f6fc1fa51fe0d178629f83947b
Running "concat_in_order:your_target" (concat_in_order) task
File "thefragebogen.js" created.
Running "uglify:dist" (uglify) task
>> 1 file created 185.14 kB โ 125.63 kB
Running "run:jsdoc" (run) task
Unknown command-line option "d=doc".
JSDoc 3.5.5 (Thu, 14 Sep 2017 02:51:54 GMT)
Options:
-a, --access <value> Only
display
symbols
with
the
given
access:
"package",
public",
"protected",
"private"
or
"undefined",
or
"all"
for
all
access
levels.
Default:
all
except
"private"
-c, --configure <value> The
path
to
the
configuration
file.
Default:
path/to/jsdoc/conf.json
-d, --destination <value> The
path
to
the
output
folder.
Default:
./out/
--debug Log
information
for
debugging
JSDoc.
-e, --encoding <value> Assume
this
encoding
when
reading
all
source
files.
Default:
utf8
-h, --help Print
this
message
and
quit.
--match <value> When
running
tests,
only
use
specs
whose
names
contain
<value>.
--nocolor When
running
tests,
do
not
use
color
in
console
output.
-p, --private Display
symbols
marked
with
the
@private
tag.
Equivalent
to
"--access
all".
Default:
false
-P, --package <value> The
path
to
the
project's
package
file.
Default:
path/to/sourcefiles/package.json
--pedantic Treat
errors
as
fatal
errors,
and
treat
warnings
as
errors.
Default:
false
-q, --query <value> A
query
string
to
parse
and
store
in
jsdoc.env.opts.query.
Example:
foo=bar&baz=true
-r, --recurse Recurse
into
subdirectories
when
scanning
for
source
files
and
tutorials.
-R, --readme <value> The
path
to
the
project's
README
file.
Default:
path/to/sourcefiles/README.md
-t, --template <value> The
path
to
the
template
to
use.
Default:
path/to/jsdoc/templates/default
-T, --test Run
all
tests
and
quit.
-u, --tutorials <value> Directory
in
which
JSDoc
should
search
for
tutorials.
-v, --version Display
the
version
number
and
quit.
--verbose Log
detailed
information
to
the
console
as
JSDoc
runs.
-X, --explain Dump
all
found
doclet
internals
to
console
and
quit.
Visit http://usejsdoc.org for more information.
There are no input files to process.
Warning: non-zero exit code 1 Use --force to continue.
Aborted due to warnings.
PS C:\Users\gabriel\Documents\Fragebogen\TheFragebogen>
ES2015 allows to provide default parameters for functions.
function(a = 1, b = 2) {...}
For all parameters not requiring range checks this would be nice (e.g., className).
https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Functions/Default_parameters
NOTE: Requires update UglfiyJS first.
So far only the final answer given to a questionnaireitem is stored and then later available for export.
For some situations, it might be interesting to see
This would require rewritting the QuestionnaireItem.setAnswer()
as well as QuestionnaireItem.getData()
.
ATTENTION: QuestionnaireItem.setAnswer()
is at the moment called by the UI as well as the internal processes to restore data.
This would need to be changed to determine the source of the call to setAnswer()
.
In the best case use two separate methods.
#62 needs to be addressed first.
Here is some not working demo code.
function ScreenUIElementsJump() {
ScreenUIElements.apply(this, arguments);
this.backCallback = null;
this.sentBy = null;
this.jumpIndex=1;
}
ScreenUIElementsJump.prototype = Object.create(ScreenUIElements.prototype);
ScreenUIElementsJump.prototype.constructor = ScreenUIElementsJump;
ScreenUIElementsJump.prototype.setOnBackCallback = function(backCallback){
this.backCallback = backCallback;
};
ScreenUIElementsJump.prototype._sendOnBackCallback = function(){
this.sentBy = -1;
this.backCallback();
}
ScreenUIElementsJump.prototype.createUI = function () {
this.node = document.createElement("div");
this.node.className = this.className;
for (var index in this.elements) {
if (this.elements[index].createUI !== undefined) {
var node = this.elements[index].createUI();
if (node !== undefined && node !== null) this.node.appendChild(node);
} else
TheFragebogen.logger.warn(this.constructor.name + ".createUI():", "Element[" + index + "] has no 'createUI' method");
}
var button = document.createElement("input");
button.type = "button";
button.value = "Replay video";
button.onclick = (this._sendOnBackCallback).bind(this);
this.node.appendChild(button);
this.node.appendChild(document.createElement("br"));
var button = document.createElement("input");
button.type = "button";
button.value = "Next";
button.onclick = (this._sendOnReadyCallback).bind(this);
button.style.marginTop = "20px";
this.node.appendChild(button);
return this.node;
};
The callbacks for the screens are set in both functions.
for (i = 0; i < this.screen.length; i++) {
if (this.screen[i].setGetDataCallback instanceof Function) {
this.screen[i].setGetDataCallback((this.requestDataCSV).bind(this));
}
if (this.screen[i].setGetRawDataCallback instanceof Function) {
this.screen[i].setGetRawDataCallback((this.requestDataArray).bind(this));
}
if (this.screen[i].setOnReadyStateChangedCallback instanceof Function) {
this.screen[i].setOnReadyStateChangedCallback((this.nextScreen).bind(this));
}
}
Replace accessing this.answer
directly and use set/get.
Should be done after ES6 integration.
Setup of TravisIC
Implement full-blown test cases that also check the exported data (i.e., screenController.requestDataAraray()
).
This will allow to spot regressions.
Do not use typeof(a) === "undefined"
, but rather use a === undefined
.
Occurences:
src/screen_uielements.js
22: if (typeof(className) !== "string" || typeof(className) === "undefined" || className === null) {
src/questionnaireitem_write.js
31: this.backgroundImg = typeof(backgroundImg) !== "undefined" ? backgroundImg : "";
examples/example_uielements.html
32: if (typeof(questionnaireItems) === "undefined") {
examples/example_screen_sequential.html
47: if (typeof(screenController) === "undefined") {
examples/developer_template.html
65: if (typeof(screenController) === "undefined") {
examples/example_dynamic.html
23: if (typeof(screenController) === "undefined") {
examples/example_paginate.html
56: if (typeof(screenController) === "undefined") {
examples/example_media.html
56: if (typeof(screenController) === "undefined") {
examples/example_print.html
55: if (typeof(screens) === "undefined") {
examples/example_iframe.html
80: if (typeof(screenController) === "undefined") {
examples/example_basic.html
93: if (typeof(screenController) === "undefined") {
examples/example_websockets.html
63: if (typeof(screenController) === "undefined") {
tests/manual_test.html
114: if (typeof(screenController) === "undefined") {
CODING.md
61:7. typeof
always returns a string
: (typeof(undefined) === "undefined")
=> true
Adding a LINTer to the grunt-based build would be nice.
QUnit makes setting up on TravisCI (or general headless CI) difficult.
Moving to another (newer) framework would be great.
Jamine or Mocha would be options.
This is blocking finalization of #23.
Sometimes (especially without preloading) it would be nice to know the number of stalling events.
This information can be retrieved from the audio/media element that is used (events: stalling, waiting).
So far, only TheFragebogen's logger.error was called.
However, this does not result in failure of tests for example and it also would be nice to inform the developer that something is wrong.
throw new Error("Something weird happened");
instead.
Only question: change this in constructors or change logger.error to emit the failure?
Convert the code base to the ES6 class definitions [1].
In addition, also calling super-methods should be added everywhere (QuestionnaireItem.releaseUI()
and its childs).
[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
#22 is related.
For some applications it might be easier to import JSON rather than CSV (e.g., processing data directly in the web browser).
So far all classes storing data provide the method ScreenController.getDataCSV()
that converts the stored data into CSV (single or multiple lines).
To implement JSON export, adding ScreenController.getDataJSON()
might be an option.
In addition, the data format needs to be configurable.
At the moment it is set in https://github.com/TheFragebogen/TheFragebogen/blob/master/src/screencontroller.js#L52
The method getData()
, setData()
, and _checkData()
were implemented to restore a questionnaire from LocalStorage.
However, the implementation was never ready for use and was therefore removed before open source release.
These method should therefore also be removed and, if needed again, re-implementation should take place.
And rename screen.getDataCSV()
to screen.getData()
.
Do after ES6 upgrade (i.e., formatting).
The generated JSDoc could be improved by reformatting the comments.
For examples, add line breaks etc.
At the moment, it is rather difficult to read.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.