0 votes
in SoSci Survey (dt.) by s109993 (12.0k points)
edited by s109993

Ich habe einen Date Picker erstellt, bei welchem die Daten bei mehrmaligen einloggen gespeichert werden mit "localStorage" (JS). Das funktioniert ganz gut, ist aber etwas risikoreich, falls ein Teilnehmer ein anderes Endgerät benutzt bzw. einen anderen Browser. Da ich sowieso mit einem Teilnahmecode arbeiten werde, würde ich gerne localstorage mit dem equivalent ersetzen, welches die Daten über den Teilnahme code speichert. Gibt es da etwas? Die angezeigten Daten könnte ich ggf. über die interen Variablen anzeigen lassen (geht das auf der gleichen Seite, da php?). Aber wie würde das mit dem Speichern der farblich hinterlegten Daten im Kalender funktionieren?

Damit es ein bisschen besser verständlich ist, hier der Pre-Test Link direkt zur Seite: https://ofb.iea-hamburg.de/test111/?act=4ZXfm1iyiW3FlkTdub8aexN2

EDIT BILD:

Danke und viele Grüße

1 Answer

0 votes
by SoSci Survey (327k points)

Sie können bei internen Variablen einstellen, dass der Inhalt "im Hintergrund übertragen" wird. Diese Optionen können Sie auswählen, wenn Sie eine interne Variable (also quasi das Item) links in der Navigation anklicken.

Ein JavaScript schreibt die Daten dann nach jeder Änderung auf den Server in den Datensatz.

Geht das in die Richtung, was Sie suchen?

by s109993 (12.0k points)
Entschuldigen Sie die späte Rückmeldung, ich war im Urlaub :) Ich glaube nicht. Ich habe quasi eine Funktion, welche die Werte aus internen Variablen anzeigt. Das habe ich (damals SoSci unabhängig) über local storage gelöst. Jetzt komme ich allerdings zu der SoSci optimierung, und da ist localstorage nicht ganz geeignet.

Die User sollen sich über eine unique SERIAL einloggen, d.h. die Werte sind ja quasi eindeutig zuzuordnen und können über die SERIAL/ den Login abgerufen werden. Ist mein Gedankengang soweit richtig?

Ich hänge gerade an der optimalen Umsetzung dafür. Ich habe den Teil des Codes (mit localstorage) einmal hier rein kopiert. Zur besseren Visualisierung hier noch der Pre-Test Link auf die Seite: https://ofb.iea-hamburg.de/test111/?act=LQeKj6elMV7y12X8DsIcyYMo

Der Code:
    // Function to save selected dates to localStorage and form variables
    function saveSelectedDates() {
        try {
            if (allSelectedDates.length > variables.length) {
                throw new Error('Not enough variables to store all selected dates.');
            }

            for (let i = 0; i < variables.length; i++) {
                const element = document.getElementById(variables[i]);

                if (i < allSelectedDates.length) {
                    const range = allSelectedDates[i];
                    const startFormatted = formatDate(range.start);
                    const endFormatted = range.end ? formatDate(range.end) : '';
                    element.value = endFormatted ? `${startFormatted} - ${endFormatted}` : startFormatted;
                } else {
                    element.value = '';
                }
            }

            localStorage.setItem('selectedDates', JSON.stringify(allSelectedDates));
            console.log('Saved dates:', allSelectedDates);
            return true; // Return true on success
        } catch (error) {
            console.error('Error saving dates:', error);
            return false; // Return false on failure
        }
    }

    // Function to load selected dates from localStorage
    function loadSelectedDates() {
        const savedDates = localStorage.getItem('selectedDates');
        if (savedDates) {
            allSelectedDates = JSON.parse(savedDates).map(range => ({
                start: new Date(range.start),
                end: range.end ? new Date(range.end) : null
            }));
            displaySelectedDates();
            updateCalendar();
        }
    }
by SoSci Survey (327k points)
> Die User sollen sich über eine unique SERIAL einloggen, d.h. die Werte sind ja quasi eindeutig zuzuordnen und können über die SERIAL/ den Login abgerufen werden. Ist mein Gedankengang soweit richtig?

SoSci Survey setzt ein Interview in aller Regel fort, wenn es mit derselben Serial wieder aufgerufen wird.

