Add frontend condition logic for questionnaire
This commit is contained in:
parent
d1dfcdd2ba
commit
2ce5b50c97
5 changed files with 719 additions and 339 deletions
|
|
@ -773,6 +773,188 @@ namespace Web.Areas.Admin.Controllers
|
||||||
return RedirectToAction(nameof(SetLogic), new { id = model.QuestionnaireId });
|
return RedirectToAction(nameof(SetLogic), new { id = model.QuestionnaireId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Add these methods to your existing controller (the one with SetLogic and SaveLogic)
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> SaveAnswerCondition([FromBody] SaveAnswerConditionRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Validate the request
|
||||||
|
if (request == null || request.AnswerId <= 0)
|
||||||
|
{
|
||||||
|
return Json(new { success = false, message = "Invalid answer ID provided." });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the questionnaire with all related data
|
||||||
|
var questionnaire = _questionnaire.GetQuestionnaireWithQuestionAndAnswer(request.QuestionnaireId);
|
||||||
|
|
||||||
|
if (questionnaire == null)
|
||||||
|
{
|
||||||
|
return Json(new { success = false, message = "Questionnaire not found." });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the specific answer
|
||||||
|
var answer = questionnaire.Questions
|
||||||
|
.SelectMany(q => q.Answers)
|
||||||
|
.FirstOrDefault(a => a.Id == request.AnswerId);
|
||||||
|
|
||||||
|
if (answer == null)
|
||||||
|
{
|
||||||
|
return Json(new { success = false, message = "Answer not found." });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and store the condition JSON
|
||||||
|
if (string.IsNullOrEmpty(request.ConditionJson))
|
||||||
|
{
|
||||||
|
// Clear the condition (set to continue)
|
||||||
|
answer.ConditionJson = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Validate JSON format
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var testParse = System.Text.Json.JsonSerializer.Deserialize<ConditionData>(request.ConditionJson);
|
||||||
|
answer.ConditionJson = request.ConditionJson;
|
||||||
|
}
|
||||||
|
catch (System.Text.Json.JsonException)
|
||||||
|
{
|
||||||
|
return Json(new { success = false, message = "Invalid condition data format." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save changes using your repository pattern
|
||||||
|
await _questionnaire.Update(questionnaire);
|
||||||
|
await _questionnaire.commitAsync();
|
||||||
|
|
||||||
|
// Generate summary for response
|
||||||
|
string summary = GetConditionSummaryFromJson(answer.ConditionJson);
|
||||||
|
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
message = "Answer condition saved successfully!",
|
||||||
|
summary = summary
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log error if you have logging configured
|
||||||
|
// _logger?.LogError(ex, "Error saving answer condition for AnswerId: {AnswerId}", request.AnswerId);
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = "An error occurred while saving the condition. Please try again."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> ResetAnswerCondition([FromBody] ResetAnswerConditionRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (request == null || request.AnswerId <= 0)
|
||||||
|
{
|
||||||
|
return Json(new { success = false, message = "Invalid answer ID provided." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var questionnaire = _questionnaire.GetQuestionnaireWithQuestionAndAnswer(request.QuestionnaireId);
|
||||||
|
|
||||||
|
if (questionnaire == null)
|
||||||
|
{
|
||||||
|
return Json(new { success = false, message = "Questionnaire not found." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var answer = questionnaire.Questions
|
||||||
|
.SelectMany(q => q.Answers)
|
||||||
|
.FirstOrDefault(a => a.Id == request.AnswerId);
|
||||||
|
|
||||||
|
if (answer == null)
|
||||||
|
{
|
||||||
|
return Json(new { success = false, message = "Answer not found." });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset to default (Continue)
|
||||||
|
answer.ConditionJson = null;
|
||||||
|
|
||||||
|
await _questionnaire.Update(questionnaire);
|
||||||
|
await _questionnaire.commitAsync();
|
||||||
|
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
message = "Answer condition reset successfully!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// _logger?.LogError(ex, "Error resetting answer condition for AnswerId: {AnswerId}", request.AnswerId);
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = "An error occurred while resetting the condition. Please try again."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to generate summary from JSON
|
||||||
|
private string GetConditionSummaryFromJson(string conditionJson)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(conditionJson))
|
||||||
|
{
|
||||||
|
return "Continue to the next question normally";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var condition = System.Text.Json.JsonSerializer.Deserialize<ConditionData>(conditionJson);
|
||||||
|
if (condition == null) return "Continue to the next question normally";
|
||||||
|
|
||||||
|
switch (condition.ActionType)
|
||||||
|
{
|
||||||
|
case 0: // Continue
|
||||||
|
return "Continue to the next question normally";
|
||||||
|
case 1: // SkipToQuestion
|
||||||
|
return condition.TargetQuestionNumber.HasValue
|
||||||
|
? $"Jump to Question {condition.TargetQuestionNumber}"
|
||||||
|
: "Jump to specific question";
|
||||||
|
case 2: // SkipCount
|
||||||
|
return $"Skip {condition.SkipCount ?? 1} question(s)";
|
||||||
|
case 3: // EndSurvey
|
||||||
|
return "End the survey immediately";
|
||||||
|
default:
|
||||||
|
return "Continue to the next question normally";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return "Continue to the next question normally";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request models - Add these classes to your project
|
||||||
|
public class SaveAnswerConditionRequest
|
||||||
|
{
|
||||||
|
public int AnswerId { get; set; }
|
||||||
|
public string ConditionJson { get; set; } = string.Empty;
|
||||||
|
public int QuestionnaireId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResetAnswerConditionRequest
|
||||||
|
{
|
||||||
|
public int AnswerId { get; set; }
|
||||||
|
public int QuestionnaireId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConditionData
|
||||||
|
{
|
||||||
|
public int ActionType { get; set; }
|
||||||
|
public int? TargetQuestionNumber { get; set; }
|
||||||
|
public int? SkipCount { get; set; }
|
||||||
|
public string? EndMessage { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -305,32 +305,88 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.answer-header {
|
.answer-header {
|
||||||
background: #e9ecef;
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
padding: 15px 20px;
|
padding: 18px 20px;
|
||||||
border-bottom: 1px solid #dee2e6;
|
border-bottom: 1px solid #dee2e6;
|
||||||
display: flex;
|
position: relative;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.answer-card.has-condition .answer-header {
|
.answer-card.has-condition .answer-header {
|
||||||
background: #ff6b6b;
|
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.answer-header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-title-section {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-number {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6c5ce7;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-card.has-condition .answer-number {
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
.answer-title {
|
.answer-title {
|
||||||
|
font-size: 1.1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #2c3e50;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-card.has-condition .answer-title {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-badges {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.active-badge {
|
.active-badge {
|
||||||
background: white;
|
background: rgba(255, 255, 255, 0.95);
|
||||||
color: #ff6b6b;
|
color: #ff6b6b;
|
||||||
padding: 4px 10px;
|
padding: 6px 12px;
|
||||||
border-radius: 12px;
|
border-radius: 15px;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-type-badge {
|
||||||
|
background: rgba(108, 92, 231, 0.1);
|
||||||
|
color: #6c5ce7;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-card.has-condition .condition-type-badge {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.answer-content {
|
.answer-content {
|
||||||
|
|
@ -348,7 +404,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-save-answer {
|
.btn-save-answer {
|
||||||
background: #28a745;
|
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
|
|
@ -359,15 +415,24 @@
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
|
box-shadow: 0 2px 8px rgba(40, 167, 69, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-save-answer:hover {
|
.btn-save-answer:hover {
|
||||||
background: #218838;
|
background: linear-gradient(135deg, #218838 0%, #1ea472 100%);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save-answer:disabled {
|
||||||
|
background: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-reset-answer {
|
.btn-reset-answer {
|
||||||
background: #dc3545;
|
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
|
|
@ -378,40 +443,54 @@
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
|
box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-reset-answer:hover {
|
.btn-reset-answer:hover {
|
||||||
background: #c82333;
|
background: linear-gradient(135deg, #c82333 0%, #a71e2a 100%);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-reset-answer:disabled {
|
||||||
|
background: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.answer-status {
|
.answer-status {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
padding: 4px 8px;
|
padding: 6px 12px;
|
||||||
border-radius: 4px;
|
border-radius: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.answer-status.saved {
|
.answer-status.saved {
|
||||||
background: #d4edda;
|
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
|
||||||
color: #155724;
|
color: #155724;
|
||||||
|
border: 1px solid #b8dabd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.answer-status.modified {
|
.answer-status.modified {
|
||||||
background: #fff3cd;
|
background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
|
||||||
color: #856404;
|
color: #856404;
|
||||||
|
border: 1px solid #ffeaa7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.answer-text {
|
.answer-status.saving {
|
||||||
font-weight: 600;
|
background: linear-gradient(135deg, #cce7ff 0%, #b3d9ff 100%);
|
||||||
color: #495057;
|
color: #0066cc;
|
||||||
font-size: 1rem;
|
border: 1px solid #99ccff;
|
||||||
margin: 0 0 20px 0;
|
}
|
||||||
padding: 12px;
|
|
||||||
background: white;
|
.answer-status.error {
|
||||||
border-radius: 6px;
|
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
|
||||||
border-left: 4px solid #6c5ce7;
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
|
|
@ -552,6 +631,17 @@
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.answer-header-content {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-badges {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
@ -612,6 +702,7 @@
|
||||||
|
|
||||||
<form id="logicForm" method="post" asp-action="SaveLogic" novalidate>
|
<form id="logicForm" method="post" asp-action="SaveLogic" novalidate>
|
||||||
<input type="hidden" name="QuestionnaireId" value="@Model.QuestionnaireId" />
|
<input type="hidden" name="QuestionnaireId" value="@Model.QuestionnaireId" />
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
|
||||||
<!-- Questions in 2-Column Layout -->
|
<!-- Questions in 2-Column Layout -->
|
||||||
<div class="questions-container">
|
<div class="questions-container">
|
||||||
|
|
@ -653,19 +744,27 @@
|
||||||
var answer = question.Answers[answerIndex];
|
var answer = question.Answers[answerIndex];
|
||||||
var hasCondition = answer.ActionType != ConditionActionType.Continue;
|
var hasCondition = answer.ActionType != ConditionActionType.Continue;
|
||||||
|
|
||||||
<div class="answer-card @(hasCondition ? "has-condition" : "")">
|
<div class="answer-card @(hasCondition ? "has-condition" : "")" data-answer-id="@answer.AnswerId">
|
||||||
<div class="answer-header">
|
<div class="answer-header">
|
||||||
<h4 class="answer-title">📝 Answer @(answerIndex + 1)</h4>
|
<div class="answer-header-content">
|
||||||
|
<div class="answer-title-section">
|
||||||
|
<div class="answer-number">Answer @(answerIndex + 1)</div>
|
||||||
|
<h4 class="answer-title">@answer.AnswerText</h4>
|
||||||
|
</div>
|
||||||
|
<div class="answer-badges">
|
||||||
@if (hasCondition)
|
@if (hasCondition)
|
||||||
{
|
{
|
||||||
<span class="active-badge">
|
<span class="active-badge">
|
||||||
<i class="fas fa-cog"></i> Active
|
<i class="fas fa-cog"></i> Active
|
||||||
</span>
|
</span>
|
||||||
|
<span class="condition-type-badge">
|
||||||
|
@GetConditionTypeBadge(answer.ActionType)
|
||||||
|
</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="answer-content">
|
<div class="answer-content">
|
||||||
<div class="answer-text">@answer.AnswerText</div>
|
|
||||||
|
|
||||||
<!-- Action Type Selection -->
|
<!-- Action Type Selection -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">When this answer is selected:</label>
|
<label class="form-label">When this answer is selected:</label>
|
||||||
|
|
@ -829,14 +928,12 @@
|
||||||
<!-- Save Section -->
|
<!-- Save Section -->
|
||||||
<div class="save-section">
|
<div class="save-section">
|
||||||
<button type="submit" class="btn-primary">
|
<button type="submit" class="btn-primary">
|
||||||
<i class="fas fa-save"></i> Save Conditional Logic
|
<i class="fas fa-save"></i> Save All Conditional Logic
|
||||||
</button>
|
</button>
|
||||||
<a href="@Url.Action("Index")" class="btn-secondary">
|
<a href="@Url.Action("Index")" class="btn-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> Back to Questionnaires
|
<i class="fas fa-arrow-left"></i> Back to Questionnaires
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="btn-secondary" onclick="previewLogic()">
|
|
||||||
<i class="fas fa-eye"></i> Preview Logic Flow
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn-secondary" onclick="clearAllConditions()">
|
<button type="button" class="btn-secondary" onclick="clearAllConditions()">
|
||||||
<i class="fas fa-eraser"></i> Clear All Conditions
|
<i class="fas fa-eraser"></i> Clear All Conditions
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -895,11 +992,11 @@
|
||||||
|
|
||||||
updateConditionSummary(selectElement);
|
updateConditionSummary(selectElement);
|
||||||
updateQuestionHeaderStatus();
|
updateQuestionHeaderStatus();
|
||||||
|
updateAnswerCardBadges(answerId, selectedValue);
|
||||||
markAnswerAsModified(answerId);
|
markAnswerAsModified(answerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateConditionSummary(selectElement) {
|
function updateConditionSummary(selectElement) {
|
||||||
const answerId = selectElement.getAttribute('data-answer-id');
|
|
||||||
const selectedValue = selectElement.value;
|
const selectedValue = selectElement.value;
|
||||||
const summaryElement = selectElement.closest('.answer-content').querySelector('.summary-text');
|
const summaryElement = selectElement.closest('.answer-content').querySelector('.summary-text');
|
||||||
|
|
||||||
|
|
@ -933,6 +1030,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateAnswerCardBadges(answerId, actionValue) {
|
||||||
|
const answerCard = document.querySelector(`[data-answer-id="${answerId}"]`);
|
||||||
|
const badgesContainer = answerCard.querySelector('.answer-badges');
|
||||||
|
|
||||||
|
// Remove existing badges
|
||||||
|
badgesContainer.innerHTML = '';
|
||||||
|
|
||||||
|
if (actionValue !== '0') {
|
||||||
|
// Add active badge
|
||||||
|
const activeBadge = document.createElement('span');
|
||||||
|
activeBadge.className = 'active-badge';
|
||||||
|
activeBadge.innerHTML = '<i class="fas fa-cog"></i> Active';
|
||||||
|
badgesContainer.appendChild(activeBadge);
|
||||||
|
|
||||||
|
// Add condition type badge
|
||||||
|
const typeBadge = document.createElement('span');
|
||||||
|
typeBadge.className = 'condition-type-badge';
|
||||||
|
switch(actionValue) {
|
||||||
|
case '1': typeBadge.textContent = 'Skip To'; break;
|
||||||
|
case '2': typeBadge.textContent = 'Skip Count'; break;
|
||||||
|
case '3': typeBadge.textContent = 'End Survey'; break;
|
||||||
|
}
|
||||||
|
badgesContainer.appendChild(typeBadge);
|
||||||
|
|
||||||
|
answerCard.classList.add('has-condition');
|
||||||
|
} else {
|
||||||
|
answerCard.classList.remove('has-condition');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateQuestionHeaderStatus() {
|
function updateQuestionHeaderStatus() {
|
||||||
document.querySelectorAll('.question-card').forEach(card => {
|
document.querySelectorAll('.question-card').forEach(card => {
|
||||||
const actionSelects = card.querySelectorAll('.action-type-select');
|
const actionSelects = card.querySelectorAll('.action-type-select');
|
||||||
|
|
@ -951,36 +1078,33 @@
|
||||||
} else {
|
} else {
|
||||||
header.classList.remove('has-logic');
|
header.classList.remove('has-logic');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update answer cards
|
|
||||||
actionSelects.forEach(select => {
|
|
||||||
const answerCard = select.closest('.answer-card');
|
|
||||||
if (select.value !== '0') {
|
|
||||||
answerCard.classList.add('has-condition');
|
|
||||||
} else {
|
|
||||||
answerCard.classList.remove('has-condition');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function markAnswerAsModified(answerId) {
|
function markAnswerAsModified(answerId) {
|
||||||
const answerCard = document.querySelector(`[data-answer-id="${answerId}"]`).closest('.answer-card');
|
const answerCard = document.querySelector(`[data-answer-id="${answerId}"]`);
|
||||||
const statusElement = answerCard.querySelector('.answer-status');
|
const statusElement = answerCard.querySelector('.answer-status');
|
||||||
|
|
||||||
if (statusElement && !statusElement.classList.contains('modified')) {
|
if (statusElement && !statusElement.classList.contains('modified') && !statusElement.classList.contains('saving')) {
|
||||||
statusElement.textContent = '● Modified';
|
statusElement.textContent = '● Modified';
|
||||||
statusElement.className = 'answer-status modified';
|
statusElement.className = 'answer-status modified';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveAnswer(answerId) {
|
function saveAnswer(answerId) {
|
||||||
const answerCard = document.querySelector(`[data-answer-id="${answerId}"]`).closest('.answer-card');
|
const answerCard = document.querySelector(`[data-answer-id="${answerId}"]`);
|
||||||
const actionSelect = answerCard.querySelector('.action-type-select');
|
const actionSelect = answerCard.querySelector('.action-type-select');
|
||||||
const statusElement = answerCard.querySelector('.answer-status');
|
const statusElement = answerCard.querySelector('.answer-status');
|
||||||
|
const saveButton = answerCard.querySelector('.btn-save-answer');
|
||||||
|
|
||||||
if (!actionSelect) return;
|
if (!actionSelect) return;
|
||||||
|
|
||||||
|
// Show saving status
|
||||||
|
statusElement.textContent = '⏳ Saving...';
|
||||||
|
statusElement.className = 'answer-status saving';
|
||||||
|
saveButton.disabled = true;
|
||||||
|
saveButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Saving';
|
||||||
|
|
||||||
const actionType = actionSelect.value;
|
const actionType = actionSelect.value;
|
||||||
let condition = {
|
let condition = {
|
||||||
ActionType: parseInt(actionType)
|
ActionType: parseInt(actionType)
|
||||||
|
|
@ -1008,42 +1132,129 @@
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here you could make an AJAX call to save individual answer
|
// Get anti-forgery token
|
||||||
// For now, we'll just show success message and update status
|
const token = document.querySelector('input[name="__RequestVerificationToken"]');
|
||||||
|
|
||||||
|
const requestData = {
|
||||||
|
AnswerId: parseInt(answerId),
|
||||||
|
ConditionJson: JSON.stringify(condition),
|
||||||
|
QuestionnaireId: @Model.QuestionnaireId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create headers with anti-forgery token
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
headers['RequestVerificationToken'] = token.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make AJAX call to save individual answer
|
||||||
|
fetch('@Url.Action("SaveAnswerCondition")', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
body: JSON.stringify(requestData)
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
statusElement.textContent = '✓ Saved';
|
statusElement.textContent = '✓ Saved';
|
||||||
statusElement.className = 'answer-status saved';
|
statusElement.className = 'answer-status saved';
|
||||||
updateQuestionHeaderStatus();
|
|
||||||
showToast('Answer condition saved successfully!', 'success');
|
|
||||||
|
|
||||||
// Optional: Make AJAX call to server
|
// Update the summary if provided
|
||||||
// fetch('/YourController/SaveAnswerCondition', {
|
if (data.summary) {
|
||||||
// method: 'POST',
|
const summaryElement = answerCard.querySelector('.summary-text');
|
||||||
// headers: {
|
if (summaryElement) {
|
||||||
// 'Content-Type': 'application/json'
|
summaryElement.textContent = data.summary;
|
||||||
// },
|
}
|
||||||
// body: JSON.stringify({
|
}
|
||||||
// AnswerId: answerId,
|
|
||||||
// ConditionJson: JSON.stringify(condition)
|
showToast('Answer condition saved successfully!', 'success');
|
||||||
// })
|
} else {
|
||||||
// });
|
statusElement.textContent = '✗ Error';
|
||||||
|
statusElement.className = 'answer-status error';
|
||||||
|
showToast(data.message || 'Failed to save answer condition.', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
statusElement.textContent = '✗ Error';
|
||||||
|
statusElement.className = 'answer-status error';
|
||||||
|
showToast('An error occurred while saving the answer condition.', 'error');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// Re-enable save button
|
||||||
|
saveButton.disabled = false;
|
||||||
|
saveButton.innerHTML = '<i class="fas fa-save"></i> Save';
|
||||||
|
updateQuestionHeaderStatus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetAnswer(answerId) {
|
function resetAnswer(answerId) {
|
||||||
if (confirm('Are you sure you want to reset this answer to "Continue to next question"?')) {
|
if (confirm('Are you sure you want to reset this answer to "Continue to next question"?')) {
|
||||||
const answerCard = document.querySelector(`[data-answer-id="${answerId}"]`).closest('.answer-card');
|
const answerCard = document.querySelector(`[data-answer-id="${answerId}"]`);
|
||||||
const actionSelect = answerCard.querySelector('.action-type-select');
|
|
||||||
const statusElement = answerCard.querySelector('.answer-status');
|
const statusElement = answerCard.querySelector('.answer-status');
|
||||||
|
const resetButton = answerCard.querySelector('.btn-reset-answer');
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
statusElement.textContent = '⏳ Resetting...';
|
||||||
|
statusElement.className = 'answer-status saving';
|
||||||
|
resetButton.disabled = true;
|
||||||
|
resetButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Resetting';
|
||||||
|
|
||||||
|
const token = document.querySelector('input[name="__RequestVerificationToken"]');
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
headers['RequestVerificationToken'] = token.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('@Url.Action("ResetAnswerCondition")', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
body: JSON.stringify({
|
||||||
|
AnswerId: parseInt(answerId),
|
||||||
|
QuestionnaireId: @Model.QuestionnaireId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Reset the form controls
|
||||||
|
const actionSelect = answerCard.querySelector('.action-type-select');
|
||||||
if (actionSelect) {
|
if (actionSelect) {
|
||||||
actionSelect.value = '0';
|
actionSelect.value = '0';
|
||||||
toggleConditionOptions(actionSelect);
|
toggleConditionOptions(actionSelect);
|
||||||
|
}
|
||||||
|
|
||||||
statusElement.textContent = '↻ Reset';
|
statusElement.textContent = '↻ Reset';
|
||||||
statusElement.className = 'answer-status modified';
|
statusElement.className = 'answer-status modified';
|
||||||
|
|
||||||
updateQuestionHeaderStatus();
|
|
||||||
showToast('Answer condition reset successfully!', 'success');
|
showToast('Answer condition reset successfully!', 'success');
|
||||||
|
} else {
|
||||||
|
statusElement.textContent = '✗ Error';
|
||||||
|
statusElement.className = 'answer-status error';
|
||||||
|
showToast(data.message || 'Failed to reset answer condition.', 'error');
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
statusElement.textContent = '✗ Error';
|
||||||
|
statusElement.className = 'answer-status error';
|
||||||
|
showToast('An error occurred while resetting the answer condition.', 'error');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
resetButton.disabled = false;
|
||||||
|
resetButton.innerHTML = '<i class="fas fa-undo"></i> Reset';
|
||||||
|
updateQuestionHeaderStatus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1055,13 +1266,16 @@
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
background: ${type === 'success' ? '#28a745' : '#dc3545'};
|
background: ${type === 'success' ? 'linear-gradient(135deg, #28a745, #20c997)' : 'linear-gradient(135deg, #dc3545, #c82333)'};
|
||||||
color: white;
|
color: white;
|
||||||
padding: 12px 20px;
|
padding: 15px 25px;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 20px ${type === 'success' ? 'rgba(40, 167, 69, 0.3)' : 'rgba(220, 53, 69, 0.3)'};
|
||||||
|
font-weight: 500;
|
||||||
|
max-width: 300px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
document.body.appendChild(toast);
|
document.body.appendChild(toast);
|
||||||
|
|
@ -1069,17 +1283,19 @@
|
||||||
// Fade in
|
// Fade in
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toast.style.opacity = '1';
|
toast.style.opacity = '1';
|
||||||
|
toast.style.transform = 'translateY(0)';
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
// Remove after 3 seconds
|
// Remove after 4 seconds
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toast.style.opacity = '0';
|
toast.style.opacity = '0';
|
||||||
|
toast.style.transform = 'translateY(-20px)';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (document.body.contains(toast)) {
|
if (document.body.contains(toast)) {
|
||||||
document.body.removeChild(toast);
|
document.body.removeChild(toast);
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
}, 3000);
|
}, 4000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAllConditions() {
|
function clearAllConditions() {
|
||||||
|
|
@ -1096,7 +1312,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
updateQuestionHeaderStatus();
|
updateQuestionHeaderStatus();
|
||||||
alert('✅ All conditions have been cleared!\n\nDon\'t forget to save your changes.');
|
showToast('All conditions have been cleared! Don\'t forget to save your changes.', 'success');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1108,8 +1324,9 @@
|
||||||
const actionSelect = container.querySelector('.action-type-select');
|
const actionSelect = container.querySelector('.action-type-select');
|
||||||
if (actionSelect && actionSelect.value !== '0') {
|
if (actionSelect && actionSelect.value !== '0') {
|
||||||
const summaryText = container.querySelector('.summary-text').textContent;
|
const summaryText = container.querySelector('.summary-text').textContent;
|
||||||
const answerText = container.querySelector('.answer-text').textContent.trim();
|
const answerCard = actionSelect.closest('.answer-card');
|
||||||
conditions.push(`• ${answerText} → ${summaryText}`);
|
const answerTitle = answerCard.querySelector('.answer-title').textContent.trim();
|
||||||
|
conditions.push(`• ${answerTitle} → ${summaryText}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1278,4 +1495,19 @@
|
||||||
return "No condition set";
|
return "No condition set";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string GetConditionTypeBadge(ConditionActionType actionType)
|
||||||
|
{
|
||||||
|
switch (actionType)
|
||||||
|
{
|
||||||
|
case ConditionActionType.SkipToQuestion:
|
||||||
|
return "Skip To";
|
||||||
|
case ConditionActionType.SkipCount:
|
||||||
|
return "Skip Count";
|
||||||
|
case ConditionActionType.EndSurvey:
|
||||||
|
return "End Survey";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -287,6 +287,28 @@ namespace Web.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//private ResponseQuestionnaireViewModel MapToViewModel(Questionnaire questionnaire)
|
||||||
|
//{
|
||||||
|
// var viewModel = new ResponseQuestionnaireViewModel
|
||||||
|
// {
|
||||||
|
// Id = questionnaire.Id,
|
||||||
|
// Title = questionnaire.Title,
|
||||||
|
// Description = questionnaire.Description,
|
||||||
|
// Questions = questionnaire.Questions.Select(q => new ResponseQuestionViewModel
|
||||||
|
// {
|
||||||
|
// Id = q.Id,
|
||||||
|
// Text = q.Text,
|
||||||
|
// Type = q.Type,
|
||||||
|
// Answers = q.Answers.Select(a => new ResponseAnswerViewModel
|
||||||
|
// {
|
||||||
|
// Id = a.Id,
|
||||||
|
// Text = a.Text
|
||||||
|
// }).ToList()
|
||||||
|
// }).ToList()
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return viewModel;
|
||||||
|
//}
|
||||||
private ResponseQuestionnaireViewModel MapToViewModel(Questionnaire questionnaire)
|
private ResponseQuestionnaireViewModel MapToViewModel(Questionnaire questionnaire)
|
||||||
{
|
{
|
||||||
var viewModel = new ResponseQuestionnaireViewModel
|
var viewModel = new ResponseQuestionnaireViewModel
|
||||||
|
|
@ -302,7 +324,8 @@ namespace Web.Controllers
|
||||||
Answers = q.Answers.Select(a => new ResponseAnswerViewModel
|
Answers = q.Answers.Select(a => new ResponseAnswerViewModel
|
||||||
{
|
{
|
||||||
Id = a.Id,
|
Id = a.Id,
|
||||||
Text = a.Text
|
Text = a.Text,
|
||||||
|
ConditionJson = a.ConditionJson // Add this line
|
||||||
}).ToList()
|
}).ToList()
|
||||||
}).ToList()
|
}).ToList()
|
||||||
};
|
};
|
||||||
|
|
@ -312,7 +335,6 @@ namespace Web.Controllers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@
|
||||||
public string? Text { get; set; } // Answer text
|
public string? Text { get; set; } // Answer text
|
||||||
|
|
||||||
public int? Count { get; set; }
|
public int? Count { get; set; }
|
||||||
|
public string? ConditionJson { get; set; } // Add this line for conditional logic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,6 @@
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<div class="QuestionContainer">
|
<div class="QuestionContainer">
|
||||||
<section class="hero container card">
|
<section class="hero container card">
|
||||||
<form id="questionnaireForm" method="post" asp-action="DisplayQuestionnaire">
|
<form id="questionnaireForm" method="post" asp-action="DisplayQuestionnaire">
|
||||||
|
|
@ -369,20 +368,17 @@
|
||||||
<input type="hidden" name="Questions[@i].Answers[@j].Text" value="@answer.Text">
|
<input type="hidden" name="Questions[@i].Answers[@j].Text" value="@answer.Text">
|
||||||
<!-- Add more hidden fields as needed for other properties of the answer -->
|
<!-- Add more hidden fields as needed for other properties of the answer -->
|
||||||
}
|
}
|
||||||
<div class="step @(i == 0 ? "active" : "")">
|
<div class="step @(i == 0 ? "active" : "")" data-question-id="@question.Id">
|
||||||
|
|
||||||
<p class="font-weight-normal">@(i + 1). @question.Text</p>
|
<p class="font-weight-normal">@(i + 1). @question.Text</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@switch (question.Type)
|
@switch (question.Type)
|
||||||
{
|
{
|
||||||
case QuestionType.Text:
|
case QuestionType.Text:
|
||||||
@foreach (var answer in question.Answers)
|
@foreach (var answer in question.Answers)
|
||||||
{
|
{
|
||||||
|
|
||||||
<input type="Text" class="form-control" id="question@(i + 1)" name="Questions[@i].SelectedText" rows="3" placeholder="Enter answer"></input>
|
<input type="Text" class="form-control" id="question@(i + 1)" name="Questions[@i].SelectedText" rows="3" placeholder="Enter answer"></input>
|
||||||
<input class="form-control hidden-textarea" id="question@(i + 1)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" rows="3" placeholder="Enter answer" ></input>
|
<input class="form-control hidden-textarea" id="question@(i + 1)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" rows="3" placeholder="Enter answer" data-condition="@answer.ConditionJson"></input>
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QuestionType.CheckBox:
|
case QuestionType.CheckBox:
|
||||||
|
|
@ -394,7 +390,7 @@
|
||||||
@foreach (var answer in question.Answers)
|
@foreach (var answer in question.Answers)
|
||||||
{
|
{
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" id="question@(i)_answer@(answer.Id)" type="checkbox" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" >
|
<input class="form-check-input" id="question@(i)_answer@(answer.Id)" type="checkbox" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" data-condition="@answer.ConditionJson">
|
||||||
<label class="form-check-label" for="question@(i)_answer@(answer.Id)">
|
<label class="form-check-label" for="question@(i)_answer@(answer.Id)">
|
||||||
@answer.Text
|
@answer.Text
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -410,7 +406,7 @@
|
||||||
@foreach (var answer in question.Answers)
|
@foreach (var answer in question.Answers)
|
||||||
{
|
{
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input answer-input" type="radio" id="question@(i)_answer@(answer.Id)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id">
|
<input class="form-check-input answer-input" type="radio" id="question@(i)_answer@(answer.Id)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" data-condition="@answer.ConditionJson">
|
||||||
|
|
||||||
<label class="form-check-label" for="question@(i)_answer@(answer.Id)">
|
<label class="form-check-label" for="question@(i)_answer@(answer.Id)">
|
||||||
@answer.Text
|
@answer.Text
|
||||||
|
|
@ -428,7 +424,7 @@
|
||||||
{
|
{
|
||||||
|
|
||||||
<textarea type="Text" class="form-control" id="question@(i + 1)" name="Questions[@i].SelectedText" value="@answer.Text" rows="3" placeholder="Enter answer"></textarea>
|
<textarea type="Text" class="form-control" id="question@(i + 1)" name="Questions[@i].SelectedText" value="@answer.Text" rows="3" placeholder="Enter answer"></textarea>
|
||||||
<input type="hidden" class="form-control" id="question@(i + 1)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" rows="3" placeholder="Enter answer"></input>
|
<input type="hidden" class="form-control" id="question@(i + 1)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" rows="3" placeholder="Enter answer" data-condition="@answer.ConditionJson"></input>
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -438,7 +434,7 @@
|
||||||
@foreach (var answer in question.Answers)
|
@foreach (var answer in question.Answers)
|
||||||
{
|
{
|
||||||
<input type="range" class="form-range " id="question@(i + 1)" name="Questions[@i].SelectedText" min="0" max="100" step="1">
|
<input type="range" class="form-range " id="question@(i + 1)" name="Questions[@i].SelectedText" min="0" max="100" step="1">
|
||||||
<input type="hidden" class="form-range " id="question@(i + 1)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" min="0" max="100" step="1">
|
<input type="hidden" class="form-range " id="question@(i + 1)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" min="0" max="100" step="1" data-condition="@answer.ConditionJson">
|
||||||
|
|
||||||
<output id="question@(i + 1)_output">50</output>
|
<output id="question@(i + 1)_output">50</output>
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -458,7 +454,7 @@
|
||||||
<div class="rating-label">@answer.Text</div>
|
<div class="rating-label">@answer.Text</div>
|
||||||
<input type="radio" id="question@(i)_rating@(answer.Id)"
|
<input type="radio" id="question@(i)_rating@(answer.Id)"
|
||||||
name="Questions[@i].SelectedAnswerIds"
|
name="Questions[@i].SelectedAnswerIds"
|
||||||
value="@answer.Id" class="rating-input" hidden>
|
value="@answer.Id" class="rating-input" hidden data-condition="@answer.ConditionJson">
|
||||||
<label for="question@(i)_rating@(answer.Id)" class="bi bi-star-fill rating-star"></label>
|
<label for="question@(i)_rating@(answer.Id)" class="bi bi-star-fill rating-star"></label>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
@ -478,7 +474,7 @@
|
||||||
<li class="draggable-item" draggable="true" id="@answer.Id" ondragstart="dragStart(event);" ondragover="allowDrop(event);" ondrop="drop(event);">
|
<li class="draggable-item" draggable="true" id="@answer.Id" ondragstart="dragStart(event);" ondragover="allowDrop(event);" ondrop="drop(event);">
|
||||||
<span class="answer-index">@index. </span> @answer.Text
|
<span class="answer-index">@index. </span> @answer.Text
|
||||||
<i class="bi bi-grip-vertical mr-auto"></i>
|
<i class="bi bi-grip-vertical mr-auto"></i>
|
||||||
<input type="hidden" name="Questions[@i].SelectedAnswerIds" value="@answer.Id">
|
<input type="hidden" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" data-condition="@answer.ConditionJson">
|
||||||
<button type="button" class="up-button btn btn-primary btn-sm " onclick="moveUp(this.parentElement)">
|
<button type="button" class="up-button btn btn-primary btn-sm " onclick="moveUp(this.parentElement)">
|
||||||
<i class="bi bi-arrow-up"></i> Up
|
<i class="bi bi-arrow-up"></i> Up
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -559,7 +555,7 @@
|
||||||
@foreach (var answer in question.Answers)
|
@foreach (var answer in question.Answers)
|
||||||
{
|
{
|
||||||
<label class="likert-option">
|
<label class="likert-option">
|
||||||
<input class="form-check-input" type="radio" id="question@(i)_answer@(answer.Id)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id">
|
<input class="form-check-input" type="radio" id="question@(i)_answer@(answer.Id)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" data-condition="@answer.ConditionJson">
|
||||||
@answer.Text
|
@answer.Text
|
||||||
</label>
|
</label>
|
||||||
}
|
}
|
||||||
|
|
@ -589,7 +585,7 @@
|
||||||
@foreach (var option in question.Answers) // Use consistent options for each sub-question
|
@foreach (var option in question.Answers) // Use consistent options for each sub-question
|
||||||
{
|
{
|
||||||
<td>
|
<td>
|
||||||
<input type="radio" name="Questions[@i].SelectedAnswerIds" value="@option.Id">
|
<input type="radio" name="Questions[@i].SelectedAnswerIds" value="@option.Id" data-condition="@option.ConditionJson">
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -625,7 +621,7 @@
|
||||||
{
|
{
|
||||||
<div class="card image-card" id="card_@answer.Id" onclick="selectImageCard('@answer.Id', '@question.Id')">
|
<div class="card image-card" id="card_@answer.Id" onclick="selectImageCard('@answer.Id', '@question.Id')">
|
||||||
<img src="@answer.Text" alt="Image option" class="img-fluid" />
|
<img src="@answer.Text" alt="Image option" class="img-fluid" />
|
||||||
<input type="radio" id="image_answer_@answer.Id" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" hidden />
|
<input type="radio" id="image_answer_@answer.Id" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" hidden data-condition="@answer.ConditionJson" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -671,11 +667,7 @@
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
|
|
||||||
|
|
||||||
@{
|
@{
|
||||||
<partial name="_ValidationScriptsPartial" />
|
<partial name="_ValidationScriptsPartial" />
|
||||||
}
|
}
|
||||||
|
|
@ -687,26 +679,114 @@
|
||||||
console.error('Form not found!');
|
console.error('Form not found!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track the actual navigation path
|
||||||
|
let navigationPath = [0]; // Start with question 1 (index 0)
|
||||||
|
|
||||||
|
// Get conditions from the CURRENT step only
|
||||||
|
function getCurrentStepConditions() {
|
||||||
|
const currentStepElement = steps[currentStep];
|
||||||
|
const activeConditions = [];
|
||||||
|
|
||||||
|
// Only check inputs in the current step
|
||||||
|
const currentStepInputs = currentStepElement.querySelectorAll('input:checked, input[type="text"]:not([value=""]), textarea:not([value=""])');
|
||||||
|
|
||||||
|
currentStepInputs.forEach(input => {
|
||||||
|
const condition = input.getAttribute('data-condition');
|
||||||
|
if (condition && condition !== '' && condition !== 'null') {
|
||||||
|
try {
|
||||||
|
const conditionData = JSON.parse(condition);
|
||||||
|
activeConditions.push(conditionData);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Invalid condition JSON:', condition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return activeConditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldApplyCondition() {
|
||||||
|
const conditions = getCurrentStepConditions();
|
||||||
|
|
||||||
|
for (let condition of conditions) {
|
||||||
|
if (condition.ActionType === 1 && condition.TargetQuestionNumber) {
|
||||||
|
// SkipToQuestion
|
||||||
|
return { action: 'jumpTo', target: condition.TargetQuestionNumber };
|
||||||
|
} else if (condition.ActionType === 2 && condition.SkipCount) {
|
||||||
|
// SkipCount
|
||||||
|
return { action: 'skip', count: condition.SkipCount };
|
||||||
|
} else if (condition.ActionType === 3) {
|
||||||
|
// EndSurvey
|
||||||
|
return { action: 'end', message: condition.EndMessage };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { action: 'continue' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentStepNumber() {
|
||||||
|
return currentStep + 1; // Convert 0-based index to 1-based question number
|
||||||
|
}
|
||||||
|
|
||||||
|
function jumpToQuestion(questionNumber) {
|
||||||
|
const targetStepIndex = questionNumber - 1; // Convert to 0-based index
|
||||||
|
if (targetStepIndex >= 0 && targetStepIndex < steps.length) {
|
||||||
|
currentStep = targetStepIndex;
|
||||||
|
navigationPath.push(currentStep); // Track the path
|
||||||
|
showStep(currentStep);
|
||||||
|
updateStepper();
|
||||||
|
console.log(`Jumped to question ${questionNumber}, path:`, navigationPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipQuestions(skipCount) {
|
||||||
|
const newStepIndex = currentStep + skipCount + 1;
|
||||||
|
if (newStepIndex < steps.length) {
|
||||||
|
currentStep = newStepIndex;
|
||||||
|
navigationPath.push(currentStep); // Track the path
|
||||||
|
showStep(currentStep);
|
||||||
|
updateStepper();
|
||||||
|
} else {
|
||||||
|
// If skip goes beyond last question, show submit
|
||||||
|
currentStep = steps.length - 1;
|
||||||
|
navigationPath.push(currentStep);
|
||||||
|
showStep(currentStep);
|
||||||
|
updateStepper();
|
||||||
|
}
|
||||||
|
console.log(`Skipped ${skipCount} questions, path:`, navigationPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function endSurvey(endMessage) {
|
||||||
|
// Hide all steps and show end message
|
||||||
|
steps.forEach(step => step.style.display = 'none');
|
||||||
|
|
||||||
|
const endMessageDiv = document.createElement('div');
|
||||||
|
endMessageDiv.className = 'alert alert-success text-center';
|
||||||
|
endMessageDiv.innerHTML = `
|
||||||
|
<h4>Survey Completed</h4>
|
||||||
|
<p>${endMessage || 'Thank you for completing the survey!'}</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const formContainer = document.querySelector('.col-md-9');
|
||||||
|
formContainer.appendChild(endMessageDiv);
|
||||||
|
|
||||||
|
// Hide submit button
|
||||||
|
submitButton.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rating functionality
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.rating-item').on('click', function () {
|
$('.rating-item').on('click', function () {
|
||||||
// Remove 'selected' class from all stars within the same rating
|
|
||||||
$(this).siblings().find('.rating-star').removeClass('selected');
|
$(this).siblings().find('.rating-star').removeClass('selected');
|
||||||
|
|
||||||
// Add 'selected' class to the clicked star and all stars before it within the same rating
|
|
||||||
$(this).find('.rating-star').addClass('selected');
|
$(this).find('.rating-star').addClass('selected');
|
||||||
$(this).prevAll().find('.rating-star').addClass('selected');
|
$(this).prevAll().find('.rating-star').addClass('selected');
|
||||||
|
|
||||||
// Toggle 'fill' class on the clicked star
|
|
||||||
$(this).find('.rating-star').toggleClass('bi-star-fill bi-star');
|
$(this).find('.rating-star').toggleClass('bi-star-fill bi-star');
|
||||||
|
|
||||||
// Check the corresponding radio input
|
|
||||||
$(this).find('.rating-input').prop('checked', true);
|
$(this).find('.rating-input').prop('checked', true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Ranking functionality
|
||||||
|
|
||||||
// Ensure we only add listeners once by checking if they've been added already
|
|
||||||
if (!window.hasEventListenersAdded) {
|
if (!window.hasEventListenersAdded) {
|
||||||
const upButtons = document.querySelectorAll('.up-button');
|
const upButtons = document.querySelectorAll('.up-button');
|
||||||
const downButtons = document.querySelectorAll('.down-button');
|
const downButtons = document.querySelectorAll('.down-button');
|
||||||
|
|
@ -723,8 +803,9 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
window.hasEventListenersAdded = true; // Set a flag to indicate listeners are added
|
window.hasEventListenersAdded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveUp(button) {
|
function moveUp(button) {
|
||||||
var li = button.parentNode;
|
var li = button.parentNode;
|
||||||
if (li.previousElementSibling) {
|
if (li.previousElementSibling) {
|
||||||
|
|
@ -738,30 +819,15 @@
|
||||||
li.parentNode.insertBefore(li.nextElementSibling, li);
|
li.parentNode.insertBefore(li.nextElementSibling, li);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function updateOrder() {
|
|
||||||
document.querySelectorAll('.rank-list').forEach((list, index) => {
|
|
||||||
list.querySelectorAll('li').forEach((li, idx) => {
|
|
||||||
// Assuming each li has a hidden input as the last child
|
|
||||||
li.querySelector('input[type="hidden"]').value = li.getAttribute('data-answer-id');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Stepper functionality
|
||||||
const steps = form.querySelectorAll('.step');
|
const steps = form.querySelectorAll('.step');
|
||||||
const stepIndicators = document.querySelectorAll('.step-indicator');
|
const stepIndicators = document.querySelectorAll('.step-indicator');
|
||||||
const submitButton = form.querySelector('.submit');
|
const submitButton = form.querySelector('.submit');
|
||||||
let currentStep = 0;
|
let currentStep = 0;
|
||||||
|
|
||||||
// Prevent form submission
|
|
||||||
form.addEventListener('submit', function (event) {
|
form.addEventListener('submit', function (event) {
|
||||||
// event.preventDefault();
|
console.log('Form submission');
|
||||||
console.log('Form submission prevented.');
|
|
||||||
|
|
||||||
// Here, you can add logic to handle the form data client-side,
|
|
||||||
// such as validating input or sending it via AJAX.
|
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
formData.forEach((value, key) => {
|
formData.forEach((value, key) => {
|
||||||
console.log(`${key}: ${value}`);
|
console.log(`${key}: ${value}`);
|
||||||
|
|
@ -784,26 +850,59 @@
|
||||||
submitButton.style.display = index === steps.length - 1 ? 'block' : 'none';
|
submitButton.style.display = index === steps.length - 1 ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixed goToNextStep - only applies conditions from current step
|
||||||
function goToNextStep() {
|
function goToNextStep() {
|
||||||
|
const conditionResult = shouldApplyCondition();
|
||||||
|
|
||||||
|
switch (conditionResult.action) {
|
||||||
|
case 'jumpTo':
|
||||||
|
console.log(`Applying condition: Jump to question ${conditionResult.target}`);
|
||||||
|
jumpToQuestion(conditionResult.target);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'skip':
|
||||||
|
console.log(`Applying condition: Skip ${conditionResult.count} questions`);
|
||||||
|
skipQuestions(conditionResult.count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'end':
|
||||||
|
console.log('Applying condition: End survey');
|
||||||
|
endSurvey(conditionResult.message);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'continue':
|
||||||
|
default:
|
||||||
|
// Normal next step
|
||||||
if (currentStep < steps.length - 1) {
|
if (currentStep < steps.length - 1) {
|
||||||
currentStep++;
|
currentStep++;
|
||||||
|
navigationPath.push(currentStep); // Track normal navigation
|
||||||
showStep(currentStep);
|
showStep(currentStep);
|
||||||
updateStepper();
|
updateStepper();
|
||||||
|
console.log(`Normal next to question ${currentStep + 1}, path:`, navigationPath);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixed goToPrevStep - follows actual navigation path
|
||||||
function goToPrevStep() {
|
function goToPrevStep() {
|
||||||
if (currentStep > 0) {
|
if (navigationPath.length > 1) {
|
||||||
currentStep--;
|
// Remove current step from path
|
||||||
|
navigationPath.pop();
|
||||||
|
// Go to previous step in the actual path
|
||||||
|
currentStep = navigationPath[navigationPath.length - 1];
|
||||||
showStep(currentStep);
|
showStep(currentStep);
|
||||||
updateStepper();
|
updateStepper();
|
||||||
|
console.log(`Previous to question ${currentStep + 1}, path:`, navigationPath);
|
||||||
|
} else {
|
||||||
|
console.log('Already at the first question');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStepper() {
|
function updateStepper() {
|
||||||
const currentStepIndex = currentStep;
|
const currentStepIndex = currentStep;
|
||||||
stepIndicators.forEach((indicator, i) => {
|
stepIndicators.forEach((indicator, i) => {
|
||||||
indicator.style.backgroundColor = i === currentStepIndex ? '#33b3ae' : ''; // Change to your primary color
|
indicator.style.backgroundColor = i === currentStepIndex ? '#33b3ae' : '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -822,210 +921,54 @@
|
||||||
showStep(currentStep);
|
showStep(currentStep);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Global functions
|
||||||
|
function allowDrop(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragStart(ev) {
|
||||||
|
ev.dataTransfer.setData("text", ev.target.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drop(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
var data = ev.dataTransfer.getData("text");
|
||||||
|
var draggedElement = document.getElementById(data);
|
||||||
|
var dropTarget = ev.target.closest('.draggable-item');
|
||||||
|
if (dropTarget) {
|
||||||
|
var targetRect = dropTarget.getBoundingClientRect();
|
||||||
|
var midPoint = targetRect.top + (targetRect.height / 2);
|
||||||
|
|
||||||
|
var list = dropTarget.parentNode;
|
||||||
|
if (ev.clientY < midPoint) {
|
||||||
|
list.insertBefore(draggedElement, dropTarget);
|
||||||
|
} else {
|
||||||
|
if (dropTarget.nextSibling) {
|
||||||
|
list.insertBefore(draggedElement, dropTarget.nextSibling);
|
||||||
|
} else {
|
||||||
|
list.appendChild(draggedElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectImageCard(answerId, questionId) {
|
||||||
|
var cards = document.querySelectorAll('.image-question[data-question-id="' + questionId + '"] .card');
|
||||||
|
cards.forEach(function (card) {
|
||||||
|
card.classList.remove('selected');
|
||||||
|
});
|
||||||
|
|
||||||
// document.addEventListener("DOMContentLoaded", function () {
|
var selectedCard = document.getElementById('card_' + answerId);
|
||||||
// const stepper = document.querySelector('.stepper');
|
selectedCard.classList.add('selected');
|
||||||
// if (!stepper) {
|
|
||||||
// console.error('Stepper not found!');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let shownTypes = new Set(); // Set to track shown question types
|
var radioButton = document.getElementById('image_answer_' + answerId);
|
||||||
// const stepIndicators = stepper.querySelectorAll('.step-indicator');
|
radioButton.checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
// stepIndicators.forEach(indicator => {
|
function removeSelected(parent) {
|
||||||
// const type = indicator.querySelector('.step-label').textContent.trim(); // Get the text content of the step-label
|
parent.querySelectorAll('.draggable-item').forEach(sibling => {
|
||||||
// if (shownTypes.has(type)) {
|
sibling.classList.remove('selected');
|
||||||
// // If this type has already been shown, hide this indicator
|
});
|
||||||
// indicator.style.display = 'none';
|
}
|
||||||
// } else {
|
|
||||||
// // Otherwise, mark this type as shown
|
|
||||||
// shownTypes.add(type);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@* <script>
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
const form = document.getElementById('questionnaireForm');
|
|
||||||
const steps = form.querySelectorAll('.step');
|
|
||||||
const stepIndicators = document.querySelectorAll('.step-indicator');
|
|
||||||
const submitButton = form.querySelector('.submit');
|
|
||||||
let currentStep = 0;
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
const form = document.getElementById('questionnaireForm'); // Replace 'myForm' with your actual form ID
|
|
||||||
|
|
||||||
form.addEventListener('submit', function (event) {
|
|
||||||
event.preventDefault(); // Stop the form from submitting
|
|
||||||
console.log('Form submission prevented.');
|
|
||||||
|
|
||||||
// Here, you can add any logic to handle the form data client-side,
|
|
||||||
// such as validating input, organizing data, or sending it via AJAX.
|
|
||||||
|
|
||||||
// Example of gathering form data:
|
|
||||||
const formData = new FormData(form);
|
|
||||||
formData.forEach((value, key) => {
|
|
||||||
console.log(`${key}: ${value}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Optionally, you could send the formData using fetch() or another AJAX method
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function handleSubmit(event) {
|
|
||||||
event.preventDefault(); // Stop the form from submitting normally
|
|
||||||
|
|
||||||
const form = event.target; // Get the form that triggered the event
|
|
||||||
const formData = new FormData(form);
|
|
||||||
|
|
||||||
// Assuming you need to do something special with `SelectedAnswerIds`
|
|
||||||
const refinedFormData = new FormData();
|
|
||||||
formData.forEach((value, key) => {
|
|
||||||
if (key.includes("SelectedAnswerIds")) {
|
|
||||||
document.querySelectorAll(`input[name="${key}"]:checked`).forEach(checkbox => {
|
|
||||||
refinedFormData.append(key, checkbox.value); // Only append checked items
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
refinedFormData.append(key, value); // Append other data normally
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Debugging: log formData to ensure it's captured correctly
|
|
||||||
console.log('Prepared formData for submission:');
|
|
||||||
refinedFormData.forEach((value, key) => {
|
|
||||||
console.log(`${key}: ${value}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch API to submit the refined FormData
|
|
||||||
fetch(form.action, {
|
|
||||||
method: 'POST',
|
|
||||||
body: refinedFormData,
|
|
||||||
})
|
|
||||||
.then(response => response.json()) // Assuming JSON response
|
|
||||||
.then(data => {
|
|
||||||
console.log('Submission successful', data);
|
|
||||||
// Handle success (e.g., display a success message, redirect, etc.)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Submission failed', error);
|
|
||||||
// Handle errors (e.g., display error message)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSelectedText(radio, text, index) {
|
|
||||||
console.log("Radio checked:", radio.checked); // Check if the radio is indeed checked
|
|
||||||
console.log("Text to set:", text); // What text is being set
|
|
||||||
console.log("Target hidden input ID:", 'selected_text_question' + index); // Which input we're targeting
|
|
||||||
|
|
||||||
var hiddenInput = document.getElementById('selected_text_question' + index);
|
|
||||||
if (radio.checked) {
|
|
||||||
hiddenInput.value = text;
|
|
||||||
console.log("Updated hidden input value:", hiddenInput.value); // Verify the value is set
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showStep(index) {
|
|
||||||
steps.forEach((step, i) => {
|
|
||||||
if (i === index) {
|
|
||||||
step.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
step.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stepIndicators.forEach((indicator, i) => {
|
|
||||||
if (i === index) {
|
|
||||||
indicator.classList.add('active');
|
|
||||||
} else {
|
|
||||||
indicator.classList.remove('active');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (index === steps.length - 1) {
|
|
||||||
submitButton.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
submitButton.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToNextStep() {
|
|
||||||
if (currentStep < steps.length - 1) {
|
|
||||||
currentStep++;
|
|
||||||
showStep(currentStep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToPrevStep() {
|
|
||||||
if (currentStep > 0) {
|
|
||||||
currentStep--;
|
|
||||||
showStep(currentStep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextButtons = form.querySelectorAll('.next');
|
|
||||||
nextButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
goToNextStep();
|
|
||||||
updateStepper();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const prevButtons = form.querySelectorAll('.prev');
|
|
||||||
prevButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
goToPrevStep();
|
|
||||||
updateStepper();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateStepper() {
|
|
||||||
const currentStepIndex = currentStep;
|
|
||||||
stepIndicators.forEach((indicator, i) => {
|
|
||||||
if (i === currentStepIndex) {
|
|
||||||
indicator.style.backgroundColor = '#33b3ae'; // Change to your primary color
|
|
||||||
} else {
|
|
||||||
indicator.style.backgroundColor = ''; // Reset to default color for non-active steps
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
showStep(currentStep);
|
|
||||||
updateStepper();
|
|
||||||
|
|
||||||
$('.rating i').on('click', function () {
|
|
||||||
var value = $(this).data('value');
|
|
||||||
var questionIndex = $(this).closest('.rating').data('question');
|
|
||||||
var selectedStars = $(this).closest('.rating').find('i.selected').length;
|
|
||||||
|
|
||||||
// Unselect all stars
|
|
||||||
$(this).closest('.rating').find('i').removeClass('selected');
|
|
||||||
|
|
||||||
// Select the clicked star and all preceding stars
|
|
||||||
$(this).addClass('selected').prevAll().addClass('selected');
|
|
||||||
|
|
||||||
// Update the hidden input value if needed
|
|
||||||
$('input[name="question' + questionIndex + '_rating"]').val(value);
|
|
||||||
|
|
||||||
// Update the label with the number of selected stars
|
|
||||||
$(this).closest('.rating').next('.selected-count').text(selectedStars);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prevent the default action for the anchor tags within the rating
|
|
||||||
$('.rating a').on('click', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
event.preventDefault();
|
|
||||||
</script> *@
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue