// Start htmlDOMnode /** * Creates HTML string that displays the common known rotating fan. * * Symbolizes an ongoing process. * * @param {string} color A CSS color value, the color of the fan. * @param {string} size A CSS value in px for the width and height of the fan. * * @return {string} HTML with an animated SVG image. * */ let waitSymbol = function(color = '#acb7b2',size = '62px'){ let h = [''); return h.join(''); }; /** * Is used for reducing amount of code when producing HTML elements. * * @param {string} type A standard HTML tag, like 'p', 'h1', 'div', 'select', 'option' etc.. * @param {object} attrs The attributes of the produced elements, like 'style', 'class', 'data-oblig' etc.. * * @return {object} An HTML object (not just a HTML string). * * @author Terje Rudi, Rudi Multimedia -20. * */ let mkNode = function(type = 'div',attrs = {}){ // Multitool by Terje Rudi let freshNode = false; try{ freshNode = document.createElement(type); for (let k in attrs){ if (['click','submit','change','focus','blur','keydown','keyup','copy','paste'].indexOf(k) < 0){ // Not an event if (k == 'innerText' || k == 'innerHTML' || k == 'text'){ freshNode[k] = attrs[k]; }else if (k == 'options'){ if (type.toLowerCase() == 'select'){ for (let o in attrs[k]){ freshNode.appendChild(mkNode('option',{ 'value' : o, 'text' : (!!attrs[k][o] ? attrs[k][o] : o) })); } }else{ console.error('Element ' + type + ' skal ikke ha option values') } }else{ freshNode.setAttribute(k,attrs[k]); } }else{ freshNode.addEventListener(k,attrs[k]); } }; }catch(e){ freshNode = mkNode('p',{ 'class' : 'error', 'innerHTML' : 'Feil: Kunne ikke tegne element ' + type + ': ' + e }); }; // Tidy up (removes data-oblig, if not set to true). if (!!freshNode.getAttribute('data-oblig') && freshNode.getAttribute('data-oblig') != 'true'){ freshNode.removeAttribute('data-oblig'); } // DEV: /*if (!freshNode.hasAttribute('list') && (freshNode.type != 'search' || freshNode.classList.contains('search')) && (freshNode.hasAttribute('data-oblig') || freshNode.hasAttribute('required'))){ freshNode.addEventListener('blur',() => {freshNode.reportValidity()}); }*/ return freshNode; }; /** * Creates a block HTML element containing a label element and an input element. * * Normally all nedeed to be provided is the label string, the alternative label string ('bokmål') and the input HTML object. Styling targeted for this form solution. * * @see Function 'mkNode'. * @see Object 'core' inside this function for a possible model of the 'content' argument. * * @param {object} content Values that might be provided, and that overrides the default values of the object variable 'core' set inside the function. * * @return {object} An HTML object (not just a HTML string). * * @example mkRow({input:htmlInputObject} // Where 'htmlInputObject' normally is an object created with 'mkNode'. * * @author Terje Rudi, HVL -21. * */ function mkRow(content = {}){ let id = (!!content.input && !!content.input.id ? content.input.id : 'Uspesifisert'); // Merk: 'input' kan bare inneholde ett - 1 - HTML-DOM-objekt! let core = { label : (!!content.label ? content.label : id).replaceAll('_',' ').replace('[]',''), input : (!!content.input ? content.input : mkNode('input',{id:'Uspesifisert',name:'Uspesifisert'})), wrap : (!!content.wrap ? content.wrap : 'div'), class : (!!content.input && !!content.input.type && /(checkbox|radio)/i.test(content.input.type) ? 'checkRow ' : 'fieldRow ') + (!!content.input && !!content.input.tagName && (content.input.tagName.toUpperCase() == 'TEXTAREA') ? 'topValign' : (!!content.input && !!content.input.type && /(checkbox|radio)/i.test(content.input.type) ? 'topValign ' : 'centerValign ')) } let wrap = mkNode(core.wrap,{class:core.class}); if (!!core.input && !!core.input.type && (/(checkbox|radio)/i.test(core.input.type))){ wrap.appendChild(core.input); wrap.appendChild(mkNode('label',{innerHTML:core.label,for:id})); }else{ wrap.appendChild(mkNode('label',{innerHTML:core.label + ':',for:id,style:'margin-right: .8em;'})); wrap.appendChild(core.input); } if (!!content['data-alt-lang']){ try{ if (!!wrap.querySelector('label')){ wrap.querySelector('label').setAttribute('data-alt-lang',content['data-alt-lang']); } }catch(e){ console.info('Kunne ikkje sette alternativ label-tekst:' + e); } } return wrap; } /** * Expands a string to the left by a certain length by appending zeroes. * @param {int} number A number. * @param {int} width The total length of the returned string. */ function zeroFillLeft( number, width ){ width -= number.toString().length; if ( width > 0 ){ return new Array( width + (/\./.test( number ) ? 2 : 1) ).join( '0' ) + number; } return number + ""; // always return a string } /** * Returns a HTML-selectobject with the slectable values of half hours in a day. * @param {string} id The id and name of the select. * @dependency {int} Function zeroFillleft. */ function mkClockSelect(id,mandatory = true,density = ['00','30']){ let sel = mkNode('select',{id:id,name:id,style:'min-width: 4rem;'}); if (mandatory){ sel.setAttribute('data-oblig','true'); } for(let i = 0; i < 23; i++){ let j = zeroFillLeft(i, 2); density.forEach((item) => { sel.appendChild(mkNode('option',{value:j+':'+item,text:j+':'+item})); }); } return sel; } /** * Creates a block HTML element containing a label element and a textarea element. * * @see Function 'mkNode'. * * @param {string} id Will become value of the id of the textarea. * @param {string} mandatory If attribute data-oblig of textarea is to be set to TRUE or FALSE. * @param {string} label Will become value of the label. If false, no label will be created. * @param {string} altLabel Akternative langue text for label. * @param {int} maxlen Maximum number of characters of the text value. * * @note Yes, we know about the misspelling of the function name. * */ function mkTexareaRow(id,mandatory = false,label,altLabel,placeholder = 'Skriv her...',altLangPlaceholder = 'Skriv her...',maxLen = 500){ let textareaRow = mkNode('div',{}); if (!!label){ textareaRow.appendChild(mkNode('label',{ innerText : label, 'data-alt-lang' : altLabel, for : id })); } let opts = { id : id, class : 'fullSpace', placeholder : placeholder, 'data-alt-lang-placeholder' : altLangPlaceholder, maxlength : maxLen }; if (!!mandatory){ opts['data-oblig'] = 'true' } textareaRow.appendChild(mkNode('textarea',opts)); return textareaRow; } /** * Creates an HTML element displaying at list of messages. * @param {array} msgs List of HTML strings. * @param {string} id Id attribute of parent display element. * @param {string} type Type of messages for setting the right styles onto them and the parent element. * * @dependency Function 'mkNode'. * * @returns {DOM-object} */ function createMsgListDisplay(msgs = ['Ingen feilmeldinger oppgitt.'],id = 'messageListDisplay',type = 'errors'){ let display = document.getElementById(id); if (!!display){ display.innerHTML = ''; }else{ display = mkNode('div',{id:id,role:'alert','aria-live':'polite',class:type}); } msgs.forEach(m => { display.appendChild(mkNode('p',{innerHTML:m})); }) return display; } /** * Activates an input field to function as a search box for searches in an external source and receive a value by the selection the user makes. * * @param {object} target The input field to be activated and to be filled with the end result at the end of the process. * @param {url} endPoint The REST-url to search with, note that the search/query string will be appendend to the end of it. * @param {function} callback A function receiving JSON response and returning massaged data in format of a simple array with values to 'populateOptions'. * @param {integer} minLen The minimum length of a query string before REST call is initiated. * * @dependency Function 'mkNode'. * * @see Used in 'Verv', 'Varsel om fare for ikke bestått'. Callback is used in an added focus event listener! * */ function autocompleteFromRemote(target,endPoint,callback,minLen = 4){ let suggestionBoxId = false; if (!!target){ suggestionBoxId = 'suggestions' + target.id; let setKeyEvent = function(event,obj){ let key = event.which || event.keyCode; if (key > 31){ // Human readable chars. if (obj.value.length >= minLen){ obj.classList.toggle('active'); let u = endPoint + encodeURIComponent(obj.value); fetch(u) .then(response => response.json()) .then(json => { // Success let arr = callback(json); populateOptions(suggestionBoxId,arr); obj.classList.toggle('active'); }) .catch(e => { obj.classList.toggle('active'); console.error('An error occured while getting data via REST: ' + e) }) }else{ document.getElementById(suggestionBoxId).innerHTML = ''; } } } let populateOptions = function(id,arr){ try{ if (arr.length > 0){ // Creates a link that both put a value into the searchfield, and potentially updates a 'tag' field. See tagValSrc in /includes/admin-view.php. let liStart = `
' + jsonval.errormsg + '
'; } }) .catch(function(err) { console.error('HVL: Fikk feil ved henting til options fra url ' + url + ': ' + err); }); }catch(e){ console.error('HVL: Feil oppstod ved oppretting av select-node med eksterne verdiar: ' + e); div.innerHTML = 'Ein feil oppstod.
'; } return div; } /** * Animates whole form rows using css display = flex, out or in. * * @param {object} elmObj Input element object of the row - not the row itself. * @param {string} inout Enters 'in' or 'out'. * @param {string} dir Enter down (enterDn), Enter up (enterUp), Sortie down (sortieDn), Sortie up (sortieUp). * */ let enterSortieRow = function(elmObj,inout = 'in',dir = 'down'){ if (inout == 'in'){ elmObj.parentNode.querySelector('label').className = 'enterLeft'; elmObj.className = 'enter' + (dir == 'down' ? 'Dn' : 'Up'); elmObj.parentNode.style.display = 'flex'; }else{ elmObj.parentNode.querySelector('label').className = 'sortieRight'; elmObj.className = 'sortie' + (dir == 'down' ? 'Dn' : 'Up'); setTimeout(function(){ elmObj.parentNode.style.display = 'none'; },400); } } /** * Fetches values from a JSON-url and creates a select or datalist bound input. * * @see Used in mkOrgAffiliations. * * @param {string} trgt Where returned object is to be delivered into the form. Parameter format fitted for querySelectorAll. * @param {string} checkBoxName Value of name attribute og checkbox input element. * @param {url} jsonSrc Url to script that returns expected JSON. * @param {string} firstOptText Text of the first option of the select element. If null or false, a datalist bound input is created. * @param {object} styleOverride Overwrites elements in object variable 'style' if same key is present in this object. * @param {function} callback Function to be performed after multi select has been delivered to the form. * @param {bool} minNumOfCheckeds Minimum mandatory number of selections. * * @return {object} Returns a HTML element ready to receive another HTML element asyncronesly. * */ let mkMultiSelect = function(trgt,checkBoxName,jsonSrc,firstOptText = 'Velg...',styleOverride = {},callback = null,minNumOfCheckeds = 1){ let style = { 'row' : 'display: flex;padding: .5rem .65rem;background: #f6f8f9;border: 0;border-bottom:1px solid #c9d5da;width: 100%;', 'select' : 'margin-top: .3rem;min-width: 100%;', 'inputdatalist' : 'min-width: 100% !important;' }; for (let s in styleOverride){ try{ style[s] = styleOverride[s]; }catch(e){ console.error('HVL: Stilelement ' + s + ' er ikkje tilgjenge'); } } // Trgt brukes i querySelectorAll // checkBoxName (feltnavn) maa slutte paa [] // Se https://v.hvl.no/verktyg/org/organisasjon.php?lang=en for eksempel JSON-struktur let interval = setInterval(function(){ let dumpInto = document.querySelectorAll(trgt); if (dumpInto.length > 0){ clearInterval(interval); dumpInto[0].className = 'multiselect'; // Fjernes, da nettverksproblemer setter in vanlig input som fallback (se under): dumpInto[0].innerHTML = waitSymbol(); // (da treng sannsynligvis ikke waitSymbol() heller lengre) let fallBackInput = mkNode('input',{ 'id' : checkBoxName.replace(/\[\]$/,'') + '_manuelt_innstastet', 'class' : 'inputArea', 'type' : 'text', 'name' : checkBoxName }); dumpInto[0].innerHTML = fallBackInput.outerHTML; fetch(jsonSrc) .then((response) => { return response.json(); }) .then((json) => { // An ID reference might be neccessary to store within the value, but will be hidden in the label of the checkbox text. let hideNumberAtEnd = new RegExp(',[0-9]*$'); let selectId = checkBoxName.replace('[]','') + 'Select'; let domNodes = []; let HelperFunctions = function(inpName){ const thisClass = this; this.mkCheckBx = function(v,html = false){ let chckBxWrap = mkNode('div',{'class' : 'enterUp','style' : 'display: flex;padding: .5rem .65rem;background: #f6f8f9;border: 0;border-bottom:1px solid #c9d5da;width: 100%;position: fixed;left: -8000px;'}); let chckBx = mkNode('input',{'type':'checkbox','name':inpName,'value':v,'style': 'zoom: 1.4;margin: 0 .8rem 0 0;'}); chckBx.addEventListener('change',function(){ thisClass.refreshCheckList(this.name); }); chckBxWrap.appendChild(chckBx); chckBxWrap.appendChild(mkNode('label',{'innerHTML' : (html === false ? v.replace(hideNumberAtEnd,'') : html)})); return chckBxWrap; }; this.refreshCheckList = function(n,minNumOfCheckeds){ let elementsByName = document.querySelectorAll('[name="' + inpName + '"]'); let countChecked = 0; try{ elementsByName.forEach(function(b){ if (b.checked){ b.parentNode.style.position = 'relative'; b.parentNode.style.left = '0'; b.parentNode.style.display = 'flex'; b.parentNode.className = 'enterUp'; countChecked++; }else{ /*try{ $(b.parentNode).slideUp(200); }catch(e){*/ b.parentNode.style.display = 'none'; /*}*/ } }); }catch(e){console.error('HVL: Refreshchecklist: ' + e)} // Bindinger til selecten - minst én boks må være avkrysset let boundSelect = document.getElementById(selectId); if (minNumOfCheckeds != undefined && minNumOfCheckeds == 1){ boundSelect.dataset.oblig = 'true'; if (countChecked >= minNumOfCheckeds){ delete boundSelect.dataset.oblig; } } if (countChecked > 0){ boundSelect.removeAttribute('required'); }else{ boundSelect.setAttribute('required','required'); } }; } let hlp = new HelperFunctions(checkBoxName); // SELECT eller INPUT MED DATALIST: if (firstOptText !== 0){ // SELECT let sel = mkNode('select',{ 'id' : selectId, 'name' : selectId, 'style' : style['select'] }); if (minNumOfCheckeds != undefined && parseInt(minNumOfCheckeds) > 0){ sel.dataset['obligCheckbox'] = checkBoxName; } sel.appendChild(mkNode('option',{'value' : '','text' : firstOptText})); let cnt = 0; let maxCnt = 250; let msg = 'For mange element for ' + checkBoxName.replace('[]','') + '-rullegardin. Kontakt utviklar!'; for(let k in json){ if (typeof json[k] == 'object'){ let optGr = mkNode('optgroup',{'label' : k}); for(let n in json[k]){ if (cnt >= maxCnt){ alert(msg); break; } optGr.appendChild(mkNode('option',{'value' : json[k][n],'text' : json[k][n]})); domNodes.push(hlp.mkCheckBx(json[k][n])); cnt++; } sel.appendChild(optGr); }else{ if (cnt >= maxCnt){ alert(msg); break; } sel.appendChild(mkNode('option',{'value' : k,'text' : (typeof json[k] == 'string' ? json[k] : k )})); domNodes.push(hlp.mkCheckBx(k)); cnt++; } } sel.addEventListener('change',function(){ if (this.selectedIndex != 0){ let v = this.options[this.selectedIndex].value; if (v == '+'){ let newName = prompt('Skriv inn det nye'); if (newName){ v = newName; dumpInto[0].insertBefore(hlp.mkCheckBx(v),dumpInto[0].lastChild); }else{ this.selectedIndex = 0; return false; } } try{ // let elementsByName = document.getElementsByName(checkBoxName); // <- Pre Chromium Edge malfunction let elementsByName = document.querySelectorAll('[name="' + checkBoxName + '"]'); elementsByName.forEach(function(elm){ if (elm.value == v){ elm.checked = true; } }); }catch(e){console.error('HVL: Adding event listener: ' + e)} hlp.refreshCheckList(checkBoxName); // Denne kommer *etter* hlp.refreshCheckList, slik at andre elementer kan lytte til når avkrysninger er oppdatert this.selectedIndex = 0; } }); domNodes.push(sel); }else{ // INPUT WITH DATALIST (argument firstOptText === 0) <- spesifikk for Tilsette[], ikke optimal løsning // Se mkOrgAffiliations for eksempel på kall, json-fetch og callback // Creating checkboxes with label from potentially previously saved values if (formInstanceFieldValues[checkBoxName] != undefined){ let storedVals = formInstanceFieldValues[checkBoxName]; for(let a = 0;a < storedVals.length;a++){ let chBx = hlp.mkCheckBx(storedVals[a]); chBx.querySelector('input[type="checkbox"]').checked = true; domNodes.push(chBx); } }else{ console.info('HVL: Nettverket er treigt.'); } let inp = mkNode('input',{ 'id': selectId, 'style' : style['inputdatalist'], 'autocomplete':'off', 'placeholder':'Søk...', 'type':'text', 'list': selectId + 'Datalist' }); if (minNumOfCheckeds != undefined && parseInt(minNumOfCheckeds) > 0){ inp.dataset['obligCheckbox'] = checkBoxName; } let datalist = mkNode('datalist',{'id': selectId + 'Datalist'}); for(let k in json['data']){ datalist.appendChild(mkNode('option',{ 'value' : json['data'][k] })); } domNodes.push(inp,datalist); } dumpInto[0].innerHTML = ''; domNodes.forEach (function(n){ dumpInto[0].appendChild(n); }); // Dersom behov for funksjon etter at multiselect er opprettet, f.eks. fylle inn verdier ... if (callback != null){ try{ callback(hlp); }catch(e){ console.error('HVL: Kunne ikke starte callback: ' + e); } } hlp.refreshCheckList(checkBoxName); }) .catch((e) => { console.error('HVL:Feil oppstod ved opprettelse av multiselect [-1]: ' + e); }); }else{ console.error('HVL: Venter på ' + trgt); } },1000); }; /** * Filopplasting blir gjort med iframe, men referansenummer blir session-id. JS leser fra iframen om opplasting er faktisk utført. * * @fires message * * @param {string} uploadLabel Teksten som kommer over velg-fil-knappen. * @param {string} trgtInput Id på felt som skal inneholde referanseteksten til opplastet fil. * @param {string} schemaNameSpace Referansestreng for dette skjema (hentes fra sessionvariabel i PHP). * @param {string} schemaInstance Referansestreng for denne sesjonen (hentes fra sessionvariabel i PHP). * @param {string} currLang Om det er norsk eller engelsk språk (ikke målform). * * @return {object} HTML-element */ let mkPrivateFileUpload = function(uploadLabel = 'Last opp:',trgtInput,schemaNameSpace,schemaInstance,currLang = 'no'){ let filopplasterKonvolutt = mkNode('div',{ 'id' : trgtInput + 'Rad', 'class' : 'fileUploadBox' }); // Response viser resultat av opplasting let fileOpplasterResponseRad = mkNode('div',{}); fileOpplasterResponseRad.appendChild(mkNode('label',{ id : trgtInput + 'ResponseLabel', 'innerText' : (currLang == 'no' ? 'Lasta opp til no:' : 'Current Uploads:'), 'style' : 'white-space: nowrap;display: none;' })); fileOpplasterResponseRad.appendChild(mkNode('ul',{ id : trgtInput + 'Response', style : 'font-family: Arial,sans-serif;font-size: small;list-style-type: none;' })); filopplasterKonvolutt.appendChild(fileOpplasterResponseRad); // iFrame let filopplastingsUrl = 'https://v.hvl.no/verktyg/filopplastar/index.php?id=' + escape(trgtInput) + '&ns=' + schemaNameSpace + '&instance=' + schemaInstance; let fileOpplasterRad = mkNode('div',{ 'class' : 'fieldRow' }); // Selv om flere filer kan lastes opp, er det bare referansen til filene som trenger bli lagret, altså ikke array // Merk at data-oblig blir endret av aktivitet i andre element enn dette fileOpplasterRad.appendChild(mkNode('label',{ 'innerHTML' : uploadLabel, 'id' : trgtInput + 'Label', 'for' : trgtInput + 'IFrame', 'style' : 'white-space: nowrap;' })); let fileOpplasterIFrame = mkNode('iframe',{ id : trgtInput + 'IFrame', // MÅ ha samme navn som feltet som lagrer sesjons-id for de opplastede filer - 'IFrame' name : trgtInput + 'IFrame', src : filopplastingsUrl, style : 'height: 4rem;width: 100%;border:none;scroll: auto;' }); fileOpplasterRad.appendChild(fileOpplasterIFrame); window.addEventListener('message', iframeMessage, false); function iframeMessage(event) { if (event.data != null){ try{ if (event.data['success'] != null){ if (event.data['data']['id'] == trgtInput){ // <- Siden window.eventListener(message) kan bestå av flere kall, må vi ha denne id-en try{ // Her er reaksjonen på ferdig opplasting document.getElementById(event.data['data']['id']).value = atob(event.data['data']['ref']); //document.getElementById(trgtInput + 'ResponseLabel').className = 'skjemaLabel'; document.getElementById(trgtInput + 'ResponseLabel').style = 'white-space: nowrap'; let fileUploadRespons = document.getElementById(trgtInput + 'Response'); fileUploadRespons.appendChild(mkNode('li',{ innerHTML : '✓ «' + atob(event.data['data']['file']) + '»' })); document.getElementById(trgtInput + 'IFrame').src = filopplastingsUrl; }catch(e){ console.error('HVL: Kunne ikke sette verdi til element ' + event.data['id'] + ': ' + e); document.getElementById(trgtInput + 'IFrame').src = filopplastingsUrl; } } }// No else here, it goes into error loop when upload is just on initial fileupload page, and not on file receiver page }catch(e){ console.error('HVL: iFrame-kommunikasjon: ' + e); document.getElementById(trgtInput + 'IFrame').src = filopplastingsUrl; } }else{ console.error('HVL: Ingen datarespons fra filopplaster'); document.getElementById(trgtInput + 'IFrame').src = filopplastingsUrl; } } filopplasterKonvolutt.appendChild(fileOpplasterRad); return filopplasterKonvolutt; } /** * Lager en rad med avkrysningsboks med tekst * * @fires change * * @param {string} inputName Navn (name-egenskap) på input * @param {string} checkboxValue Verdien avkrysningsboksen inneholder * @param {string} labelText Teksten som er lesbar ved siden av checkboxen * @param {string} labelAltLangText Teksten i alternativ målform som er lesbar ved siden av checkboxen * @param {bool} isOblig Om det skal kreves at avkrysning er gjort * * @return {object} HTML-element */ let mkCheckboxWithLabel = function(feltNavn,checkboxValue = 'Verdi manglar',labelText = 'Ingen tekst er definert',labelAltLangText = 'Ingen alternativ tekst er definert',isOblig = null){ let confirmationItem = mkNode('div',{'class' : 'enterUp checkboxRow'}); //checkboxRow let confCheckBox = mkNode('input',{ 'id' : feltNavn, 'name' : feltNavn, 'type' : 'checkbox', 'value' : checkboxValue }); let obligClass = ''; if (isOblig != null){ confCheckBox.dataset.oblig = 'true'; obligClass = ' mandatory'; } confirmationItem.appendChild(confCheckBox); confirmationItem.appendChild(mkNode('label',{ 'for' : feltNavn, 'class' : 'fieldRow' + obligClass })); confirmationItem.querySelector('label').appendChild(mkNode('span',{ 'innerText' : labelText, 'data-alt-lang' : labelAltLangText })); return confirmationItem; } /** * Creates a field row where label is custom and input is a checkbox with a claim. * When checked, a text input (default) og custom element shows up under. * * @param {string|object} idOrElement Id for final default input, a statement instead of question OR an HTML input element with attibutes like id. * @param {string} data.checkedVal The value of the checkbox. * @param {string} data.claim Will be the id of the final input, remember underscores for spaces. * @param {string} data.claimAltLang Same as above, but will not become the id and is in a different language and. * @param {string} data.placeholder Placeholder of the final input. * * @dependencies Functions 'mkNode', 'conditionalRequired' and CSS. * * @return {HTMLobject} * * @example let replacementConfirmationRow = mkAppendElementIfChecked('Replacement',{label:'Check if true',checkedVal:'Yes',claim:'There is a replacement',claimAltLang:'Es gibt einen Ersatz',placeholder:'Full Name...'}) */ let mkAppendElementIfChecked = function(idOrElement,data = {label:'Check',checkedVal:'Yes',claim:'What we are confirming',claimAltLang:'What we are confirming in alternative language',placeholder:'Text...'},attrs = {required:'true'}){ let claimAsId = data.claim.replaceAll(' ','_'); const regex = new RegExp("^[A-ZÆØÅa-zæøå][A-ZÆØÅa-zæøå0-9_:\.-]*$"); if (regex.test(claimAsId) === true){ let wrap = mkNode('div',{id:'wrap-' + claimAsId,class:'wrapCheckBoxWithInp'}); let checkBox = mkCheckboxWithLabel(claimAsId,data.checkedVal,data.claim,data.claimAltLang); wrap.appendChild(checkBox); let questionChecked = false; if (typeof idOrElement == 'object'){ questionChecked = idOrElement; }else{ if (regex.test(idOrElement) === true){ questionChecked = mkNode('input',{idOrElement,type:'text',placeholder:data.placeholder,required:attrs.required}); }else{ questionChecked = mkNode('p',{class:'errors',innerText:'Error: Variable idOrElement Has Incorrect Syntax: ' + idOrElement}); } } conditionalRequired(questionChecked,[checkBox.querySelector('input[type=checkbox]'),true]); wrap.appendChild(questionChecked); return mkRow({input:wrap,label:data.label}); }else{ return mkNode('p',{class:'errors',innerText:'Error: Not Valid Claim Id: ' + claimAsId}); } } /** * Lager en rad med avkrysningsboks med tekst og med filopplaster som dukker opp når kryss er aktivt. * * @fires change * @fires mkPrivateFileUpload * * @param {string} referenceFieldName Navn på skjult felt som skal lagre en for mennesker ikke-tydbar referanse til opplastet fil. * @param {string} checkboxName Navn på felt som inneholder verdien for hva man krysser av på. * @param {string} checkboxValue Teksten bruker ser ved siden av avkrysningsboks. * @param {string} checkboxAltLangText Alternativ målformversjon av checkboxValue. * @param {bool} isOblig Om det skal kreves at fil er opplastet når checkboxName er checked. * @param {string} fileUploadLabel Teksten som kommer over velg-fil-knappen. * @param {string} noDataAlert Teksten som blir varsel dersom isOblig er sann og fil ikke er lastet opp. * * @dependency Den globale konstanten JSSKJEMA_ENV må være initialisert. * * @return {object} HTML-element */ let mkFileUpload = function(referenceFieldName,checkboxName,checkboxValue,checkboxText,checkboxAltLangText,isOblig = false,fileUploadLabel = 'Last opp dokument',noDataAlert = 'Fil må lastes opp!'){ let wrapper = mkNode('div',{id : referenceFieldName + 'UploadUI'}); let refField = mkNode('input',{ 'type' : 'hidden', 'name' : referenceFieldName, 'id' : referenceFieldName, 'data-nodata-alert-override' : noDataAlert }); let obligClass = ''; if (isOblig == true){ refField.dataset.oblig = 'true'; obligClass = ' mandatory'; } let item = mkNode('div',{'class' : 'checkboxRow'}); item.appendChild(refField); let checkBox = mkNode('input',{ 'id' : checkboxName, 'name' : checkboxName, 'type' : 'checkbox', 'value' : checkboxValue, }); checkBox.addEventListener('change',function(){ let tUpl = document.getElementById(referenceFieldName + 'Rad'); if (this.checked == true){ tUpl.style.display = 'block'; if (isOblig == true){ document.getElementById(referenceFieldName).dataset.oblig = 'true'; } }else{ tUpl.style.display = 'none'; if (isOblig != true){ try{ delete document.getElementById(referenceFieldName).dataset.oblig; }catch(e){ console.error('HVL: Kunne ikkje avsetja oblig-parameter ' + e); } } } }); item.appendChild(checkBox); item.appendChild(mkNode('label',{ 'id' : checkboxName, 'class' : obligClass, 'for' : checkboxName, 'innerText' : checkboxText, 'data-alt-lang' : checkboxAltLangText })); wrapper.appendChild(item); let fileUpl = mkPrivateFileUpload(fileUploadLabel,referenceFieldName,JSSKJEMA_ENV.meta.tag,JSSKJEMA_ENV.meta.session); fileUpl.style.display = 'none'; wrapper.appendChild(fileUpl); return wrapper; } /** * Lager en rad med avkrysningsboks med tekst og med filopplaster som dukker opp når kryss er aktivt. * * @fires change * @fires mkPrivateFileUpload * * @param {string} referenceFieldName Navn på skjult felt som skal lagre en for mennesker ikke-tydbar referanse til opplastet fil. * @param {string} checkboxName Navn på felt som inneholder verdien for hva man krysser av på. * @param {string} checkboxValue Teksten bruker ser ved siden av avkrysningsboks. * @param {string} checkboxAltLangText Alternativ målformversjon av checkboxValue. * @param {string} schemaNameSpace Referansestreng for dette skjema (hentes fra sessionvariabel i PHP). * @param {string} schemaInstance Referansestreng for denne sesjonen (hentes fra sessionvariabel i PHP). * @param {bool} isOblig Om det skal kreves at fil er opplastet når checkboxName er checked. * @param {string} fileUploadLabel Teksten som kommer over velg-fil-knappen. * @param {string} noDataAlert Teksten som blir varsel dersom isOblig er sann og fil ikke er lastet opp. * * @return {object} HTML-element */ let mkCheckboxWithFileUpload = function(referenceFieldName,checkboxName,checkboxValue,checkboxText,checkboxAltLangText,schemaNameSpace,schemaInstance,isOblig = false,fileUploadLabel = 'Last opp dokument',noDataAlert = 'Fil må lastes opp!'){ let wrapper = mkNode('div',{id : referenceFieldName + 'UploadUI'}); let refField = mkNode('input',{ 'type' : 'hidden', 'name' : referenceFieldName, 'id' : referenceFieldName, 'data-nodata-alert-override' : noDataAlert }); let item = mkNode('div',{'class' : 'checkboxRow'}); item.appendChild(refField); let checkBox = mkNode('input',{ 'id' : checkboxName, 'name' : checkboxName, 'type' : 'checkbox', 'value' : checkboxValue, }); let obligClass = ''; if (isOblig == true){ refField.dataset.oblig = 'true'; checkBox.dataset.oblig = 'true'; obligClass = ' mandatory'; } checkBox.addEventListener('change',function(){ let tUpl = document.getElementById(referenceFieldName + 'Rad'); if (this.checked == true){ tUpl.style.display = 'block'; if (isOblig == true){ document.getElementById(referenceFieldName).dataset.oblig = 'true'; document.getElementById(checkboxName).dataset.oblig = 'true'; } }else{ tUpl.style.display = 'none'; if (isOblig != true){ try{ delete document.getElementById(referenceFieldName).dataset.oblig; delete document.getElementById(checkboxName).dataset.oblig; }catch(e){ console.error('HVL: Kunne ikkje avsetja oblig-parameter ' + e); } } } }); item.appendChild(checkBox); item.appendChild(mkNode('label',{ 'class' : obligClass, 'for' : checkboxName, 'innerText' : checkboxText, 'data-alt-lang' : checkboxAltLangText })); wrapper.appendChild(item); let fileUpl = mkPrivateFileUpload(fileUploadLabel,referenceFieldName,schemaNameSpace,schemaInstance); fileUpl.style.display = 'none'; wrapper.appendChild(fileUpl); return wrapper; } /** * Oppdaterer visning av en liste med checkboxes og label etter endring av avkrysning. * For det meste uaktuell i selvstendig bruk. * * @param {string} selectId Id for select eller input som leverer data til inputs, f.eks. input med navn Tilsett[] gir selectId TilsettSelect. * @param {string} n Navn (egenskap 'name') til checkbox-input som skal oppdateres. * @param {bool} minNumOfCheckeds Minimum antall valgte verdier. * */ let refreshACheckboxLabelList = function(selectId,n,minNumOfCheckeds = 1){ let elementsByName = document.querySelectorAll('[name="' + n + '"]'); let countChecked = 0; try{ elementsByName.forEach(function(b){ if (b.checked){ b.parentNode.style.position = 'relative'; b.parentNode.style.left = '0'; b.parentNode.style.display = 'flex'; countChecked++; }else{ /*try{ $(b.parentNode).slideUp(200); }catch(e){*/ b.parentNode.style.display = 'none'; /*}*/ } }); }catch(e){console.error('HVL: Refreshchecklist: ' + e)} // Bindinger til selecten - minst én boks må være avkrysset let boundSelect = document.getElementById(selectId); boundSelect.dataset.oblig = 'true'; if (countChecked >= minNumOfCheckeds){ delete boundSelect.dataset.oblig; } }; /** * * Makes a two lined row with a long text above a select. * @see mkNode. * * @param {string} longLabel A string to be visualized above the select element. * @param {string} longAltLangLabel A string to be used by language swap. * @param {string} forRef Id of the select element, but to be used in the for attribute in the label. * @param {object} elm A HTML DOM element (normally a select element). * * @return {object} An HTML-element to be used as a row in a fieldset. * */ function mkDblLineSelect(longLabel,longAltLangLabel,forRef,elm){ let dblLineRow = mkNode('div',{}); dblLineRow.appendChild(mkNode('label',{ 'for' : forRef, 'innerText' : longLabel, 'data-alt-lang' : longAltLangLabel })); dblLineRow.appendChild(elm); return dblLineRow; } /** * Creates a sub form within the main form, which allows the user to attach unlimited number og sub data to the main data. * Concept of 'on to many'. * * @see mkNode, CSS, includes/admin-view.php (collect formData). * * @param {string} id The id of the element in the main form that will store the JSON string of the instances from the inserted sub data. * @param {function} collectFormDataFunction The same function that is used to loop through the parent form and check and collect input ids and values. * @param {function} mkSubFormCallback A function that defines the sub form elements (in the same manner a the main form i defined). * * @return {object} A HTML-element that contains all logic for handling the data of the sub form submission instances. * */ let mkDynamicSubForm = function(id,collectFormDataFunction,mkSubFormCallback){ let drawSubmittedSubForms = function(sfId){ try{ let lookup = getLabelForId(); let j = getValAsObject(sfId); let t = document.getElementById(sfId + 'ShowInserted'); t.style.display = 'none'; t.innerHTML = ''; for(let k in j){ if (j[k] != null){ t.style.display = 'block'; let elm = mkNode('li',{'class':'enterDn subFormElement'}); // Remove item button (Cross in square symbol) let closeItemUI = mkNode('a',{'innerHTML':'×','title':'Fjern element','href':'#' + id + 'ShowInserted','class':'closeSymb','style':'margin-right: .6rem;'}); closeItemUI.addEventListener('click',function(){ if (confirm('Sikker på at du vil slette?')){ let i = getValAsObject(sfId); if (delete i[k]){ let cleanArray = i.filter(item => (item != null)); document.getElementById(sfId).value = JSON.stringify(cleanArray); drawSubmittedSubForms(sfId); }else{ alert('Kunne ikke fjerne element'); } } event.preventDefault(); }); elm.appendChild(closeItemUI); // Render content for item let dl = mkNode('dl',{}); for(let lbl in j[k]){ if (j[k][lbl].length > 0){ dl.appendChild(mkNode('dt',{'innerHTML' : (lookup[lbl] != undefined ? lookup[lbl].replace(/\:$/,'') : lbl.replace(/\_/g,' ').replace(/(\[\])$/i,''))})); dl.appendChild(mkNode('dd',{'innerHTML' : j[k][lbl]})); } } elm.appendChild(dl); t.appendChild(elm); } } }catch(e){ console.error('Kunne ikkje teikne opp innskrivne element. ' + e); } } let getLabelForId = function(){ let subForm = new mkSubFormCallback; let labels = subForm.querySelectorAll('label'); let lookup = {}; for(let i = 0; i < labels.length;i++){ if (labels[i].getAttribute('for') != undefined){ lookup[labels[i].getAttribute('for')] = labels[i].innerHTML; } } return lookup; } let getValAsObject = function(fId){ let clickAddElm = function(dfId){ // Open subform if no value has been set try{ document.getElementById(dfId + 'Add').click(); }catch(e){ console.info('HVL: Kunne ikkje opna underskjema ' + dfId + ': ' + e); } } let valueContainer = document.getElementById(fId); let j = []; if (!!valueContainer && valueContainer.value.length > 1){ if (valueContainer.value == '[]'){ clickAddElm(fId); } j = JSON.parse(valueContainer.value); }else{ clickAddElm(fId); } return j; } let mkAddSubFormUI = function(){ let closeSubForm = function(sfId){ try{ let ui = document.getElementById(sfId + 'UI'); ui.removeChild(document.getElementById(sfId + 'SubForm')); ui.appendChild(mkAddSubFormUI()); }catch(e){ console.error('HVL: ' + e); } } let addUI = mkNode('button',{ 'id' : id + 'Add', 'innerText' : '+ Legg til', 'class' : 'button button--text button--footer enterDn', 'type' : 'button' }); addUI.addEventListener('click',function(){ // Create sub form object, but add cancel and save interface to it first let subForm = new mkSubFormCallback; subForm.id = id + 'SubForm'; let cancelSaveUI = mkNode('div',{ 'style' : 'border-top: dotted 1px lightgrey;padding: .4rem 0 .6rem 0;text-align: right;' }); // Cancel button let cancelUI = mkNode('button',{ 'innerText' : 'Avbryt', 'class' : 'button button--text button--footer', 'style' : 'margin-right: .5rem;', 'type' : 'button' }); cancelUI.addEventListener('click',function(){ if (confirm('Sikker? Du må skrive inn omatt om du angrar.')){ closeSubForm(id); } }); cancelSaveUI.appendChild(cancelUI); // Save button (id is used to check if subform is unsaved) let saveUI = mkNode('button',{ 'id' : 'subFormSaveUI', 'innerText' : 'Legg dette til', 'class' : 'button button--text button--footer', 'type' : 'button' }); saveUI.addEventListener('click',function(){ try{ let valueContainer = document.getElementById(id); let collectedData = collectFormDataFunction(document.getElementById(id + 'SubForm')); if (collectedData !== false){ let j = getValAsObject(id); j.push(collectedData); valueContainer.value = JSON.stringify(j); closeSubForm(id); drawSubmittedSubForms(id); document.querySelector('#' + id + 'Form').scrollIntoView(); } }catch(e){ console.error('HVL: ' + e); alert('Kunne ikkje samle data. Kontakt webtutvikler@hvl.no!'); } }) cancelSaveUI.appendChild(saveUI); subForm.appendChild(cancelSaveUI); // Remove interface for adding, replacing with the sub form let ui = document.getElementById(id + 'UI'); ui.removeChild(document.getElementById(id + 'Add')); ui.appendChild(subForm); }); return addUI; } let subForm = mkNode('div',{ 'id' : id + 'Form' }); subFormDataStorage = mkNode('input',{ 'id' : id, 'type' : 'hidden' }); subForm.appendChild(subFormDataStorage); subForm.appendChild(mkNode('ol',{ 'id' : id + 'ShowInserted' })); let subFormUI = mkNode('div',{ 'id' : id + 'UI' }); subFormUI.appendChild(mkAddSubFormUI()); subForm.appendChild(subFormUI); // Refresh list if any present setTimeout(function(){ drawSubmittedSubForms(id); },2000); return subForm; } /** * Creates a select element for choosing a single year form an offered span of years, with current year as reference point. * @param {string} id Id and name attribute for select element. * @param {int} from Number of years before current year. * @param {int} to Number of years after current year. * @dependencies mkNode, mkRow. * @note Select is always mandatory */ function mkSelectYear(id = 'År',from = -4,to = 10){ const d = new Date(); let year = d.getFullYear(); let opts = {}; if (parseInt(from) && parseInt(from)){ for (let i = parseInt(year) + from;i < (parseInt(year) + to + 1);i++){ opts[String(i)] = i; } }else{ opts[String(year)] = year; } let nodeOpts = { id : id, 'data-oblig' : true, options : opts }; let sel = mkNode('select',nodeOpts); // add default option. // the default option is created afterwards and not inside of the opts object due to objects looping through integers before string keys, and the '' option should be first. // has to be defaultSelected as otherwise the browser will choose the first option in insertion order. sel.add(new Option('...', '', true, true), 0) return(mkRow({input:sel})); } /** * Creates an input with a date chooser calendar attached. * * @param {string} id The id of the field. * @param {string} oblig If field value input is mandatory. * @param {int} minY The first chooseable year. * @param {int} maxY The last chooseable year. * * @return {object} HTML-input element that creates a calendar instance on click. * * Fjernet: 'data-type' : 'date', readonly : 'readonly' på input, den 8/2-24. * Samt datepicker, siden den impl. i admin-view.php. */ let mkDateField = function(id,oblig = false,minY = 2000,maxY = 2038){ let fld = mkNode('input',{ 'id' : id, type : 'date', lang : 'no', 'class' : 'datovelger', placeholder : 'YYYY-MM-DD', min: `${minY}-01-01`, max: `${maxY}-12-31`, }); if (oblig){ fld.dataset.oblig = 'true'; } return fld; } /** * Gives the user the opprtunity to collect a study subject with relation to previously chosen faculty. * * @see Function 'mkOrgAffiliations'. Note: Faculty must have been selected! Jsonfeed: https://-vår-tjener-/verktyg/studieinfo/finn-emne.php. * * @param {bool} singleSelect If true, only one subject allowed to be selected. * @param {bool} showSingleEmneValue If true, shows chosen subject in a none writeable input field. * @param {bool} codeOnly Pupulates datalist with only subject codes, otherwise both code and subject name. * @param {bool} date Deprecated (see comment int the function code!) * * @return {object} HTML-input element connected to a datalist. * */ let mkEmneSelect = function(singleSelect = false,showSingleEmneValue = false,codeOnly = false,date = false,oblig = true){ let emneValgSection = mkNode('div',{}); let emneValgChosenRow = mkNode('div',{ 'id' : 'chosenEmner' }); emneValgSection.appendChild(emneValgChosenRow); let emneValgMainRow = mkNode('div',{ 'class' : 'fieldRow centerValign', 'style' : 'width: 100%;' }); emneValgMainRow.appendChild(mkNode('label',{ 'innerText' : 'Legg til emne:', 'data-english' : 'Add a Subject:', 'data-alt-lang' : 'Legg til emner:', 'for' : 'emnesoek' })); let emneValgMsg = mkNode('input',{ class : 'search', 'id' : 'emnesoek', 'list' : 'emneListe', 'type' : 'text', 'placeholder' : 'Skriv inn del av emnekode eller emnenamn...', 'data-alt-lang-placeholder' : 'Skriv inn del av emnekode eller emnenavn...' }); emneValgMsg.addEventListener('focus',function(){ let fs = document.querySelector('#FakultetSelect'); if (!!fs){ let checkedFakultet = document.querySelectorAll('[name="Fakultet[]"]:checked'); if (checkedFakultet.length == 0){ alert('Du må først velje fakultet for å kunne søkje i emnekodar'); this.blur(); try{ fs.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'nearest'}); fs.className = 'markedStrongly'; fs.addEventListener('change',function(){this.className = 'markedOK'}); }catch(e){ console.error('HVL: Kunne ikkje rulle til Fakultets-select: ' + e); } } } if (!!singleSelect){ let chosenEmner = document.getElementById('chosenEmner').getElementsByTagName('div'); if (!!chosenEmner && chosenEmner.length > 0){ alert('Du kan berre velje eitt emne!'); this.blur(); } } }); emneValgMsg.addEventListener('blur',function(){ // change <- Pre Chromium Edge malfunction if (!(this.value) || this.value.length < 1){ //console.error('HVL: Ingen tekststreng for emne'); return false; } // Sjekker emnenavnsgyldighet try{ let listOfEmner = document.getElementById('emneListe').querySelectorAll('option'); let i = 0; for(i;i < listOfEmner.length;i++){ if (listOfEmner[i].value == this.value){ //console.info('Fant likhet ved option nummer ' + i + ' av i alt ' + listOfEmner.length + ' emner'); if (!!singleSelect){ try{ let soekRow = document.getElementById('emnesoek').parentNode; soekRow.style.transform = 'scaleY(0)'; soekRow.style.transitionDuration = '.4s'; }catch(e){ console.error('HVL: Kunne ikkje skjule emnesøkefelt: ' + e); } } break; } } if (i == listOfEmner.length){ alert('Ukjent emne!'); //console.info('Letet igjennom ' + i + ' emner'); this.value = ''; this.focus(); return false; } }catch(e){ console.error('Kunne ikke kontrollere emnenavn: ' + e); } if (!!document.getElementById('Emne')){ // ved singleSelect er dette feltet tilgjengelig document.getElementById('Emne').value = this.value; } let chosenEmne = mkCheckboxWithLabel("Emner[]", this.value.toUpperCase(), this.value.toUpperCase(), this.value.toUpperCase(),true); chosenEmne.style = 'width: 100%;align-items: center;'; chosenEmne.classList.add('enterUp'); let chk = chosenEmne.querySelector('input[type=checkbox]'); chk.checked = true; let regex = / /g; // HTML5 tillater alle tegn utenom space. Erstatter space med underscore let id = 'Dato_' + this.value.toUpperCase().replace(regex,'_'); chk.addEventListener('click',function(){ if (this.checked != true){ if (confirm('Fjerne dette emnet?')){ this.parentNode.outerHTML = ''; if (!!singleSelect){ document.getElementById('Emne').value = ''; try{ let soekRow = document.getElementById('emnesoek').parentNode; soekRow.style.transform = 'scaleY(1)'; soekRow.style.transitionDuration = '.4s'; }catch(e){ console.error('HVL: Kunne ikkje gjere emnesøkefelt synleg: ' + e); } } }else{ this.checked = true; } } }); document.getElementById('chosenEmner').appendChild(chosenEmne); this.value = ''; }); // Lytte på select for å finne ut hvilke(t) fakultet som har blitt valgt og dermed hente inn dataliste for emnevalg // Brukes for å foreta søk etter emner pr. fakultet, samt sette data-attributt i datalist for om emner for dette fakultetet allerede er lastet inn /* Fram til 1. jan -24: let emneSokFakultetKeys = { 'FakultetforkonomiogsamfunnsvitskapFS' : '40', 'FakultetforlrarutdanningkulturogidrettFLKI' : '30', 'FakultetforingenirognaturvitskapFIN' : '20', 'FakultetforhelseogsosialvitskapFHS' : '10' } */ let emneSokFakultetKeys = { 'FakultetforlrarutdanningkulturogidrettFLKI' : '30', 'FakultetforteknologimiljoogsamfunnsvitskapFTMS' : '20', 'FakultetforhelseogsosialvitskapFHS' : '10' } // Sørger for at datalista med emner ut ifra valgt fakultet blir oppdatert ettersom valg blir gjort med fakultets-selecten let fakultetInterval = setInterval(function(){ try{ if (document.body.contains(document.getElementById('FakultetSelect'))){ let sel = document.getElementById('FakultetSelect'); sel.addEventListener('change',function(){ if (this.selectedIndex == 0){ // Selecten stiller seg tilbake til førstevalg først etter at fakultetslista er tegnet opp let regex = /[^a-z0-9]+/gi; let fakultetsNumbers = []; document.querySelectorAll('[name="Fakultet[]"]').forEach(function(elm){ if (elm.checked == true){ try{ fakultetsNumbers.push(emneSokFakultetKeys[elm.value.replace(regex,'')]); }catch(e){ console.error('HVL: Kunne ikke finne key i var emneSokFakultetKeys: ' + e); } } }); if (fakultetsNumbers.length > 0){ // Sletter først bestående emneliste, lager heller en ny helt fresh (for alfabetiseringens del) if (document.body.contains(document.getElementById('emneListe'))){ document.getElementById('emneListe').remove(); } // Lager ny emneliste (for testing er denne også tilgjengelig: emne-fake-test.php) let datalistSrc = 'https://v.hvl.no/verktyg/studieinfo/finn-emne.php?compact=1&faculty=' + fakultetsNumbers.join(',') + (!!codeOnly ? '&short=1' : ''); document.body.appendChild(mkNode('datalist',{ 'id': 'emneListe', 'data-src' : datalistSrc })); // Fyller datalista (se /includes/admin-view.php) populateDatalist(document.getElementById('emneListe')); }else{ console.error('HVL: Ingen fakultetsnumre ble generert'); } } }); clearInterval(fakultetInterval); } }catch(e){ console.error('HVL: FEIL: Finn ikkje fakultetsselecten: ' + e); } },500); emneValgMainRow.appendChild(emneValgMsg); emneValgSection.appendChild(emneValgMainRow); if (!!singleSelect){ if (!!showSingleEmneValue){ emneValgSection.appendChild(mkRow({ label:'Oppgitt emne', input : mkNode('input',{id:'Emne',style:'cursor:pointer;',type: 'text',readonly:'readonly','data-oblig':oblig,click:function(){alert('Bruk «Legg til emne» for å sette inn verdi her!')}}) })); }else{ emneValgSection.appendChild(mkNode('input',{id:'Emne',type: 'hidden',readonly:'readonly','data-oblig':true,'data-nodata-alert-override' : 'Det må oppgis et emne!'})); } } return emneValgSection; } let mkChooseTimeSpan = function(id = 'chooseTimeSpan',labels = { 'from' : 'Frå', 'to' : 'Til', 'fromAlt' : 'Fra', 'toAlt' : 'Til', 'placeholder' : 'Klikk for å velje dato', 'placeholderAlt' : 'Klikk for å velge dato' }){ // Alltid obligatoriske - kans skjules med CSS om ikke ønsket let cals = mkNode('div',{'id':id}); let cal = mkNode('div',{ 'class' : 'fieldRow centerValign' }); cal.appendChild(mkNode('label',{ 'innerText' : labels['from'] + ':', 'data-english' : 'From:', 'data-alt-lang' : labels['fromAlt'] + ':', 'for' : 'Tidsgyldigheit' })); cal.appendChild(mkNode('input',{ 'id' : labels['from'], 'type' : 'text', 'placeholder' : labels['placeholder'], 'data-alt-lang-placeholder' : labels['placeholderAlt'], 'data-type' : 'date' })); cals.appendChild(cal); // Reset cal = mkNode('div',{ 'class' : 'fieldRow centerValign' }); cal.appendChild(mkNode('label',{ 'innerText' : labels['to'] + ':', 'data-english' : 'To:', 'data-alt-lang' : labels['toAlt'] + ':', 'for' : 'Tidsgyldigheit' })); cal.appendChild(mkNode('input',{ 'id' : labels['to'], 'type' : 'text', 'placeholder' : labels['placeholder'], 'data-alt-lang-placeholder' : labels['placeholderAlt'], 'data-type' : 'date' })); cals.appendChild(cal); return cals; } /** * Creates search field which presents suggestions from a function call (like 'autocompleteFromRemote') * and lets the user select one singe element. * * @param {string} inputId The id of the input containing the selected value. * @param {obj} attrs JS object with element attributes to append to the search input. * @param {function} call The function that collects and presents the suggestion data. * * @return {object} An HTML-element representing a whole row with label and all in a form. */ let mkAutocompleteSingleSelect = function(inputId,rowWrapped = true,attrs = {required:'required'},call = console.info(`No Function Defined In Element Created By 'mkAutocompleteSingleSelect'`)){ const mergedAttrs = Object.assign({id:inputId,type:'text',class:'search',autocomplete:'off',focus: call},attrs); const inp = mkNode('input',mergedAttrs); if (rowWrapped === true){ return mkRow({label:inputId,input : inp}); } return inp; } /** * * Creates select for choosing one or more employees at HVL. * * @param {object} coreVars Global settings from parent script. * @param {string} currLang Value of 'no' sets language to norwegian, oterhwise english. Not to be confused with 'data-alt-lang'. * @param {string} fieldName HTML-input id. * @param {string} label The label in the default language. * @param {string} labelAlt The label when alternative langue is displayed. * @param {array} checkboxVal Which node(s) of info from REST call to be set as value. Defaults to only tilsattnummer. Example: ['Navn','job_title']. * * @note Custom for HVL! * * @return {object} A pack of HTML-elements. The select itself is put automatically async to the DOM targets. */ let mkSelectEmployees = function(coreVars,currLang,fieldName = 'Tilsett',label = 'HVL-kontaktpersonar',labelAlt = 'HVL-kontaktpersoner',checkboxVal = []){ let employeeR = mkNode('div',{ 'class': 'fieldRow topValign' }); employeeR.appendChild(mkNode('label',{ 'innerText' : label + ':', 'data-english' : 'HVL Contacts:', 'data-alt-lang' : labelAlt + ':', 'for' : fieldName + 'Select' })); employeeR.appendChild(mkNode('div',{ 'id' : fieldName })); let asyncEmployeeInsert = function(helperClass = false){ tapDataAndInsertIntoForm(formInstanceFieldValues); // Konverterer ansattnummer til navn og lenke: let renderEmployees = function(items){ for (let i = 0;i < items.length;i++){ let inpVal = items[i].value; let label = items[i].parentNode.querySelector('label'); if (inpVal == label.innerText){ label.innerHTML += waitSymbol(false,'10px'); fetch('https://v.hvl.no/verktyg/tilsette/fetchByEmployeeNumber.php?q=' + inpVal) .then(response => {return response.json()}) .then(json => { let html = []; for(let id in json['data']){ // Her komponeres den informasjonen om tilsatt som vises for brukeren. html.push('MERK: Frå 01.01.24 er fakulteta FIN og FØS slått saman til det nye fakultetet FTMS!
', // Campus if (nodes['campus'] != undefined && nodes['campus']['display'] == true){ let campusValR = mkNode('div',{ 'class' : 'fieldRow centerValign' }); campusValR.appendChild(mkNode('label',{ 'innerText' : (nodes.campus.label != null ? nodes.campus.label : 'HVL-Studiestad:'), 'data-english' : 'HVL Campus:', 'data-alt-lang' : (nodes.campus['data-alt-lang'] != null ? nodes.campus['data-alt-lang'] : 'HVL-Studiested:'), 'for' : 'Campus' })); let campusValSelect = mkNode('select',{ 'id' : 'Campus', 'data-oblig' : 'true', 'options' : { '' : '···', 'Bergen' : 'Bergen', 'Førde' : 'Førde', 'Haugesund' : 'Haugesund', 'Sogndal' : 'Sogndal', 'Stord' : 'Stord', 'Campusuavhengig/Fleircampus' : 'Campusuavhengig/Fleircampus' } }); campusValR.appendChild(campusValSelect); nodePack.appendChild(campusValR); } // Fakultet if (nodes['faculty'] != undefined && nodes['faculty']['display'] == true){ let fakultetR = mkNode('div',{ 'class': 'fieldRow topValign' }); fakultetR.appendChild(mkNode('label',{ 'innerText' : (nodes.faculty.label != null ? nodes.faculty.label : 'HVL-Fakultet:'), 'data-english' : 'Faculty at HVL:', 'data-alt-lang' : (nodes.faculty['data-alt-lang'] != null ? nodes.faculty['data-alt-lang'] : 'HVL-Fakultet:'), 'for' : 'FakultetSelect' })); fakultetR.appendChild(mkNode('div',{ 'id' : 'Fakultet' })); let asyncInsert = function(){ tapDataAndInsertIntoForm(formInstanceFieldValues); }; //console.info('Oppdat: JSSKJEMA_ENV: ' + JSON.stringify(JSSKJEMA_ENV,'',' ')); let drawFakultet = mkMultiSelect('#Fakultet','Fakultet[]','https://v.hvl.no/verktyg/org/organisasjon.php?filter=fakultet&orgrole=' + JSSKJEMA_ENV.meta.orgRole + (currLang == 'no' ? '' : '&lang=en'),(currLang == 'no' ? 'Tilknytta fakultet...' : 'Affiliated to faculty...'),{},asyncInsert); nodePack.appendChild(fakultetR); } // Enheter (institutt, avdelinger m.m.) if (nodes['unit'] != undefined && nodes['unit']['display'] == true){ let einingR = mkNode('div',{ 'class': 'fieldRow topValign' }); einingR.appendChild(mkNode('label',{ 'innerText' : (nodes.unit.label != null ? nodes.unit.label : 'HVL-Eining:'), 'data-english' : 'HVL Unit:', 'data-alt-lang' : (nodes.unit['data-alt-lang'] != null ? nodes.unit['data-alt-lang'] : 'HVL-Enhet:'), 'for' : 'EiningSelect' })); einingR.appendChild(mkNode('div',{ 'id' : 'Eining' })); let asyncInsert = function(){ console.info('Enheter run - remove at #1587'); tapDataAndInsertIntoForm(formInstanceFieldValues); //insertFormValues(formInstanceFieldValues,coreVars); }; let drawEining = mkMultiSelect('#Eining','Eining[]','https://v.hvl.no/verktyg/org/organisasjon.php?filter=institutt&appendix=' + (currLang == 'no' ? 'Anna' : 'Other') + (currLang == 'no' ? '' : '&lang=en'),(currLang == 'no' ? 'Tilknytta eining...' : 'Affiliated to unit...'),{},asyncInsert); nodePack.appendChild(einingR); } if (nodes['study'] != undefined && nodes['study']['display'] == true){ // Studiumrad let studiumRad = mkNode('div',{ 'class' : 'fieldRow centerValign' }); studiumRad.appendChild(mkNode('label',{ 'innerText' : (nodes.study.label != null ? nodes.study.label : 'HVL-Studieprogram:'), 'data-english' : 'HVL Study:', 'data-alt-lang' : (nodes.study['data-alt-lang'] != null ? nodes.study['data-alt-lang'] : 'HVL-Studieprogram:'), 'for' : 'Studieprogram' })); studiumRad.appendChild(mkNode('input',{ 'type' : 'text', 'list' : 'studieprogramliste', 'id' : 'Studieprogram', 'placeholder' : 'Søk på del av studieprogram', 'data-alt-lang-placeholder' : 'Søk på del av studieprogramnavn', 'data-oblig' : 'true', })); nodePack.appendChild(studiumRad); let studiumDatalist = mkNode('datalist',{ 'id': 'studieprogramliste', 'data-src' : 'https://v.hvl.no/verktyg/studieinfo/studieprogramliste.php' }); nodePack.appendChild(studiumDatalist); } if (nodes['employee'] != undefined && nodes['employee']['display'] == true){ nodePack.appendChild(mkSelectEmployees(coreVars,currLang)); } return nodePack; } /** * Fetches all inputs that has the attribute data-tag and their value of that attribute. Another * input (which normally is not visible to the user) will receive all these inputs values as a comma * delimited string as its own value. Such a 'tag' input is most commonly used for the first input * of a form, and its value vil be the readable reference for the element in the form list. * @param {string} tag Id of the text/hidden input that is to receive the concated values. * @see Initializing of variable tagValSrc in /includes/admin-view.php. */ function updateTag(tag,reverse = true){ let tagValSrc = document.querySelectorAll('[data-tag="' + tag + '"]'); let vals = []; tagValSrc.forEach(t => { if (t.value.length > 0){ vals.push(t.value); } }); if (reverse){ vals.reverse(); } try{ document.getElementById(tag).value = vals.join(', ') }catch(e){ console.error('Could not find target tag field for "' + tag + '"'); } }; /** * Creates and returns an element containing a group of radio inputs for each statement displayed in a matrix. * @author Ole Brede * @see mkNode * * @param {Object} question An object holding information about the question and answer options. * @param {Object} question.label The question text. * @param {Object[]} question.statements An array of objects holding information about the statements. * @param {string} question.statements[].id Value used as the `name` attribute of the radio input. * @param {string} question.statements[].label The statement text. * @param {Object[]} question.options An array of objects holding information about the answer options. * @param {string} [question.options[].value] If omitted label will be used as value. * @param {string} question.options[].label Visual text for the radio input. * @param {Object} [options] Object to hold additional options. * @param {Object.