Beschreiben Sie mir doch bitte kurz die Situation (wird das Interviews mehrfach aufgerufen? Geht es um eine routinemäßige Unterbrechung/Fortsetzung?) und womit Ihr JavaScript genau interagiert? Ich lese aus demm Code heraus, dass Sie ein Kalender-Element ansteuern - und etwas daraus speichern möchten.

Statt dem localStorage.getItem('selectedDates') könnten Sie aus meiner Sicht auch ein document.getElementById('<InterneVariable>').value verwenden.
by s109993 (12.0k points)
Genau, es ist ein Kalender mit dem Testleiter Daten zur Verfügbarkeit eintragen sollen. Da der SoSci Kalender keine Multi-Daten Unterstützt, habe ich den Kalender selbst erstellt. Testleiter sollen sich einloggen, Daten auswählen und speichern. Die Umfrage wird dabei nicht beendet, sondern die Daten nur abgespeichert und angezeigt. Dadurch können sich die Testleiter später wieder einloggen, und ihren Auswahl verändern, löschen etc. Das ganze Script ist etwas über 12000 Zeichen, deshalb kann ich es hier nicht posten.
by s109993 (12.0k points)
<div class="date-picker-container">
    <div class="header">
        <button type="button" id="prev-year" class="double-arrow">&lt;&lt;</button>
        <button type="button" id="prev-month" class="arrow">&lt;</button>
        <span id="month-year"></span>
        <button type="button" id="next-month" class="arrow">&gt;</button>
        <button type="button" id="next-year" class="double-arrow">&gt;&gt;</button>
    </div>
    <div class="calendar" id="calendar"></div>
    <br>
    <button type="button" id="show-dates">Datum auswählen</button>
    <div class="selected-dates" id="selected-dates"></div>
