diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..e0d29e1 Binary files /dev/null and b/.DS_Store differ diff --git a/Gemfile b/Gemfile old mode 100644 new mode 100755 diff --git a/Gemfile.lock b/Gemfile.lock old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/app/.DS_Store b/app/.DS_Store new file mode 100755 index 0000000..dc6d923 Binary files /dev/null and b/app/.DS_Store differ diff --git a/app/assets/.DS_Store b/app/assets/.DS_Store new file mode 100755 index 0000000..11a75f5 Binary files /dev/null and b/app/assets/.DS_Store differ diff --git a/app/assets/javascripts/.DS_Store b/app/assets/javascripts/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/app/assets/javascripts/.DS_Store differ diff --git a/app/assets/javascripts/collaborators.coffee b/app/assets/javascripts/collaborators.coffee old mode 100644 new mode 100755 diff --git a/app/assets/javascripts/table.js b/app/assets/javascripts/table.js new file mode 100755 index 0000000..294abea --- /dev/null +++ b/app/assets/javascripts/table.js @@ -0,0 +1,125 @@ +function tableDraw() + {this.span(); + + var props = ""; + var i = 0; + var dummyRows = this.content.length; + var dummyCols = this.content[0].length; + + var dummyCode = '= this.propsLengthPublic) {break;} + dummyCode = dummyCode+' '+prop+'="'+this[prop]+'"';i++; + } + dummyCode = dummyCode+'>\n'; + + for (var i=1;i<=dummyRows;i++) + {dummyCode = dummyCode+'\n'; + for (var k=1;k<=dummyCols;k++) + {if (this.content[(i-1)][(k-1)][2] == "false") + {dummyCode = dummyCode+' '+this.content[(i-1)][(k-1)][1]+'\n';} + } + dummyCode = dummyCode+'\n'; + } + + dummyCode = dummyCode+'\n'; + return dummyCode; + } + +function tableSpan() + {var dummyRows = this.content.length; + var dummyCols = this.content[0].length; + + for (var i=1;i<=dummyRows;i++) + {var rowspan = 0;var colspan = 0; + var fromHere = 0;var tillThere = 0; + var myString = ""; + for (var k=1;k<=dummyCols;k++) + {rowspan = 0;colspan = 0; + if (this.content[(i-1)][(k-1)][2] == "false") + {myString = (this.content[(i-1)][(k-1)][0]) + ""; + if (myString.indexOf("colspan") >= 0) + {fromHere = (myString.indexOf("colspan"))+9; + tillThere = (myString.indexOf('"',fromHere))-1; + if ((fromHere-tillThere) == 0) + {colspan = myString.charAt(fromHere);} + else + {colspan = myString.substring(fromHere,tillThere);} + colspan = parseInt(colspan); + } + if (myString.indexOf("rowspan") >= 0) + {fromHere = (myString.indexOf("rowspan"))+9; + tillThere = (myString.indexOf('"',fromHere))-1; + if ((fromHere-tillThere) == 0) + {rowspan = myString.charAt(fromHere);} + else + {rowspan = myString.substring(fromHere,tillThere);} + rowspan = parseInt(rowspan); + } + if ((colspan >= 2) && (rowspan <= 1)) + {for (var m=2;m<=colspan;m++) + {this.content[(i-1)][((k-1)+(m-1))][2] = "true";} + } + if ((rowspan >= 2) && (colspan <= 1)) + {for (var m=2;m<=rowspan;m++) + {this.content[((i-1)+(m-1))][(k-1)][2] = "true";} + } + if ((rowspan >= 2) && (colspan >= 2)) + {for (var m=1;m<=rowspan;m++) + {for (var p=1;p<=colspan;p++) + {this.content[((i-1)+(m-1))][((k-1)+(p-1))][2] = "true";} + } + this.content[(i-1)][(k-1)][2] = "false"; + } } } } } + +function tableContent(dummyCols,dummyRows) + {var dummyContent = "["; + for (var i=1;i<=dummyRows;i++) + {dummyContent = dummyContent+"[" + for (var k=1;k<=dummyCols;k++) + {dummyContent = dummyContent+"[['align="+'"left"'+" valign="+'"top"'+"'],[' '],['false']],";} + dummyContent = dummyContent.substring(0,((dummyContent.length)-1)); + dummyContent = dummyContent+"]," + } + dummyContent = dummyContent.substring(0,((dummyContent.length)-1)); + dummyContent = dummyContent+"]"; + return eval(dummyContent); + } + +function tableContentArraysToArguments() + {var dummyRows = this.content.length; + var dummyCols = this.content[0].length; + for (var i=1;i<=dummyRows;i++) + {for (var k=1;k<=dummyCols;k++) + {this.content[(i-1)][(k-1)].props = this.content[(i-1)][(k-1)][0]; + this.content[(i-1)][(k-1)].value = this.content[(i-1)][(k-1)][1]; + this.content[(i-1)][(k-1)].spans = this.content[(i-1)][(k-1)][2]; + } } } + +function tableContentArgumentsToArrays() + {var dummyRows = this.content.length; + var dummyCols = this.content[0].length; + for (var i=1;i<=dummyRows;i++) + {for (var k=1;k<=dummyCols;k++) + {this.content[(i-1)][(k-1)][0] = this.content[(i-1)][(k-1)].props; + this.content[(i-1)][(k-1)][1] = this.content[(i-1)][(k-1)].value; + this.content[(i-1)][(k-1)][2] = this.content[(i-1)][(k-1)].spans; + } } } + +function table() + {var propsDefaultLength = 2; + this.cols = 1; + this.rows = 1; + for (var i=1;i<=table.arguments.length;i++) + {if (table.arguments[(i-1)].indexOf("cols") >= 0) {propsDefaultLength--;} + if (table.arguments[(i-1)].indexOf("rows") >= 0) {propsDefaultLength--;} + eval("this."+table.arguments[(i-1)]); + } + this.propsLengthPublic = (table.arguments.length + propsDefaultLength); + this.propsLengthPrivat = 7; + this.content = tableContent(this.cols,this.rows); + this.span = tableSpan; + this.draw = tableDraw; + this.contentArraysToArguments = tableContentArraysToArguments; + this.contentArgumentsToArrays = tableContentArgumentsToArrays; + } \ No newline at end of file diff --git a/app/assets/stylesheets/.DS_Store b/app/assets/stylesheets/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/app/assets/stylesheets/.DS_Store differ diff --git a/app/assets/stylesheets/events.css.less b/app/assets/stylesheets/events.css.less old mode 100644 new mode 100755 diff --git a/app/assets/stylesheets/layout.css.less b/app/assets/stylesheets/layout.css.less old mode 100644 new mode 100755 index 1939791..8fa9423 --- a/app/assets/stylesheets/layout.css.less +++ b/app/assets/stylesheets/layout.css.less @@ -49,6 +49,13 @@ time { margin: 1em 0; } +.bulletless_plus_margin { + list-style: none; + li { + margin: 5px; + } +} + .lead { i { position: relative; @@ -86,4 +93,4 @@ time { .island { padding: 1em; -} +} \ No newline at end of file diff --git a/app/assets/stylesheets/questions.css.less b/app/assets/stylesheets/questions.css.less old mode 100644 new mode 100755 index cee4043..134ef5c --- a/app/assets/stylesheets/questions.css.less +++ b/app/assets/stylesheets/questions.css.less @@ -24,3 +24,18 @@ input[type="checkbox"].questions-table__checkbox { .import-info, #compatibility { display: none; } + +.move_handler { + cursor: move; +} + +.ghost { + opacity: .5; + background: #FECA40; +} + +.create_order_option_list { + list-style-type: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/app/assets/stylesheets/surveys.css.less b/app/assets/stylesheets/surveys.css.less index 989a590..a3aa150 100755 --- a/app/assets/stylesheets/surveys.css.less +++ b/app/assets/stylesheets/surveys.css.less @@ -2,18 +2,35 @@ font-style: italic; font-size: 80%; } + #survey-options-list li { margin-bottom:10px; } + +.move_handler { + cursor: move; +} + +.categoryDiv { + border: 1px solid white; + width: 100%; + height: 36px; +} + +.ghost { + opacity: .5; + background: #FECA40; +} + #overmsg { display:none; background-color: #FCC; - border: solid 1px #C66; - -moz-border-radius: 5px; - border-radius: 5px; - -moz-box-shadow: 5px 5px 2px #C66; - -webkit-box-shadow: 5px 5px 2px #C66; - box-shadow: 5px 5px 2px #C66; + border: solid 1px #C66; + -moz-border-radius: 5px; + border-radius: 5px; + -moz-box-shadow: 5px 5px 2px #C66; + -webkit-box-shadow: 5px 5px 2px #C66; + box-shadow: 5px 5px 2px #C66; margin-bottom:10px; } #not_running { @@ -33,6 +50,15 @@ padding-bottom: 0.5em; font-weight: bold; } + +#survey-table { + width: 100%; + + td { + width: 50%; + } +} + .pointer { cursor: pointer; } @@ -56,7 +82,66 @@ } } +.selectable { + list-style-type: none; + margin: 0; + padding: 0; + + li { + border: 1px solid LightGray; + padding: 0.4em; + margin-top: 6px; + margin-bottom: 6px; + text-align: center; + border-radius: 5px; + word-wrap: break-word; + } +} + +.leftOptions { + margin-right: 2px; +} + +.rightOptions { + margin-left: 2px; +} + +.select-result { + padding: 0em; + margin: 0px; +} + +.select-result div { + border: 1px solid LightGray; + margin-bottom: 5px; + padding: 0em; + width:100%; + text-align:center; + border-radius: 5px; +} + +.selected_left { + width:45%; + word-break: break-all; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 10px; +} +.selected_right { + width:45%; + word-break: break-all; + padding-top: 5px; + padding-bottom: 5px; + padding-right: 5px; +} + +.remove-btn { + -moz-box-shadow: inset 0 0 2px #000000; + -webkit-box-shadow: inset 0 0 2px #000000; + box-shadow: inset 0 0 2px #000000; + margin:0.4em; +} /* ======================================== @@ -96,7 +181,9 @@ display: inline; } - +.selected { + background: #FECA40; +} @@ -185,3 +272,59 @@ } } } + + +/* ======================================== + RESULT TABLE +======================================== */ + +.result-table { + table-layout:fixed; + + div { + margin: 0px !important; + padding: 0px !important; + } + + th { + text-align: center; + vertical-align: middle; + word-wrap: break-word; + } +} + +.result-table-td { + text-align: center; + word-wrap: break-word; + margin: 0px !important; + padding: 0px !important; + + div { + position: relative; + height: 100%; + width:100%; + text-align:center; + + div { + position: absolute; + left: 0px; + bottom: 0px; + z-index:-1; + background-color: #DAEDF7; + width: 100%; + } + + p { + height:any; + width:any; + position:relative; + top:50%; + margin-top:-HalfYourHeigth; + line-height: 0%; + } + } +} + +.greenBorder { + border: 3px solid green !important; +} diff --git a/app/controllers/.DS_Store b/app/controllers/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/app/controllers/.DS_Store differ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb old mode 100644 new mode 100755 diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb old mode 100644 new mode 100755 diff --git a/app/controllers/question_comments_controller.rb b/app/controllers/question_comments_controller.rb old mode 100644 new mode 100755 diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb old mode 100644 new mode 100755 index 3dbfbd7..69336e5 --- a/app/controllers/questions_controller.rb +++ b/app/controllers/questions_controller.rb @@ -99,10 +99,16 @@ def new @question_multi = MultipleChoiceQuestion.new.tap { |q| q.question_options.build } @question_text = TextQuestion.new @question_number = NumberQuestion.new #refactor this maybe? + @question_match = MatchQuestion.new.tap { |q| q.answer_pairs.build } + @question_order = OrderQuestion.new.tap { |q| q.order_options.build } + @question_category = CategoryQuestion.new.tap do |q| + q.categories.build + q.sub_words.build + end end def edit - + end def update @@ -122,6 +128,7 @@ def update format.json { render json: @question.errors, status: :unprocessable_entity } end end + end def transform @@ -145,6 +152,28 @@ def create @question.add_setting("answers", params[:options]) end + if @question.has_order_options? + votesString = "" + for index in 1..@question.order_options.length + if index == @question.order_options.length + votesString += "0" + else + votesString += "0," + end + end + @question.order_options.each do |option| + option.votes = votesString + end + @question.relative_option_order_object = Hash.new + end + + # shitty work-around: Don't know why, but the first answer_pair doesn't seem to get into + # the params[:question][:answer_pairs_attributes] but in the params[:question][:answer_pair]. + # So we have to get it out of there and into the right place. + if params[:question][:answer_pair] && @question.has_answer_pairs? + @question.answer_pairs << AnswerPair.new(:answer1 => params[:question][:answer_pair][:answer1].to_s, :answer2 => params[:question][:answer_pair][:answer2].to_s, :correct => true) + end + respond_to do |format| if @question.save @event = Event.find_by_id_or_token(params[:redirect_to_session]) if params[:redirect_to_session] @@ -175,6 +204,22 @@ def destroy end end + def clone + original_question = Question.find(params[:id]) + @question_duplicate = Question.new_from_existing(original_question) + @question_duplicate.user = current_user + + respond_to do |format| + if @question_duplicate.save + format.html { redirect_to (questions_path), notice: t("messages.question_successfully_duplicated") } + format.json { render json: @question_duplicate, status: :created, location: @question_duplicate } + else + format.html { render action: "show" } + format.json { render json: @question_duplicate.errors, status: :unprocessable_entity } + end + end + end + def add_to_own original_question = Question.find(params[:id]) @@ -251,7 +296,7 @@ def get_parser(p) when "moodle_xml" return "xml", MoodleXmlParser.new when "gift" - return "gift", GiftImporter.new + return "txt", GiftTxtParser.new when "ilias" return "xml", IliasParser.new else @@ -268,6 +313,12 @@ def set_js_tags params[:question][:tags] = params["text_question"][:tags] elsif params["number_question"] && params["number_question"][:tags] params[:question][:tags] = params["number_question"][:tags] + elsif params["match_question"] && params["match_question"][:tags] + params[:question][:tags] = params["match_question"][:tags] + elsif params["order_question"] && params["order_question"][:tags] + params[:question][:tags] = params["order_question"][:tags] + elsif params["category_question"] && params["category_question"][:tags] + params[:question][:tags] = params["category_question"][:tags] end end @@ -279,9 +330,17 @@ def check_access redirect_to questions_path, status: :forbidden and return false end true - end + end def question_params - params.require(:question).permit(:name, :type, :description, :tags, :public, :collaborators_form, question_options_attributes: [:name, :correct, :id, :_destroy]) + params.require(:question).permit(:name, :type, :description, :tags, + :public, :collaborators_form, + question_options_attributes: [:name, :correct, :id, :_destroy], + answer_pairs_attributes: [:answer1, :answer2, :correct, :id, :_destroy], + order_options_attributes: [:name, :position, :id, :_destroy], + relative_option_order_object_attributes: [:content_hash, :id, :_destroy], + categories_attributes: [:name, :sub_words, :id, :_destroy], + sub_words_attributes: [:name, :category, :id, :_destroy]) end + end diff --git a/app/controllers/surveys_controller.rb b/app/controllers/surveys_controller.rb old mode 100644 new mode 100755 index 864a7fe..f74ac1e --- a/app/controllers/surveys_controller.rb +++ b/app/controllers/surveys_controller.rb @@ -44,6 +44,9 @@ def show def new @survey = Survey.new @survey.options.build + @survey.answer_pairs.build + @survey.categories.build + @survey.sub_words.build respond_to do |format| format.html # new.html.erb @@ -201,6 +204,18 @@ def repeat original_survey.options.map do |option| @survey.options.new(name: option.name, correct: option.correct) end + original_survey.answer_pairs.map do |pair| + @survey.answer_pairs.new(answer1: pair.answer1, answer2: pair.answer2, correct: pair.correct) + end + original_survey.order_options.map do |option| + @survey.order_options.new(name: option.name, position: option.position) + end + original_survey.categories.map do |category| + @survey.categories.new(name: category.name, sub_words: category.sub_words) + end + original_survey.sub_words.map do |sub_word| + @survey.sub_words.new(name: sub_word.name, category: sub_word.category) + end @survey.type = original_survey.type @survey.settings = original_survey.settings @survey.original_survey = original_survey @@ -244,6 +259,38 @@ def vote else voted_for = @survey.options.only(:name).find(params[:option]).name end + elsif @survey.has_answer_pairs? + if @survey.type == "match" + unless params[:option].nil? + voted_for = "
" + else + voted_for = t("matrix_keys.no_answer") + end + end + elsif @survey.has_order_options? + if @survey.type == "order" + unless params[:option].nil? + voted_for = '
' + else + voted_for = t("matrix_keys.no_answer") + end + end + elsif @survey.has_categories? + if @survey.type == "category" + unless params[:option].nil? + voted_for = '
' + else + voted_for = t("matrix_keys.no_answer") + end + end else if params[:option].respond_to? :each voted_for = "
- " + params[:option].reject {|o| o.blank? }.join("
- ") + "
" @@ -388,7 +435,9 @@ def exit_question end def changed - @survey = Survey.only(:original_survey_id, :type, :options, :voters_hash, :event_id).find(params[:id]).service + @survey = Survey.only(:original_survey_id, :type, :options, :answer_pairs, + :order_options, :relative_option_order_object, :voters_hash, :categories, + :sub_words, :event_id).find(params[:id]).service check_access return if performed? @@ -425,7 +474,9 @@ def changed end def changed_aggregated - @survey = Survey.only(:original_survey_id, :type, :options, :voters_hash, :event_id).find(params[:id]).service + @survey = Survey.only(:original_survey_id, :type, :options, :answer_pairs, + :order_options, :relative_option_order_object, :voters_hash, :categories, + :sub_words, :event_id).find(params[:id]).service check_access return if performed? @@ -452,7 +503,9 @@ def changed_aggregated end def results - @survey = Survey.only(:type, :options, :voters_hash, :event_id, :voters).find(params[:id]).service + @survey = Survey.only(:type, :options, :answer_pairs, :order_options, + :relative_option_order_object, :voters_hash, :categories, + :sub_words, :event_id, :voters).find(params[:id]).service check_access return if performed? @@ -492,7 +545,13 @@ def api protected def survey_params - params.require(:survey).permit(:name, :description, options_attributes: [:name, :correct, :id]) + params.require(:survey).permit(:name, :description, + options_attributes: [:name, :correct, :id], + answer_pairs_attributes: [:answer1, :answer2, :correct, :id], + order_options_attributes: [:name, :position, :id, :_destroy], + relative_option_order_object_attributes: [:content_hash, :id, :_destroy], + categories_attributes: [:name, :sub_words, :id, :_destroy], + sub_words_attributes: [:name, :category, :id, :_destroy]) end def check_access diff --git a/app/helpers/.DS_Store b/app/helpers/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/app/helpers/.DS_Store differ diff --git a/app/models/.DS_Store b/app/models/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/app/models/.DS_Store differ diff --git a/app/models/answer_pair.rb b/app/models/answer_pair.rb new file mode 100755 index 0000000..9125670 --- /dev/null +++ b/app/models/answer_pair.rb @@ -0,0 +1,30 @@ +class AnswerPair + include Mongoid::Document + + embedded_in :question + embedded_in :survey + field :answer1, type: String + field :answer2, type: String + field :votes, type: Integer, default: 0 + field :description, type: String + field :correct, type: Boolean, default: true + + validates_presence_of :answer1 + validates_presence_of :answer2 + + validates_format_of :answer1, :without => / - / + validates_format_of :answer2, :without => / - / + + + def vote_up + self.inc(:votes, 1) + end + + def vote_down + self.inc(:votes, -1) + end + + def correct? + self.correct + end +end diff --git a/app/models/category.rb b/app/models/category.rb new file mode 100644 index 0000000..8771674 --- /dev/null +++ b/app/models/category.rb @@ -0,0 +1,16 @@ +class Category + include Mongoid::Document + + embedded_in :question + embedded_in :survey + field :name, type: String + field :description, type: String + field :sub_words, type: String + + validates_presence_of :name + validates_presence_of :sub_words # semicolon-seperated words, e.g. + # "word1;word2;word3" + + validates_format_of :name, :without => / - / + +end diff --git a/app/models/event.rb b/app/models/event.rb old mode 100644 new mode 100755 diff --git a/app/models/option.rb b/app/models/option.rb index 3b6322f..7119cf4 100755 --- a/app/models/option.rb +++ b/app/models/option.rb @@ -5,7 +5,7 @@ class Option field :name, type: String field :description, type: String field :votes, type: Integer, default: 0 - field :correct, type: Boolean, defaut: nil + field :correct, type: Boolean, default: nil validates_presence_of :name diff --git a/app/models/order_option.rb b/app/models/order_option.rb new file mode 100644 index 0000000..b78c029 --- /dev/null +++ b/app/models/order_option.rb @@ -0,0 +1,41 @@ +class OrderOption + include Mongoid::Document + + embedded_in :question + embedded_in :survey + field :name, type: String + field :position, type: Integer, default: nil + field :description, type: String + field :votes, type: String, default: "" #Actually we'd like to have an array of + # integers here, where every entry stands for the votes for this option at this + # (entry's) postion. But since there's is no Mongoid field type "IntegerArray", + # we do it via a String, e.g. "5,8,0,0,5,3" + + # TODO: Why are the messages not working? + validates_presence_of :name, message: ": Name of option can't be blank." + validates_presence_of :position, message: ": Positions of options are missing." + validates_format_of :name, :without => / - /, message: ": No ' - ' inside option names." + + def vote_up(position) + votesArray = self.votes.split(",") + votesArray[position-1] = Integer(votesArray[position-1]) + 1 + self.votes = votesArray.to_s + self.votes.gsub! '"', '' + self.votes.gsub! '[', '' + self.votes.gsub! ']', '' + self.votes.gsub! ' ', '' + self.save! + end + + def vote_down(position) + votesArray = self.votes.split(",") + votesArray[position-1] = Integer(votesArray[position-1]) - 1 + self.votes = votesArray.to_s + self.votes.gsub! '"', '' + self.votes.gsub! '[', '' + self.votes.gsub! ']', '' + self.votes.gsub! ' ', '' + self.save! + end + +end \ No newline at end of file diff --git a/app/models/question.rb b/app/models/question.rb old mode 100644 new mode 100755 index 2343b47..786d28f --- a/app/models/question.rb +++ b/app/models/question.rb @@ -12,7 +12,7 @@ class Question field :settings, type: Hash, default: {} def self.question_types - ["multi","single", "text", "exit_q", "number"] + ["multi", "single", "text", "exit_q", "number", "match", "order", "category"] end validates :type, inclusion: {in: Question.question_types} @@ -23,6 +23,21 @@ def self.question_types embeds_many :question_comments + embeds_many :answer_pairs + accepts_nested_attributes_for :answer_pairs, allow_destroy: true + + embeds_many :order_options + accepts_nested_attributes_for :order_options, allow_destroy: true + + embeds_one :relative_option_order_object + accepts_nested_attributes_for :relative_option_order_object, allow_destroy: true + + embeds_many :categories + accepts_nested_attributes_for :categories, allow_destroy: true + + embeds_many :sub_words + accepts_nested_attributes_for :sub_words, allow_destroy: true + belongs_to :user # TODO can questions exist without user? belongs_to :original_question, class_name: "Question", inverse_of: :copied_questions has_many :copied_questions, class_name: "Question", inverse_of: :original_question @@ -30,6 +45,7 @@ def self.question_types has_and_belongs_to_many :collaborators, class_name: "User", inverse_of: :shared_questions index :collaborator_ids, sparse: true + after_save :delete_all_false_answer_pairs, :fill_up_answer_pairs # this is where we setup getting the service objects def service @@ -42,6 +58,12 @@ def service MultipleChoiceQuestion.new(self) when "number" NumberQuestion.new(self) + when "match" + MatchQuestion.new(self) + when "order" + OrderQuestion.new(self) + when "category" + CategoryQuestion.new(self) else self end @@ -59,6 +81,19 @@ def to_survey question_options.each do |qo| survey.options.push qo.to_option end + answer_pairs.each do |ap| + survey.answer_pairs.push ap + end + order_options.each do |oo| + survey.order_options.push oo + end + categories.each do |ca| + survey.categories.push ca + end + sub_words.each do |sw| + survey.sub_words.push sw + end + survey.relative_option_order_object = self.relative_option_order_object survey.question = self survey.settings = self.settings if self.settings survey @@ -74,6 +109,22 @@ def self.new_from_existing(original_question) original_question.question_options.each do |option| question.question_options.build(name: option.name, correct: option.correct) end + original_question.answer_pairs.each do |pair| + question.answer_pairs.build( + answer1: pair.answer1, + answer2: pair.answer2, + correct: pair.correct) + end + original_question.order_options.each do |option| + question.order_options.build(name: option.name, position: option.position) + end + question.relative_option_order_object = original_question.relative_option_order_object + original_question.categories.each do |category| + question.categories.build(name: category.name, sub_words: category.sub_words) + end + original_question.sub_words.each do |sub_word| + question.sub_words.build(name: sub_word.name, category: sub_word.category) + end question.type = original_question.type question.original_question = original_question question.description = original_question.description @@ -104,4 +155,27 @@ def collaborators_form=(v) end end -end + def delete_all_false_answer_pairs + if(self.answer_pairs.any?) + self.answer_pairs.where(correct: false).each do |pair| + pair.delete + end + end + end + + # adds all wrong answer pairs to the collection answer_pairs of the just saved match question + def fill_up_answer_pairs + if(self.answer_pairs.any?) + self.answer_pairs.where(correct: true).each do |pair1| + self.answer_pairs.where(correct: true).each do |pair2| + if(pair1.answer1 != pair2.answer1) + if(pair1.answer2 != pair2.answer2) + self.answer_pairs.create(:answer1 => pair1.answer1, :answer2 => pair2.answer2, :correct => false) + end + end + end + end + end + end + +end \ No newline at end of file diff --git a/app/models/relative_option_order_object.rb b/app/models/relative_option_order_object.rb new file mode 100644 index 0000000..b782f68 --- /dev/null +++ b/app/models/relative_option_order_object.rb @@ -0,0 +1,50 @@ +class RelativeOptionOrderObject + include Mongoid::Document + + embedded_in :question + embedded_in :survey + + # Every order_option is a key in this hash. + # Its value is another hash, in which all + # the other order_options are a key, and + # behind each key you find the number of + # votes, indicating that this order_option + # belongs after the super order_option. E.g. + # + # content_hash: + # { + # "A" => { + # "B" => 2, //i.e. 2 people voted, that A belongs before B + # "C" => 4 //i.e. 4 people voted, that A belongs before B + # }, + # "B" => { + # "A" => 2, + # "C" => 3 + # }, + # "C" => { + # "A" => 0, + # "B" => 1 + # } + # } + field :content_hash, type: Hash, default: Hash.new + + def vote_up(beforeName, afterName) + if self.content_hash[beforeName].nil? + self.content_hash[beforeName] = Hash[afterName, 1] + elsif self.content_hash[beforeName][afterName].nil? + self.content_hash[beforeName][afterName] = 1 + else + self.content_hash[beforeName][afterName] += 1 + end + self.save! + end + + def get_votes_for(beforeName, afterName) + if self.content_hash[beforeName].nil? || self.content_hash[beforeName][afterName].nil? + return(0) + else + return(self.content_hash[beforeName][afterName]) + end + end + +end \ No newline at end of file diff --git a/app/models/sub_word.rb b/app/models/sub_word.rb new file mode 100644 index 0000000..0127749 --- /dev/null +++ b/app/models/sub_word.rb @@ -0,0 +1,46 @@ +class SubWord + include Mongoid::Document + + embedded_in :question + embedded_in :survey + field :name, type: String + field :category, type: String + field :description, type: String + field :votes, type: Hash, default: Hash.new + # This sub_word may be voted + # into different categories. This Hash contains for + # every voted category one key and behind that key, + # we have the number of votes for this sub_word belonging + # into this category, e.g. the sub_word is "Berlin" and + # the categories are "cities", "trees" and "animals". Then + # the votes Hash could look like this: + # { + # "cities" => 4, + # "trees" => 2, + # "animals" => 0 + # } + + validates_presence_of :name + validates_presence_of :category + + validates_format_of :name, :without => / - / + validates_format_of :name, :without => /;/ + + def vote_up(category) + if self.votes[category].nil? + self.votes[category] = 1 + else + self.votes[category] += 1 + end + self.save! + end + + def get_votes_for(category) + if self.votes[category].nil? + return(0) + else + return(self.votes[category]) + end + end + +end diff --git a/app/models/survey.rb b/app/models/survey.rb index 74e975f..78a0b61 100755 --- a/app/models/survey.rb +++ b/app/models/survey.rb @@ -12,6 +12,21 @@ class Survey embeds_many :options accepts_nested_attributes_for :options, :allow_destroy => true + + embeds_many :answer_pairs + accepts_nested_attributes_for :answer_pairs, :allow_destroy => true + + embeds_many :order_options + accepts_nested_attributes_for :order_options, :allow_destroy => true + + embeds_one :relative_option_order_object + accepts_nested_attributes_for :relative_option_order_object, :allow_destroy => true + + embeds_many :categories + accepts_nested_attributes_for :categories, :allow_destroy => true + + embeds_many :sub_words + accepts_nested_attributes_for :sub_words, :allow_destroy => true field :name, type: String field :description, type: String @@ -36,8 +51,13 @@ class Survey belongs_to :question scope :current, where(:starts.gte => DateTime.now).and(:ends.lt => DateTime.now) - scope :display_fields, only(:description, :ends, :name, :options, :starts, :event_id, :quick, :created_at, :multi, :type, :settings, :voters, :voters_hash, :original_survey_id, :exit_q, :question_id) - scope :participate_fields, only(:description, :ends, :name, :options, :starts, :event_id, :quick, :multi, :type, :exit_q, :settings) + scope :display_fields, only(:description, :ends, :name, :options, :answer_pairs, + :order_options, :relative_option_order_object, :starts, :event_id, :quick, + :created_at, :multi, :type, :settings, :voters, :voters_hash, :original_survey_id, + :exit_q, :question_id, :categories, :sub_words) + scope :participate_fields, only(:description, :ends, :name, :options, :answer_pairs, + :order_options, :relative_option_order_object, :starts, :event_id, :quick, :multi, + :type, :exit_q, :settings, :categories, :sub_words) scope :worker_fields, only(:voters, :multi, :type, :starts, :ends) validates :event, presence: true @@ -55,6 +75,12 @@ def service NumberSurvey.new(self) when "exit_q" ExitSurvey.new(self) + when "match" + MatchSurvey.new(self) + when "order" + OrderSurvey.new(self) + when "category" + CategorySurvey.new(self) else self end @@ -119,7 +145,6 @@ def track_vote(vid) def mathjax? self.event.mathjax? end - private def delete_cache diff --git a/app/models/user.rb b/app/models/user.rb old mode 100644 new mode 100755 diff --git a/app/parsers/aiken_parser.rb b/app/parsers/aiken_parser.rb index f53a1d6..3e88745 100755 --- a/app/parsers/aiken_parser.rb +++ b/app/parsers/aiken_parser.rb @@ -1,9 +1,19 @@ class AikenParser def export(questions) + result = "" # Über alle Fragen iterieren questions.each_with_index do |question, index| + # aiken doesn't support matching, order nor category questions, so skip them + if question.has_answer_pairs? + next + elsif question.has_order_options? + next + elsif question.has_categories? + next + end + result << question.name + "\r\n" option_index = "A" diff --git a/app/parsers/csv_parser.rb b/app/parsers/csv_parser.rb index 15666aa..f445b79 100755 --- a/app/parsers/csv_parser.rb +++ b/app/parsers/csv_parser.rb @@ -1,12 +1,31 @@ require "csv" +require 'logger' +@@log = Logger.new('log.txt') +@@log.debug "Log file created" class CsvParser def export(questions) csv_string = CSV.generate(quote_char: '"') do |csv| # CSV-Header einfügen - csv << ["Type", "Question", "Answer 1", "Answer 2", "Answer 3", "Answer 4", "Answer 5", "Answer 6", "Answer 7", "Answer 8", "Answer 9", "Correct"] + csv << ["Type", "Question", "Tags", "Correct", "Answers"] questions.each do |question| - current = [question.type, question.name] + current = [question.type, question.name, ""] + + # Hinzufügen von tags, falls vorhanden + unless question.tags.nil? || question.tags.empty? + question.tags.split(",").each do |tag| + if current[2]=="" + current[2] = tag + else + current[2] = current[2] + ";" + tag + end + end + end + + current << "" + + @@log.debug current.to_s + correct_options = [] option_number = 1 if question.type == "single" || question.type == "multi" @@ -22,12 +41,22 @@ def export(questions) elsif answer_type == TextSurvey::THREE_ANSWERS correct_options << '3' end + elsif question.type == 'match' + question.answer_pairs.where(correct: true).each do |pair| + current << pair.answer1 + ' - ' + pair.answer2 + end + elsif question.type == 'order' + question.order_options.each do |option| + current << option.position.to_s + ") " + option.name + end + elsif question.type == 'category' + question.categories.each do |category| + current << category.name + "|||" + category.sub_words + end end unless correct_options.empty? correct_options = correct_options.join ";" - current[11] = correct_options # Korrekte Antworten anhängen - else - current[11] = "" + current[3] = correct_options # Korrekte Antworten anhängen end csv << current end @@ -58,16 +87,53 @@ def import(csv_file, user, tags, separator = ",") #Frage erstellen q = Question.new(type:question[0], name:question[1]).service + # Hinzufügen von tags, die nur für diese Frage gelten sollen + unless question[2].nil? || question[2].empty? + question[2].split(";").each do |tag| + unless q.tags.nil? || q.tags.empty? + q.tags = q.tags + "," + tag + else + q.tags = tag + end + end + end + + # Hinzufügen von tags, die für alle importierten Fragen gelten sollen + unless q.tags.nil? || q.tags.empty? + q.tags = q.tags + "," + tags + else + q.tags = tags + end + # Korrekte Antwortmöglichkeiten extrahieren if question[0] == "single" || question[0] == "text" - answers = [question[11]] - elsif question[0] == "multi" - answers = question[11].split(";") + answers = [question[3]] + elsif question[0] == "multi" || question[0] == "match" + answers = question[3].split(";") end - question.drop(2).take(question.size-3).each do |option| # Letztes und die beiden ersten Elemente beinhalten keine Antworten - unless option.nil? || option == "" - q.question_options << QuestionOption.new(name: option) # QuestionOptions sind in der Frage eingebettet und werden mitgesichert + if question[0] == "match" + question.drop(4).each do |pair| # Die ersten vier Elemente beinhalten keine Antworten + q.answer_pairs << AnswerPair.new(answer1: pair.split(' - ')[0], answer2: pair.split(' - ')[1]) + end + elsif question[0] == "order" + question.drop(4).each do |option| + q.order_options << OrderOption.new(name: option.split(') ')[1], position: option.split(') ')[0]) + end + elsif question[0] == "category" + question.drop(4).each do |categoryAndSubWords| + currentCategory = categoryAndSubWords.split("|||")[0] + currentSubWords = categoryAndSubWords.split("|||")[1] + q.categories << Category.new(name: currentCategory, sub_words: currentSubWords) + currentSubWords.split(";").each do |sub_word| + q.sub_words << SubWord.new(name: sub_word, category: currentCategory) + end + end + else + question.drop(4).each do |option| # Die ersten vier Elemente beinhalten keine Antworten + unless option.nil? || option == "" + q.question_options << QuestionOption.new(name: option) # QuestionOptions sind in der Frage eingebettet und werden mitgesichert + end end end @@ -89,7 +155,6 @@ def import(csv_file, user, tags, separator = ",") end q.user = user - q.tags = tags unless q.save # true/false je nachdem ob erfolgreich gesichert wurde errors << {"type" => "unknown_error", "text" => q.name} else diff --git a/app/parsers/gift_importer.rb b/app/parsers/gift_importer.rb deleted file mode 100755 index 4ec9ad5..0000000 --- a/app/parsers/gift_importer.rb +++ /dev/null @@ -1,83 +0,0 @@ -class GiftImporter - def import (gift_file, user, tags) - errors = [] - successes = [] - begin - gift_string = gift_file.read - gift_string = Converter.new.convert_to_utf8 gift_string - # Leerzeile an String anhängen - gift_string << "\n\n" - - # String parsen und über Fragen iterieren, dabei nicht unterstützte fragen merken - questions = GiftParser.new.parse(gift_string).questions - questions.each do |question, index| - q = Question.new - - if question.is_a? Gift::TrueFalseQuestion - # Bei True/False-Fragen müssen die zwei Antwortoptionen explizit eingetragen werden. - q = Question.new(type:"single").service - elsif question.is_a? Gift::MultipleChoiceQuestion - # Wenn die Frage ein Gewicht hat, hat sie potentiell mehrere richtige Antworten - unless question.answers[0][:weight].nil? - q = Question.new(type:"multi").service - else - q = Question.new(type:"single").service - end - elsif question.is_a?(Gift::ShortAnswerQuestion) || question.is_a?(Gift::EssayQuestion) - q = Question.new(type:"text").service - elsif question.is_a? Gift::NumericQuestion - q = Question.new(type:"number") - elsif question.is_a?(Gift::DescriptionQuestion) || question.is_a?(Gift::MatchQuestion) || question.is_a?(Gift::FillInQuestion) - # Abfrage nicht unterstützter Typen - errors << {"type" => "unsupported_type", "text" => question.text} - next - else - # Wenn der Fragetyp unbekannt ist wird die Frage nicht importiert sondern gemerkt - errors << {"type" => "unknown_type", "text" => question.text} - next - end - - # Fragetext setzen - q.name = question.text - - # Antwortoptionen setzen - # Zunächst Ausnahme für True/False, Numeric und ShortAnswer-Fragen - if question.is_a? Gift::TrueFalseQuestion - if question.answers[0][:value] - q.question_options << QuestionOption.new(name: 'Wahr / true', correct: true) - q.question_options << QuestionOption.new(name: 'Falsch / false', correct: false) - else - q.question_options << QuestionOption.new(name: 'Wahr / true', correct: false) - q.question_options << QuestionOption.new(name: 'Falsch / false', correct: true) - end - elsif question.is_a? Gift::NumericQuestion - elsif question.is_a?(Gift::ShortAnswerQuestion) || question.is_a?(Gift::EssayQuestion) - q.add_setting "answers", TextSurvey::ONE_ANSWER - else - question.answers.each do |answer| - # Korrektheit gilt, wenn das Gewicht >0 ist. Existiert kein Gewicht stimmt der ":correct"-Wert - if answer[:weight].nil? - correct = answer[:correct] - else - correct = answer[:weight]>0 - end - q.question_options << QuestionOption.new(name: answer[:value], correct: correct) - end - end - q.user = user - q.tags = tags - unless q.save - errors << {"type" => "unknown_error", "text" => q.name} - else - successes << {"text" => q.name} - end - end - rescue Exception - # Fallback für alle nicht behandelten Fehler - errors << {"type" => "file_error", "text" => "all"} - end - - # Fehler-Array zurückgeben - return errors, successes - end -end diff --git a/app/parsers/gift_txt_parser.rb b/app/parsers/gift_txt_parser.rb new file mode 100755 index 0000000..5b05ff8 --- /dev/null +++ b/app/parsers/gift_txt_parser.rb @@ -0,0 +1,479 @@ +class GiftTxtParser + + def export(questions) + txt_content = "" + questions.each do |question| + # the gift format doesn't support order- nor category questions, so skip them + if question.has_order_options? + next + elsif question.has_categories? + next + end + + # tags unter $CATEGORY vermerken + unless question.tags.nil? || question.tags.empty? + txt_content << "$CATEGORY: " + question.tags + "\r\n\n" + else + txt_content << "$CATEGORY: " + "\r\n\n" + end + + txt_content << question.name + + if(question.has_options?) + txt_content << "{\r\n" + question.question_options.each do |option| + if(option.correct) + txt_content << "\t=" + else + txt_content << "\t~" + end + txt_content << option.name + txt_content << "\r\n" + end + txt_content << '}' + elsif(question.type == "text") + txt_content << '{}' + elsif(question.type == "number") + txt_content << '{#}' + elsif(question.has_answer_pairs?) + txt_content << "{\r\n" + question.answer_pairs.where(correct: true).each do |pair| + txt_content << "\t=" + txt_content << pair.answer1 + txt_content << ' -> ' + txt_content << pair.answer2 + txt_content << "\r\n" + end + txt_content << '}' + end + txt_content << "\r\n\n" + end + txt_content + end + + def import (gift_file, user, tags) + errors = [] + successes = [] + + begin + gift_string = gift_file.read + + gift_string.gsub! /\t/, '' + gift_string = Converter.new.convert_to_utf8 gift_string + + categoriesWithLines = extractCategoriesWithLines(gift_string) + + for key in categoriesWithLines.keys + current_category = key + current_questions_lines = categoriesWithLines[key] + + questions = extractQuestions(current_questions_lines) + questions.each do |question| + + currentTitleArray = extractTitle(question) + + currentTitle = currentTitleArray[0] + if currentTitle == "[[questionMalFormatted]]" + next + end + question = currentTitleArray[1] + + q = Question.new + questionTextAndAnswers = separateTextAndAnswers(question) + + # ignore descriptions + if(questionTextAndAnswers[0] == "[[isDescription]]") + errors << {"type" => "is_description", "text" => questionTextAndAnswers[1]} + next + end + + if isNumericalQuestion(questionTextAndAnswers[1]) + q = Question.new(type:"number").service + elsif isTrueFalseQuestion(questionTextAndAnswers[1]) + q = Question.new(type:"single").service + # Bei True/False-Fragen müssen die zwei Antwortoptionen explizit eingetragen werden. + if statementIsTrue(questionTextAndAnswers[1]) + q.question_options << QuestionOption.new(name: 'Wahr / true', correct: true) + q.question_options << QuestionOption.new(name: 'Falsch / false', correct: false) + else + q.question_options << QuestionOption.new(name: 'Wahr / true', correct: false) + q.question_options << QuestionOption.new(name: 'Falsch / false', correct: true) + end + elsif isMatchingQuestion(questionTextAndAnswers[1]) + q = Question.new(type:"match").service + answerPairs = extractAnswerPairs(questionTextAndAnswers[1]) + for pair in answerPairs + q.answer_pairs << AnswerPair.new(answer1: pair[0].strip, + answer2: pair[1].strip, + correct: true) + end + elsif questionTextAndAnswers[1] == "" + q = Question.new(type:"text").service + q.add_setting "answers", TextSurvey::ONE_ANSWER + elsif isShortAnswerQuestion(questionTextAndAnswers[1]) + q = Question.new(type:"text").service + q.add_setting "answers", TextSurvey::ONE_ANSWER + elsif onlyWrongWeightBasedAnswers(questionTextAndAnswers[1]) + errors << {"type" => "only_wrong_weight_based_answers", "text" => questionTextAndAnswers[0]} + next + elsif isSingleChoiceQuestion(questionTextAndAnswers[1]) + q = Question.new(type:"single").service + answers = extractAnswers(questionTextAndAnswers[1].strip) + for answer in answers + q.question_options << QuestionOption.new(name: answer[0].strip, correct: answer[1]) + end + elsif isMultipleChoiceQuestion(questionTextAndAnswers[1]) + q = Question.new(type:"multi").service + answers = extractAnswers(questionTextAndAnswers[1].strip) + for answer in answers + q.question_options << QuestionOption.new(name: answer[0].strip, correct: answer[1]) + end + else + #Wenn der Fragetyp unbekannt ist wird die Frage nicht importiert sondern gemerkt + errors << {"type" => "unknown_type", "text" => question.text} + next + end + if(currentTitle == "") + q.name = questionTextAndAnswers[0] + else + q.name = currentTitle + " - " + questionTextAndAnswers[0] + end + q.user = user + + if current_category == "__NO_CATEGORY__" + q.tags = tags + else + q.tags = tags + "," + current_category + end + unless q.save + errors << {"type" => "unknown_error", "text" => q.name} + else + successes << {"text" => q.name} + end + end + end + + + rescue Exception + # Fallback für alle nicht behandelten Fehler + errors << {"type" => "file_error", "text" => "all"} + end + + # Fehler-Array zurückgeben + return errors, successes + end + + def isTrueFalseQuestion(questionAsString) + return questionAsString == "T" || + questionAsString == "TRUE" || + questionAsString.starts_with?("T#") || + questionAsString.starts_with?("TRUE#") || + questionAsString == "F" || + questionAsString == "FALSE" || + questionAsString.starts_with?("F#") || + questionAsString.starts_with?("FALSE#") + end + + def statementIsTrue(questionAsString) + if(questionAsString == "T" || + questionAsString == "TRUE" || + questionAsString.starts_with?("T#") || + questionAsString.starts_with?("TRUE#")) + return true + else + return false + end + end + + def isNumericalQuestion(questionAsString) + return questionAsString.starts_with?("#") + end + + def isMatchingQuestion(questionAsString) + firstIndex = getIndexOfFirstUnescapedCharacter("=", questionAsString, 0) + if(firstIndex == -1) + return false + else + if questionAsString.match(/=.{1,}->.{1,}/) == nil + return false + else + return true + end + end + end + + def extractAnswerPairs(answerString) + pairs = Array.new + pairIndex = 0 + indeces = getIndecesOfAllUnescapedCharacters("=", answerString) + for i in 0..(indeces.length-1) + if(i == indeces.length-1) + pairs[pairIndex] = answerString[indeces[i]+1..(answerString.length-1)].split('->') + else + pairs[pairIndex] = answerString[indeces[i]+1..indeces[i+1]-1].split('->') + pairIndex += 1 + end + end + return pairs + end + + # short-answer-question have only correct answers + def isShortAnswerQuestion(answerAsString) + firstIndex = getIndexOfFirstUnescapedCharacter("~", answerAsString, 0) + if(firstIndex == -1) + return true + else + return false + end + end + + # singleChoiceQuestions have just one correct answer + def isSingleChoiceQuestion(answerAsString) + firstIndex = getIndexOfFirstUnescapedCharacter("=", answerAsString, 0) + secondIndex = getIndexOfFirstUnescapedCharacter("=", answerAsString, firstIndex+1) + if(secondIndex != -1) + return false + else + return true + end + end + + def onlyWrongWeightBasedAnswers(answerAsString) + firstIndex = getIndexOfFirstUnescapedCharacter("=", answerAsString, 0) + if(firstIndex == -1) + index = getIndexOfFirstUnescapedCharacter("~", answerAsString, 0) + if(index == -1) + return false + else + return true + end + else + return false + end + end + + # multipleChoiceQuestions have more than one correct answers + def isMultipleChoiceQuestion(answerAsString) + firstIndex = getIndexOfFirstUnescapedCharacter("=", answerAsString, 0) + secondIndex = getIndexOfFirstUnescapedCharacter("=", answerAsString, firstIndex+1) + if(secondIndex == -1) + return false + else + return true + end + end + + # returns an array of arrays. + # every array consists of an answer (string) + # and a boolean value (correctness of the answer) + def extractAnswers(answersAsString) + answers = Array.new + answerIndex = 0 + indeces = getIndecesOfAllUnescapedCharacters("=", answersAsString) + indeces = indeces.concat(getIndecesOfAllUnescapedCharacters("~", answersAsString)) + indeces.sort! + for i in 0..(indeces.length-1) + if(i == indeces.length-1) + currentString = answersAsString[indeces[i]..(answersAsString.length-1)] + else + currentString = answersAsString[indeces[i]..indeces[i+1]-1] + end + + if(currentString.starts_with?('=')) + correct = true + end + + if(currentString.starts_with?('~')) + correct = false + end + + currentString = currentString[1..(currentString.length-1)] + currentString.strip! + + # ignore feedback to answers + index = getIndexOfFirstUnescapedCharacter("#", currentString, 0) + if(index == -1) + answers[answerIndex] = [currentString, correct] + else + answers[answerIndex] = [currentString[0..index-1], correct] + end + + #ignore percentage weights of answers + index = getIndexOfFirstUnescapedCharacter("%", answers[answerIndex][0], 0) + if(index != -1) + secondIndex = getIndexOfFirstUnescapedCharacter("%",answers[answerIndex][0], index+1) + if(secondIndex != -1) + if(index == 0) + answers[answerIndex][0] = answers[answerIndex][0][secondIndex+1..answers[answerIndex][0].length-1] + else + answers[answerIndex][0] = answers[answerIndex][0][0..index-1] + + answers[answerIndex][0][secondIndex+1..answers[answerIndex][0].length-1] + end + end + end + + answerIndex += 1 + end + + return answers + end + + # Extracts the categories in the given string into a hash + def extractCategoriesWithLines(utf8string) + categoriesWithLines = Hash.new + current_category = "__NO_CATEGORY__" + categoriesWithLines[current_category] = Array.new + + linesAsStrings = utf8string.split(/\r?\n/) + linesAsStrings.each do |line| + line.strip! + if(line.start_with?("$CATEGORY:")) + if line.length > 11 + current_category = line[11..line.length] + current_category.strip! + categoriesWithLines[current_category] = Array.new + else + current_category = "__NO_CATEGORY__" + end + else + categoriesWithLines[current_category] << line + end + end + + return categoriesWithLines + end + + # Extracts the questions in the given lines int an array + def extractQuestions(lines) + questions = Array.new + questionIndex = 0 + + lines.each do |line| + + line.strip! + + #ignore comments + if(line.start_with?("//")) + next + end + + #separate at blank lines + if(/^\s{1,}$/.match(line) || line.length == 0) + questionIndex += 1 + else + if questions[questionIndex].nil? + questions[questionIndex] = "" + end + questions[questionIndex] += " " + line + questions[questionIndex].strip! + questions[questionIndex].gsub! /\s{2,}/, ' ' + end + end + + questions.compact! + return questions + end + + # Returns an array with 2 components! + # Extracts a title if there is one ("" otherwise; + # "[[questionMalFormatted]]" if mal formatted) as the first component. + # The second components has the argument without the title section + def extractTitle(questionAsString) + firstIndex = getIndexOfFirstUnescapedCharacter("::", questionAsString, 0) + if(-1 == firstIndex) + return ["",questionAsString] + else + secondIndex = getIndexOfFirstUnescapedCharacter("::", questionAsString, firstIndex+2) + if(-1 == secondIndex) + return ["[[questionMalFormatted]]", ""] + else + # there are more unescaped :: + if(getIndexOfFirstUnescapedCharacter("::", questionAsString, secondIndex+2) != -1) + return ["[[questionMalFormatted]]", ""] + else + if(firstIndex != 0) + return [questionAsString[firstIndex + 2 .. secondIndex-1], + questionAsString[0..firstIndex-1] + questionAsString[secondIndex+2..questionAsString.length-1].strip] + else + [questionAsString[firstIndex + 2 .. secondIndex-1], + questionAsString[secondIndex+2..questionAsString.length-1].strip] + end + end + end + end + end + + def getIndexOfFirstUnescapedCharacter(characterAsString, questionAsString, startIndex) + charLength = characterAsString.length + indecesOfCharacter = (startIndex ... questionAsString.length).find_all { |i| questionAsString[i,charLength] == characterAsString } + for index in indecesOfCharacter + if(0 == index) + return index + elsif(questionAsString[index-1] == "\\") + next + else + return index + end + end + return -1 + end + + def getIndecesOfAllUnescapedCharacters(characterAsString, questionAsString) + charLength = characterAsString.length + indecesOfCharacter = (0..questionAsString.length-1).find_all { |i| questionAsString[i,charLength] == characterAsString } + indecesToBeDeleted = Array.new + for index in indecesOfCharacter + if(index == 0) + next + end + if(questionAsString[index-1] == '\\') + indecesToBeDeleted << index + end + end + indecesOfCharacter = indecesOfCharacter - indecesToBeDeleted + return indecesOfCharacter + end + + # Returns an array with two components. + # The first component is the question text (a string). + # The scond component is are the answers (a string) + def separateTextAndAnswers(questionAsString) + # ignore special formats + questionAsString = cutOutFormats(questionAsString) + + firstIndex = getIndexOfFirstUnescapedCharacter("{", questionAsString, 0) + if(-1 == firstIndex) + return ["[[isDescription]]",questionAsString] + else + secondIndex = getIndexOfFirstUnescapedCharacter("}", questionAsString, firstIndex+1) + if(-1 == secondIndex) + return ["[[questionMalFormatted]]", "NOSECONDINDEX"] + else + if(getIndexOfFirstUnescapedCharacter("{", questionAsString, secondIndex+1) != -1 || + getIndexOfFirstUnescapedCharacter("}", questionAsString, secondIndex+1)) != -1 + return ["[[questionMalFormatted]]", "TOOMANYUNESCAPED"] + else + if(firstIndex == 0) + return ["... " + questionAsString[secondIndex+1..questionAsString.length-1].strip, + questionAsString[firstIndex + 1 .. secondIndex-1].strip] + elsif(secondIndex == questionAsString.length-1) + return [questionAsString[0..firstIndex-1].strip, + questionAsString[firstIndex+1 .. secondIndex-1].strip] + else + return [questionAsString[0..firstIndex-1].strip + " ... " + questionAsString[secondIndex+1..questionAsString.length-1].strip, + questionAsString[firstIndex+1 .. secondIndex-1].strip] + end + end + end + end + end + + def cutOutFormats(questionAsString) + questionAsString.gsub! '[html]', '' + questionAsString.gsub! '[moodle]', '' + questionAsString.gsub! '[plain]', '' + questionAsString.gsub! '[markdown]', '' + return questionAsString + end + +end diff --git a/app/parsers/ilias_parser.rb b/app/parsers/ilias_parser.rb old mode 100644 new mode 100755 index 7914716..389aeea --- a/app/parsers/ilias_parser.rb +++ b/app/parsers/ilias_parser.rb @@ -7,10 +7,25 @@ def export(questions) # Über Fragen iterieren questions.each do |question| + + #qti doesn't support category questions + if question.type == "category" + next + end + question_node = root.add_element "item" + # tags im qticomment anlegen, falls sie existieren + unless question.tags.nil? || question.tags.empty? + @@log.debug question.tags.to_s + tags_node = question_node.add_element "qticomment" + question.tags.split(',').each do |tag| + tag_node = tags_node.add_element "tag" + (tag_node.add_element "text").text = tag + end + end + # Elemente der Frage hinzufügen und teilweise für später merken - question_node.add_element "qticomment" qtimetadata_node = (question_node.add_element "itemmetadata").add_element "qtimetadata" # Den Ilias-Typ bestimmen @@ -22,6 +37,10 @@ def export(questions) ilias_question_type = "NUMERIC QUESTION" elsif question.type == "text" ilias_question_type = "TEXT QUESTION" + elsif question.type == "match" + ilias_question_type = "MATCHING QUESTION" + elsif question.type == "order" + ilias_question_type = "ORDER QUESTION" end # Metadaten einfügen @@ -107,6 +126,87 @@ def export(questions) setvar_node = respcondition_node.add_element "setvar" setvar_node.text = "3" setvar_node.attributes["action"] = "Add" + # following code concerning match questions is based on qti 2.0 schemata + # see http://www.imsglobal.org/question/qti_v2p0/examples/items/match.xml + elsif question.type == "match" + capital_alphabet = %W(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) + small_alphabet = %W(a b c d e f g h i j k l m n o p q r s t u v w x y z) + responseDeclaration_node = question_node.add_element "responseDeclaration" + responseDeclaration_node.attributes["identifier"] = "RESPONSE" + responseDeclaration_node.attributes["identifier"] = "multiple" + responseDeclaration_node.attributes["baseType"] = "directedPair" + correctResponse_node = responseDeclaration_node.add_element "correctResponse" + mapping_node = responseDeclaration_node.add_element "mapping" + mapping_node.attributes["defaultValue"] = "0" + alphabetIndex = 0 + question.answer_pairs.where(correct: true).each do |pair| + value_node = correctResponse_node.add_element "value" + value_node.text = capital_alphabet[alphabetIndex] + " " + small_alphabet[alphabetIndex] + mapEntry_node = mapping_node.add_element "mapEntry" + mapEntry_node.attributes["mapKey"] = capital_alphabet[alphabetIndex] + " " + small_alphabet[alphabetIndex] + mapEntry_node.attributes["mappedValue"] = "1" + alphabetIndex += 1 + end + outcomeDeclaration_node = question_node.add_element "outcomeDeclaration" + outcomeDeclaration_node.attributes["identifier"] = "SCORE" + outcomeDeclaration_node.attributes["cardinality"] = "single" + outcomeDeclaration_node.attributes["baseType"] = "float" + itemBody_node = question_node.add_element "itemBody" + matchInteraction_node = itemBody_node.add_element "matchInteraction" + matchInteraction_node.attributes["responseIdentifier"] = "RESPONSE" + matchInteraction_node.attributes["shuffle"] = "true" + matchInteraction_node.attributes["maxAssociations"] = question.answer_pairs.where(correct: true).length.to_s + prompt_node = matchInteraction_node.add_element "prompt" + prompt_node.text = question.name + simpleMatchSet_node = matchInteraction_node.add_element "simpleMatchSet" + alphabetIndex = 0 + question.answer_pairs.where(correct: true).each do |pair| + simpleAssociableChoice_node = simpleMatchSet_node.add_element "simpleAssociableChoice" + simpleAssociableChoice_node.attributes["identifier"] = capital_alphabet[alphabetIndex] + "" + simpleAssociableChoice_node.attributes["matchMax"] = "1" + simpleAssociableChoice_node.text = pair.answer1 + alphabetIndex += 1 + end + simpleMatchSet_node = matchInteraction_node.add_element "simpleMatchSet" + alphabetIndex = 0 + question.answer_pairs.where(correct: true).each do |pair| + simpleAssociableChoice_node = simpleMatchSet_node.add_element "simpleAssociableChoice" + simpleAssociableChoice_node.attributes["identifier"] = small_alphabet[alphabetIndex] + "" + simpleAssociableChoice_node.attributes["matchMax"] = "1" + simpleAssociableChoice_node.text = pair.answer2 + alphabetIndex += 1 + end + responseProcessing_node = question_node.add_element "responseProcessing" + responseProcessing_node.attributes["template"] = "http://www.imsglobal.org/question/qti_v2p0/rptemplates/map_response" + # following code concerning order questions is based on qti 2.0 schemata + # see http://www.imsglobal.org/question/qti_v2p0/examples/items/order.xml + elsif question.type = "order" + responseDeclaration_node = question_node.add_element "responseDeclaration" + responseDeclaration_node.attributes["identifier"] = "RESPONSE" + responseDeclaration_node.attributes["cardinality"] = "ordered" + responseDeclaration_node.attributes["baseType"] = "identifier" + correctResponse_node = responseDeclaration_node.add_element "correctResponse" + for position in 1..question.order_options.length + value_node = correctResponse_node.add_element "value" + value_node.text = position.to_s + end + outcomeDeclaration_node = responseDeclaration_node.add_element "outcomeDeclaration" + outcomeDeclaration_node.attributes["identifier"] = "SCORE" + outcomeDeclaration_node.attributes["cardinality"] = "single" + outcomeDeclaration_node.attributes["baseType"] = "integer" + itemBody_node = question_node.add_element "itemBody" + orderInteraction_node = itemBody_node.add_element "orderInteraction" + orderInteraction_node.attributes["responseIdentifier"] = "RESPONSE" + orderInteraction_node.attributes["shuffle"] = "true" + prompt_node = orderInteraction_node.add_element "prompt" + prompt_node.text = question.name + for position in 1..question.order_options.length + simpleChoice_node = orderInteraction_node.add_element "simpleChoice" + simpleChoice_node.text = question.order_options.where(position: position).first.name + simpleChoice_node.attributes["identifier"] = position.to_s + end + responseProcessing_node = question_node.add_element "responseProcessing" + responseProcessing_node.attributes["template"] = "http://www.imsglobal.org/question/qti_v2p0/rptemplates/match_correct" end @@ -137,7 +237,7 @@ def import (xml_file, user, tags) end # Bekannte aber nicht unterstützte Formate filtern - if question_type.in? ['assOrderingHorizontal', 'ORDERING QUESTION', 'assFileUpload', 'assFlashQuestion', 'IMAGE MAP QUESTION', 'assErrorText', 'MATCHING QUESTION', 'CLOZE QUESTION'] + if question_type.in? ['assOrderingHorizontal', 'ORDERING QUESTION', 'assFileUpload', 'assFlashQuestion', 'IMAGE MAP QUESTION', 'assErrorText', 'CLOZE QUESTION'] errors << {"type" => "unsupported_type", "text" => element.elements["presentation/flow/material/mattext"].text.gsub(/<\S*>/,"")} next end @@ -152,11 +252,28 @@ def import (xml_file, user, tags) elsif question_type.in? ['TEXT QUESTION', 'TEXTSUBSET QUESTION'] q = Question.new(type:"text").service q.add_setting "answers", TextSurvey::MULTI_ANSWERS + elsif question_type == 'MATCHING QUESTION' + q = Question.new(type:"match").service + elsif question_type == 'ORDER QUESTION' + q = Question.new(type:"order").service else errors << {"type" => "unknown_type", "text" => element.elements["presentation/flow/material/mattext"].text.gsub(/<\S*>/,"")} next end + # Tags dieser spezifischen Frage auslesen und setzen + tags_string = "" + element.elements.each("qticomment/tag") do |tag_element| + if tags_string == "" + tags_string = tag_element.elements["text"].text + else + tags_string << "," + tag_element.elements["text"].text + end + end + unless tags_string == "" + q.tags = tags_string + end + # Fragetext setzen q.name = element.elements["presentation/flow/material/mattext"].text.gsub(/<\S*>/,"") @@ -184,10 +301,43 @@ def import (xml_file, user, tags) end end end + elsif question_type == 'MATCHING QUESTION' + element.elements.each("itemBody/matchInteraction/prompt") do |elem| + q.name = elem.text + break + end + element.elements.each("itemBody/matchInteraction/simpleMatchSet/simpleAssociableChoice") do |elem| + if(/[[:upper:]]/.match(elem.attributes["identifier"])) + element.elements.each("itemBody/matchInteraction/simpleMatchSet/simpleAssociableChoice") do |innerElem| + if(innerElem.attributes["identifier"]!=elem.attributes["identifier"].downcase) + next + else + q.answer_pairs << AnswerPair.new(answer1: elem.text, answer2: innerElem.text) + end + break + end + else + next + end + end + elsif question_type == 'ORDER QUESTION' + element.elements.each("itemBody/orderInteraction/prompt") do |elem| + q.name = elem.text + break + end + element.elements.each("itemBody/orderInteraction/simpleChoice") do |elem| + q.order_options << OrderOption.new(name: elem.text, position: Integer(elem.attributes["identifier"])) + end end q.user = user - q.tags = tags + + # Hinzufügen von tags, die für alle importierten Fragen gelten sollen + unless q.tags.nil? || q.tags.empty? + q.tags = q.tags + "," + tags + else + q.tags = tags + end unless q.save errors << {"type" => "unknown_error", "text" => q.name} else diff --git a/app/parsers/moodle_xml_parser.rb b/app/parsers/moodle_xml_parser.rb index 4db0eac..9a45d71 100755 --- a/app/parsers/moodle_xml_parser.rb +++ b/app/parsers/moodle_xml_parser.rb @@ -1,11 +1,18 @@ require "rexml/document" -class MoodleXmlParser +class MoodleXmlParser include REXML def export(questions) root = Element.new "quiz" # Über Fragen iterieren questions.each do |question| + + # moodle xml doesn't support neither order- nor category questions, so we skip them + if question.type == "order" + next + elsif question.type == "category" + next + end # Grundbaum der Frage aufbauen question_node = root.add_element "question" name_node = question_node.add_element "name" @@ -14,6 +21,15 @@ def export(questions) question_text_node.attributes["format"] = "html" (question_text_node.add_element "text").text = question.name + # tags anlegen, falls sie existieren + unless question.tags.nil? || question.tags.empty? + tags_node = question_node.add_element "tags" + question.tags.split(',').each do |tag| + tag_node = tags_node.add_element "tag" + (tag_node.add_element "text").text = tag + end + end + # Question Options hinzufügen für single- und multi-choice fragen if question.type == "multi" || question.type == "single" question_node.attributes["type"] = "multichoice" @@ -55,6 +71,18 @@ def export(questions) if question.type == "single" || question.type == "multi" (question_node.add_element "answernumbering").text = "abc" end + + if question.type == "match" + question_node.attributes["type"] = "match" + question.answer_pairs.where(correct: true).each do |pair| + subquestion_node = question_node.add_element "subquestion" + (subquestion_node.add_element "text").text = pair.answer1 + answer_node = subquestion_node.add_element "answer" + (answer_node.add_element "text").text = pair.answer2 + end + (question_node.add_element "shuffleanswers").text = "true" + end + end s = "" @@ -78,11 +106,6 @@ def import (xml_file, user, tags) question_type = element.attributes["type"] q = Question.new - # Category-Elemente überspringen - if question_type == 'category' - next - end - # Zunächst den Questiontype feststellen if question_type == 'multichoice' || question_type == 'truefalse' if element.elements["single"].text || question_type == 'truefalse' @@ -94,7 +117,9 @@ def import (xml_file, user, tags) q = Question.new(type:"text").service elsif question_type == 'numerical' q = Question.new(type:"number").service - elsif question_type == 'cloze' || question_type == 'match' || question_type == 'description' + elsif question_type == 'match' + q = Question.new(type:"match").service + elsif question_type == 'cloze' || question_type == 'description' # Nicht unterstützte Fragen abfangen errors << {"type" => "unsupported_type", "text" => element.elements["questiontext/text"].text} next @@ -107,8 +132,28 @@ def import (xml_file, user, tags) # Fragetext setzen q.name = element.elements["questiontext/text"].text + # Tags dieser spezifischen Frage auslesen und setzen + tags_string = "" + element.elements.each("tags/tag") do |tag_element| + if tags_string == "" + tags_string = tag_element.elements["text"].text + else + tags_string << "," + tag_element.elements["text"].text + end + end + unless tags_string == "" + q.tags = tags_string + end + # Über Antwortmöglichkeiten iterieren, bei Number-Fragen oder Freitext-Fragen werden die korrekten Antworten ignoriert - unless question_type == "numerical" || question_type == "essay" || question_type == "shortanswer" + if question_type == "numerical" || question_type == "essay" || question_type == "shortanswer" + elsif question_type == "match" + element.elements.each("subquestion") do |pair| + answer1 = pair.elements["text"].text + answer2 = pair.elements["answer"].elements["text"].text + q.answer_pairs << AnswerPair.new(answer1: answer1, answer2: answer2) + end + else element.elements.each("answer") do |answer| correct = Integer(answer.attributes["fraction"]) > 0 ? true : false text = answer.elements["text"].text @@ -121,7 +166,14 @@ def import (xml_file, user, tags) q.add_setting "answers", TextSurvey::ONE_ANSWER end q.user = user - q.tags = tags + + # Hinzufügen von tags, die für alle importierten Fragen gelten sollen + unless q.tags.nil? || q.tags.empty? + q.tags = q.tags + "," + tags + else + q.tags = tags + end + unless q.save errors << {"type" => "unknown_error", "text" => q.name} else diff --git a/app/services/.DS_Store b/app/services/.DS_Store new file mode 100755 index 0000000..5008ddf Binary files /dev/null and b/app/services/.DS_Store differ diff --git a/app/services/category_question.rb b/app/services/category_question.rb new file mode 100644 index 0000000..cf30047 --- /dev/null +++ b/app/services/category_question.rb @@ -0,0 +1,20 @@ +class CategoryQuestion < GenericQuestion + def initialize(question = Question.new(type: "category")) + raise "type of question not correct" if question.type != "category" + super + self.question.type = "category" unless self.question.persisted? + end + + def has_categories? + true + end + + def form_partial + "category_form" + end + + def to_survey + CategorySurvey.new(self.question.to_survey) + end + +end \ No newline at end of file diff --git a/app/services/category_survey.rb b/app/services/category_survey.rb new file mode 100644 index 0000000..01da742 --- /dev/null +++ b/app/services/category_survey.rb @@ -0,0 +1,50 @@ +class CategorySurvey < GenericSurvey + def initialize(survey) + raise "type of survey (#{survey.type}) not correct" if survey.type != "category" + super + end + + def prompt + I18n.t "surveys.participate.categorize-words" + end + + def participate_partial + "category_lists" + end + + def has_categories? + true + end + + def results_comparable? + true + end + + def vote(voter, category_subwords_pairs) + if self.survey.running?(false) + unless self.survey.matches?(voters: voter) #:fixme: is this "enough" concurrency safe? + self.survey.add_to_set(:voters, voter.to_s) + if category_subwords_pairs.respond_to?(:each) + category_subwords_pairs.each do |pair| + pairArray = pair.split(' - ') + pairArray[1].split(';').each do |sub_word| + self.survey.sub_words.where(name: sub_word).first.vote_up(pairArray[0]) + end + end + elsif category_subwords_pairs.nil? + # MC and nothing selected + else + pairArray = category_subwords_pairs.split(' - ') + pairArray[1].split(';').each do |sub_word| + self.survey.sub_words.where(name: sub_word).first.vote_up(pairArray[0]) + end + end + self.survey.add_to_set("voters_hash."+voter.to_s, (category_subwords_pairs || :no_answer)) + self.survey.track_vote(voter) + return true + end + end + return false + end + +end \ No newline at end of file diff --git a/app/services/generic_question.rb b/app/services/generic_question.rb index 1e4c898..99ab2eb 100755 --- a/app/services/generic_question.rb +++ b/app/services/generic_question.rb @@ -21,6 +21,22 @@ def has_settings? false end + def has_options? + false + end + + def has_answer_pairs? + false + end + + def has_order_options? + false + end + + def has_categories? + false + end + def add_setting(key, value) @question.settings ||= {} @question.settings[key.to_s] = value.to_s diff --git a/app/services/generic_survey.rb b/app/services/generic_survey.rb index 172f762..791aedc 100755 --- a/app/services/generic_survey.rb +++ b/app/services/generic_survey.rb @@ -24,6 +24,18 @@ def has_settings? def has_options? false end + + def has_answer_pairs? + false + end + + def has_order_options? + false + end + + def has_categories? + false + end def terms_numeric? # are individual answers numeric? false diff --git a/app/services/match_question.rb b/app/services/match_question.rb new file mode 100755 index 0000000..00f2591 --- /dev/null +++ b/app/services/match_question.rb @@ -0,0 +1,20 @@ +class MatchQuestion < GenericQuestion + def initialize(question = Question.new(type: "match")) + raise "type of question not correct" if question.type != "match" + super + self.question.type = "match" unless self.question.persisted? + end + + def has_answer_pairs? + true + end + + def form_partial + "match_form" + end + + def to_survey + MatchSurvey.new(self.question.to_survey) + end + +end \ No newline at end of file diff --git a/app/services/match_survey.rb b/app/services/match_survey.rb new file mode 100755 index 0000000..564ea92 --- /dev/null +++ b/app/services/match_survey.rb @@ -0,0 +1,79 @@ +class MatchSurvey < GenericSurvey + def initialize(survey) + raise "type of survey (#{survey.type}) not correct" if survey.type != "match" + super + end + + def prompt + I18n.t "surveys.participate.choose-match" + end + + def participate_partial + "match_lists" + end + + def has_answer_pairs? + true + end + + def results_comparable? + true + end + + def get_all_answer1 + if(self.survey.answer_pairs.any?) + answer1s = [] + self.survey.answer_pairs.where(correct: true).each do |pair| + answer1s += [pair.answer1] + end + return answer1s + end + end + + def get_all_answer2 + if(self.survey.answer_pairs.any?) + answer2s = [] + self.survey.answer_pairs.where(correct: true).each do |pair| + answer2s += [pair.answer2] + end + return answer2s + end + end + + def findAnswerPairID(anzwer1, anzwer2) + ap = self.survey.answer_pairs.where(answer1: anzwer1, answer2: anzwer2).first + if ap.blank? + raise "Couldn't find answer_pair: " + anzwer1 + " - " + anzwer2 + else + return ap.id + end + end + + def vote(voter, word_pairs) + if self.survey.running?(false) + unless self.survey.matches?(voters: voter) #:fixme: is this "enough" concurrency safe? + self.survey.add_to_set(:voters, voter.to_s) + if word_pairs.respond_to?(:each) + word_pairs.each do |pair| + pairArray = pair.split(' - ') + if(pairArray.length == 2) + self.survey.answer_pairs.where(answer1: pairArray[0], answer2: pairArray[1]).first.vote_up + end + end + elsif word_pairs.nil? + # MC and nothing selected + else + pairArray = word_pairs.split(' - ') + if(pairArray.length == 2) + self.survey.answer_pairs.where(answer1: pairArray[0], answer2: pairArray[1]).first.vote_up + end + end + self.survey.add_to_set("voters_hash."+voter.to_s, (word_pairs || :no_answer)) + self.survey.track_vote(voter) + return true + end + end + return false + end + +end \ No newline at end of file diff --git a/app/services/multiple_choice_question.rb b/app/services/multiple_choice_question.rb old mode 100644 new mode 100755 diff --git a/app/services/number_question.rb b/app/services/number_question.rb index de7e528..794f063 100755 --- a/app/services/number_question.rb +++ b/app/services/number_question.rb @@ -9,10 +9,6 @@ def to_survey NumberSurvey.new(self.question.to_survey) end - def has_options? - false - end - def form_partial "number_form" end diff --git a/app/services/order_question.rb b/app/services/order_question.rb new file mode 100644 index 0000000..2131764 --- /dev/null +++ b/app/services/order_question.rb @@ -0,0 +1,20 @@ +class OrderQuestion < GenericQuestion + def initialize(question = Question.new(type: "order")) + raise "type of question not correct" if question.type != "order" + super + self.question.type = "order" unless self.question.persisted? + end + + def has_order_options? + true + end + + def form_partial + "order_form" + end + + def to_survey + OrderSurvey.new(self.question.to_survey) + end + +end \ No newline at end of file diff --git a/app/services/order_survey.rb b/app/services/order_survey.rb new file mode 100644 index 0000000..9d305f8 --- /dev/null +++ b/app/services/order_survey.rb @@ -0,0 +1,65 @@ +class OrderSurvey < GenericSurvey + def initialize(survey) + raise "type of survey (#{survey.type}) not correct" if survey.type != "order" + super + end + + def prompt + I18n.t "surveys.participate.choose-order" + end + + def participate_partial + "order_list" + end + + def has_order_options? + true + end + + def results_comparable? + true + end + + def vote(voter, option_position_pairs) + if self.survey.running?(false) + unless self.survey.matches?(voters: voter) #:fixme: is this "enough" concurrency safe? + self.survey.add_to_set(:voters, voter.to_s) + + # fill relative_option_order_object + if option_position_pairs.length > 0 + for outer_index in 0..(option_position_pairs.length-1) + beforeName = option_position_pairs[outer_index].split(' - ')[0] + if option_position_pairs.length > (outer_index + 1) + for inner_index in (outer_index + 1)..(option_position_pairs.length - 1) + afterName = option_position_pairs[inner_index].split(' - ')[0] + self.survey.relative_option_order_object.vote_up(beforeName, afterName) + end + end + end + end + + # fill order_option.votes + if option_position_pairs.respond_to?(:each) + option_position_pairs.each do |pair| + pairArray = pair.split(' - ') + if(pairArray.length == 2) + self.survey.order_options.where(name: pairArray[0]).first.vote_up(Integer(pairArray[1])) + end + end + elsif option_position_pairs.nil? + # MC and nothing selected + else + pairArray = option_position_pairs.split(' - ') + if(pairArray.length == 2) + self.survey.order_options.where(name: pairArray[0]).first.vote_up(Integer(pairArray[1])) + end + end + self.survey.add_to_set("voters_hash."+voter.to_s, (option_position_pairs || :no_answer)) + self.survey.track_vote(voter) + return true + end + end + return false + end + +end \ No newline at end of file diff --git a/app/services/single_choice_question.rb b/app/services/single_choice_question.rb old mode 100644 new mode 100755 diff --git a/app/services/text_question.rb b/app/services/text_question.rb index 4ada2d8..3f37b5b 100755 --- a/app/services/text_question.rb +++ b/app/services/text_question.rb @@ -9,10 +9,6 @@ def to_survey TextSurvey.new(self.question.to_survey) end - def has_options? - false - end - def form_partial "text_form" end diff --git a/app/views/.DS_Store b/app/views/.DS_Store new file mode 100755 index 0000000..0152900 Binary files /dev/null and b/app/views/.DS_Store differ diff --git a/app/views/events/_advanced_settings.html.erb b/app/views/events/_advanced_settings.html.erb old mode 100644 new mode 100755 diff --git a/app/views/events/_collaborators.html.erb b/app/views/events/_collaborators.html.erb old mode 100644 new mode 100755 diff --git a/app/views/events/_form.html.erb b/app/views/events/_form.html.erb old mode 100644 new mode 100755 diff --git a/app/views/events/edit.html.erb b/app/views/events/edit.html.erb old mode 100644 new mode 100755 diff --git a/app/views/questions/.DS_Store b/app/views/questions/.DS_Store new file mode 100755 index 0000000..e25b020 Binary files /dev/null and b/app/views/questions/.DS_Store differ diff --git a/app/views/questions/_answer_pair.html.erb b/app/views/questions/_answer_pair.html.erb new file mode 100755 index 0000000..43ac62b --- /dev/null +++ b/app/views/questions/_answer_pair.html.erb @@ -0,0 +1,5 @@ +
  • + <%= answer_pair.answer1 %> + - + <%= answer_pair.answer2 %> +
  • diff --git a/app/views/questions/_category_form.html.erb b/app/views/questions/_category_form.html.erb new file mode 100644 index 0000000..7f2468c --- /dev/null +++ b/app/views/questions/_category_form.html.erb @@ -0,0 +1,108 @@ +<%= semantic_form_for question, namespace: "category", html: { class: "form-inline", id: "new_category_question" } do |form| %> + <%= error_msg_for question %> +
    +
    + <%= form.input :name, input_html: {class: "input-xxlarge", placeholder: ""} %> +
    + <%= form.hidden_field :type, value: "category" %> + <%= hidden_field_tag :redirect_to_session, params[:redirect_to_session] %> +
    + <%= form.input :tags, input_html: { class: "input-xxlarge" , placeholder: "Tags" }, required: false %> + <% if current_user.question_tags && !current_user.question_tags.blank? %> + <%= select_tag "tags", options_for_select([[t("questions.add_from_your_tags"), '']] + current_user.question_tags.sort.map{ |t| [t, t]}), id: "category_select_tag" %> +
      + <% end %> +
    +
    + +
    +
    + +

    <%= t("surveys.edit.categories_and_sub_words_for_category_survey") %>

    +
    +
    + <% question.categories.each do |cat| %> + <%= form.semantic_fields_for :categories, cat, class: "form-inline" do |category| %> + <%= render "category_question_category_fields", f: category, question: question, form: form, cat: cat %> + <% end %> + <% end %> + +
    +
    +
    +
    + <%= form.hidden_field :collaborators_form, class:"collaborators-form" %> + <%= form.submit class: "btn btn-primary", id: "single_question_submit" %> + + <% if params[:redirect_to_session] %> +
    +
    + <% end %> + +<% end %> +<% content_for :javascript do %> + +<% end %> \ No newline at end of file diff --git a/app/views/questions/_category_question_category_fields.html.erb b/app/views/questions/_category_question_category_fields.html.erb new file mode 100644 index 0000000..9c60df5 --- /dev/null +++ b/app/views/questions/_category_question_category_fields.html.erb @@ -0,0 +1,17 @@ +
    +
    + <%= link_to_remove_association icon_tag("minus-sign"), f %> + <%= f.text_field :name, placeholder: t("surveys.category_name"), class: "category_name_field" %> + <%= f.hidden_field :sub_words, value: "", class: "hidden_sub_words_field" %> +
      + <% question.sub_words.where(category: cat.name).each do |sub_word| %> + <%= form.semantic_fields_for :sub_words, sub_word, class: "form-inline" do |word| %> + <%= render "category_question_sub_words_fields", f: word %> + <% end %> + <% end %> + +
    +
    +
    diff --git a/app/views/questions/_category_question_category_fields_without_locals.html.erb b/app/views/questions/_category_question_category_fields_without_locals.html.erb new file mode 100644 index 0000000..f01c52d --- /dev/null +++ b/app/views/questions/_category_question_category_fields_without_locals.html.erb @@ -0,0 +1,16 @@ +
    +
    + <%= link_to_remove_association icon_tag("minus-sign"), f %> + <%= f.text_field :name, placeholder: t("surveys.category_name"), class: "category_name_field" %> + <%= f.hidden_field :sub_words, value: "", class: "hidden_sub_words_field" %> +
      + <%= form.semantic_fields_for :sub_words, class: "form-inline" do |word| %> + <%= render "category_question_sub_words_fields", f: word %> + <% break %> + <% end %> + +
    +
    +
    diff --git a/app/views/questions/_category_question_sub_words_fields.html.erb b/app/views/questions/_category_question_sub_words_fields.html.erb new file mode 100644 index 0000000..743d10e --- /dev/null +++ b/app/views/questions/_category_question_sub_words_fields.html.erb @@ -0,0 +1,9 @@ +
  • + + <%= icon_tag("minus-sign") %> + + <%= f.text_field :name, placeholder: t("surveys.sub_word_name"), class: "sub_word_name_field" %> + <%= f.hidden_field :category, value: "", class: "hidden_category_name_field" %> + <%= f.hidden_field :_destroy, value: "", class: "hidden_destroy_field" %> +
  • + diff --git a/app/views/questions/_export_button.html.erb b/app/views/questions/_export_button.html.erb old mode 100644 new mode 100755 index f8a1643..f9d99ae --- a/app/views/questions/_export_button.html.erb +++ b/app/views/questions/_export_button.html.erb @@ -10,6 +10,9 @@
  • <%= link_to_export "Moodle XML", "moodle_xml" %>
  • +
  • + <%= link_to_export "Gift", "gift" %> +
  • <%= link_to_export "Aiken", "aiken" %>
  • diff --git a/app/views/questions/_match_form.html.erb b/app/views/questions/_match_form.html.erb new file mode 100755 index 0000000..f5582e3 --- /dev/null +++ b/app/views/questions/_match_form.html.erb @@ -0,0 +1,60 @@ +<%= semantic_form_for question, namespace: "match", html: { class: "form-inline", id: "new_match_question" } do |form| %> + <%= error_msg_for question %> +
    +
    + <%= form.input :name, input_html: {class: "input-xxlarge"} %> +
    + <%= form.hidden_field :type, value: "match" %> + <%= hidden_field_tag :redirect_to_session, params[:redirect_to_session] %> +
    + <%= form.input :tags, input_html: { class: "input-xxlarge" , placeholder: "Tags" }, required: false %> + <% if current_user.question_tags && !current_user.question_tags.blank? %> + <%= select_tag "tags", options_for_select([[t("questions.add_from_your_tags"), '']] + current_user.question_tags.sort.map{ |t| [t, t]}), id: "match_select_tag" %> +
      + <% end %> +
    +
    + +
    +
    + +

    <%= t("surveys.edit.answer_pairs_for_match_survey") %>

    +
    +
    + <% question.answer_pairs.where(correct: true).each do |trueAP| %> + <%= form.semantic_fields_for :answer_pairs, trueAP, class: "form-inline" do |pair| %> + <%= render "match_question_answer_pair_fields", f: pair %> + <% end %> + <% end %> + +
    +
    +
    +
    + <%= form.hidden_field :collaborators_form, class:"collaborators-form" %> + <%= form.submit class: "btn btn-primary", id: "single_question_submit" %> + + <% if params[:redirect_to_session] %> +
    +
    + <% end %> + +<% end %> +<% content_for :javascript do %> + +<% end %> \ No newline at end of file diff --git a/app/views/questions/_match_question_answer_pair_fields.html.erb b/app/views/questions/_match_question_answer_pair_fields.html.erb new file mode 100755 index 0000000..f9e19cf --- /dev/null +++ b/app/views/questions/_match_question_answer_pair_fields.html.erb @@ -0,0 +1,7 @@ +
    +
    + <%= link_to_remove_association icon_tag("minus-sign"), f %> + <%= f.text_field :answer1, placeholder: t("surveys.match_input_field_1") %> + <%= f.text_field :answer2, placeholder: t("surveys.match_input_field_2") %> +
    +
    diff --git a/app/views/questions/_multi_form.html.erb b/app/views/questions/_multi_form.html.erb old mode 100644 new mode 100755 diff --git a/app/views/questions/_number_form.html.erb b/app/views/questions/_number_form.html.erb old mode 100644 new mode 100755 diff --git a/app/views/questions/_order_form.html.erb b/app/views/questions/_order_form.html.erb new file mode 100644 index 0000000..531cc5c --- /dev/null +++ b/app/views/questions/_order_form.html.erb @@ -0,0 +1,104 @@ +<%= semantic_form_for question, namespace: "order", html: { class: "form-inline", id: "new_order_question" } do |form| %> + <%= error_msg_for question %> +
    +
    + <%= form.input :name, input_html: {class: "input-xxlarge"} %> +
    + <%= form.hidden_field :type, value: "order" %> + <%= hidden_field_tag :redirect_to_session, params[:redirect_to_session] %> +
    + <%= form.input :tags, input_html: { class: "input-xxlarge" , placeholder: "Tags" }, required: false %> + <% if current_user.question_tags && !current_user.question_tags.blank? %> + <%= select_tag "tags", options_for_select([[t("questions.add_from_your_tags"), '']] + current_user.question_tags.sort.map{ |t| [t, t]}), id: "order_select_tag" %> +
      + <% end %> +
    +
    + +
    +
    + +

    <%= t("surveys.edit.order_options_for_order_survey") %>

    +
    +
    +
      + <%= form.semantic_fields_for :order_options, class: "form-inline" do |option| %> + <%= render "order_question_order_option_fields", f: option %> + <% end %> + +
    +
    +
    +
    +
    + <%= form.hidden_field :collaborators_form, class:"collaborators-form" %> + <%= form.submit class: "btn btn-primary", id: "single_question_submit" %> + + <% if params[:redirect_to_session] %> +
    +
    + <% end %> + +<% end %> +<% content_for :javascript do %> + + + + + +<% end %> \ No newline at end of file diff --git a/app/views/questions/_order_option.html.erb b/app/views/questions/_order_option.html.erb new file mode 100644 index 0000000..d2a5db9 --- /dev/null +++ b/app/views/questions/_order_option.html.erb @@ -0,0 +1,5 @@ +
  • + <%= order_option.position %> + ) + <%= order_option.name %> +
  • diff --git a/app/views/questions/_order_question_order_option_fields.html.erb b/app/views/questions/_order_question_order_option_fields.html.erb new file mode 100644 index 0000000..0399c46 --- /dev/null +++ b/app/views/questions/_order_question_order_option_fields.html.erb @@ -0,0 +1,8 @@ +
  • +
    +
    + <%= link_to_remove_association icon_tag("minus-sign"), f %> + <%= f.text_field :name, placeholder: t("name"), class: "name_field_for_order_option" %> +
    +
    +
  • diff --git a/app/views/questions/_share_button.html.erb b/app/views/questions/_share_button.html.erb old mode 100644 new mode 100755 diff --git a/app/views/questions/_single_form.html.erb b/app/views/questions/_single_form.html.erb old mode 100644 new mode 100755 index 565e59b..41862eb --- a/app/views/questions/_single_form.html.erb +++ b/app/views/questions/_single_form.html.erb @@ -24,7 +24,7 @@
    <%= form.semantic_fields_for :question_options do |option| %> - <%= render "single_question_option_fields", f: option %> + <%= render "single_question_option_fields", f: option %> <% end %>

    <%= t(".csv_info") %>

    - <%= link_to(icon_tag(:file) + "Excelvorlage laden", "/templates/csv-import-template.xlsx", class: "btn") %> + <%= link_to(icon_tag(:file) + t(".csv_template_load"), "/templates/csv-import-template.csv", class: "btn") %>
    <%= t(".aiken_info") %> @@ -131,6 +131,18 @@   Erste Spalte enthält "number" Numerische Frage + +   Erste Spalte enthält "match" + Zuordnungsfrage + + +   Erste Spalte enthält "order" + Reihenfolgefrage + + +   Erste Spalte enthält "category" + Kategoriefrage + Moodle XML @@ -153,7 +165,7 @@   match - Nicht unterstützt + Zuordnungsfrage   cloze @@ -177,7 +189,7 @@   Multiple Choice mit einer korrekten Antwort - Single Choice-frage + Single Choice-Frage   True - False @@ -189,11 +201,11 @@   Matching - Nicht unterstützt + Zuordnungsfrage   Missing Word - Nicht unterstützt + Freitext-Frage   Numerical @@ -265,11 +277,11 @@   Anordnungsfrage - Nicht unterstützt + Reihenfolgefrage   Zuordnungsfrage - Nicht unterstützt + Zuordnungsfrage   Freitext @@ -336,7 +348,7 @@   match - Not supported + Match questions   cloze @@ -372,7 +384,7 @@   Matching - Not supported + Match question   Missing Word @@ -448,11 +460,11 @@   Order - Not Supported + Order questions   Assign - Not Supported + Match questions   Text-Question diff --git a/app/views/questions/index.html.erb b/app/views/questions/index.html.erb old mode 100644 new mode 100755 index 2511848..274c100 --- a/app/views/questions/index.html.erb +++ b/app/views/questions/index.html.erb @@ -53,11 +53,17 @@
  • "> <%= link_to icon_tag("asterisk") + t(".all_tags"), questions_path(tag: params[:tag], public: params[:public], shared: params[:shared]) %>
  • "> - <%= link_to icon_tag("align-left") + t("type.choice"), questions_path(tag: params[:tag], q_type: "choice", public: params[:public], shared: params[:shared]), id: "questionsSidebarChoiceLink" %> + <%= link_to icon_tag("check") + t("type.choice"), questions_path(tag: params[:tag], q_type: "choice", public: params[:public], shared: params[:shared]), id: "questionsSidebarChoiceLink" %>
  • "> - <%= link_to icon_tag("th") + t("type.text"), questions_path(tag: params[:tag], q_type: "text", public: params[:public], shared: params[:shared]) %>
  • + <%= link_to icon_tag("align-left") + t("type.text"), questions_path(tag: params[:tag], q_type: "text", public: params[:public], shared: params[:shared]) %>
  • "> -<%= link_to icon_tag("th") + t("type.number"), questions_path(tag: params[:tag], q_type: "number", public: params[:public], shared: params[:shared]) %>
  • + <%= link_to icon_tag("th") + t("type.number"), questions_path(tag: params[:tag], q_type: "number", public: params[:public], shared: params[:shared]) %> +
  • "> + <%= link_to icon_tag("random") + t("type.match"), questions_path(tag: params[:tag], q_type: "match", public: params[:public], shared: params[:shared]) %>
  • +
  • "> + <%= link_to icon_tag("list") + t("type.order"), questions_path(tag: params[:tag], q_type: "order", public: params[:public], shared: params[:shared]) %>
  • +
  • "> + <%= link_to icon_tag("th-large") + t("type.category"), questions_path(tag: params[:tag], q_type: "category", public: params[:public], shared: params[:shared]) %>
  • @@ -78,40 +84,41 @@ <% unless @questions.empty? %> - - - - - + + + + + + <% @questions.each do |question| %> - + <%= + if (question.public) + icon_tag("globe") + elsif (question.collaborators.size > 0) + icon_tag("share") + end + %> + + <% if params[:public] %> - <% if question.user == current_user %> - - - <% else %> - - - <% end %> + <% if question.user == current_user %> + + + + <% else %> + + + <% end %> <% else %> - - + + + <% end %> <% end %> @@ -121,7 +128,6 @@ <%= hidden_field_tag "share_user_id" %> <%= render :partial => 'export_button' %> <%= render :partial => 'share_button', locals: {no_modal: false} %> - <% else %>

    <%= t(".no_questions_yet") %>

    diff --git a/app/views/questions/new.html.erb b/app/views/questions/new.html.erb old mode 100644 new mode 100755 index dcca525..f575e74 --- a/app/views/questions/new.html.erb +++ b/app/views/questions/new.html.erb @@ -9,6 +9,9 @@
  • <%= t "type.multi" %>
  • <%= t "type.text" %>
  • <%= t "type.number" %> BETA
  • +
  • <%= t "type.match" %>
  • +
  • <%= t "type.order" %>
  • +
  • <%= t "type.category" %>
  • @@ -26,6 +29,15 @@
    <%= render partial: @question_number.form_partial, locals: {question: @question_number} %>
    +
    + <%= render partial: @question_match.form_partial, locals: {question: @question_match} %> +
    +
    + <%= render partial: @question_order.form_partial, locals: {question: @question_order} %> +
    +
    + <%= render partial: @question_category.form_partial, locals: {question: @question_category} %> +
    diff --git a/app/views/questions/show.html.erb b/app/views/questions/show.html.erb old mode 100644 new mode 100755 index bbf94a4..dbb8c95 --- a/app/views/questions/show.html.erb +++ b/app/views/questions/show.html.erb @@ -13,7 +13,13 @@ <% elsif @question.type == "text" %> Text <% elsif @question.type == "number" %> - <%= t("type_number") %> + <%= t("questions.show.type_number") %> +<% elsif @question.type == "match" %> + <%= t("questions.show.type_match") %> +<% elsif @question.type == "order" %> + <%= t("questions.show.type_order") %> +<% elsif @question.type == "category" %> + <%= t("questions.show.type_category") %> <% end %>

    Tags: <%= @question.tags_array.join(", ") %>

    @@ -26,7 +32,35 @@ <% end %> +<% if @question.has_answer_pairs? %> + <%= t("surveys.show.answer_pairs") %> +
      + <%= render partial: "answer_pair", collection: @question.answer_pairs.where(correct: true) %> +
    +<% end %> +<% if @question.has_order_options? %> + <%= t("surveys.show.order_options") %> +
      + <%= render partial: "order_option", collection: @question.order_options %> +
    +<% end %> + +<% if @question.has_categories? %> + <%= t("surveys.show.categories") %> +
      + <% @question.categories.each do |category| %> +
    • + <%= category.name %>: +
        + <% category.sub_words.split(';').each do |sub_word| %> +
      • <%= sub_word %>
      • + <% end %> +
      +
    • + <% end %> +
    +<% end %> <% if @question.can_be_accessed_by?(current_user) %> <%= link_to t("edit"), edit_question_path(@question), class: "btn btn-small" %> diff --git a/app/views/surveys/.DS_Store b/app/views/surveys/.DS_Store new file mode 100755 index 0000000..5008ddf Binary files /dev/null and b/app/views/surveys/.DS_Store differ diff --git a/app/views/surveys/_answer_pair_result_table.html.erb b/app/views/surveys/_answer_pair_result_table.html.erb new file mode 100755 index 0000000..5f17541 --- /dev/null +++ b/app/views/surveys/_answer_pair_result_table.html.erb @@ -0,0 +1,44 @@ + +
      - <%= t "name" %> 
     <%= t "name" %> 
    <%= check_box_tag("question_ids[]", question.id, false, class: "questions-table__checkbox") %> - <%= - if (question.public) - icon_tag("globe") - elsif (question.collaborators.size > 0) - icon_tag("share") - end - %> - <%= link_to (question.name||""), question_path(question), class: "questionLink" %> <%= link_to (question.name||""), question_path(question), class: "questionLink" %> <%= link_to icon_tag("pencil"), edit_question_path(question), title: t("edit") %> <%= link_to icon_tag("minus-sign"), question, - title: t(".delete_question"), confirm: t(".delete_sure"), method: :delete %><%= link_to icon_tag("plus-sign"), add_to_own_question_path(question, format: :json), remote: true, method: :post, class: "add_to_own_link", title: t("questions.show.add_to_own") %>  <%= link_to icon_tag("pencil"), edit_question_path(question), title: t("edit") %> <%= link_to icon_tag("plus-sign"), {:action => 'clone', :id => question.id}, title: t("duplicate") %> <%= link_to icon_tag("minus-sign"), question, title: t(".delete_question"), confirm: t(".delete_sure"), method: :delete %><%= link_to icon_tag("plus-sign"), add_to_own_question_path(question, format: :json), remote: true, method: :post, class: "add_to_own_link", title: t("questions.show.add_to_own") %>  <%= link_to icon_tag("pencil"), edit_question_path(question), title: t("edit"), class: "question_edit_link" %> <%= link_to icon_tag("minus-sign"), question, - title: t(".delete_question"), confirm: t(".delete_sure"), method: :delete %><%= link_to icon_tag("pencil"), edit_question_path(question), title: t("edit"), class: "question_edit_link" %> <%= link_to icon_tag("plus-sign"), {:action => 'clone', :id => question.id}, title: t("duplicate") %> <%= link_to icon_tag("minus-sign"), question, + title: t(".delete_question"), confirm: t(".delete_sure"), method: :delete %>
    + + + <% answer2s = survey.get_all_answer2 %> + <% columnWidthPercentage = number_to_percentage(1.to_f / (1 + answer2s.length).to_f * 100.to_f, :precision => 0) %> + + <% answer2s.shuffle! %> + <% answer2s.each do |answer2| %> + + <% end %> + + + + <% survey.answer_pairs.where(correct: true).each do |pair1| %> + + <% answer2s.each do |answer2| %> + <% percentage = "0%" %> + <% current_answer_pair = survey.answer_pairs.where(answer1: pair1.answer1, answer2: answer2).first %> + <% if survey.total_votes > 0 %> + <% percentage = number_to_percentage(current_answer_pair.votes.to_f / survey.total_votes.to_f * 100.to_f, :precision => 0) %> + <% end %> + <% rowHeightPercentage = number_to_percentage(1.to_f / (1 + answer2s.length).to_f * 100.to_f, :precision => 0) %> + <% if current_answer_pair.correct? %> + + <% else %> + + <% end %> + <% end %> + + <% end %> + +
     <%= answer2 %>
    <%= pair1.answer1 %> +
    +
    +

    <%= percentage %>

    +
    +
    +
    +
    +

    <%= percentage %>

    +
    +
    + \ No newline at end of file diff --git a/app/views/surveys/_category_lists.html.erb b/app/views/surveys/_category_lists.html.erb new file mode 100644 index 0000000..0aa49b0 --- /dev/null +++ b/app/views/surveys/_category_lists.html.erb @@ -0,0 +1,56 @@ + + <% categories = survey.categories %> + <% sub_words = survey.sub_words %> + <% categories.shuffle! %> + <% sub_words.shuffle! %> + + + + +
    +
      + <% for j in 0..(categories.length - 1) %> +
    • + <%= categories[j].name %>:
      +
      +
    • + <% end %> +
    +
    +
      + <% for j in 0..(sub_words.length - 1) %> +
    • <%= sub_words[j].name %>
    • + <% end %> +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/app/views/surveys/_category_results_table.html.erb b/app/views/surveys/_category_results_table.html.erb new file mode 100644 index 0000000..cc66b61 --- /dev/null +++ b/app/views/surveys/_category_results_table.html.erb @@ -0,0 +1,43 @@ + + + + + <% numberOfCategories = survey.categories.length %> + <% columnWidthPercentage = number_to_percentage(1.to_f / (1 + numberOfCategories).to_f * 100.to_f, :precision => 0) %> + + <% survey.categories.each do |category| %> + + <% end %> + + + + <% sub_words = survey.sub_words.shuffle %> + <% sub_words.each do |sub_word| %> + + <% survey.categories.each do |category| %> + <% percentage = "0%" %> + <% currently_correct_category = sub_word.category %> + <% if survey.total_votes > 0 %> + <% percentage = number_to_percentage(sub_word.get_votes_for(category.name).to_f / survey.total_votes.to_f * 100.to_f, :precision => 0) %> + <% end %> + <% if category.name == currently_correct_category %> + + <% else %> + + <% end %> + <% end %> + + <% end %> + +
     <%= category.name %>
    <%= sub_word.name %> +
    +
    +

    <%= percentage %>

    +
    +
    +
    +
    +

    <%= percentage %>

    +
    +
    + \ No newline at end of file diff --git a/app/views/surveys/_match_lists.html.erb b/app/views/surveys/_match_lists.html.erb new file mode 100755 index 0000000..1a61e73 --- /dev/null +++ b/app/views/surveys/_match_lists.html.erb @@ -0,0 +1,138 @@ + + + + + + <% answer1s = survey.get_all_answer1 %> + <% answer2s = survey.get_all_answer2 %> + <% answer2s.shuffle! %> + + + + +
    +
      + <% for j in 0..(answer1s.length - 1) %> +
    1. <%= answer1s[j] %>
    2. + <% end %> +
    +
    +
      + <% for j in 0..(answer2s.length - 1) %> +
    1. <%= answer2s[j] %>
    2. + <% end %> +
    +
    +
    +

    + <%= t "surveys.participate.chosen-match" %>: +

    +
    +
    + + + \ No newline at end of file diff --git a/app/views/surveys/_order_list.html.erb b/app/views/surveys/_order_list.html.erb new file mode 100644 index 0000000..f992038 --- /dev/null +++ b/app/views/surveys/_order_list.html.erb @@ -0,0 +1,17 @@ +
      + <% survey.order_options.shuffle! %> + <% survey.order_options.each do |option| %> +
    • <%= option.name %>
    • + <% end %> +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/app/views/surveys/_order_option_absolute_results_table.html.erb b/app/views/surveys/_order_option_absolute_results_table.html.erb new file mode 100644 index 0000000..9a56d42 --- /dev/null +++ b/app/views/surveys/_order_option_absolute_results_table.html.erb @@ -0,0 +1,50 @@ + + + + <% numberOfOptions = survey.order_options.length %> + <% columnWidthPercentage = number_to_percentage(1.to_f / (1 + numberOfOptions).to_f * 100.to_f, :precision => 0) %> + + <% for index in 1..numberOfOptions %> + + <% end %> + + + + <% survey.order_options.shuffle! %> + <% survey.order_options.each do |option| %> + + <% currentPosition = 1 %> + <% if option.votes == "" %> + <% survey.order_options.each do |opt| %> + <% option.votes = option.votes + "0," %> + <% end %> + <% option.votes = option.votes[0..-2] %> + <% end %> + <% option.votes.split(",").each do |votesForPosition| %> + <% votesForPositionInteger = Integer(votesForPosition) %> + <% percentage = "0%" %> + <% if survey.total_votes > 0 %> + <% percentage = number_to_percentage(votesForPositionInteger.to_f / survey.total_votes.to_f * 100.to_f, :precision => 0) %> + <% end %> + <% rowHeightPercentage = number_to_percentage(1.to_f / (1 + survey.order_options.length).to_f * 100.to_f, :precision => 0) %> + <% if option.position == currentPosition %> + + <% else %> + + <% end %> + <% currentPosition += 1 %> + <% end %> + + <% end %> + +
    Position<%= index %>
    <%= option.name %> +
    +
    +

    <%= percentage %>

    +
    +
    +
    +
    +

    <%= percentage %>

    +
    +
    \ No newline at end of file diff --git a/app/views/surveys/_order_option_ordered_relative_results_table.html.erb b/app/views/surveys/_order_option_ordered_relative_results_table.html.erb new file mode 100644 index 0000000..061260b --- /dev/null +++ b/app/views/surveys/_order_option_ordered_relative_results_table.html.erb @@ -0,0 +1,40 @@ + + + + <% numberOfOptions = survey.order_options.length %> + <% columnWidthPercentage = number_to_percentage(1.to_f / (1 + numberOfOptions).to_f * 100.to_f, :precision => 0) %> + + + <% if survey.total_votes > 0 %> + <% for position in 1..numberOfOptions %> + + <% end %> + + + + <% for before_position in 1..numberOfOptions %> + + <% for after_position in 1..numberOfOptions %> + <% rowHeightPercentage = number_to_percentage(1.to_f / (1 + numberOfOptions).to_f * 100.to_f, :precision => 0) %> + <% if before_position == after_position %> + + <% else %> + <% percentage = number_to_percentage(survey.relative_option_order_object.get_votes_for(survey.order_options.where(position: before_position).first.name, survey.order_options.where(position: after_position).first.name).to_f / survey.total_votes.to_f * 100.to_f, :precision => 0) %> + + <% end %> + <% end %> + + <% end %> + <% end %> + +
    <%= t("x_before_y") %><%= survey.order_options.where(position: position).first.name %>
    <%= survey.order_options.where(position: before_position).first.name %> +
    +
    +

    /

    +
    +
    +
    +
    +

    <%= percentage %>

    +
    +
    diff --git a/app/views/surveys/_order_option_relative_results_table.html.erb b/app/views/surveys/_order_option_relative_results_table.html.erb new file mode 100644 index 0000000..df71a42 --- /dev/null +++ b/app/views/surveys/_order_option_relative_results_table.html.erb @@ -0,0 +1,54 @@ + + + + <% numberOfOptions = survey.order_options.length %> + <% columnWidthPercentage = number_to_percentage(1.to_f / (1 + numberOfOptions).to_f * 100.to_f, :precision => 0) %> + + + <% if survey.total_votes > 0 %> + <% name_highest_voted_position_map = Hash.new %> + <% survey.order_options.each do |option| %> + <% current_high_score = 0 %> + <% most_voted_position = 0 %> + <% current_index = 0 %> + <% option.votes.split(',').each do |vote_number| %> + <% if current_high_score < vote_number.to_i %> + <% most_voted_position = current_index %> + <% end %> + <% current_index += 1 %> + <% end %> + <% name_highest_voted_position_map[option.name] = most_voted_position %> + <% end %> + <% name_highest_voted_position_map = name_highest_voted_position_map.sort {|a,b| a[1]<=>b[1]} %> + <% name_highest_voted_position_map.each do |name_position| %> + + <% end %> + + + + <% for before_index in 0..(name_highest_voted_position_map.length-1) %> + + <% for after_index in 0..(name_highest_voted_position_map.length-1) %> + <% rowHeightPercentage = number_to_percentage(1.to_f / (1 + survey.order_options.length).to_f * 100.to_f, :precision => 0) %> + <% if before_index == after_index %> + + <% else %> + <% percentage = number_to_percentage(survey.relative_option_order_object.get_votes_for(name_highest_voted_position_map[before_index][0], name_highest_voted_position_map[after_index][0]).to_f / survey.total_votes.to_f * 100.to_f, :precision => 0) %> + + <% end %> + <% end %> + + <% end %> + <% end %> + +
    <%= t("x_before_y") %><%= name_position[0] %>
    <%= name_highest_voted_position_map[before_index][0] %> +
    +
    +

    /

    +
    +
    +
    +
    +

    <%= percentage %>

    +
    +
    diff --git a/app/views/surveys/_show.html.erb b/app/views/surveys/_show.html.erb index 17f5368..6d5194e 100755 --- a/app/views/surveys/_show.html.erb +++ b/app/views/surveys/_show.html.erb @@ -21,6 +21,15 @@ <% if survey.type == "text" %>

    <%= t ".is_text" %>

    <% end %> + <% if survey.type == "match" %> +

    <%= t ".is_match" %>

    + <% end %> + <% if survey.type == "order" %> +

    <%= t ".is_order" %>

    + <% end %> + <% if survey.type == "category" %> +

    <%= t ".is_category" %>

    + <% end %> <% if survey.running?(true) %>

    @@ -54,11 +63,6 @@ <% end %>

    - <% if survey.has_options? and survey.options.count > 0 %>

    <%= t ".options" %>:

    @@ -72,42 +76,87 @@
    - <%= icon_tag("download-alt") %> + <%= icon_tag("download-alt") %> <% if survey.options.any?(&:correct) %> - Korrekte markieren + <%= t("surveys.show.mark_corrects") %> <% end %> - <% if survey.original_survey %> -
      -
    • <%= t("surveys.show.chart_first") %>
    • -
    • <%= t("surveys.show.chart_repeated") %>
    • -
    + <% if survey.original_survey %> +
      +
    • <%= t("surveys.show.chart_first") %>
    • +
    • <%= t("surveys.show.chart_repeated") %>
    • +
    + <% end %>
    - <% end %>
    + +<% elsif survey.has_answer_pairs? %> +
    +
    +

    <%= t("results") %>

    +
    + <%= render partial: "answer_pair_result_table", locals: {survey: survey} %> + <%= icon_tag("download-alt") %> + <%= t("surveys.show.mark_corrects") %> +
    +
    +
    + +<% elsif survey.has_order_options? %> +
    +
    +

    <%= t("absolute_results") %>

    +
    + <%= render partial: "order_option_absolute_results_table", locals: {survey: survey} %> + <%= icon_tag("download-alt") %> +
    + <%= t("surveys.show.correct_order_and_mark") %> +

    <%= t("relative_results") %>

    +
    +
    <%= render partial: "order_option_relative_results_table", locals: {survey: survey} %>
    +
    <%= render partial: "order_option_ordered_relative_results_table", locals: {survey: survey} %>
    + <%= icon_tag("download-alt") %> +
    +
    +
    + +<% elsif survey.has_categories? %> +
    +
    +

    <%= t("results") %>

    +
    + <%= render partial: "category_results_table", locals: {survey: survey} %> + <%= icon_tag("download-alt") %> + <%= t("surveys.show.mark_corrects") %> +
    +
    +
    + <% elsif survey.type == "text" %> - <% if !survey.running? && survey.total_votes > 0 %> -
    - - -
    -
    - <% if survey.total_votes > 6 %> - <%= render "text_tag_cloud_result", survey: @survey %> - <% else %> - <%= render "text_table_results", survey: @survey %> - <% end %> -
    + <% if !survey.running? && survey.total_votes > 0 %> +
    + + +
    +
    + <% if survey.total_votes > 6 %> + <%= render "text_tag_cloud_result", survey: @survey %> + <% else %> + <%= render "text_table_results", survey: @survey %> + <% end %> +
    - <% else %> -
    <%= image_tag "tag_cloud_placeholder.png" %>
    - <% end %> + <% else %> +
    <%= image_tag "tag_cloud_placeholder.png" %>
    + <% end %> @@ -164,8 +213,10 @@ <%= select_tag "duration", options_for_select(duration_choices(false), 120), id: "repeatDuration" %> - + <% end %>
    diff --git a/app/views/surveys/_text_tag_cloud_result.html.erb b/app/views/surveys/_text_tag_cloud_result.html.erb old mode 100644 new mode 100755 diff --git a/app/views/surveys/participate.html.erb b/app/views/surveys/participate.html.erb index 8eaff9a..646e5f8 100755 --- a/app/views/surveys/participate.html.erb +++ b/app/views/surveys/participate.html.erb @@ -33,7 +33,7 @@ fieldset[data-role="controlgroup"] label { <% end %> <% end %> - <%= form_tag("/vote", style: 'clear:both;') %> + <%= form_tag("/vote", style: 'clear:both;', id: "participate_order_form") %>
    @@ -173,6 +173,61 @@ fieldset[data-role="controlgroup"] label { connect_timeout = setTimeout(function() {if(!connected && !running) { location.reload(); }}, 7500); + <% if @survey.type == "order" %> + + <% end %> + + <% if @survey.type == "category" %> + + <% end %> + <% if @survey && @survey.mathjax? %>