`) this.enlarged.hide() this.enlarged.appendTo("body") this.enlarged.fadeIn(this.config.fadeSpeed) this.enlarged.on("click", () => this.hide()) } hide() { if (!this.enlarged) { return null; } this.enlarged.fadeOut(this.config.fadeSpeed, () => { this.enlarged.remove() this.enlarged = null }) } } function setupEnlargeableImages(target) { target.find(".enlargeable").each(function () { new EnlargeableImage($(this)) }) } setups.push(setupEnlargeableImages) // HEADLINE function setupHeadline(target) { let headline = target.find(".headline") let hasRemoveHeadline = target.find("noheadline").length > 0 let hasHeadline = headline.length > 0 let hasEmptyHeadline = headline.length > 0 && headline.html().trim() === "" if (!hasHeadline) { return } if (hasRemoveHeadline || hasEmptyHeadline) { headline.remove() } } setups.push(setupHeadline) // SLIDER class Slider { defaults = { controls: true, start: 1, // starting position speed: 200, // slide speed auto: 1, // auto-sliding autospeed: 5000, // auto-sliding interval autodirection: "right", // direction of auto-sliding ("left" or "right") dots: true, // show dots for navigation } /** * Content slider * @param element * @param config */ constructor(element, config = {}) { this.element = $(element) this._autoSlidingInterval = null // combine configurations with priority defaults -> constructor config -> data attributes this.config = $.extend({}, this.defaults, config, this.element.data()) this._validateConfig() // add refresh event this.element.on("refresh", () => this.refresh()) // wait for all images to load, so dimensions can be calculated correctly this._allImagesLoaded().then(() => { this.pos = this.config.start - 1 // starting position of the slider this.refresh() // Store original count before cloning this.originalLength = this.items.length; // skip sliding functionality if only one slide is available if(this.items.length > 1) { // auto sliding if (this.config.auto) this.startAutoSliding() // create control arrows if (this.config.controls) this.addControls() // create dots for navigation if(this.config.dots) this.addDots() } }) } _validateConfig() { let validator = new Validator(this.config, { scope: "Slider", transform: true, defaults: this.defaults, }) return validator.validate([ ["controls", "boolean"], ["start", "number"], ["start", (val) => { let slideCount = this.element.children("ul").children("li").length return val 0 }, {errorMsg: "'start' must be greater than zero and cannot exceed the number of slides"}], ["speed", "number"], ["auto", "boolean"], ["autospeed", "number"], ["autospeed", (val) => this.config.speed ["left", "right"].indexOf(val) !== -1, {errorMsg: "autodirection must be 'left' or 'right'"}] ]) } /** * refresh properties and calculated dimensions */ refresh() { this.ul = this.element.children("ul") // list containing the slides this.items = this.ul.children("li") // sliding items // reset previously set properties this.element.css({width: "", height: ""}) this.ul.css({width: "", left: ""}) this.items.css({width: "", height: ""}) // find the highest slider item and use it as the sliders height let maxSlideHeight = 0 this.items.each(function () { let slideHeight = $(this).height() if (slideHeight > maxSlideHeight) maxSlideHeight = slideHeight }) this._height = maxSlideHeight this._width = this.element.width() // slider width this._uiWidth = this.items.length * this._width // width of all items combined // set slider dimensions this.element.css({ width: this._width, height: this._height, }) this.ul.css({ width: this._uiWidth, left: this.pos * -this._width, // move viewpoint to current position }) this.items.css({ width: this._width, height: this._height, }) } /** * Add dots for navigation */ addDots() { this.dotsContainer = $('
'); // Only create dots for original slides, not cloned ones for (let i = 0; i
'); if (i === this.pos) dot.addClass('active'); dot.on('click', () => { this.stopAutoSliding(); this.moveTo(i); setTimeout(() => { if (this.config.auto) this.startAutoSliding(); }, this.config.speed + 100); }); this.dotsContainer.append(dot); } this.element.append(this.dotsContainer); } /** * update dots for navigation */ updateDots() { if (!this.config.dots) return this.dotsContainer.find('.dot').removeClass('active') this.dotsContainer.find('.dot').eq(this.pos).addClass('active') } /** * move to previous slide item or last, if there is no previous item */ prev() { // currently at the first item, prepend the last item and move there this.pos = (this.pos === 0) ? this.items.length - 1 : this.pos - 1; this.moveTo(this.pos) this.dotpos--; } /** * move to next slide item or first, if there is no next item */ next() { // currently at the last item, append the first item and move there this.pos= (this.pos === this.items.length - 1) ? 0 : this.pos+1; this.moveTo(this.pos) this.dotpos++; } /** * move to given slide item * @param pos */ moveTo(pos) { this.pos = pos this.ul.animate({left: this.pos * -this._width}, this.config.speed) this.updateDots() // update dots for navigation } /** * start auto sliding * @returns {null} */ startAutoSliding() { // abort if auto sliding is already running if (this._autoSlidingInterval !== null) return null // begin auto sliding this._autoSlidingInterval = setInterval(() => { if (this.config.autodirection === "right") this.next() else this.prev() this.updateDots() }, this.config.autospeed) } /** * stop auto sliding * @returns {null} */ stopAutoSliding() { // abort if auto sliding isn't running if (!this._autoSlidingInterval) return null // abort auto sliding clearInterval(this._autoSlidingInterval) this._autoSlidingInterval = null // Important: Reset the interval } /** * toggle auto sliding * @returns {null} */ toggleAutoSliding() { if (this._autoSlidingInterval) this.startAutoSliding() else this.stopAutoSliding() } /** * Add control arrows to slide to the next or previous item */ addControls() { if (!this.controlPrev) { this.controlPrev = $('
') this.controlPrev.on("click", () => { this.stopAutoSliding() this.prev() }); this.element.prepend(this.controlPrev) } if (!this.controlNext) { this.controlNext = $('
') this.controlNext.on("click", () => { this.stopAutoSliding() this.next() }); this.element.prepend(this.controlNext) } } /** * remove control arrows */ removeControls() { if (this.controlPrev) this.controlPrev.remove() if (this.controlNext) this.controlNext.remove() } /** * promise to wait for loading of all images within the slider * @returns {Promise} */ async _allImagesLoaded() { let loadPromises = [] this.element.find("img").each((i, imageElement) => { loadPromises.push(new Promise(res => { $("").on("load", () => res()).attr("src", $(imageElement).attr("src")) })) }) await Promise.all(loadPromises) } } function setupSliders(target) { target.find(".slider").each(function () { new Slider($(this)) }) } setups.push(setupSliders) // TABLE OF CONTENTS class TableOfContents { defaults = { auto: true, // try to automatically generate the toc scrollspeed: 500, // animation duration of scroll effect when a link is clicked scrolloffset: 200, // offset for headingsselector: "[data-accordion]:not(.no-toc),.toc", // selector for relevant content headings, that should be included in the toc } /** * Content slider * @param element {jQuery|HTMLElement} * @param config {object} */ constructor(element, config = {}) { this.element = $(element) // combine configurations with priority defaults -> constructor config -> data attributes this.config = $.extend({}, this.defaults, config, this.element.data()) this._validateConfig() // automatically create toc if (this.config.auto) this.update() } _validateConfig() { let validator = new Validator(this.config, { scope: "TableOfContents", transform: true, defaults: this.defaults, }) return validator.validate([ ["auto", "boolean"], ["scrollspeed", "number"], ["scrolloffset", "number"], ["scrollselector", "string"], ["headingsselector", "string"], ]) } /** * re-render the table of contents box */ update() { const data = this._retrieveTocData() const tocBox = $(`