</div>
by s109993 (12.0k points)
<script>
document.addEventListener('DOMContentLoaded', function () {
    // DOM elements
    const calendarElement = document.getElementById('calendar');
    const selectedDatesElement = document.getElementById('selected-dates');
    const showDatesButton = document.getElementById('show-dates');
    const submitButton = document.getElementById('submit0');
    const monthYearDisplay = document.getElementById('month-year');
    const prevMonthButton = document.getElementById('prev-month');
    const nextMonthButton = document.getElementById('next-month');
    const prevYearButton = document.getElementById('prev-year');
    const nextYearButton = document.getElementById('next-year');

    // Variables for date selection and current date
    let selectedStartDate = null;
    let selectedEndDate = null;
    let currentDate = new Date();
    let allSelectedDates = [];
    const variables = ["XX01_01", "XX01_02", "XX01_03", "XX01_04", "XX01_05", "XX01_06", "XX01_07", "XX01_08", "XX01_10", "XX01_11", "XX01_12"];

    // Weekdays array
    const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

    // Function to create the calendar for a given year and month
    function createCalendar(year, month) {
        calendarElement.innerHTML = '';

        // Create weekday headers
        weekdays.forEach(day => {
            const dayElement = document.createElement('div');
            dayElement.textContent = day;
            dayElement.classList.add('weekday');
            calendarElement.appendChild(dayElement);
        });

        // Calculate the first day of the month and total days in the month
        const firstDayOfMonth = new Date(year, month, 1).getDay();
        const daysInMonth = new Date(year, month + 1, 0).getDate();
        
        // Adjust start day to match the calendar's layout
        const startDay = firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1;

        // Create placeholders for days before the first day of the month
        for (let i = 0; i < startDay; i++) {
            const placeholder = document.createElement('div');
            calendarElement.appendChild(placeholder);
        }

        // Create day elements for each day in the month
        for (let day = 1; day <= daysInMonth; day++) {
            const dayElement = document.createElement('div');
            dayElement.textContent = day;
            const date = new Date(year, month, day);
            if (date.getDay() === 6 || date.getDay() === 0) {
                dayElement.classList.add('weekend');
            } else {
                dayElement.addEventListener('click', (event) => toggleDateSelection(date, event));
            }
            calendarElement.appendChild(dayElement);
        }

        // Update the calendar to show selected dates
        updateCalendar();
    }

    // Function to handle date selection logic
    function toggleDateSelection(date, event) {
        if (date.getDay() === 6 || date.getDay() === 0) {
            return;
        }

        // Check if Ctrl key is pressed for multi-select
        if (event.ctrlKey) {
            const range = { start: date, end: null };
            allSelectedDates.push(range);
        } else {
            if (!selectedStartDate || (selectedStartDate && selectedEndDate)) {
                selectedStartDate = date;
                selectedEndDate = null;
            } else if (date < selectedStartDate) {
                selectedEndDate = selectedStartDate;
                selectedStartDate = date;
            } else {
                selectedEndDate = date;
            }
        }
        updateCalendar();
    }

    // Function to update the calendar display with selected dates
    function updateCalendar() {
        const dayElements = calendarElement.querySelectorAll('div:not(.weekday)');
        const currentYear = currentDate.getFullYear();
        const currentMonth = currentDate.getMonth();

        dayElements.forEach(dayElement => {
            const day = dayElement.textContent;
            const date = new Date(currentYear, currentMonth, day);
            dayElement.classList.remove('selected', 'in-range');

            // Highlight all selected date ranges
            allSelectedDates.forEach(range => {
                if (!range.end && date.toDateString() === range.start.toDateString()) {
                    dayElement.classList.add('selected');
                } else if (range.start && range.end && date >= range.start && date <= range.end) {
                    dayElement.classList.add('in-range');
                }
            });

            // Highlight currently selected start and end dates
            if (selectedStartDate && date.toDateString() === selectedStartDate.toDateString()) {
                dayElement.classList.add('selected');
            }
            if (selectedEndDate && date.toDateString() === selectedEndDate.toDateString()) {
                dayElement.classList.add('selected');
            }
            if (selectedStartDate && selectedEndDate && date > selectedStartDate && date < selectedEndDate) {
                dayElement.classList.add('in-range');
            }
        });

        // Display the current month and year
        monthYearDisplay.textContent = `${currentDate.toLocaleString('default', { month: 'long' })} ${currentYear}`;
    }

    // Function to show selected date ranges
    function showSelectedDates() {
        if (selectedStartDate) {
            const range = { start: selectedStartDate, end: selectedEndDate };
            allSelectedDates.push(range);
            selectedStartDate = null;
            selectedEndDate = null;
        }
        displaySelectedDates();
        updateCalendar();
    }

    // Function to display selected date ranges
    function displaySelectedDates() {
        selectedDatesElement.innerHTML = '';
        allSelectedDates.forEach((range, index) => {
            const rangeElement = document.createElement('div');
            if (range.end) {
                const startDateFormatted = formatDate(range.start);
                const endDateFormatted = formatDate(range.end);
                rangeElement.innerHTML = `<span>${startDateFormatted}</span> -&nbsp; <span> ${endDateFormatted}</span>`;
            } else {
                const startDateFormatted = formatDate(range.start);
                rangeElement.innerHTML = `<span>${startDateFormatted}</span>`;
            }

            const deleteButton = document.createElement('button');
            deleteButton.innerHTML = '&#128465;';
            deleteButton.classList.add('delete-button');
            deleteButton.addEventListener('click', () => deleteSelectedDate(index));

            rangeElement.appendChild(deleteButton);
            selectedDatesElement.appendChild(rangeElement);
        });
    }

    // Function to delete a selected date range
    function deleteSelectedDate(index) {
        // Remove the selected date range from the list
        allSelectedDates.splice(index, 1);

        // Clear the form variables and shift remaining values
        for (let i = index; i < variables.length - 1; i++) {
            const currentElement = document.getElementById(variables[i]);
            const nextElement = document.getElementById(variables[i + 1]);

            if (currentElement && nextElement) {
                currentElement.value = nextElement.value;
            }
        }

        // Clear the last variable
        const lastElement = document.getElementById(variables[allSelectedDates.length]);

        if (lastElement) lastElement.value = '';

        saveSelectedDates();
        displaySelectedDates();
        updateCalendar();
    }

    // Function to format dates as DD.MM.YYYY
    function formatDate(date) {
        const day = date.getDate().toString().padStart(2, '0');
        const month = (date.getMonth() + 1).toString().padStart(2, '0');
        const year = date.getFullYear();
        return `${day}.${month}.${year}`;
    }

    // Function to change the current month
    function changeMonth(offset) {
        currentDate.setMonth(currentDate.getMonth() + offset);
        createCalendar(currentDate.getFullYear(), currentDate.getMonth());
    }

    // Function to change the current year
    function changeYear(offset) {
        currentDate.setFullYear(currentDate.getFullYear() + offset);
        createCalendar(currentDate.getFullYear(), currentDate.getMonth());
    }

    // Function to save selected dates to localStorage and form variables
    function saveSelectedDates() {
        try {
            if (allSelectedDates.length > variables.length) {
                throw new Error('Not enough variables to store all selected dates.');
            }

            for (let i = 0; i < variables.length; i++) {
                const element = document.getElementById(variables[i]);

                if (i < allSelectedDates.length) {
                    const range = allSelectedDates[i];
                    const startFormatted = formatDate(range.start);
                    const endFormatted = range.end ? formatDate(range.end) : '';
                    element.value = endFormatted ? `${startFormatted} - ${endFormatted}` : startFormatted;
                } else {
                    element.value = '';
                }
            }

            localStorage.setItem('selectedDates', JSON.stringify(allSelectedDates));
            console.log('Saved dates:', allSelectedDates);
            return true; // Return true on success
        } catch (error) {
            console.error('Error saving dates:', error);
            return false; // Return false on failure
        }
    }

    // Function to load selected dates from localStorage
    function loadSelectedDates() {
        const savedDates = localStorage.getItem('selectedDates');
        if (savedDates) {
            allSelectedDates = JSON.parse(savedDates).map(range => ({
                start: new Date(range.start),
                end: range.end ? new Date(range.end) : null
            }));
            displaySelectedDates();
            updateCalendar();
        }
    }

    // Initial setup
    createCalendar(currentDate.getFullYear(), currentDate.getMonth());
    loadSelectedDates();

    // Event listeners for buttons
    showDatesButton.addEventListener('click', showSelectedDates);
    prevMonthButton.addEventListener('click', () => changeMonth(-1));
    nextMonthButton.addEventListener('click', () => changeMonth(1));
    prevYearButton.addEventListener('click', () => changeYear(-1));
    nextYearButton.addEventListener('click', () => changeYear(1));

    // Event listener for the submit button
    submitButton.addEventListener('click', () => {
        const success = saveSelectedDates();
        if (success) {
            alert('Dates have been saved successfully.');
        } else {
            alert('Ups, there was a problem, please contact the admin.');
        }
    });
});
</script>
by SoSci Survey (327k points)
Okay, dann bleibe ich bei meiner Empfehlung:

