momente şi schiţe de informatică şi matematică
anti point—and—click

Pagină HTML pentru prezentarea orarului şcolii

HTML | R | jQuery | orar şcolar
2021 may

La /orar am postat o pagină HTML destul de simplă ca structură, care prezintă orarul unei anumite şcoli, generat cum am arătat în [1]. Ne vom referi numai în treacăt, la constituirea fişierului HTML respectiv (sursa este disponibilă); ceea ce vizăm aici ţine iarăşi de R (continuând cumva [1]): cum extragem (şi „formatăm”) datele de inserat în pagina HTML, din obiectele R (şi fişierele asociate) în care am generat orarul.

Avem două diviziuni <div> principale: una conţine 8 elemente <button> şi are proprietatea CSS position:fixed (încât derularea paginii nu-i afectează vizibilitatea); cealaltă apare în dreapta celei fixe (având float:left) şi serveşte pentru a reda un conţinut sau altul în funcţie de butonul activat prin click:

Avem de redat (asociind butoanelor) încadrarea profesorilor pe clase şi obiecte, repartizarea pe zile şi schimburi, orarele pe zile, pe profesori, pe discipline şi pe clase.

Pe de altă parte, ar fi de precizat (onest) criteriile sau principiile adoptate pentru construcţia orarului: se admite sau nu, desfăşurarea într-o aceeaşi zi a celor 3 ore de "Română" (sau de "Fizică" etc.) la o aceeaşi clasă; se acceptă sau nu, ca un profesor care are 18 ore pe săptămână, să şi le facă în 3 zile; se acceptă sau nu, ca o clasă (analog pentru profesor) să aibă 3 sau 4 ore într-o zi şi 7 sau 8 în alta; etc.

Şi ar mai fi ceva: dacă întrebi "Ce foloseşti, ca să faci orarul şcolii?" obţii cel mai probabil răspunsul "folosim ascTimetables"; aşa că, exceptând situaţia "generated by ascTimetables", trebuie precizat eventual şi cum anume a fost construit orarul pe care îl prezinţi (pe scurt, am răspunde „folosind… calculatorul”; v. [1]).

Încadrarea profesorilor o avem direct din [1] şi doar trebuie să copiem fişierul text respectiv într-un element <pre> din pagina HTML a orarului; repartizarea pe zile şi schimburi o avem din aplicaţia /recast şi doar trebuie să „periem” puţin tabelul HTML respectiv şi să-l inserăm ca atare, în orar.html.

În schimb, avem orarele pe zile doar ca fişiere CVS (rezultate interactiv în final – v. [2]); din cele 10 fişiere "ext{zi}{1|2}.csv" avem de extras (şi de „formatat” pentru prezentarea în câte un element <pre>) orarul fiecărui profesor, fiecărei discipline şi fiecărei clase.

Prin următorul program, mai întâi obţinem orarele zilelor (în forma necesară prezentării) şi constituim un obiect tibble S (cu o coloană 'prof' şi 12 coloane corespunzătoare orelor zilei) conţinând orarele celor 5 zile:

# expCSV2df.R 
library(tidyverse)
Zile <- c("Lu", "Ma", "Mi", "Jo", "Vi")
S <- tibble()  # va combina orarele tuturor zilelor
for(zi in Zile) {
    S1 <- read_csv(paste0("ext", zi, "1.csv"))  # orarul primului schimb
    S2 <- read_csv(paste0("ext", zi, "2.csv"))  # orarul schimbului 2
    S12 <- full_join(S1, S2) %>%  # combină liniile celor două orare
           replace(is.na(.), "-") %>% 
           arrange(prof)
    if('a7' %in% colnames(S12)) {  # ora 'a7' coincide cu ora 'p1'
        S12$p1 <- ifelse(S12$a7 != '-', S12$a7, S12$p1)
        S12$a7 <- NULL
    }
    if('p7' %in% colnames(S12)) {  # ora 'p7' coincide cu ora 'a6'
        S12$a6 <- ifelse(S12$p7 != '-', S12$p7, S12$a6)
        S12$p7 <- NULL
    }
    S12$zi <- zi;
    S <- rbind(S, S12)  # adaugă în 'S' orarul zilei curente (indicate în S$zi)
    S12$zi <- NULL
    sink(paste0(zi, "12.txt"))  # orarul fiecărei zile ("{zi}12.txt")
    print(as.data.frame(S12), print.gap=2)
    sink()
}