Inhaltsverzeichnis

`) const tocList = this._createIndexList(data) this.element.html("") tocBox.append(tocList) this.element.append(tocBox) } /** * searches the document for headings and sorts them hierarchically * Output: * [ * { * level: (int), // heading level from 1 to 6 * title: (string), // heading title * id: (string), // element DOM id (optional) * children: [ // headings that are of a lower level and followed this heading * {level: ..., title, ...}, * ] * }, ... * ] * @returns {*[]} * @private */ _retrieveTocData() { const headings = $(this.config.headingsselector) const tocData = []; const stack = []; $.each(headings, (i, heading) => { heading = $(heading) const level = this._getHeadingLevel(heading) const title = this._getHeadingTitle(heading) const id = this._getHeadingId(heading) if (!level || !title) return null const node = {level: level, title: title, id: id, element: heading}; while (stack.length > 0 && stack[stack.length - 1].level >= level) { stack.pop(); } if (stack.length > 0) { const parent = stack[stack.length - 1]; if (!parent.children) { parent.children = []; } parent.children.push(node); } else { tocData.push(node); } stack.push(node); }) return tocData; } /** * Tries to retrieve the heading level (1-6) from an element by the following methods in this order: * 1. By data-attribute (e.g. data-level="1") * 2. By class (e.g. class="h1") * 3. By Tag (e.g.

) * @param heading {jQuery|HTMLElement} * @returns {null|number} * @private */ _getHeadingLevel(heading) { const hRegex = "\\b[hH][1-6]\\b" const byData = heading.data("level") if (byData) return parseInt(byData) const byClass = heading.attr("class") ? heading.attr("class").match(hRegex) : null if (byClass) return parseInt(byClass[0].slice(1)) const byTag = heading.prop("tagName").match(hRegex) if (byTag) return parseInt(byTag[0].slice(1)) return null } /** * Tries to retrieve the heading title from an element by the following methods in this order: * 1. By data (e.g. data-title="Hello World") * 2. By content, stripping all html tags in the process (e.g.

Hello World

) * @param heading {jQuery|HTMLElement} * @returns {null|string} * @private */ _getHeadingTitle(heading) { const byData = heading.data("title") if (byData) return byData const byContent = heading.html() ? heading.text().trim() : null if (byContent) return byContent return null } /** * Tries to retrieve the heading ID from an element by the following methods in this order: * 1. By ID (e.g. id="my-element") * 2. By Accordion-Target (e.g. data-accordion="#my-element") * @param heading {jQuery|HTMLElement} * @returns {*|jQuery|string|null} * @private */ _getHeadingId(heading) { const byId = heading.attr("id") if (byId) return byId let byAccordion = heading.data("accordion") byAccordion = byAccordion && byAccordion[0] === "#" ? byAccordion.substring(1) : null if (byAccordion) return byAccordion return null } /** * Creates an element with a numbered list based on the given data (see: this._retrieveTocData()) * Lower levels are indented and get their own running number, separated by a point (e.g. 2.1.-2.4 as children of 2.) * @param tocNodesData {[]} * @returns {jQuery|HTMLElement} * @private */ _createIndexList(tocNodesData) { let list = $('
    ') $.each(tocNodesData, (i, node) => { // create list item with heading title let listItem = $(`
  1. `) let itemLink = $(`${node.title}`) // scroll to the connected heading element on click itemLink.on("click", (e) => { scrollTo(node.element, this.config.scrollspeed, this.config.scrolloffset) }) listItem.append(itemLink) // add list item list.append(listItem) // append list of children to the next level of the list if(typeof node.children !== "undefined") { listItem.append(this._createIndexList(node.children)) } }) return list } } function setupTableOfContents(target) { target.find(".table-of-contents").each(function () { new TableOfContents($(this)) }) } setups.push(setupTableOfContents) // Validator class Validator { defaults = { transform: false, // try to transform values into the desired format consoleErrors: true, // print errors to console scope: false, // print scope to error messages to identify the validation messages defaults: {} // default values to reset the variable to if the validation failed } /** * @param data {{}} * @param config {{}} */ constructor(data, config = []) { this.data = data this.config = $.extend({}, this.defaults, config) } /** * Validate a given array of validator configurations * @param validators {[]} * @returns {*[]} */ validate(validators = []) { if (!Array.isArray(validators)) return null let failed = [] validators.forEach(validatorConfig => { let varName = validatorConfig[0] let validatorFunc = validatorConfig[1] let userConfig = validatorConfig[2] || {} let isValid = false if (typeof validatorFunc === "string" && typeof this[validatorFunc] === "function") { // validatorFunc is a standard method of Validator isValid = this[validatorFunc](varName, userConfig) } else if (typeof validatorFunc === "function") { // validatorFunc is a custom validation isValid = this._execValidator({ varName: varName, validator: validatorFunc, }, userConfig) } // validation failed, list the variable name as failed if (!isValid) failed.push(validatorConfig.varName) }) // return the names of all failed validations return failed } /** * Execute standard or custom validator configuration * @param config {{}} * @param customConfig {{}} * @returns boolean * @private */ _execValidator(config, customConfig) { // merge the configuration with priority default -> config given to constructor -> standard method config -> custom validator config config = $.extend({}, this.config, config, customConfig) config = this._validateConfiguration(config) if (!config) return false let val = config.val // transform the variable if configured to do so and set it to the given dataset if the validation is successful val = config.transform ? config.transformator(val) : val let isValid = config.validator(val, this) if (isValid && config.transform) this.data[config.varName] = val // print errors if configured to do so if (config.consoleErrors && !isValid) console.error(`${config.scope ? `${config.scope}: ` : ''}${config.errorMsg}`) // set default value if it exists and the validation failed if (!isValid && this.config.defaults.hasOwnProperty(config.varName)) this.data[config.varName] = this.config.defaults[config.varName] return isValid } /** * Validate the validator configuration itself * @param config {{}} * @returns {boolean|*} * @private */ _validateConfiguration(config) { // is the validator executable? if (typeof config.validator !== "function") { console.error("validator is not a function") return false } // does the validator know what it should validate? if (!config.varName) { console.error("varName must be set in validation configuration") return false } // set default configurations if no custom configuration was set config.errorMsg = config.errorMsg || `Validation failed for ${config.varName}` config.transform = config.transform || this.config.transform config.transformator = config.transformator || function (val) { return val } // read variables value from dataset config.val = this.data[config.varName] return config } /** * Validate if variable contains a boolean (or something that could be interpreted as one) * @param varName {string} * @param config {{}} * @returns {boolean} */ boolean(varName, config) { return this._execValidator({ varName: varName, transformator: (val) => { if (["0", 0, "false", "False", "FALSE", "F", "f"].indexOf(val) !== -1) return false if (["1", 1, "true", "True", "TRUE", "T", "t"].indexOf(val) !== -1) return true return val }, validator: (val) => typeof val == "boolean", errorMsg: `${varName} must be boolean` }, config) } /** * Validate if variable contains a numeric value * @param varName {string} * @param config {{}} * @returns {boolean} */ number(varName, config) { return this._execValidator({ varName: varName, transformator: (val) => Number(val), validator: (val) => !isNaN(val), errorMsg: `${varName} must be a number` }, config) } /** * Validate if variable contains a string value * @param varName {string} * @param config {{}} * @returns {boolean} */ string(varName, config) { return this._execValidator({ varName: varName, transformator: (val) => String(val), validator: (val) => typeof val === "string" || val instanceof String, errorMsg: `${varName} must be a string` }, config) } } // SETUPS $(document).ready(function () { let main = $("#main"); $.each(setups, (i, setupFunction) => { if (typeof setupFunction === "function") { setupFunction(main) } }) // fix scrolling to anchor const anchor = window.location.hash; if(anchor !== "") scrollTo(`${anchor}, [data-accordion="${anchor}"]`); }) // Scroll method function scrollTo(target, speed, offset) { target = $(target) speed = speed ?? 0 offset = offset ?? 200 const scrollContainer = $("#scrollable-area") target.trigger("Accordion:show") let position = target.offset().top - scrollContainer.offset().top + scrollContainer.scrollTop() - offset; scrollContainer.animate({ scrollTop: position }, speed); } // Fix navigation height when the page is scrolled and the menu is moved to the top function fixedNav() { let viewportWidth = $(window).width(); let main = $('#main'); let scrollableArea = $("#scrollable-area"); let headerLogoContainer = $("#header-logo-container"); let navigationContainerMobile = $('#navigation-container-mobil'); let navigationContainer = $('#navigation-container'); if (viewportWidth headerLogoContainer.height() + 3) { navigationContainerMobile.css('width', navigationContainerMobile.width() + "px"); main.css('padding-top', navigationContainerMobile.height() + 'px'); // ADD TO ORIGINAL SCRIPT: main.css('background-position', '0 ' + (parseInt(navigationContainerMobile.height()) * 2) + 'px'); navigationContainerMobile.css('position', 'fixed'); navigationContainerMobile.css('top', '0'); } else { navigationContainerMobile.css('width', "100%"); main.css('padding-top', '0'); // ADD TO ORIGINAL SCRIPT: main.css('background-position', '0 ' + navigationContainerMobile.height() + 'px'); navigationContainerMobile.css('position', 'relative'); } } else { if (scrollableArea.scrollTop() > headerLogoContainer.height() + 3) { navigationContainer.css('width', navigationContainer.width() + "px"); main.css('padding-top', navigationContainer.height() + 'px'); // ADD TO ORIGINAL SCRIPT: main.css('background-position', '0 ' + (parseInt(navigationContainerMobile.height()) * 2) + 'px'); navigationContainer.css('position', 'fixed'); navigationContainer.css('top', '0'); } else { navigationContainer.css('width', "100%"); main.css('padding-top', '0'); // ADD TO ORIGINAL SCRIPT: main.css('background-position', '0 ' + navigationContainerMobile.height() + 'px'); navigationContainer.css('position', 'relative'); } } }

    O-Phasen in den Lehramtsfächern

    Auf dieser Seite finden Sie alle Informationen zu den O-Phasen in den jeweiligen Studienfächern im 2-Fächer-Bachelor (Profil Lehramt):