(1) Das Kalender-Script sollte die Daten in einer internen Variable ablegen (als JSON), das würde einfach den localstorage ersetzen.

(2a) Die interne Variable sollte so konfiguriert werden, dass sie die Daten direkt im Hintergrund übermittelt.

(2b) Alternativ könnte man einen Weiter-Knopf "Bestätigen" auf die Seite packen, um die Daten in den Datensatz zu schreiben - verbunden mit einem setNextPage(), welches dann wieder zur selben Seite zurück führt.

Schildern Sie mir gerne, welche Hürden/nicht erfüllten Anforderungen Sie bei dieser Umsetzung sehen?
by s109993 (12.0k points)
(1) das wird schon perfekt in den internen Variablen abgespeichert und klappt wunderbar.

(2b) hier verwende ich diesen Weg, mit einem "Speicher Button".. Auf der nächsten Seite ist ein GoToPage(); Verweis, der wieder eine Seite zurück geht. Für die Nutzer sieht es dann nur so aus, als würde die Seite aktualisiert werde.

Was nicht geht (bzw. momentan geht alles, aber eben nur über localStorage) ist, dass die gespeicherten Werte (Daten) bei einem erneuten Login mit einer Serial in einem anderen Engerät/Browser nicht mehr angezeigt werden. Es macht nur Sinn, wenn die User Ihre ausgewählten Daten sehen und dann ggf. löschen oder verändern können.
by SoSci Survey (327k points)
>  Für die Nutzer sieht es dann nur so aus, als würde die Seite aktualisiert werde.

Wenn Sie auf der folgenden Seite ein repeatPage() verwenden statt dem goToPage(), könnten Sie eine Meldung "Wurde gespeichert" anzeigen - oder eben nicht, wenn das Interview erneut aufgerufen wird.

> dass die gespeicherten Werte (Daten) bei einem erneuten Login mit einer Serial in einem anderen Engerät/Browser nicht mehr angezeigt werden.

Lassen Sie sich bitte mal anzeigen, ob das richtige Interview fortgesetzt wird und ob die Daten vorliegen:

debug(caseNumber());
debug(value('IV01_01'));