N-avem decât să copiem cele 5 fişiere {zi}12.txt rezultate, în câte un element <pre> în diviziunea asociată butonului prevăzut pentru redarea orarelor zilnice; bineînţeles că înaintea acestor elemente <pre>, prevedem şi 5 butoane cu câte un handler de 'click' corespunzător fiecărei zile (am adăugat şi un al 6-lea buton, 'Obs.', pentru a reda şi o mică statistică asupra ferestrelor existente).

Pentru celelalte categorii de orar (pe discipline, etc.) avem nevoie şi de obiectele asociate profesorilor; putem pleca de la orarNorm.RDS obţinut iniţial în [1]:

orar <- readRDS("orarNorm.RDS") %>%
        mutate(prof = factor(prof, ordered=TRUE),
               cls = clasa) %>%
        select(obj, prof, cls)
nOre <- sort(table(orar$obj), decreasing=TRUE)
orar$obj <- factor(orar$obj, levels=names(nOre), ordered=TRUE)
levels(orar$obj) <- str_to_title(abbreviate(levels(orar$obj), 3))
levels(orar$obj)[12] <- "Geo"  # era "GEOGRAFIE"
levels(orar$obj)[18] <- "GeR"  # era "GEOGRAFIE/RUSĂ"
saveRDS(orar, file="orarObj.RDS")

Prin această secvenţă obţinem orarObj.RDS, cu structura obj|prof|cls, primele două coloane având clasa "factor" ordonat (descrescător, după numărul total respectiv de ore); am abreviat denumirile obiectelor şi le-am „umanizat” (prin str_to_title(), obţinând de exemplu "Mat" în loc de "MAT", pentru denumirea iniţială "MATEMATICĂ").

Producem acum (folosind map()) o listă care asociază fiecărui profesor, obiectul pe care este încadrat (sau, dacă ar fi cazul, vectorul acestor obiecte):

# orar <- readRDS("orarObj.RDS")
obj_of_prof <- function(pr) {
    as.vector(unique(orar[orar$prof == pr, ]$obj))
}
obp <- map(levels(orar$prof), obj_of_prof)  # 'obp' va servi pentru orarele claselor
names(obp) <- levels(orar$prof)

Folosind S (în care combinasem mai sus orarele zilelor) şi funcţia obj_of_prof(), putem formula acum un fişier text conţinând orarul fiecărui profesor (precizând şi disciplina asociată), ambalat în <div><pre> (de observat că încă nu avem nevoie de lista 'obp'):

sink("orareProf.txt")
for(pr in levels(orar$prof)) {
    cat('<div><pre>\n<b>',pr, '</b>: ', obj_of_prof(pr), '\n', sep="")
    or <- S %>% filter(prof == pr) %>% select(2:14) %>% relocate(zi)
    print(as.data.frame(or), row.names=FALSE, print.gap=2)
    cat('</pre></div>\n')
}
sink()

N-avem decât să copiem fişierul rezultat "orareProf.txt", în diviziunea din orar.html asociată butonului de redare a orarelor profesorilor; desigur, se cuvine să prefixăm diviziunea respectivă cu un element <select> conţinând ca opţiuni numele profesorilor şi având montat un handler de eveniment "change" (prin care, la alegerea unui profesor, să se redea orarul acestuia).

Procedăm analog pentru orarele pe discipline; vom avea nevoie şi de o listă (analogă cu 'obp') care asociază fiecărui obiect vectorul profesorilor încadraţi pe acel obiect:

prof_of_obj <- function(ob) {
    as.vector(unique(orar[orar$obj == ob, ]$prof))
}
pof <- map(levels(orar$obj), prof_of_obj)
names(pof) <- levels(orar$obj)
sink("orareObj.txt")  # diviziuni cu orarele disciplinelor
for(ob in names(pof)) {
    cat('<div><pre>\n<b>',ob, '</b>\n')
    or <- S %>% filter(prof %in% pof[[ob]]) 
    print(as.data.frame(or), row.names=FALSE, print.gap=2)
    cat('</pre></div>\n\n')
}
sink()

Pentru orarele claselor, lucrurile devin ceva mai complicate. Avem nevoie de numele claselor, în ordinea firească a nivelelor; de coloana S$zi (ignorată în etapele precedente); în plus, vrem să anexăm orarului clasei un sumar informativ (profesorii clasei, obiectele şi numărul corespunzător de ore):

ocl <- S %>% gather("ora", "cls", 2:13) %>% filter(cls != '-')
Cls <- sort(unique(ocl$cls))[c(31:40, 1:30)]  # 9A..9J 10A..10J 11A..11J 12A..12J
ocl$cls <- factor(ocl$cls, levels=Cls, ordered=TRUE)
ocl$zi <- factor(ocl$zi, levels=Zile, ordered=TRUE)
socl <- ocl %>% split(.$cls)  # listă [clasă => orarul clasei (ca 'tibble')]
sink("orareCls.txt")  # diviziuni pentru orarele claselor
for(ql in levels(ocl$cls)) {
    cat('<div><pre class="bkgnone">\n<b>', ql, '</b>\n', sep="")
    or <- socl[[ql]] %>% 
          select(prof, zi, ora) %>% 
          arrange(zi, ora)
    or1 <- or %>% 
           mutate(prof = unlist(obp[.$prof])) %>%
           spread(zi, prof) 
    print(as.data.frame(or1), row.names=FALSE, na.print='-', print.gap=2)
    cat('</pre></div>\n')
    # sumar Prof - Obiect - Nr.ore
    or3 <- or %>% 
           mutate(obj = unlist(obp[.$prof])) 
    poc <- table(or3[c('prof')])
    D <- data.frame(poc)
    D <- D %>% spread(Var1, Freq)
    ob <- unlist(obp[colnames(D)])
    DD <- rbind(D, ob) 
    cat('<div><pre>\n')
    print(DD, row.names=FALSE)
    cat('</pre></div>\n\n')
}
sink()

Desigur, diviziunea în care vom copia "orareCls.txt" trebuie şi ea prefixată cu un element <select> prin care să se indice clasa al cărei orar trebuie redat.

În final, orar.html conţine (în elemente <div><pre>) toate datele care trebuie prezentate (alternativa era de a prevedea câte o pagină HTML pentru fiecare categorie de orar). Handlerele de eveniment necesare se bazează pe hide() şi show() (din jQuery): se „ascund” toate diviziunile, apoi se arată aceea indicată. De exemplu:

    $('.ambdisc p').html(opt_disc());  // inserează un <select> pentru discipline
    $('.ambdisc select').on('change', function() {
        let idx = parseInt($(this).val());  // indexul disciplinei selectate
        $('.ambdisc').find('div').hide().eq(idx).show();
    });
    $('.ambdisc select').change();  // orarul primei discipline din lista <select>

Pentru orarele disciplinelor, avem o diviziune <div class="ambdisc"> conţinând un element <p> – în care înscriem un <select>, conţinând lista disciplinelor – şi apoi elemente succesive <div><pre>, conţinând orarul câte uneia dintre discipline; alegând o disciplină, se activează evenimentul 'change' pentru <select> şi se execută funcţia indicată, prin care se determină indexul 'idx' al disciplinei selectate, se ascund conţinuturile <div>-urilor disciplinelor şi apoi, se arată numai <div>-ul de index 'idx'.

vezi Cărţile mele (de programare)

docerpro | Prev | Next