Skip to content
Snippets Groups Projects
Select Git revision
  • fd79564f43de846e34cfdb8eb5e434471fe47863
  • main default protected
  • pdf-annotieren
  • pdf-annotieren-2.0
  • issue-4244
  • issues-4244-b
  • pdf-annotieren-old
  • biest-4274
  • issue-2982
  • issue-660
  • issue-3326
  • issue-3270
  • issue-3616
  • 5.1
  • 5.2
  • 5.3
  • 5.4
  • 5.5
  • issue-4255
  • issue-4261
  • issue-4262
  • v5.4.2
  • v5.3.5
  • v5.2.7
  • v5.1.8
  • v5.4.1
  • v5.3.4
  • v5.2.6
  • v5.1.7
  • v5.0.9
  • v5.4
  • v5.3.3
  • v5.2.5
  • v5.1.6
  • v5.0.8
  • v5.3.2
  • v5.2.4
  • v5.1.5
  • v5.0.7
  • v5.3.1
  • v5.2.3
41 results

StudipTreeTable.vue

Blame
  • Forked from Stud.IP / Stud.IP
    Source project has a limited visibility.
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    StudipTreeTable.vue 15.05 KiB
    <template>
        <div v-if="isLoading">
            <studip-progress-indicator></studip-progress-indicator>
        </div>
        <article v-else class="studip-tree-table">
            <header>
                <tree-breadcrumb v-if="currentNode.id !== 'root'" :node="currentNode"
                                 :icon="breadcrumbIcon" :editable="editable" :edit-url="editUrl" :create-url="createUrl"
                                 :delete-url="deleteUrl" :show-navigation="showStructureAsNavigation"
                                 :num-children="children.length" :num-courses="courses.length"
                                 :assignable="assignable" :visible-children-only="visibleChildrenOnly"></tree-breadcrumb>
            </header>
            <section v-if="withChildren && !currentNode.attributes['has-children']" class="studip-tree-node-no-children">
                {{ $gettext('Auf dieser Ebene existieren keine weiteren Unterebenen.')}}
            </section>
    
            <span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span>
    
            <div v-if="currentNode.attributes.description?.trim() !== ''"
                 v-html="currentNode.attributes['description-formatted']"></div>
    
            <section v-if="thisLevelCourses === 0" class="studip-tree-node-no-courses">
                {{ $gettext('Auf dieser Ebene sind keine Veranstaltungen zugeordnet.')}}
            </section>
    
            <section v-if="thisLevelCourses + subLevelsCourses > 0">
                <span v-if="withCourses && showingAllCourses">
                    <button type="button" @click="showAllCourses(false)"
                            :title="$gettext('Veranstaltungen auf dieser Ebene anzeigen')">
                        {{ $gettext('Veranstaltungen auf dieser Ebene anzeigen') }}
                    </button>
                </span>
                <template v-if="thisLevelCourses > 0 && subLevelsCourses > 0">
                    |
                </template>
                <span v-if="withCourses && subLevelsCourses > 0 && !showingAllCourses">
                    <button type="button" @click="showAllCourses(true)"
                            :title="$gettext('Veranstaltungen auf allen Unterebenen anzeigen')">
                        {{ $gettext('Veranstaltungen auf allen Unterebenen anzeigen') }}
                    </button>
                </span>
            </section>
    
            <table v-if="currentNode.attributes['has-children'] || courses.length > 0" class="default">
                <caption class="studip-tree-node-info">
                    <span v-if="withChildren && children.length > 0">
                        {{ $gettextInterpolate($gettext('%{ count } Unterebenen'), { count: children.length }) }}
                    </span>
                    <span v-if="withChildren && children.length > 0 && withCourses && courses.length > 0">
                        ,
                    </span>
                </caption>
                <colgroup>
                    <col style="width: 20px">
                    <col style="width: 30px">
                    <col>
                    <col style="width: 40%">
                </colgroup>
                <thead>
                    <tr>
                        <th></th>
                        <th>{{ $gettext('Typ') }}</th>
                        <th>{{ $gettext('Name') }}</th>
                        <th>{{ $gettext('Information') }}</th>
                    </tr>
                </thead>
                <draggable v-model="children" handle=".drag-handle" :animation="300"
                           @end="dropChild" tag="tbody" role="listbox">
                    <tr v-for="(child, index) in children" :key="index" class="studip-tree-child">
                        <td>
                            <a v-if="editable && children.length > 1" class="drag-link" role="option"
                               tabindex="0"
                               :title="$gettextInterpolate($gettext('Sortierelement für Element %{node}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.'), {node: child.attributes.name})"
                               @keydown="keyHandler($event, index)"
                               :ref="'draghandle-' + index">
                                <span class="drag-handle"></span>
                            </a>
                        </td>
                        <td>
                            <studip-icon :shape="child.attributes['has-children'] ? 'folder-full' : 'folder-empty'"
                                         :size="26"></studip-icon>
                        </td>
                        <td>
                            <a :href="nodeUrl(child.id, semester !== 'all' ? semester : null)" tabindex="0"
                               @click.prevent="openNode(child)"
                               :title="$gettextInterpolate($gettext('Unterebene %{ node } öffnen'),
                                    { node: node.attributes.name })">
                                {{ child.attributes.name }}
                            </a>
                        </td>
                        <td>
                            <tree-node-course-info :node="child" :semester="semester"
                                                   :sem-class="semClass"></tree-node-course-info>
                        </td>
                    </tr>
                    <tr v-for="(course) in courses" :key="course.id" class="studip-tree-child studip-tree-course">
                        <td></td>
                        <td>
                            <studip-icon shape="seminar" :size="26"></studip-icon>
                        </td>
                        <td>
                            <a :href="courseUrl(course.id)" tabindex="0"
                               :title="$gettextInterpolate($gettext('Zur Veranstaltung %{ course }'),
                                    { course: course.attributes.title })">
                                <template v-if="course.attributes['course-number']">
                                    {{ course.attributes['course-number'] }}
                                </template>
                                {{ course.attributes.title }}
                            </a>
                            <div :id="'course-dates-' + course.id" class="course-dates"></div>
                        </td>
                        <td :colspan="editable ? 2 : null">
                            <tree-course-details :course="course.id"></tree-course-details>
                        </td>
                    </tr>
                </draggable>
            </table>
            <MountingPortal v-if="showExport" mountTo="#export-widget" name="sidebar-export">
                <tree-export-widget v-if="courses.length > 0" :title="$gettext('Download des Ergebnisses')" :url="exportUrl()"
                                    :export-data="courses"></tree-export-widget>
            </MountingPortal>
            <MountingPortal v-if="withCourseAssign" mountTo="#assign-widget" name="sidebar-assign-courses">
                <assign-link-widget v-if="courses.length > 0" :node="currentNode" :courses="courses"></assign-link-widget>
            </MountingPortal>
        </article>
    </template>
    
    <script>
    import draggable from 'vuedraggable';
    import { TreeMixin } from '../../mixins/TreeMixin';
    import TreeExportWidget from './TreeExportWidget.vue';
    import TreeBreadcrumb from './TreeBreadcrumb.vue';
    import StudipProgressIndicator from '../StudipProgressIndicator.vue';
    import StudipIcon from '../StudipIcon.vue';
    import TreeNodeCourseInfo from './TreeNodeCourseInfo.vue';
    import TreeCourseDetails from "./TreeCourseDetails.vue";
    import AssignLinkWidget from "./AssignLinkWidget.vue";
    
    export default {
        name: 'StudipTreeTable',
        components: {
            draggable, TreeExportWidget, TreeCourseDetails, StudipIcon, StudipProgressIndicator, TreeBreadcrumb,
            TreeNodeCourseInfo, AssignLinkWidget
        },
        mixins: [ TreeMixin ],
        props: {
            node: {
                type: Object,
                required: true
            },
            breadcrumbIcon: {
                type: String,
                default: 'literature'
            },
            editable: {
                type: Boolean,
                default: false
            },
            editUrl: {
                type: String,
                default: ''
            },
            createUrl: {
                type: String,
                default: ''
            },
            deleteUrl: {
                type: String,
                default: ''
            },
            withCourses: {
                type: Boolean,
                default: false
            },
            withExport: {
                type: Boolean,
                default: false
            },
            withChildren: {
                type: Boolean,
                default: true
            },
            visibleChildrenOnly: {
                type: Boolean,
                default: true
            },
            assignable: {
                type: Boolean,
                default: false
            },
            withCourseAssign: {
                type: Boolean,
                default: false
            },
            semester: {
                type: String,
                default: ''
            },
            semClass: {
                type: Number,
                default: 0
            },
            showStructureAsNavigation: {
                type: Boolean,
                default: false
            }
        },
        data() {
            return {
                currentNode: this.node,
                isLoading: false,
                isLoaded: false,
                children: [],
                courses: [],
                assistiveLive: '',
                subLevelsCourses: 0,
                thisLevelCourses: 0,
                showingAllCourses: false
            }
        },
        computed: {
            showExport() {
                return this.withExport && document.getElementById('export-widget');
            }
        },
        methods: {
            openNode(node, pushState = true) {
                this.currentNode = node;
                this.$emit('change-current-node', node);
    
                if (this.withChildren) {
                    this.getNodeChildren(node, this.visibleChildrenOnly).then(response => {
                        this.children = response.data.data;
                    });
                }
    
                this.getNodeCourseInfo(node, this.semester, this.semClass)
                    .then(response => {
                        this.thisLevelCourses = response?.data.courses;
                        this.subLevelsCourses = response?.data.allCourses;
                    });
    
                if (this.withCourses) {
    
                    this.getNodeCourses(node, this.semester, this.semClass, '', false)
                        .then(response => {
                            this.courses = response.data.data;
                        });
                }
    
                // Update browser history.
                if (pushState) {
                    const nodeId = node.id;
                    const url = STUDIP.URLHelper.getURL('', {node_id: nodeId});
                    window.history.pushState({nodeId}, '', url);
                }
    
                // Update node_id for semester selector.
                const semesterSelector = document.querySelector('#semester-selector-node-id');
                semesterSelector.value = node.id;
            },
            dropChild() {
                this.updateSorting(this.currentNode.id, this.children);
            },
            keyHandler(e, index) {
                switch (e.keyCode) {
                    case 38: // up
                        e.preventDefault();
                        this.decreasePosition(index);
                        this.$nextTick(() => {
                            this.$refs['draghandle-' + (index - 1)][0].focus();
                            this.assistiveLive = this.$gettextInterpolate(
                                this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
                                { pos: index, listLength: this.children.length }
                            );
                        });
                        break;
                    case 40: // down
                        e.preventDefault();
                        this.increasePosition(index);
                        this.$nextTick(function () {
                            this.$refs['draghandle-' + (index + 1)][0].focus();
                            this.assistiveLive = this.$gettextInterpolate(
                                this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
                                { pos: index + 2, listLength: this.children.length }
                            );
                        });
                        break;
                }
            },
            decreasePosition(index) {
                if (index > 0) {
                    const temp = this.children[index - 1];
                    this.children[index - 1] = this.children[index];
                    this.children[index] = temp;
                    this.updateSorting(this.currentNode.id, this.children);
                }
            },
            increasePosition(index) {
                if (index < this.children.length) {
                    const temp = this.children[index + 1];
                    this.children[index + 1] = this.children[index];
                    this.children[index] = temp;
                    this.updateSorting(this.currentNode.id, this.children);
                }
            },
            showAllCourses(state) {
                this.getNodeCourses(this.currentNode, this.semester, this.semClass, '', state)
                    .then(courses => {
                        this.courses = courses.data.data;
                        this.showingAllCourses = state;
                    });
            }
        },
        mounted() {
            if (this.withChildren) {
                this.getNodeChildren(this.node, this.visibleChildrenOnly).then(response => {
                    this.children = response.data.data;
                });
            }
    
            this.getNodeCourseInfo(this.currentNode, this.semester, this.semClass)
                .then(response => {
                    this.thisLevelCourses = response?.data.courses;
                    this.subLevelsCourses = response?.data.allCourses;
                });
    
            if (this.withCourses) {
                this.getNodeCourses(this.currentNode, this.semester, this.semClass)
                    .then(courses => {
                        this.courses = courses.data.data;
                    });
            }
    
            this.globalOn('open-tree-node', node => {
                STUDIP.eventBus.emit('cancel-search');
                this.openNode(node);
            });
    
            this.globalOn('load-tree-node', id => {
                STUDIP.eventBus.emit('cancel-search');
                this.getNode(id).then(response => {
                    this.openNode(response.data.data);
                });
            });
    
            this.globalOn('sort-tree-children', data => {
                if (this.currentNode.id === data.parent) {
                    this.children = data.children;
                }
            });
    
            window.addEventListener('popstate', (event) => {
                if (event.state) {
                    if ('nodeId' in event.state) {
                        this.getNode(event.state.nodeId).then(response => {
                            this.openNode(response.data.data, false);
                        });
                    }
                } else {
                    this.openNode(this.node, false);
                }
            });
    
            // Add current node to semester selector widget.
            this.$nextTick(() => {
                const semesterForm = document.querySelector('#semester-selector .sidebar-widget-content form');
                const nodeField = document.createElement('input');
                nodeField.id = 'semester-selector-node-id';
                nodeField.type = 'hidden';
                nodeField.name = 'node_id';
                nodeField.value = this.node.id;
                semesterForm.appendChild(nodeField);
            });
        },
        beforeDestroy() {
            STUDIP.eventBus.off('open-tree-node');
            STUDIP.eventBus.off('load-tree-node');
            STUDIP.eventBus.off('sort-tree-children');
        }
    }
    </script>