Index: lams_central/conf/language/lams/ApplicationResources_en_AU.properties =================================================================== diff -u -r1b530a88ae6d3a1e553081b3d56117ec5ad3622a -r6cbd849584c40532c6be292f9f009c88cde9439c --- lams_central/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 1b530a88ae6d3a1e553081b3d56117ec5ad3622a) +++ lams_central/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 6cbd849584c40532c6be292f9f009c88cde9439c) @@ -899,4 +899,71 @@ msg.import.file.format =The import file must be .xml file exported from assessment tool and not exceed size of {0} error.import.file.format =The import file is not an .xml file. +error.form.validation.error =You have 1 error in a form. It has been highlighted +error.form.validation.errors =You have {0} errors in a form. They have been highlighted +label.authoring.choice.add.multiple.choice =Add multiple choice +label.authoring.choice.field.required =This field is required. +label.authoring.choice.enter.integer =Please enter an integer. +label.authoring.choice.enter.float =Please enter a floating point number. +label.authoring.choice.one.multiple.answers =One or multiple answers? +label.authoring.choice.one.answer =One answer only +label.authoring.choice.multiple.answers =Multiple answers allowed +label.authoring.choice.overall.feedback =Overall feedback +label.authoring.choice.feedback.on.correct =Feedback shown on any correct response +label.authoring.choice.feedback.on.partially.correct =Feedback shown on any partially correct response +label.authoring.choice.feedback.on.incorrect =Feedback shown on any incorrect response +label.authoring.choice.add.option =Add another answer +label.authoring.matching.pairs.matching.pairs =Matching pairs +label.authoring.matching.pairs.error.one.matching.pair =You should provide at least 1 matching pair. +label.authoring.matching.pairs.add.matching.pair =Add another +label.authoring.short.answer.no.case.unimportant =No, case is unimportant +label.authoring.short.answer.yes.case.must.match =Yes, case must match +label.authoring.short.answer.case.sensitivity =Case sensitivity +label.authoring.short.answer.add.answer =Add another answer +label.authoring.true.false.question =True/False question +label.authoring.true.false.correct.answer =Correct answer +label.authoring.true.false.feedback.on.true =Feedback for the response 'True'. +label.authoring.true.false.feedback.on.false =Feedback for the response 'False'. +label.authoring.numerical.question =Numerical question +label.authoring.numerical.units =Units +label.authoring.numerical.error.answer =You should provide at least one possible answer. +label.authoring.numerical.add.answer =Add another answer +label.authoring.numerical.add.unit =Add another unit +label.settings =Settings +label.enter.question.title =Question's title +label.enter.question.description =Question's description +label.authoring.answer.required =Answer required? +label.authoring.basic.default.question.grade =Default question grade +label.required.field =Required field +label.authoring.basic.allow.learners.rich.editor =Allow learners to use rich text editor +label.maximum.number.words =Maximum number of words +label.minimum.number.words =Minimum number of words +label.authoring.basic.general.feedback =General feedback +label.authoring.basic.shuffle.the.choices =Shuffle answers? +label.ask.for.hedging.justification =Ask for hedging justification? +label.authoring.basic.penalty.factor =Penalty factor +error.form.validation.hundred.score =One of the answers should have a grade of 100% so it is possible to get full marks for this question. +error.form.validation.positive.accepted.errors =All the accepted errors should be positive. +label.prefix.sequential.letters.for.each.answer =Prefix sequential letters for each answer +label.authoring.basic.option.question =Question +label.authoring.basic.option.grade =Grade +label.authoring.basic.option.feedback =Feedback +label.authoring.basic.option.accepted.error =Accepted error +label.authoring.basic.unit.unit =Unit +label.authoring.basic.unit.multiplier =Multiplier +label.authoring.true.false.false =False +label.authoring.true.false.true =True +label.authoring.basic.type.multiple.choice =Multiple choice +label.authoring.basic.type.matching.pairs =Matching pairs +label.authoring.basic.type.short.answer =Short answer +label.authoring.basic.type.numerical =Numerical +label.authoring.basic.type.true.false =True/False +label.authoring.basic.type.essay =Essay +label.authoring.basic.type.ordering =Ordering +label.authoring.basic.type.mark.hedging =Mark hedging +label.incorrect.answer.nullifies.mark =Assigns full mark only when the correct answers are selected. +label.authoring.basic.option.answer =Answer +label.authoring.basic.delete =Delete +label.authoring.basic.none =None + #======= End labels: Exported 872 labels for en AU ===== Index: lams_central/src/java/org/lamsfoundation/lams/web/qb/EditQbQuestionController.java =================================================================== diff -u -rda30e197f54c6bc1c0d6d9d5dbcdde3c4632d329 -r6cbd849584c40532c6be292f9f009c88cde9439c --- lams_central/src/java/org/lamsfoundation/lams/web/qb/EditQbQuestionController.java (.../EditQbQuestionController.java) (revision da30e197f54c6bc1c0d6d9d5dbcdde3c4632d329) +++ lams_central/src/java/org/lamsfoundation/lams/web/qb/EditQbQuestionController.java (.../EditQbQuestionController.java) (revision 6cbd849584c40532c6be292f9f009c88cde9439c) @@ -1,18 +1,23 @@ package org.lamsfoundation.lams.web.qb; import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.List; -import java.util.Optional; +import java.util.Map; +import java.util.Set; import java.util.TreeSet; import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.log4j.Logger; import org.lamsfoundation.lams.qb.QbConstants; @@ -22,22 +27,24 @@ import org.lamsfoundation.lams.qb.model.QbQuestion; import org.lamsfoundation.lams.qb.model.QbQuestionUnit; import org.lamsfoundation.lams.qb.service.IQbService; -import org.lamsfoundation.lams.qb.service.QbUtils; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; -import org.lamsfoundation.lams.util.CommonConstants; import org.lamsfoundation.lams.util.Configuration; import org.lamsfoundation.lams.util.ConfigurationKeys; import org.lamsfoundation.lams.util.FileUtil; import org.lamsfoundation.lams.util.MessageService; import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.session.SessionManager; import org.lamsfoundation.lams.web.util.AttributeNames; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.context.WebApplicationContext; @Controller @@ -58,22 +65,18 @@ /** * Display empty page for new assessment question. */ - @RequestMapping("/newQuestionInit") - public String newQuestionInit(HttpServletRequest request, HttpServletResponse response, - @RequestParam Long collectionUid) throws ServletException, IOException { - - //TODO think about where do we need to get ContentFolderID, and whether it's a good idea to generate a new one each time - String contentFolderID = FileUtil.generateUniqueContentFolderID(); + @RequestMapping("/initNewQuestion") + public String initNewQuestion(@ModelAttribute("assessmentQuestionForm") QbQuestionForm form, + HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - QbQuestionForm questionForm = new QbQuestionForm(); - //we need to set form as a request attribute, as long as we use jsps from another context from the Assessment tool - request.setAttribute("assessmentQuestionForm", questionForm); - questionForm.setDisplayOrder(-1);//which signifies it's a new question - questionForm.setContentFolderID(contentFolderID); - questionForm.setMaxMark("1"); - questionForm.setPenaltyFactor("0"); - questionForm.setAnswerRequired(true); - questionForm.setCollectionUid(collectionUid); + form.setUid(-1L);//which signifies it's a new question + form.setMaxMark(1); + form.setPenaltyFactor("0"); + form.setAnswerRequired(true); + + // generate a new contentFolderID for new question + String contentFolderId = FileUtil.generateUniqueContentFolderID(); + form.setContentFolderID(contentFolderId); List optionList = new ArrayList<>(); for (int i = 0; i < QbConstants.INITIAL_OPTIONS_NUMBER; i++) { @@ -95,44 +98,102 @@ unitList.add(unit); } request.setAttribute(QbConstants.ATTR_UNIT_LIST, unitList); + + //prepare data for displaying collections + Integer userId = getUserId(); + Collection userCollections = qbService.getUserCollections(userId); + request.setAttribute("userCollections", userCollections); + //in case request came from assessment tool - set private collection as default, otherwise collectioUid is already supplied as parameter from collections.jsp + final boolean isRequestCameFromAssessmentTool = StringUtils.isNotBlank(form.getSessionMapID()); + if (isRequestCameFromAssessmentTool) { + for (QbCollection collection : userCollections) { + if (collection.isPersonal()) { + form.setCollectionUid(collection.getUid()); + break; + } + } + } + Integer type = NumberUtils.toInt(request.getParameter(QbConstants.ATTR_QUESTION_TYPE)); -// sessionMap.put(QbConstants.ATTR_QUESTION_TYPE, type); - request.setAttribute(AttributeNames.PARAM_CONTENT_FOLDER_ID, contentFolderID); - - String jspPageName = getAuthoringJspByQuestionType(type); - forwardToAssessmentJsp(jspPageName, request, response); - return null; + return findForwardByQuestionType(type); } /** - * Display edit page for existed assessment question. + * Display edit page for existing question. */ @RequestMapping("/editQuestion") - public String editQuestion(HttpServletRequest request, HttpServletResponse response, - @RequestParam(defaultValue = "-1") Long collectionUid) throws ServletException, IOException { + public String editQuestion(@ModelAttribute("assessmentQuestionForm") QbQuestionForm form, + HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Long qbQuestionUid = WebUtil.readLongParam(request, "qbQuestionUid"); QbQuestion qbQuestion = qbService.getQbQuestionByUid(qbQuestionUid); if (qbQuestion == null) { throw new RuntimeException("QbQuestion with uid:" + qbQuestionUid + " was not found!"); } - QbQuestionForm questionForm = new QbQuestionForm(); - questionForm.setCollectionUid(collectionUid); - //we need to set form as a request attribute, as long as we use jsps from another context from the Assessment tool - request.setAttribute("assessmentQuestionForm", questionForm); - QbUtils.fillFormWithQbQuestion(qbQuestion, questionForm, request); - //store uid as displayOrder in order to use it later during question saving - questionForm.setDisplayOrder(qbQuestionUid.intValue()); + //populate question information to its form for editing + form.setUid(qbQuestion.getUid()); + //TODO remove hardcoded value, once we transfer contentFolderId from old DB entries + form.setContentFolderID(qbQuestion.getContentFolderId() == null ? "temp" : qbQuestion.getContentFolderId()); + form.setTitle(qbQuestion.getName()); + form.setQuestion(qbQuestion.getDescription()); + form.setMaxMark(qbQuestion.getMaxMark()); + form.setPenaltyFactor(String.valueOf(qbQuestion.getPenaltyFactor())); + form.setAnswerRequired(qbQuestion.isAnswerRequired()); + form.setFeedback(qbQuestion.getFeedback()); + form.setMultipleAnswersAllowed(qbQuestion.isMultipleAnswersAllowed()); + form.setIncorrectAnswerNullifiesMark(qbQuestion.isIncorrectAnswerNullifiesMark()); + form.setFeedbackOnCorrect(qbQuestion.getFeedbackOnCorrect()); + form.setFeedbackOnPartiallyCorrect(qbQuestion.getFeedbackOnPartiallyCorrect()); + form.setFeedbackOnIncorrect(qbQuestion.getFeedbackOnIncorrect()); + form.setShuffle(qbQuestion.isShuffle()); + form.setPrefixAnswersWithLetters(qbQuestion.isPrefixAnswersWithLetters()); + form.setCaseSensitive(qbQuestion.isCaseSensitive()); + form.setCorrectAnswer(qbQuestion.getCorrectAnswer()); + form.setAllowRichEditor(qbQuestion.isAllowRichEditor()); + form.setMaxWordsLimit(qbQuestion.getMaxWordsLimit()); + form.setMinWordsLimit(qbQuestion.getMinWordsLimit()); + form.setHedgingJustificationEnabled(qbQuestion.isHedgingJustificationEnabled()); + + Integer questionType = qbQuestion.getType(); + if ((questionType == QbQuestion.TYPE_MULTIPLE_CHOICE) + || (questionType == QbQuestion.TYPE_ORDERING) + || (questionType == QbQuestion.TYPE_MATCHING_PAIRS) + || (questionType == QbQuestion.TYPE_SHORT_ANSWER) + || (questionType == QbQuestion.TYPE_NUMERICAL) + || (questionType == QbQuestion.TYPE_MARK_HEDGING)) { + List optionList = qbQuestion.getQbOptions(); + request.setAttribute(QbConstants.ATTR_OPTION_LIST, optionList); + } + if (questionType == QbQuestion.TYPE_NUMERICAL) { + List unitList = qbQuestion.getUnits(); + request.setAttribute(QbConstants.ATTR_UNIT_LIST, unitList); + } - //TODO think about where do we need to get ContentFolderID, and whether it's a good idea to generate a new one each time - String contentFolderID = FileUtil.generateUniqueContentFolderID(); - questionForm.setContentFolderID(contentFolderID); - request.setAttribute(AttributeNames.PARAM_CONTENT_FOLDER_ID, contentFolderID); - - String jspPageName = getAuthoringJspByQuestionType(qbQuestion.getType()); - forwardToAssessmentJsp(jspPageName, request, response); - return null; + //prepare data for displaying collections + Integer userId = getUserId(); + Collection userCollections = qbService.getUserCollections(userId); + request.setAttribute("userCollections", userCollections); + //in case request came from assessment tool - set private collection as default, otherwise collectioUid is already supplied as parameter from collections.jsp + final boolean isRequestCameFromAssessmentTool = StringUtils.isNotBlank(form.getSessionMapID()); + if (isRequestCameFromAssessmentTool) { + Collection questionCollections = qbService.getQuestionCollections(qbQuestionUid); + + Long collectionUid = null; + if (questionCollections.isEmpty()) { + for (QbCollection collection : userCollections) { + if (collection.isPersonal()) { + collectionUid = collection.getUid(); + break; + } + } + } else { + collectionUid = questionCollections.iterator().next().getUid(); + } + form.setCollectionUid(collectionUid); + } + + return findForwardByQuestionType(qbQuestion.getType()); } /** @@ -141,30 +202,31 @@ * HttpSession temporarily. Only they will be persist when the entire authoring page is being * persisted. * @throws IOException + * @throws ServletException */ @RequestMapping("/saveOrUpdateQuestion") - @ResponseBody - public String saveOrUpdateQuestion(@ModelAttribute("assessmentQuestionForm") QbQuestionForm questionForm, - HttpServletRequest request, HttpServletResponse response) throws IOException { + public String saveOrUpdateQuestion(@ModelAttribute("assessmentQuestionForm") QbQuestionForm form, + HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + //find according question QbQuestion qbQuestion = null; Long oldQuestionUid = null; + boolean isQuestionNew = form.getUid() == -1; // add - if (questionForm.getDisplayOrder() == -1) { + if (isQuestionNew) { qbQuestion = new QbQuestion(); - qbQuestion.setType(questionForm.getQuestionType()); + qbQuestion.setType(form.getQuestionType()); // edit } else { - oldQuestionUid = Long.valueOf(questionForm.getDisplayOrder()); + oldQuestionUid = Long.valueOf(form.getUid()); qbQuestion = qbService.getQbQuestionByUid(oldQuestionUid); + qbService.releaseFromCache(qbQuestion); } - boolean IS_AUTHORING_RESTRICTED = false; - int isQbQuestionModified = QbUtils.extractFormToQbQuestion(qbQuestion, questionForm, request, qbService, - IS_AUTHORING_RESTRICTED); - switch (isQbQuestionModified) { + int questionModificationStatus = extractFormToQbQuestion(qbQuestion, form, request); + switch (questionModificationStatus) { case IQbService.QUESTION_MODIFIED_VERSION_BUMP: { // new version of the old question gets created qbQuestion = qbQuestion.clone(); @@ -180,47 +242,186 @@ qbQuestion.setVersion(1); qbQuestion.setQuestionId(qbService.getMaxQuestionId()+1); qbQuestion.setCreateDate(new Date()); - } break; } - boolean belongsToNoCollection = qbQuestion.getUid() == null; userManagementService.save(qbQuestion); + //take care about question's collections + Long collectionUid = form.getCollectionUid(); + qbService.addQuestionToCollection(collectionUid, qbQuestion.getUid(), false); + //remove from the old collection, if needed + //TODO after merging Marcin's collection changes + if (!isQuestionNew) { + Collection oldQuestionCollections = qbService.getQuestionCollections(oldQuestionUid); + Collection newQuestionCollections = qbService.getQuestionCollections(qbQuestion.getUid()); + oldQuestionCollections.removeAll(newQuestionCollections); + for (QbCollection obsoleteOldQuestionCollection : oldQuestionCollections) { + qbService.removeQuestionFromCollection(obsoleteOldQuestionCollection.getUid(), qbQuestion.getUid()); + } + } + + + +// boolean belongsToNoCollection = qbQuestion.getUid() == null; //in case of new question - add it to specified collection - if (belongsToNoCollection) { +// if (belongsToNoCollection) { +// +// Long collectionUid = questionForm.getCollectionUid(); +// //try to get collection from the old question +// if (collectionUid != null && collectionUid.equals(-1L)) { +// Collection existingCollections = qbService.getQuestionCollections(oldQuestionUid); +// collectionUid = existingCollections.stream().findFirst().map(collection -> collection.getUid()) +// .orElse(null); +// } +// +// if (collectionUid != null && !collectionUid.equals(-1L)) { +// qbService.addQuestionToCollection(collectionUid, qbQuestion.getUid(), false); +// } +// } + + final boolean IS_REQUEST_CAME_FROM_ASSESSMENT_TOOL = StringUtils.isNotBlank(form.getSessionMapID()); + if (IS_REQUEST_CAME_FROM_ASSESSMENT_TOOL) { + String params = "?qbQuestionUid=" + qbQuestion.getUid(); + params += "&questionModificationStatus=" + questionModificationStatus; - Long collectionUid = questionForm.getCollectionUid(); - //try to get collection from the old question - if (collectionUid != null && collectionUid.equals(-1L)) { - Collection existingCollections = qbService.getQuestionCollections(oldQuestionUid); - collectionUid = existingCollections.stream().findFirst().map(collection -> collection.getUid()) - .orElse(null); + String serverURLContextPath = Configuration.get(ConfigurationKeys.SERVER_URL_CONTEXT_PATH); + serverURLContextPath = serverURLContextPath.startsWith("/") ? serverURLContextPath + : "/" + serverURLContextPath; + serverURLContextPath += serverURLContextPath.endsWith("/") ? "" : "/"; + applicationcontext.getServletContext().getContext(serverURLContextPath + "tool/laasse10/") + .getRequestDispatcher("/authoring/saveOrUpdateQuestion.do" + params) + .forward(request, response); + return null; + + } else { + + // in case adding new question - return nothing + if (isQuestionNew) { + return null; + + // edit question case - return question's uid + } else { + return "forward:returnQuestionUid.do?qbQuestionUid=" + qbQuestion.getUid(); +// response.setContentType("text/plain"); +// response.setCharacterEncoding("UTF-8"); +// return qbQuestion.getUid().toString(); } + } + } + + @RequestMapping("/returnQuestionUid") + @ResponseBody + public String returnQuestionUid(HttpServletResponse response, @RequestParam Long qbQuestionUid) { + response.setContentType("text/plain"); + response.setCharacterEncoding("UTF-8"); + return qbQuestionUid.toString(); + } - if (collectionUid != null && !collectionUid.equals(-1L)) { - qbService.addQuestionToCollection(collectionUid, qbQuestion.getUid(), false); + /** + * Extract web form content to QB question. + * + * BE CAREFUL: This method will copy necessary info from request form to an old or new AssessmentQuestion + * instance. It gets all info EXCEPT AssessmentQuestion.createDate, which need be set when + * persisting this assessment Question. + * + * @return qbQuestionModified + */ + private int extractFormToQbQuestion(QbQuestion qbQuestion, QbQuestionForm form, HttpServletRequest request) { + QbQuestion oldQuestion = qbQuestion.clone(); + // evict everything manually as we do not use DTOs, just real entities + // without eviction changes would be saved immediately into DB + qbService.releaseFromCache(oldQuestion); + + qbQuestion.setName(form.getTitle()); + qbQuestion.setDescription(form.getQuestion()); + + if (!form.isAuthoringRestricted()) { + qbQuestion.setMaxMark(form.getMaxMark()); + } + qbQuestion.setFeedback(form.getFeedback()); + qbQuestion.setAnswerRequired(form.isAnswerRequired()); + qbQuestion.setContentFolderId(form.getContentFolderID()); + + Integer type = form.getQuestionType(); + if (type == QbQuestion.TYPE_MULTIPLE_CHOICE) { + qbQuestion.setMultipleAnswersAllowed(form.isMultipleAnswersAllowed()); + boolean incorrectAnswerNullifiesMark = form.isMultipleAnswersAllowed() + ? form.isIncorrectAnswerNullifiesMark() + : false; + qbQuestion.setIncorrectAnswerNullifiesMark(incorrectAnswerNullifiesMark); + qbQuestion.setPenaltyFactor(Float.parseFloat(form.getPenaltyFactor())); + qbQuestion.setShuffle(form.isShuffle()); + qbQuestion.setPrefixAnswersWithLetters(form.isPrefixAnswersWithLetters()); + qbQuestion.setFeedbackOnCorrect(form.getFeedbackOnCorrect()); + qbQuestion.setFeedbackOnPartiallyCorrect(form.getFeedbackOnPartiallyCorrect()); + qbQuestion.setFeedbackOnIncorrect(form.getFeedbackOnIncorrect()); + } else if ((type == QbQuestion.TYPE_MATCHING_PAIRS)) { + qbQuestion.setPenaltyFactor(Float.parseFloat(form.getPenaltyFactor())); + qbQuestion.setShuffle(form.isShuffle()); + } else if ((type == QbQuestion.TYPE_SHORT_ANSWER)) { + qbQuestion.setPenaltyFactor(Float.parseFloat(form.getPenaltyFactor())); + qbQuestion.setCaseSensitive(form.isCaseSensitive()); + } else if ((type == QbQuestion.TYPE_NUMERICAL)) { + qbQuestion.setPenaltyFactor(Float.parseFloat(form.getPenaltyFactor())); + } else if ((type == QbQuestion.TYPE_TRUE_FALSE)) { + qbQuestion.setPenaltyFactor(Float.parseFloat(form.getPenaltyFactor())); + qbQuestion.setCorrectAnswer(form.isCorrectAnswer()); + qbQuestion.setFeedbackOnCorrect(form.getFeedbackOnCorrect()); + qbQuestion.setFeedbackOnIncorrect(form.getFeedbackOnIncorrect()); + } else if ((type == QbQuestion.TYPE_ESSAY)) { + qbQuestion.setAllowRichEditor(form.isAllowRichEditor()); + qbQuestion.setMaxWordsLimit(form.getMaxWordsLimit()); + qbQuestion.setMinWordsLimit(form.getMinWordsLimit()); + } else if (type == QbQuestion.TYPE_ORDERING) { + qbQuestion.setPenaltyFactor(Float.parseFloat(form.getPenaltyFactor())); + qbQuestion.setFeedbackOnCorrect(form.getFeedbackOnCorrect()); + qbQuestion.setFeedbackOnIncorrect(form.getFeedbackOnIncorrect()); + } else if (type == QbQuestion.TYPE_MARK_HEDGING) { + qbQuestion.setShuffle(form.isShuffle()); + qbQuestion.setFeedbackOnCorrect(form.getFeedbackOnCorrect()); + qbQuestion.setFeedbackOnPartiallyCorrect(form.getFeedbackOnPartiallyCorrect()); + qbQuestion.setFeedbackOnIncorrect(form.getFeedbackOnIncorrect()); + qbQuestion.setHedgingJustificationEnabled(form.isHedgingJustificationEnabled()); + } + + // set options + if ((type == QbQuestion.TYPE_MULTIPLE_CHOICE) + || (type == QbQuestion.TYPE_ORDERING) + || (type == QbQuestion.TYPE_MATCHING_PAIRS) + || (type == QbQuestion.TYPE_SHORT_ANSWER) + || (type == QbQuestion.TYPE_NUMERICAL) + || (type == QbQuestion.TYPE_MARK_HEDGING)) { + Set optionList = getOptionsFromRequest(request, true); + List options = new ArrayList<>(); + int displayOrder = 0; + for (QbOption option : optionList) { + option.setDisplayOrder(displayOrder++); + options.add(option); } + qbQuestion.setQbOptions(options); } - - // add question case - return nothing - if (questionForm.getDisplayOrder() == -1) { - return null; - - // edit question case - return question's uid - } else { - response.setContentType("text/plain"); - response.setCharacterEncoding("UTF-8"); - return qbQuestion.getUid().toString(); + // set units + if (type == QbQuestion.TYPE_NUMERICAL) { + Set unitList = getUnitsFromRequest(request, true); + List units = new ArrayList<>(); + int displayOrder = 0; + for (QbQuestionUnit unit : unitList) { + unit.setDisplayOrder(displayOrder++); + units.add(unit); + } + qbQuestion.setUnits(units); } + + return qbQuestion.isQbQuestionModified(oldQuestion); } /** * Ajax call, will add one more input line for new resource item instruction. */ @RequestMapping("/addOption") public String addOption(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - TreeSet optionList = QbUtils.getOptionsFromRequest(qbService, request, false); + TreeSet optionList = getOptionsFromRequest(request, false); QbOption option = new QbOption(); int maxSeq = 1; if ((optionList != null) && (optionList.size() > 0)) { @@ -235,16 +436,15 @@ request.setAttribute(QbConstants.ATTR_QUESTION_TYPE, WebUtil.readIntParam(request, QbConstants.ATTR_QUESTION_TYPE)); request.setAttribute(QbConstants.ATTR_OPTION_LIST, optionList); - forwardToAssessmentJsp("optionlist.jsp", request, response); - return null; + return "qb/authoring/optionlist"; } /** * Ajax call, will add one more input line for new Unit. */ @RequestMapping("/newUnit") public String newUnit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - TreeSet unitList = QbUtils.getUnitsFromRequest(qbService, request, false); + TreeSet unitList = getUnitsFromRequest(request, false); QbQuestionUnit unit = new QbQuestionUnit(); int maxSeq = 1; if ((unitList != null) && (unitList.size() > 0)) { @@ -255,56 +455,224 @@ unitList.add(unit); request.setAttribute(QbConstants.ATTR_UNIT_LIST, unitList); - forwardToAssessmentJsp("unitlist.jsp", request, response); - return null; + return "qb/authoring/unitlist"; } + /** + * Get answer options from HttpRequest + * + * @param request + * @param isForSaving + * whether the blank options will be preserved or not + */ + private TreeSet getOptionsFromRequest(HttpServletRequest request, boolean isForSaving) { + Map paramMap = splitRequestParameter(request, QbConstants.ATTR_OPTION_LIST); + + int count = NumberUtils.toInt(paramMap.get(QbConstants.ATTR_OPTION_COUNT)); + int questionType = WebUtil.readIntParam(request, QbConstants.ATTR_QUESTION_TYPE); + Integer correctOptionIndex = (paramMap.get(QbConstants.ATTR_OPTION_CORRECT) == null) ? null + : NumberUtils.toInt(paramMap.get(QbConstants.ATTR_OPTION_CORRECT)); + + TreeSet optionList = new TreeSet<>(); + for (int i = 0; i < count; i++) { + + String displayOrder = paramMap.get(QbConstants.ATTR_OPTION_DISPLAY_ORDER_PREFIX + i); + //displayOrder is null, in case this item was removed using Remove button + if (displayOrder == null) { + continue; + } + + QbOption option = null; + String uidStr = paramMap.get(QbConstants.ATTR_OPTION_UID_PREFIX + i); + if (uidStr != null) { + Long uid = NumberUtils.toLong(uidStr); + option = qbService.getQbOptionByUid(uid); + + } else { + option = new QbOption(); + } + option.setDisplayOrder(NumberUtils.toInt(displayOrder)); + + if ((questionType == QbQuestion.TYPE_MULTIPLE_CHOICE) + || (questionType == QbQuestion.TYPE_SHORT_ANSWER)) { + String name = paramMap.get(QbConstants.ATTR_OPTION_NAME_PREFIX + i); + if ((name == null) && isForSaving) { + continue; + } + + option.setName(name); + float maxMark = Float.valueOf(paramMap.get(QbConstants.ATTR_OPTION_MAX_MARK_PREFIX + i)); + option.setMaxMark(maxMark); + option.setFeedback(paramMap.get(QbConstants.ATTR_OPTION_FEEDBACK_PREFIX + i)); + + } else if (questionType == QbQuestion.TYPE_MATCHING_PAIRS) { + String matchingPair = paramMap.get(QbConstants.ATTR_MATCHING_PAIR_PREFIX + i); + if ((matchingPair == null) && isForSaving) { + continue; + } + + option.setName(paramMap.get(QbConstants.ATTR_OPTION_NAME_PREFIX + i)); + option.setMatchingPair(matchingPair); + + } else if (questionType == QbQuestion.TYPE_NUMERICAL) { + String numericalOptionStr = paramMap.get(QbConstants.ATTR_NUMERICAL_OPTION_PREFIX + i); + String acceptedErrorStr = paramMap.get(QbConstants.ATTR_OPTION_ACCEPTED_ERROR_PREFIX + i); + String maxMarkStr = paramMap.get(QbConstants.ATTR_OPTION_MAX_MARK_PREFIX + i); + if (numericalOptionStr.equals("0.0") && numericalOptionStr.equals("0.0") && maxMarkStr.equals("0.0") + && isForSaving) { + continue; + } + + try { + float numericalOption = Float.valueOf(numericalOptionStr); + option.setNumericalOption(numericalOption); + } catch (Exception e) { + option.setNumericalOption(0); + } + try { + float acceptedError = Float.valueOf(acceptedErrorStr); + option.setAcceptedError(acceptedError); + } catch (Exception e) { + option.setAcceptedError(0); + } + float maxMark = Float.valueOf(paramMap.get(QbConstants.ATTR_OPTION_MAX_MARK_PREFIX + i)); + option.setMaxMark(maxMark); + option.setFeedback(paramMap.get(QbConstants.ATTR_OPTION_FEEDBACK_PREFIX + i)); + + } else if (questionType == QbQuestion.TYPE_ORDERING) { + String name = paramMap.get(QbConstants.ATTR_OPTION_NAME_PREFIX + i); + if ((name == null) && isForSaving) { + continue; + } + + option.setName(name); + + } else if (questionType == QbQuestion.TYPE_MARK_HEDGING) { + String name = paramMap.get(QbConstants.ATTR_OPTION_NAME_PREFIX + i); + if ((name == null) && isForSaving) { + continue; + } + + option.setName(name); + if ((correctOptionIndex != null) && correctOptionIndex.equals(Integer.valueOf(displayOrder))) { + option.setCorrect(true); + } + option.setFeedback(paramMap.get(QbConstants.ATTR_OPTION_FEEDBACK_PREFIX + i)); + } + + optionList.add(option); + } + return optionList; + } + + /** + * Get units from HttpRequest + * + * @param request + */ + private TreeSet getUnitsFromRequest(HttpServletRequest request, boolean isForSaving) { + Map paramMap = splitRequestParameter(request, QbConstants.ATTR_UNIT_LIST); + + int count = NumberUtils.toInt(paramMap.get(QbConstants.ATTR_UNIT_COUNT)); + TreeSet unitList = new TreeSet<>(); + for (int i = 0; i < count; i++) { + String name = paramMap.get(QbConstants.ATTR_UNIT_NAME_PREFIX + i); + if (StringUtils.isBlank(name) && isForSaving) { + continue; + } + + QbQuestionUnit unit = null; + String uidStr = paramMap.get(QbConstants.ATTR_UNIT_UID_PREFIX + i); + if (uidStr != null) { + Long uid = NumberUtils.toLong(uidStr); + unit = qbService.getQbQuestionUnitByUid(uid); + + } else { + unit = new QbQuestionUnit(); + } + String displayOrder = paramMap.get(QbConstants.ATTR_UNIT_DISPLAY_ORDER_PREFIX + i); + unit.setDisplayOrder(NumberUtils.toInt(displayOrder)); + unit.setName(name); + float multiplier = Float.valueOf(paramMap.get(QbConstants.ATTR_UNIT_MULTIPLIER_PREFIX + i)); + unit.setMultiplier(multiplier); + unitList.add(unit); + } + + return unitList; + } + + /** + * Split Request Parameter from HttpRequest + * + * @param request + * @param parameterName + * parameterName + */ + private static Map splitRequestParameter(HttpServletRequest request, String parameterName) { + String list = request.getParameter(parameterName); + if (list == null) { + return null; + } + + String[] params = list.split("&"); + Map paramMap = new HashMap<>(); + String[] pair; + for (String item : params) { + pair = item.split("="); + if ((pair == null) || (pair.length != 2)) { + continue; + } + try { + paramMap.put(pair[0], URLDecoder.decode(pair[1], "UTF-8")); + } catch (UnsupportedEncodingException e) { + log.error("Error occurs when decode instruction string:" + e.toString()); + } + } + return paramMap; + } + + /** * Get back jsp name. */ - private static String getAuthoringJspByQuestionType(Integer type) { - String jspName; + private String findForwardByQuestionType(Integer type) { + String forward; switch (type) { case QbQuestion.TYPE_MULTIPLE_CHOICE: - jspName = "addmultiplechoice.jsp"; + forward = "qb/authoring/addmultiplechoice"; break; case QbQuestion.TYPE_MATCHING_PAIRS: - jspName = "addmatchingpairs.jsp"; + forward = "qb/authoring/addmatchingpairs"; break; case QbQuestion.TYPE_SHORT_ANSWER: - jspName = "addshortanswer.jsp"; + forward = "qb/authoring/addshortanswer"; break; case QbQuestion.TYPE_NUMERICAL: - jspName = "addnumerical.jsp"; + forward = "qb/authoring/addnumerical"; break; case QbQuestion.TYPE_TRUE_FALSE: - jspName = "addtruefalse.jsp"; + forward = "qb/authoring/addtruefalse"; break; case QbQuestion.TYPE_ESSAY: - jspName = "addessay.jsp"; + forward = "qb/authoring/addessay"; break; case QbQuestion.TYPE_ORDERING: - jspName = "addordering.jsp"; + forward = "qb/authoring/addordering"; break; case QbQuestion.TYPE_MARK_HEDGING: - jspName = "addmarkhedging.jsp"; + forward = "qb/authoring/addmarkhedging"; break; default: - jspName = null; + forward = null; break; } - return jspName; + return forward; } - - /** - * Forwards to the specified jsp page from Assessment tool. - */ - private void forwardToAssessmentJsp(String jspPageName, HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - String serverURLContextPath = Configuration.get(ConfigurationKeys.SERVER_URL_CONTEXT_PATH); - serverURLContextPath = serverURLContextPath.startsWith("/") ? serverURLContextPath : "/" + serverURLContextPath; - serverURLContextPath += serverURLContextPath.endsWith("/") ? "" : "/"; - applicationcontext.getServletContext().getContext(serverURLContextPath + "tool/" + CommonConstants.TOOL_SIGNATURE_ASSESSMENT) - .getRequestDispatcher("/pages/authoring/parts/" + jspPageName +"?lessonID=1").forward(request, response); + + private Integer getUserId() { + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + return user != null ? user.getUserID() : null; } + } Index: lams_central/src/java/org/lamsfoundation/lams/web/qb/SearchQBController.java =================================================================== diff -u -rf9c66e78afa51f175afcaf22ee81f9b3460afea8 -r6cbd849584c40532c6be292f9f009c88cde9439c --- lams_central/src/java/org/lamsfoundation/lams/web/qb/SearchQBController.java (.../SearchQBController.java) (revision f9c66e78afa51f175afcaf22ee81f9b3460afea8) +++ lams_central/src/java/org/lamsfoundation/lams/web/qb/SearchQBController.java (.../SearchQBController.java) (revision 6cbd849584c40532c6be292f9f009c88cde9439c) @@ -121,7 +121,12 @@ request.setAttribute("questionType", questionTypeDefault); request.setAttribute("questionTypesAvailable", questionTypesAvailable.toString()); - return "qb/search"; + boolean isFullJspRequested = WebUtil.readBooleanParam(request, "isFullJspRequested", true); + if (isFullJspRequested) { + return "qb/search"; + } else { + return "qb/searchWidget"; + } } /** Index: lams_central/web/WEB-INF/tags/CKEditor.tag =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r6cbd849584c40532c6be292f9f009c88cde9439c --- lams_central/web/WEB-INF/tags/CKEditor.tag (.../CKEditor.tag) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_central/web/WEB-INF/tags/CKEditor.tag (.../CKEditor.tag) (revision 6cbd849584c40532c6be292f9f009c88cde9439c) @@ -3,8 +3,10 @@ <%@ taglib uri="tags-function" prefix="fn" %> <%@ attribute name="id" required="true" rtexprvalue="true"%> +<%@ attribute name="placeholder" required="false" rtexprvalue="true"%> <%@ attribute name="value" required="true" rtexprvalue="true"%> <%@ attribute name="toolbarSet" required="false" rtexprvalue="true"%> +<%@ attribute name="classes" required="false" rtexprvalue="true"%> <%@ attribute name="height" required="false" rtexprvalue="true"%> <%@ attribute name="width" required="false" rtexprvalue="true"%> <%@ attribute name="contentFolderID" required="false" rtexprvalue="true"%> @@ -39,8 +41,18 @@ - + + +
+ + + + + +
+
+ /lams/ckeditor/ @@ -69,6 +81,7 @@ width : "${width}", height : "${height}", toolbar : "${toolbarSet}", + classes : "${classes}", language : "${language}", defaultLangugage : "en", toolbarStartupExpanded : ${displayExpanded}, Index: lams_central/web/common/taglibs.jsp =================================================================== diff -u --- lams_central/web/common/taglibs.jsp (revision 0) +++ lams_central/web/common/taglibs.jsp (revision 6cbd849584c40532c6be292f9f009c88cde9439c) @@ -0,0 +1,6 @@ +<%@ page language="java" errorPage="/error.jsp" pageEncoding="UTF-8" contentType="text/html;charset=utf-8" %> +<%@ taglib uri="tags-function" prefix="fn" %> +<%@ taglib uri="tags-core" prefix="c" %> +<%@ taglib uri="tags-fmt" prefix="fmt" %> +<%@ taglib uri="tags-lams" prefix="lams" %> +<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> Index: lams_central/web/css/bootstrap-toggle.css =================================================================== diff -u --- lams_central/web/css/bootstrap-toggle.css (revision 0) +++ lams_central/web/css/bootstrap-toggle.css (revision 6cbd849584c40532c6be292f9f009c88cde9439c) @@ -0,0 +1,7 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * ======================================================================== */.checkbox label .toggle,.checkbox-inline .toggle{margin-left:-20px;margin-right:5px}.toggle{position:relative;overflow:hidden}.toggle input[type=checkbox]{display:none}.toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none}.toggle.off .toggle-group{left:-100%}.toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0}.toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0}.toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px}.toggle.btn{min-width:59px;min-height:34px}.toggle-on.btn{padding-right:24px}.toggle-off.btn{padding-left:24px}.toggle.btn-lg{min-width:79px;min-height:45px}.toggle-on.btn-lg{padding-right:31px}.toggle-off.btn-lg{padding-left:31px}.toggle-handle.btn-lg{width:40px}.toggle.btn-sm{min-width:50px;min-height:30px}.toggle-on.btn-sm{padding-right:20px}.toggle-off.btn-sm{padding-left:20px}.toggle.btn-xs{min-width:35px;min-height:22px}.toggle-on.btn-xs{padding-right:12px}.toggle-off.btn-xs{padding-left:12px} \ No newline at end of file Index: lams_central/web/css/bootstrap-toggle.scss =================================================================== diff -u --- lams_central/web/css/bootstrap-toggle.scss (revision 0) +++ lams_central/web/css/bootstrap-toggle.scss (revision 6cbd849584c40532c6be292f9f009c88cde9439c) @@ -0,0 +1,28 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * ======================================================================== */ +.checkbox label .toggle,.checkbox-inline .toggle{margin-left:-20px;margin-right:5px} +.toggle{position:relative;overflow:hidden} +.toggle input[type=checkbox]{display:none} +.toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none} +.toggle.off .toggle-group{left:-100%} +.toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0} +.toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0} +.toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px} +.toggle.btn{min-width:59px;min-height:34px} +.toggle-on.btn{padding-right:24px} +.toggle-off.btn{padding-left:24px} +.toggle.btn-lg{min-width:79px;min-height:45px} +.toggle-on.btn-lg{padding-right:31px} +.toggle-off.btn-lg{padding-left:31px} +.toggle-handle.btn-lg{width:40px} +.toggle.btn-sm{min-width:50px;min-height:30px} +.toggle-on.btn-sm{padding-right:20px} +.toggle-off.btn-sm{padding-left:20px} +.toggle.btn-xs{min-width:35px;min-height:22px} +.toggle-on.btn-xs{padding-right:12px} +.toggle-off.btn-xs{padding-left:12px} \ No newline at end of file Index: lams_central/web/css/qb-question.css =================================================================== diff -u --- lams_central/web/css/qb-question.css (revision 0) +++ lams_central/web/css/qb-question.css (revision 6cbd849584c40532c6be292f9f009c88cde9439c) @@ -0,0 +1 @@ +div.error{display:none}#content{width:91%;margin-bottom:10px}table.alternative-color td{padding-left:0}.short-input-text{max-width:100px}#max-words-limit-checkbox,#min-words-limit-checkbox{vertical-align:sub}.fake-validation-input{visibility:hidden;height:0;width:0;float:right !important}.greyed-out-label{opacity:.55}#hasOptionFilled-error{display:table}i.fa-asterisk.text-danger{vertical-align:super}.checkbox+.checkbox,.radio+.radio{margin-top:0}.checkbox,.radio{margin-bottom:15px}.panel{margin-bottom:0 !important}.panel-default>.panel-heading{border-color:transparent}#unitArea .table>tbody>tr>td{border-top:0}label.alert{display:initial}.input-xs{height:22px;padding:2px 5px;font-size:12px;line-height:1.5;border-radius:3px}.ui-spinner .ui-icon{left:50%}:focus{outline:0;outline:0}label{font-weight:initial}.voffset5-bottom{margin-bottom:5px}.voffset10-bottom{margin-bottom:10px}#title{height:34px;font-weight:bold}#title-container,div.error{padding-right:80px}:not(.has-error)>input.borderless-text-input{border:0;box-shadow:none;padding:0}input.borderless-text-input::-webkit-input-placeholder{font-weight:normal;color:#999}input.borderless-text-input::-moz-placeholder{font-weight:normal;color:#999}input.borderless-text-input:-ms-input-placeholder{font-weight:normal;color:#999}input.borderless-text-input:-o-input-placeholder{font-weight:normal;color:#999}.settings-tab{display:none}.delete-button{opacity:.6;cursor:pointer}#question-settings-link{margin-top:-10px;margin-bottom:5px;position:absolute;top:60px;right:20px;z-index:2;visibility:hidden}#assessmentQuestionForm:hover #question-settings-link,#question-settings-link.btn-primary{visibility:visible}div[contenteditable]{display:block;width:100%;padding:0;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:0;border-radius:0;-webkit-box-shadow:inset 0 0 0 rgba(0,0,0,0.075);box-shadow:0;-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;overflow:hidden;min-height:60px;-webkit-appearance:textfield}html{position:relative;min-height:100%}body{margin-bottom:44px}footer{position:absolute;bottom:0;width:100%;height:44px;background-color:#f9f9f9}footer>div{height:44px}#option-table table{width:100%}#option-table td:first-child{width:60px;vertical-align:top}#option-table td:first-child span{display:inline-block;width:4rem;height:4rem;text-align:center;line-height:2em;font-size:1.8rem;color:#fff;border:.2rem solid #313537;-webkit-border-radius:50%;border-radius:50%;border-color:#337ab7 !important;background-color:#337ab7 !important}.sortable-placeholder .option-ckeditor{pointer-events:none}.sortable-chosen{background-color:#fff}.sortable-placeholder{background-color:rgba(241,241,241,0.7)}.ui-widget-content.ui-slider{border:.02em solid #bdc3c7;background:#e1e1e1;color:#222;margin-top:-0.02em;padding:.08em;opacity:.8}.ui-state-default,.ui-widget-content.ui-slider .ui-state-default{background:transparent !important;border:none !important}.ui-slider .ui-slider-handle label{background:#c3c3c3;border-radius:20px;width:5.2em}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:5.2em;height:100px;cursor:default;margin:0 -40px auto !important;text-align:center;line-height:28px;color:#fff}.ui-slider .ui-slider-handle .fa{color:#fff;margin:0 3px;font-size:18px;opacity:.5;font-weight:bolder}.ui-slider-horizontal .ui-slider-handle{top:-.9em}.ui-state-default,.ui-widget-content.ui-slider .ui-state-default{border:1px solid #f9f9f9;background:#3498db}.ui-slider-horizontal .ui-slider-handle{margin-left:-0.5em}.ui-slider .ui-slider-handle{cursor:pointer}.ui-slider a,.ui-slider a:focus{cursor:pointer;outline:0}.grade-slider{height:30px;padding-top:15px}.grade-slider>div:first-child{margin-top:-7px;padding:0}.cke-div{position:relative}.cke-label-floating{will-change:left,top,contents;margin:0;line-height:1.4;font-weight:400;position:absolute;pointer-events:none;transition:all .3s ease;color:#999 !important;top:0}div[contenteditable]:focus+.cke-label-floating,div[contenteditable].cke_filled+.cke-label-floating{top:-1rem;left:0;font-size:.9rem}.switch{position:relative;display:inline-block;width:40px;height:23px}.switch input{opacity:0;width:0;height:0}.switch-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#ccc;-webkit-transition:.4s;transition:.4s}.switch-slider:before{position:absolute;content:"";height:17px;width:17px;left:3px;bottom:.2em;background-color:white;-webkit-transition:.4s;transition:.4s}input:checked ~ .switch-slider{background-color:#2196f3}input:focus ~ .switch-slider{box-shadow:0 0 1px #2196f3}input:checked ~ .switch-slider:before{-webkit-transform:translateX(17px);-ms-transform:translateX(17px);transform:translateX(17px)}.switch-slider.round{border-radius:23px}.switch-slider.round:before{border-radius:50%}.toggle.btn-xs{min-width:59px}#title-container .form-control-feedback{right:80px}.col-sm-9.has-feedback .form-control-feedback{left:80px} \ No newline at end of file Index: lams_central/web/css/qb-question.scss =================================================================== diff -u --- lams_central/web/css/qb-question.scss (revision 0) +++ lams_central/web/css/qb-question.scss (revision 6cbd849584c40532c6be292f9f009c88cde9439c) @@ -0,0 +1,394 @@ +@import "_lams_variables.scss"; + +div.error { + display:none; +} + +#content { + width: 91%; + margin-bottom: 10px; +} +table.alternative-color td { + padding-left: 0; +} +.short-input-text { + max-width: 100px; +} + +#max-words-limit-checkbox, #min-words-limit-checkbox { + vertical-align: sub; +} + +.fake-validation-input { + visibility: hidden; + height: 0; + width: 0; + float: right !important; +} + +.greyed-out-label { + opacity: 0.55; +} +#hasOptionFilled-error { + display: table; +} +i.fa-asterisk.text-danger { + vertical-align: super; +} + +/* overwriting bootstrap values */ +.checkbox + .checkbox, .radio + .radio { + margin-top: 0; +} +.checkbox, .radio { + margin-bottom: 15px; +} +.panel { + margin-bottom: 0 !important; +} +.panel-default>.panel-heading { + border-color: transparent; +} +#unitArea .table>tbody>tr>td { + border-top: none; +} +label.alert { + display: initial; +} +.input-xs { + height: 22px; + padding: 2px 5px; + font-size: 12px; + line-height: 1.5; /* If Placeholder of the input is moved up, rem/modify this. */ + border-radius: 3px; +} + +/* overwriting jqeury-ui values */ +.ui-spinner .ui-icon { + left: 50%; +} + +/* Remove focus effect from title */ +:focus { + outline: 0; + /* or */ + outline: none; +} + +label { + font-weight: initial; +} + +.voffset5-bottom { + margin-bottom: 5px; +} +.voffset10-bottom { + margin-bottom: 10px; +} + +#title { + height: 34px; + font-weight: bold; +} +#title-container, div.error { + padding-right: 80px; +} + +/*Style for text input fields. Rule does not apply in case this input has an error*/ +:not(.has-error) > input.borderless-text-input{ + border: 0; + box-shadow: none; + padding: 0; +} + +/* Remove bold style from title field */ +input.borderless-text-input::-webkit-input-placeholder { + font-weight: normal; + color:#999; +} +input.borderless-text-input::-moz-placeholder { + font-weight: normal; + color:#999; +} +input.borderless-text-input:-ms-input-placeholder { + font-weight: normal; + color:#999; +} +input.borderless-text-input:-o-input-placeholder { + font-weight: normal; + color:#999; +} + +.settings-tab { + display:none; +} +.delete-button { + opacity: 0.6; + cursor: pointer; +} + +/* hover effect for Settings button */ +#question-settings-link{ + margin-top: -10px; + margin-bottom: 5px; + position: absolute; + top: 60px; + right: 20px; + z-index: 2; + visibility:hidden; +} +#assessmentQuestionForm:hover #question-settings-link, #question-settings-link.btn-primary { + visibility:visible; +} + +/* Overwrite default CKEditor style */ +div[contenteditable] { + display: block; + width: 100%; + padding: 0; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 0; + border-radius: 0; + -webkit-box-shadow: inset 0 0 0 rgba(0,0,0,0.075); + box-shadow: 0; + -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + overflow: hidden; + min-height: 60px; + -webkit-appearance: textfield; +} + +/*----------STICKY FOOTER----------------*/ +html { + position: relative; + min-height: 100%; +} +body { + margin-bottom: 44px; +} +footer { + position: absolute; + bottom: 0; + width: 100%; + height: 44px; + background-color: #f9f9f9; +} +footer > div { + height: 44px; +} + +/*----------numbers before answers----------------*/ +#option-table table { + width: 100%; +} +#option-table td:first-child { + width: 60px; + vertical-align: top; +} +#option-table td:first-child span{ + display: inline-block; + width: 4rem; + height: 4rem; + text-align: center; + line-height: 2em; + font-size: 1.8rem; + color: #fff; + border: .2rem solid #313537; + -webkit-border-radius: 50%; + border-radius: 50%; + border-color: #337ab7 !important; + background-color: #337ab7 !important; +} + +/*----------SORTABLE----------------*/ +/* prevent inserting text into CKEditors */ +.sortable-placeholder .option-ckeditor { + pointer-events: none +} +.sortable-chosen { + background-color: #fff; +} +.sortable-placeholder { + background-color: rgba(241, 241, 241, 0.7); +} + +/*----------SLIDER----------------*/ + +.ui-widget-content.ui-slider { + border: 0.02em solid #bdc3c7; + background: #e1e1e1; + color: #222222; + margin-top: -0.02em; + padding: 0.08em; + opacity:0.8; +} + +.ui-state-default, .ui-widget-content.ui-slider .ui-state-default{ + background:transparent !important; + border:none !important; +} +.ui-slider .ui-slider-handle label{ + background: #c3c3c3; + border-radius: 20px; + width:5.2em; +} + +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 5.2em; + height: 100px; + cursor: default; + margin: 0 -40px auto !important; + text-align: center; + line-height: 28px; + color: #FFFFFF; +} + +.ui-slider .ui-slider-handle .fa { + color: #FFFFFF; + margin: 0 3px; + font-size: 18px; + opacity: 0.5; + font-weight: bolder; +} + +.ui-slider-horizontal .ui-slider-handle { + top: -.9em; +} + +.ui-state-default, .ui-widget-content.ui-slider .ui-state-default { + border: 1px solid #f9f9f9; + background: #3498db; +} + +.ui-slider-horizontal .ui-slider-handle { + margin-left: -0.5em; +} + +.ui-slider .ui-slider-handle { + cursor: pointer; +} + +.ui-slider a, .ui-slider a:focus { + cursor: pointer; + outline: none; +} + +.grade-slider { + height: 30px; + padding-top: 15px; +} + +.grade-slider>div:first-child { + margin-top: -7px; + padding: 0; +} + +/*----------CKEDITOR FLOATING LABEL----------------*/ + +.cke-div { + position: relative; +} + +/* +div[contenteditable].placeholder-shown { + margin-top: 22px; +}*/ + +.cke-label-floating { + will-change: left,top,contents; + margin: 0; + line-height: 1.4; + font-weight: 400; + position: absolute; + pointer-events: none; + transition: all .3s ease; + color: #999 !important; + top: 0; +} +div[contenteditable]:focus + .cke-label-floating, div[contenteditable].cke_filled + .cke-label-floating { + top: -1rem; + left: 0; + font-size: .9rem; +} + +/*----------SWITCH (https://www.w3schools.com/howto/howto_css_switch.asp)----------------*/ + +.switch { + position: relative; + display: inline-block; + width: 40px; + height: 23px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.switch-slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.switch-slider:before { + position: absolute; + content: ""; + height: 17px; + width: 17px; + left: 3px; + bottom: 0.2em; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked ~ .switch-slider { + background-color: #2196F3; +} + +input:focus ~ .switch-slider { + box-shadow: 0 0 1px #2196F3; +} + +input:checked ~ .switch-slider:before { + -webkit-transform: translateX(17px); + -ms-transform: translateX(17px); + transform: translateX(17px); +} + +/* Rounded switch-sliders */ +.switch-slider.round { + border-radius: 23px; +} + +.switch-slider.round:before { + border-radius: 50%; +} + +/*----------Modifications to bootstrap-toggle----------------*/ +.toggle.btn-xs { + min-width: 59px; +} + +/*----------Validation rules----------------*/ +#title-container .form-control-feedback { + right: 80px; +} +.col-sm-9.has-feedback .form-control-feedback { + left: 80px; +} Index: lams_central/web/includes/javascript/bootstrap-toggle.js =================================================================== diff -u --- lams_central/web/includes/javascript/bootstrap-toggle.js (revision 0) +++ lams_central/web/includes/javascript/bootstrap-toggle.js (revision 6cbd849584c40532c6be292f9f009c88cde9439c) @@ -0,0 +1,9 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap-toggle.js v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * ======================================================================== */ ++function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.toggle"),f="object"==typeof b&&b;e||d.data("bs.toggle",e=new c(this,f)),"string"==typeof b&&e[b]&&e[b]()})}var c=function(b,c){this.$element=a(b),this.options=a.extend({},this.defaults(),c),this.render()};c.VERSION="2.2.0",c.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"default",size:"normal",style:"",width:null,height:null},c.prototype.defaults=function(){return{on:this.$element.attr("data-on")||c.DEFAULTS.on,off:this.$element.attr("data-off")||c.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||c.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||c.DEFAULTS.offstyle,size:this.$element.attr("data-size")||c.DEFAULTS.size,style:this.$element.attr("data-style")||c.DEFAULTS.style,width:this.$element.attr("data-width")||c.DEFAULTS.width,height:this.$element.attr("data-height")||c.DEFAULTS.height}},c.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var b="large"===this.options.size?"btn-lg":"small"===this.options.size?"btn-sm":"mini"===this.options.size?"btn-xs":"",c=a('