Wenn das Interview nicht fortgesetzt wird, würde ich auf die Einstellungen zum Grenzwert unter "Fragebogen zusammenstellen" -> "Einstellungen" tippen.

Wenn die Daten vorliegen, der Kalender sie aber nicht anzeigt, dann wäre meine Vermutung, dass beim Einlesen aus dem <input> im JavaScript etwas nicht passt.
by s109993 (12.0k points)
> Wenn Sie auf der folgenden Seite ein repeatPage() verwenden statt dem goToPage(), könnten Sie eine Meldung "Wurde gespeichert" anzeigen - oder eben nicht, wenn das Interview erneut aufgerufen wird.

Das mache ich über alert() in JS, aber könnte das auch noch entsprechend umändern.


>Wenn die Daten vorliegen, der Kalender sie aber nicht anzeigt, dann wäre meine Vermutung, dass beim Einlesen aus dem <input> im JavaScript etwas nicht passt.

Das Interview wird nicht fortgesetzt, sondern es wird immer eine neue Datenspalte erstellt. Ich versuche morgen noch ein paar Dinge. Da dieser Kalender bei uns fü die Testleiter Orga benutzt werden soll und dementsprechen für viele Studien im Einsatz sein wird noch eine Frage: Falls ich die Anpassung an SoSci nicht hinbekommen sollte, wäre es möglich den Arbeitsauftrag an Sie auf Rechnung abzugeben?
by SoSci Survey (327k points)
Vermutlich müssen Sie nur in den Fragebogen-Einstellungen festlegen, dass das Interview unabhängig von MISSING immer fortgesetzt werden soll. Aber ja, langjährigen Kunden greife ich bei Bedarf natürlich auch mal unter die Arme.
by s109993 (12.0k points)
Den Teil mit der Interview Fortsetzung läuft inzwischen, das war auch nicht das Hauptproblem :)  ich habe oben in die Frage ein Screenshot gesetzt. Die ausgewählten und dann gespeicherten Daten werden unterhalb desKalenders angezeigt. Wenn ich jetzt den kalender mit einem anderen Endgerät öffne (aber mit der gleichen SERIAL), dann werden die ausgewählten Daten nicht mehr angezeigt, da diese eben über localstorage gespeichert wurden und nicht über die URL. Das ist für mich momentan der Knackpunkt.
by s109993 (12.0k points)
So... ich habe es wohl doch hinbekommen. Ehrlicherweise mit Hilfe von Chatgpt, aber es funktioniert so wie es soll und ich habe bisher keine Fehler finden können. Das muss jetzt von meinen Kolleg:innen noch ausführlich getestet werden. Danke für Ihre Tipps und Unterstützung.

  // Function to save selected dates to SoSci placeholders
    function saveSelectedDates() {
        try {
            if (allSelectedDates.length > variables.length) {
                throw new Error('Not enough variables to store all selected dates.');
            }

            for (let i = 0; i < variables.length; i++) {
                const element = document.getElementById(variables[i]);

                if (i < allSelectedDates.length) {
                    const range = allSelectedDates[i];
                    const startFormatted = formatDate(range.start);
                    const endFormatted = range.end ? formatDate(range.end) : '';
                    element.value = endFormatted ? `${startFormatted} - ${endFormatted}` : startFormatted;
                } else {
                    element.value = '';
                }
            }

            console.log('Saved dates:', allSelectedDates);
            return true; // Return true on success
        } catch (error) {
            console.error('Error saving dates:', error);
            return false; // Return false on failure
        }
    }

    // Function to load selected dates from SoSci placeholders
    function loadSelectedDates() {
        allSelectedDates = variables
            .map(variable => {
                const element = document.getElementById(variable);
                if (element && element.value) {
                    const [startStr, endStr] = element.value.split(' - ');
                    return {
                        start: new Date(startStr.split('.').reverse().join('-')),
                        end: endStr ? new Date(endStr.split('.').reverse().join('-')) : null
                    };
                }
                return null;
            })
            .filter(range => range !== null);

        displaySelectedDates();
        updateCalendar();
    }

Willkommen im Online-Support von SoSci Survey.

Hier bekommen Sie schnelle und fundierte Antworten von anderen Projektleitern und direkt von SoSci Survey.

→ Eine Frage stellen


Welcome to the SoSci Survey online support.

Simply ask a question to quickly get answers from other professionals, and directly from SoSci Survey.

→ Ask a Question

...