PageSourceSearch

https://www.thejakartapost.com/skin/js/tjp-feedback.js

js thejakartapost.com collected 2026-09-24 08:25:51 UTC 17,183 bytes, 389 lines download raw bytes

1function capitalizeEachWord(str) {
2    return str.split(' ').map(function(word) {
3        return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
4    }).join(' ');
5}
6
7$(document).ready(function() {
8    // Fetch base URL from meta tag
9    const baseURL = $('meta[name="base-url"]').attr('content');
10    const csrfToken = $('meta[name="csrf-token"]').attr('content');
11    const isPartnerIP = $('meta[name="is-partner-ip"]').attr('content');
12    const isPayPerArticle = $('meta[name="is-pay-per-article"]').attr('content');
13
14    let currentStep = 1;  // Start at the first step
15    let totalSteps = 0; 
16    let question = []; // Array to store question IDs
17    let answer = [];   // Array to store answers
18
19    // Function to initialize the arrays for question IDs and answers
20    function initializeQuestionnaire(data) {
21        totalSteps = data.length + 1;
22        question = data.map(item => item.id);  // Store all question IDs
23        answer = Array((totalSteps - 1)).fill('');   // Initialize the answer array with empty strings
24    }
25
26    // Function to get the current step's answer and update the answer array
27    function storeCurrentStepAnswer() {
28        // Only store answers if we're at step 2 or higher
29        if (currentStep > 1) { // Check if we are at step 2 or higher
30            const currentItem = $('.tjp-feedback-item.active');
31            const currentQuestionId = question[currentStep - 2]; // Get current question ID (adjust for zero-based index)
32
33            let userAnswer = '';
34
35            // Check if the current question is a radio button question
36            let selectedRadio = currentItem.find('input[type="radio"]:checked');
37            if (selectedRadio.length > 0) {
38                userAnswer = selectedRadio.val();
39
40                // If the selected option is "Others", get the text input value
41                if (userAnswer === "Others") {
42                    const otherInput = currentItem.find('input[type="text"][name="question_' + currentQuestionId + '_other"]');
43                    userAnswer = otherInput.val().trim(); // Get value of the "Others" text input
44                }
45            }
46
47            // Check if the current question is a text area question
48            let textAreaInput = currentItem.find('textarea');
49            if (textAreaInput.length > 0) {
50                userAnswer = textAreaInput.val();
51            }
52
53            // Store the answer in the array
54            answer[currentStep - 2] = userAnswer || ''; // Adjust index for zero-based array
55        }
56    }
57
58    // Function to handle the "I Don't Want To" action
59    function handleSkipStep() {
60        const validationMessage = $('.tjp-popup-feedback__message');
61        // Clear any previous validation messages
62        validationMessage.text('').removeClass('active');
63        answer[currentStep - 1] = '';  // Set answer as empty for "I Don't Want To"
64    }
65
66    // Function to handle AJAX request to the questionnaire list
67    function fetchQuestionnaireList() {
68        $.ajax({
69            url: baseURL + '/questionnaire/list', // Construct full URL using baseURL
70            type: 'GET',
71            dataType: 'json',
72            success: function(response) {
73                if (!response.error) {
74                    const greetings = response.data.greetings;
75                    const questions = response.data.questions;
76                    totalSteps = questions.length + 1;  
77                    showFeedback();
78                    clearFeedbackItems();
79                    initializeQuestionnaire(questions);  // Initialize arrays for questions/answers
80                    generateGreetingItem(greetings);
81                    generateFeedbackItems(questions); // Call function to generate HTML
82                    updateProgress(); // Initialize progress bar
83                } else {
84                    console.error('Error fetching data:', response.message);
85                }
86            },
87            error: function(xhr, status, error) {
88                console.error('Error fetching questionnaire list:', error);
89            }
90        });
91    }
92
93    // Function to submit the questionnaire
94    function submitQuestionnaire() {
95        $(".tjp-feedback-btn--next").html(`<i class="fa fa-spinner fa-spin"></i> Processing`);
96        $(".tjp-feedback-btn--next").prop("disabled", true);
97        // Create a new FormData object
98        let formData = new FormData();
99
100        // Append question and answer pairs to the FormData object
101        question.forEach((q, index) => {
102            formData.append('questionnaire_id[]', q);  // Append each question ID
103            formData.append('answer[]', answer[index]); // Append corresponding answer
104        });
105        formData.append('unique_id', getRandomId());  // Append each question ID
106
107        // Make the AJAX POST request
108        $.ajax({
109            url: baseURL + '/questionnaires/submit', // Your endpoint URL
110            headers: {
111                'X-CSRF-Token': csrfToken,
112            },
113            type: 'POST',
114            data: formData,
115            processData: false, // Important to prevent jQuery from processing the data
116            contentType: false, // Important to set this to false for FormData
117            success: function(response) {
118                $(".tjp-feedback-btn--next").html(`Submit`);
119                $(".tjp-feedback-btn--next").prop("disabled", false);
120                localStorage.setItem('questionnaireSubmitted', 'true');
121                showOutro();
122            },
123            error: function(xhr, status, error) {
124                $(".tjp-feedback-btn--next").html(`Submit`);
125                $(".tjp-feedback-btn--next").prop("disabled", false);
126                console.error('Submission failed:', error);
127            }
128        });
129    }
130
131    // Function to clear only dynamic feedback items (index > 0)
132    function clearFeedbackItems() {
133        $('.tjp-feedback-item').not(':first').remove(); // Remove all feedback items except the first
134    }
135
136    // Function to clear input fields in the current step
137    function clearCurrentStepInputs() {
138        const currentItem = $('.tjp-feedback-item.active');
139
140        // Clear text inputs
141        currentItem.find('input[type="text"]').val('');
142        
143        // Clear radio buttons
144        currentItem.find('input[type="radio"]').prop('checked', false);
145        
146        // Clear text areas
147        currentItem.find('textarea').val('');
148    }
149
150    function validateCurrentStepInputs() {
151        const currentItem = $('.tjp-feedback-item.active');  // Current active feedback step
152        const validationMessage = $('.tjp-popup-feedback__message');
153    
154        // Clear any previous validation messages
155        validationMessage.text('').removeClass('active');
156    
157        // Check for radio buttons
158        const radioButtons = currentItem.find('input[type="radio"]');
159        const selectedRadio = currentItem.find('input[type="radio"]:checked');
160    
161        // If there are radio buttons and none are selected, show an error
162        if (radioButtons.length > 0 && selectedRadio.length == 0) {
163            validationMessage.text('Please select one of the options.').addClass('active');
164            return false;
165        }
166    
167        // If "Others" is selected, validate the accompanying text input
168        if (selectedRadio.val() == "Others") {
169            const otherInput = currentItem.find('input[type="text"][name^="question_"][name$="_other"]');
170            if (otherInput.val().trim() == '') {
171                validationMessage.text('Please fill in the "Others" field.').addClass('active');
172                return false;
173            }
174        }
175    
176        // Check for text area input
177        const textAreaInput = currentItem.find('textarea');
178        if (textAreaInput.length > 0 && textAreaInput.val().trim() == '') {
179            validationMessage.text('Please fill in the text area.').addClass('active');
180            return false;
181        }
182    
183        const otherRadio = currentItem.find('input[type="radio"][value="Others"]');
184        // Check for standalone text inputs (e.g., "Others" input without radio button)
185        const textInput = currentItem.find('input[type="text"]');
186        if (textInput.length > 0 && !otherRadio.length && textInput.val().trim() === '') {
187            validationMessage.text('Please fill in the required text field.').addClass('active');
188            return false;
189        }
190    
191        // If no validation conditions are triggered, return true (valid)
192        return true;
193    }    
194
195    function generateGreetingItem(data) {
196        const feedbackContainer = $('.tjp-popup-feedback__content'); // Assuming this is where you want to display it
197        const greetingsHTML = `
198            <div class="tjp-feedback-item active">
199                <h1 class="tjp-feedback-title">${data.title}</h1>
200                <div class="tjp-feedback-description">
201                    <p>${data.description}</p>
202                </div>
203            </div>
204        `;
205        feedbackContainer.prepend(greetingsHTML); // Add greetings to the top of the feedback content
206    }
207
208    // Function to generate feedback items based on the response
209    function generateFeedbackItems(data) {
210        var feedbackContainer = $('.tjp-popup-feedback__content');
211
212        data.forEach(function(item, index) {
213            // Create the feedback item container
214            var feedbackItem = $('<div class="tjp-feedback-item"></div>');
215
216            // Add the question title
217            var questionTitle = $('<h1 class="tjp-feedback-title"></h1>').text(item.question);
218            feedbackItem.append(questionTitle);
219
220            // Check question type and append relevant input fields
221            if (item.question_type === 'option') {
222                var optionsContainer = $('<div class="tjp-feedback-options"></div>');
223                
224                // Loop through the options and create radio buttons
225                item.options.forEach(function(option, idx) {
226                    var optionItem = $('<div class="tjp-feedback-options__item"></div>');
227                    var radioButton = `
228                        <div class="tjp-radio-button">
229                            <input type="radio" id="q${item.id}_option_${idx}" name="question_${item.id}" value="${option}">
230                            <label for="q${item.id}_option_${idx}">
231                                <span class="name">${capitalizeEachWord(option)}</span>
232                            </label>
233                        </div>`;
234                    optionItem.append(radioButton);
235                    optionsContainer.append(optionItem);
236                });
237                feedbackItem.append(optionsContainer);
238
239            } else if (item.question_type === 'option_with_text') {
240                // Handle "option_with_text" question type
241                var optionsContainer = $('<div class="tjp-feedback-options"></div>');
242                
243                // Loop through the options and create radio buttons
244                item.options.forEach(function(option, idx) {
245                    if (option.toLowerCase() === "others") {
246                        var otherInput = `
247                            <div class="tjp-feedback-options__item tjp-other-option">
248                                <div class="tjp-radio-button">
249                                    <input type="radio" id="q${item.id}_other_radio" name="question_${item.id}" value="Others">
250                                    <label for="q${item.id}_other_radio">
251                                        <span class="name">&nbsp;</span>
252                                    </label>
253                                    <input type="text" id="q${item.id}_other_text" name="question_${item.id}_other" placeholder="Others...">
254                                </div>
255                            </div>`;
256                        optionsContainer.append(otherInput);
257                    
258                        // Attach event listener for the input text field
259                        optionsContainer.on('input', `#q${item.id}_other_text`, function() {
260                            let textValue = $(this).val();
261                    
262                            // Automatically select the "Others" radio button when the user types something
263                            if (textValue.trim() !== "") {
264                                $(`#q${item.id}_other_radio`).prop('checked', true);
265                            } else {
266                                $(`#q${item.id}_other_radio`).prop('checked', false);
267                            }
268                        });
269                    } else {
270                        var optionItem = $('<div class="tjp-feedback-options__item"></div>');
271                        var radioButton = `
272                            <div class="tjp-radio-button">
273                                <input type="radio" id="q${item.id}_option_${idx}" name="question_${item.id}" value="${option}">
274                                <label for="q${item.id}_option_${idx}">
275                                    <span class="name">${capitalizeEachWord(option)}</span>
276                                </label>
277                            </div>`;
278                        optionItem.append(radioButton);
279                        optionsContainer.append(optionItem);
280                    }
281                });
282                feedbackItem.append(optionsContainer);
283
284            } else if (item.question_type === 'text') {
285                // For "text" type, append a textarea
286                var textArea = `
287                    <div class="tjp-feedback-form">
288                        <textarea class="tjp-feedback-form__input" id="q${item.id}_textarea" name="question_${item.id}" rows="6"  placeholder="Type here..."></textarea>
289                    </div>`;
290                feedbackItem.append(textArea);
291            }
292
293            // Append the generated feedback item to the container
294            feedbackContainer.append(feedbackItem);
295        });
296    }
297
298    function showFeedback() {
299        $(".tjp-popup-feedback__loader").removeClass("active");
300        $(".tjp-popup-feedback__content").addClass("active");
301        $(".tjp-popup-feedback__footer").addClass("active");
302    }
303
304    function showOutro() {
305        $(".tjp-popup-feedback__loader").removeClass("active");
306        $(".tjp-popup-feedback__content").removeClass("active");
307        $(".tjp-popup-feedback__message").removeClass("active");
308        $(".tjp-popup-feedback__footer").removeClass("active");
309        $(".tjp-popup-feedback__complete").addClass("active");
310    }
311
312    function loadFeedback() {
313        $(".tjp-popup-feedback__loader").addClass("active");
314        $(".tjp-popup-feedback__content").removeClass("active");
315        $(".tjp-popup-feedback__footer").removeClass("active");
316    }
317
318    // Update the progress bar width
319    function updateProgress() {
320        if (currentStep <= totalSteps) {
321            let progressPercentage = (currentStep / totalSteps) * 100;
322            $('.tjp-feedback-progress').css('width', progressPercentage + '%');
323        }
324    }
325
326    // Function to move to the next feedback step
327    function moveToNextFeedback() {
328        $('.tjp-feedback-item.active').removeClass('active').next().addClass('active');
329        currentStep++;
330        updateProgress();
331    }
332
333    // Event handler for the "Next" button
334    $('.tjp-popup-feedback').on('click', '.tjp-feedback-btn--next', function() {
335        if (currentStep > 0) {
336            $(".tjp-feedback-btn--skip").addClass("active");
337        }
338
339        // Validate inputs for the current step
340        if (currentStep <= totalSteps) {
341            if (validateCurrentStepInputs()) {
342                storeCurrentStepAnswer(); // Store current answer
343
344                // Move to the next step
345                moveToNextFeedback();  
346            }
347        }
348
349        // Update button text and visibility
350        if (currentStep == totalSteps) {
351            $(".tjp-feedback-btn--next").html("Submit");
352            $(".tjp-feedback-btn--skip").hide();
353        }
354
355        // If currentStep exceeds totalSteps, submit the questionnaire
356        if (currentStep > totalSteps) {
357            submitQuestionnaire();
358        }
359    });
360
361    // Event handler for the "I Don't Want To" button
362    $('.tjp-popup-feedback').on('click', '.tjp-feedback-btn--skip', function() {
363        if (currentStep <= totalSteps) {
364            clearCurrentStepInputs();
365            handleSkipStep();  // Set answer to empty
366            moveToNextFeedback();  // Move to next step
367        }
368    });
369
370    $(".tjp-feedback-close").click(function() {
371        $(".tjp-popup-feedback").removeClass("active");
372    })
373
374    $('.tjp-popup-feedback').on('click', '.tjp-feedback-btn--close', function() {
375        $(".tjp-popup-feedback").removeClass("active");
376    });
377
378
379    if (isPartnerIP == '1' && isPayPerArticle == '0') {
380        if (localStorage.getItem('questionnaireSubmitted') !== 'true') {
381            loadFeedback();
382            fetchQuestionnaireList();
383
384            setTimeout(() => {
385                $(".tjp-popup-feedback").addClass("active");
386            }, 3000);
387        }
388    }
389});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.