Arvostelu JavaScriptillä
Aina ei riitä yksinrtainen pisteytys tyyliin: "Jos vastauksessa lukee kissa, anna 1p". Tällöin voidaan turvautua csPlugin ja/tai JsRunnerin voimaan. Välimuotona voidaan pluginiin kirjoittaa arvostelun hoitava JavaScript-koodi.
1. Ensimmäinen esimerkki
Olkoon meillä tehtävä, jossa pitää kirjoittaa kaksi merkkijonoa, joista jälkimmäisen ollessa ensimmäistä pidempi, saadaan 1p.
Meni rikki, kun ajoin. Vai oliko tarkoitus dumpata kasa debug-tulostetta?
DZ: Joo, tarkoituksena on nähdä, mitä kaikkea dataa JavaScript-koodi saa käyttöön.
—Tämä on tehty koodilla:
``` {#e1 dquestion="true" plugin="qst"}
answerFieldType: inputText
answerLimit:
expl: {}
headers:
- ''
matrixType: textArea
questionText: Jonot
questionTitle: e1
questionType: matrix
size: "15"
rows:
- '1. jono a:'
- '2. jono b:'
postprogram: |!!
function points(a,b) {
print(a + "," + b + "\n"); // tulostetaan debhuggausta varten. Oikeasti ei.
if (a.length > 0 && a.length < b.length) return 1;
return 0;
}
print("\<pre\>" + JSON.stringify(data, null, 2) + "\</pre\>"); // debuggausta varten
let matrix = data.save_object;
data.points = points(matrix[0][0], matrix[1][0]);
return data;
!!
stem: "Kirjoita kaksi jonoa, joista jälkimmäinen on pidempi"
```
2. Käytössä olevat kentät
Funktiota postprogram
kutsutaan tallennusvaiheen lopussa ja se saa käyttönsä seuraavan tietueen:
data = { 'points': points,
'save_object': save_object,
'tags': tags,
'is_valid': is_valid,
'force_answer': force_answer,
'allow_save': true,
'refresh': false,
}
save_object
: tallennukseen menevä tietojen raakamuoto. Se mitä tämä sisältää, pitää tutkia jokaista plugintyyppiä varten erikseen. Helpoiten tämä onnistuu kun pistää koodin alkuun kehityksen alkuvaiheessa tulostuslauseen:print(JSON.stringify(data)+"\n");
voi kokeilla myös
println("md:```\n" + JSON.stringify(data, null, 4) + "\n```");
niin saa paremmin muotoiltua tulosta.
tags
: ???is_valid
: tallennetaanko tieto "laillisena" vai eiforce_answer
: pakotetaan tallennus vaikka sama data olisi jo tallessaallow_save
: tallennetaanko vastausta lainkaan (edes invalid)refresh
: pakotetaanko tehtävä latautumaan uudestaan vastauksen jälkeen. Kaikkipostprogram
-funktiossa tehdyt vastauksen muutokset eivät välttämättä näy tehtävässä ennen tehtävän tai sivun uudelleenlatausta
Funktio voi muuttaa näitä arvoja ennen saman datan palauttamista. Voi muuttaa jopa käyttäjän vastausta, mutta käyttäjä näkee sen vasta seuraavalla sivunlatauksella ellei koodissa ole pakotettu tehtävän uudelleenlautumista laittamalla data.refresh = true
.
Alkeellista ohjetulostetta saa käyttäjälle toistaiseksi käyttämällä tuota debuggaukseen käytettyä print
-lausetta.
Itse pääohjelman pitää päättyä aina
return data;
Oletuksena print
-lauseella tulostettu teksti tulostuu komponentin alapuolelle info-laatikkoon, jonka käyttäjä voi sulkea.
Tuloste voidaan ohjata myös menemään eri paikkaan käyttäen attribuuttia
postoutput: topfeedback # tulee komponentin yläpuolelle
postoutput: error # yläpuolelle hälytyslistaan
postoutput: web.error # pluginin error osaan, toiminta riippuu pluginista
3. Sisällön hakeminen eri pluguintyypeissä
Seuraavassa esimerkkejä eri plugineista. Katso lähdekoodi "View Source" -toiminnolla.
3.1 csPlugin
csPluginissa käyttäjän syöte ja esim ohjelman tuloste saadaan
let koodi = data.save_object.usercode;
let input = data.save_object.userinput;
let args = data.save_object.userargs;
let output = data.web.console;
let error = data.web.error;
Tässä tulos voidaan laittaa myös konsoliin:
data.web.console = palaute;
tai lisätä se tarvittaessa ohjelman tulostaman perään
data.web.console += "\n" + palaute;
Tai sama saadaan aikaiseksi myös ilmoittamalla tulostuksen kohde attribuutilla
postoutput: web.console
3.2 fields
Fieldeissä sisältömerkkijono on yleensä
let s = data.sava_object.c;
3.3 qst-monivalinnat
qst-monivalinnoissa tieto on 2-ulotteisessa talukossa, jossa riveillä on ruksien tapauksessa riveiltä valittujen kohtien numerot. Yhden sarakkeen "taulukoissa" sarake edustaa riviä.
let s = data.save_object;
3.4 TimTable
TimTablesta käyttäjän vastaus saadaan oliona:
let cells = data.save_object.userdata.cells
Tämän muoto on esimerkiksi:
{"B2":"musta","C5":"harmaa","D5":{"backgroundColor":"#FFFF00","cell":"keltainen"}}
Eli tuosta lukea mitä arvoja käyttäjä on muuttanut oletusarvoihin verrattuna. Yhden solun kohdalla voi olla joka solun sisältö tai jos myös sen attribuutteja (värejä yms) on muutettu, niin niiden arvot.
4. Extrafiles esimerkki JavaScriptillä
csPluginin ohjeissa on esimerkki Arvostelu extrafiles avulla. Tehdään vastaava esimerkki jsrunnerilla.
Esimerkki, jossa pitäisi antaa joku kissaeläimen nimi ja sitten saa pisteitä seuraavasti:
- tiikeri: 0.8 p
- kissa: 0.5 p
- muut 0 p
ja noista kustakin tulee joku palaute:
Koodi, jolla em. plugin on tuotettu:
``` {#lueKissa plugin="csPlugin"}
type: text
placeholder: "kirjoita joku kissaeläimen nimi tähän"
postprogram: |!!
// print(JSON.stringify(data)+"\n"); // debuggausta varten
let lines = data.save_object.usercode;
// print(lines);
let pisteet = 0;
let palaute = "";
if (lines.match(/.*tiikeri.*/)) {
pisteet = 0.8;
palaute = "Tämä oli hyvä";
} else if (lines.match(/.*kissa.*/)) {
pisteet = 0.5;
palaute = "Aika tavallinen";
} else {
palaute = "En tunne tätä kissaeläimeksi!";
}
data.points = pisteet;
print(palaute);
return data;
!!
```
5. Esimerkki jossa ohjelman antamia pisteitä vähennetään
Oletetaan että meillä on tehtävä, jossa pitää muuttaa ohjelma niin että siinä olevaa tulostetta on muutettava. Kääntyvästä ohjelmasta saa kaksi pistettä, mutta jos koodissa on edelleen "Hello" edes osana tai kokonainen pelkkä sana "world", niin vähennetään piste.
Laskentakoodi (kokonaisuudesta katso lähdekoodi):
postprogram: |!!
let illegals=[/hello/i, /\bworld\b/i];
let code = data.save_object.usercode;
let minus = 0;
for (let w of illegals)
if (m = code.match(w)) { print("Koodissa esiintyy " + m[0] + "\n"); minus++; }
minus = Math.min(minus, 1);
if ( minus > 0 ) print("Vähennetään " + minus + " pistettä!\n");
data.points = Math.max(data.points-minus, 0);
return data;
!!
- JavaScriptin regex ks: Regular_Expressions
6. Ulkoisia JavaScript-kirjastoja
Jos koodi on kirjoitettu ulkoiseen JS-tiedostoon, voidaan se ottaa mukaan attribuutilla postlibraries
:
postlibraries:
- https://tim.jyu.fi/csstatic/dfa/vars.js
postlibrariesEdit:
0:
deleteAfter: // ------------------ Variables END
Attribuutilla postlibrariesEdit
voidaan antaa joukko muokkauskomentoja, jolla kirjastoa muokataan ennen runnerille antamista. Usein tällä poistetaan esimerkiksi JavaScript modulia varten oleva jono
export {funktio1, funktio 2};
koska runnerin käyttämä JavaScript ei tätä tunne.
7. Arvottuja tehtäviä
Katso arvonnasta lisää dokumentista Satunnaistus
7.1 Perusmalli
Satunnaistus ei oikeastaan tuo tilanteeseen muuta uutta kuin, että arvostelussa pitää ehkä laskea vaadittava tulos arvonnan perusteella. Seuraavassa tämä esimerkkinä yhteenlaskuja kyselevästä versiosta.
Tämä on tehty koodilla:
``` {#summa plugin="csPlugin" rnd="[[-10,10],[-10,-1]]"}
type: text/tiny
stem: "Laske: %%rnd[0]%% + %%rnd[1]%%"
postprogram: |!!
// print(JSON.stringify(data)+"\n"); // debuggausta varten
let a = %%rnd[0]%%;
let b = %%rnd[1]%%;
let c = a + b;
let u = parseInt(data.save_object.usercode);
let t = "väärin!";
if (c === u) {
t = "OK";
data.points = 1;
}
print(`${a} + ${b} = ${c}; ${u} on ${t}`);
return data;
!!
```
7.2 Useita samankaltaisia tehtäviä toistuvasti
Mikäli halutaan sellainen harjoittelukomponentti, jolla voi tuottaa jatkuvasti uusia tehtäviä, voidaan lisätä pluginin otsikkoriville
seed="answernr"
jolloin siemenluku riippuu siitä monesko vastauskerta on menossa. Jokaisen vastauksen jälkeen voidaan pyytää uusi tehtävä, mutta uutta tehtävää ei saa ennen kuin entiseen on vastattu jotakin. Vanhoja tehtäviä ja vastauksia voi selata ja kokeilla uudelleen
Uuden tehtävän tuottavan painikkeen tekstin voi vaihtaa:
buttonNewTask: Uusi tehtävä
Näin tehty kysymys sopii parhaiten sellaiseen, missä pisteillä ei ole merkitystä, vaan harjoitellaan jotakin tiettyä tehtävätyyppiä niin kauan kuin haluaa. Toki pisteitäkin voi käyttää ja silloin oletuksena viimeisen tehdyn tehtävän pisteet jäävät voimaan. Alempana on esimerkki miten postprogram
-ohjelmalla voidaan tehdä sellainen, missä pitää kerätä 3 onnistunutta peräkkäistä suoritusta jotta saa pisteet.
Oletuksena uusiinkiin tehtäviin tulee viimeisin vanha vastaus. Jos tämän haluaa muuttaa niin, että uusiin tehtäviin tulee tyhjä vastauspohja voi lisätä attribuutin:
initNewAnswer: ""
Jos tämän lisäksi haluaa tietyn pohja-alustuksen jokaiselle uudelle tehtävälle, voi laittaa csPlugin-tehtävissä esimerkiksi:
fullprogram: "123"
7.2.1 Saved-tekstin poisto
Monissa plugineissa vastaaminen tuottaa esim tekstin Saved
. Tämä voi olla hämäävää, koska jos vastataan "vanhaan" kysymykseen uudelleen, uutta vastausta EI tallenneta eikä siihen vastaaminen muuta pisteitä. csPlugin-pohjaisissa tehtävissä voi laittaa attribuutin:
savedText: ""
jolloin ko tekstiä ei näy koskaan. Jos csPlugin pohjaisissa tehtävissä halutaan kuitenkin näyttää teksti oikean tallennuksen yhteydessä, mutta ei muulloin, voidaan laittaa postprogram
-ohjelmaan:
if (!data.answer_call_data.info.askNew) data.web.error = "";
Toki pitäisi myös tarkistaa että tuo error
teksti on vain tuo Saved
, muuten hukataan mahdollinen virheviesti. Ja tarvittaessa tietysti voisi laittaa muunkin tekstin silloin kun vastataan vanhoihin tehtäviin.
7.3 Täytyy saada monen tehtävän vastaus oikein
Seuraava muutos edelliseen esimerkkiin voisi olla sellainen, että pitää saada kolme peräkkäistä vastausta oikein, jotta saa yhden pisteen ja tämän jälkeen pisteet eivät muutu. Esimerkin perusteella koodia muokkaamalla voi mielikuvituksen rajoissa tehdä lähes mitä vaan. Esimerkki antaa lisäpisteitä peräkkäisten oikeiden määrän perusteella tms.
Funktion postprogram
data
-oliossa on attribuutti state
, josta saa edellisen tallennuksen tietoja. Ja koska uusi tallennus menee attribuutin save_object
kautta, voidaan näiden välisellä tiedonsiirrolla rakennella mitä vaan.
Katso esimerkin toteutus painamalla Edit
-menusta View source
.
7.4 Erota vastauksen tutkiminen omaksi tiedostokseen
Edellinen esimerkki toimii sellaisenaan. Mutta jos haluaisi tehdä paljon tuollaisia samankaltaisia tehtäviä, niin se kolmen yrityksen testaaminen ja datan palauttamien pitäisi kopioida jokaiseen tehtävään erikseen.
Siksi tuo koodi kannattaa kirjoittaa omaksi funktiokseen ja sitten siirtää omaan tiedostoonsa.
7.4.1 Tutkiminen omaan funktioon
7.4.2 Funktio "kirjastoon"
Luodaan uusi tiedosto, esimerkiksi funktiota/tarkistaonkovalmis.js
ja kopioidaan sinne funktion sisältö. Jotta dokumenttia ei muotoilla, lisätään dokumentin asetuksiin
textplain: true
Dokumentilla pitää antaa katseluoikeus (view
) anonyymeille käyttäjille.
Sitten lisätään pluginin attribuutteihin esimerkiksi:
postlibraries:
- https://tim.jyu.fi/print/tim/ohjeita/funktioita/tarkistaonkovalmis.js
Huomaa että tuo on muuten dokumentin osoite, mutta siinä on print
, jotta suoritetaan vain pelkkä tekstimuotinen "tulostus" kun tiedosto otetaan mukaan.
Oman tiedostoon kirjoitettavan funktion hyöty on siinä, että sitten kuka tahansa voi sitä käyttää mistä tahansa dokumentista ja näin voi luoda "kirjaston" yleiskäyttöisistä tarkistimista.
7.4.3 Funktio makroon
Toinen tapa tehdä funktiosta yleiskäyttöinen oman dokumenttihierarkian sisällä olisi laittaa dokumentin asetuksiin:
macros:
tarkistaonkovalmis: |!!
// Tarkistaa onko jo "tarvitaan" perättäistä oikeata vastausta
function tarkistaOnkoValmis(oikein, tarvitaan) {
let state = data.answer_call_data.state;
... muuta funktion rivit
}
!!
ja sitten itse pluginissa kirjoittaa laajennuspyyntö tähän makroon postprogram
- funktion alkuun:
postprogram: |!!
%%tarkistaonkovalmis%%
let a =...
Jos samaa funktiota tarvitaan hierarkiassa useammassa dokumentissa, voitaisiin makro laittaa sopivaan preamble-dokumenttiin. Kuitenkin jos funktiota tarvitaan useammassa tiedostossa, niin edellisen esimerkin oma dokumentti on suositeltavampi.
8. Tallennus vain tietyn ajan päästä
Jos halutaan tehdä sellainen tehtävä, johon saa vastata vain tietyn ajan päästä edellisestä, voidaan käyttää preprogram
attribuuttia hyväksi:
Esiohjelmassa katsotaan onko koskaan tallennettu aikaa. Jos ei, niin jatketaan normaalisti.
Jos on, verrataan kuinka paljon on kulunut edellisesti kerrasta ja estetään tallentaminen ja mahdollinen ajaminen yleensäkin.
Jälkiohjelmassa varmistetaan ettei edes tyhjää oliota tallenneta. Jos tallennus on sallittu, tallennetaan sitä vastaava aika sekunteina seuraavaa yritystä varten:
preprogram: |!!
// print("\<pre\>" + JSON.stringify(data, null, 2) + "\</pre\>"); // debug
let raja = 10; // kauanko pitää olla tallennusten väli
if (!data.state) return data; // Ei tallennettu vielä.
let last = data.state.date;
if (!last) return data; // Ei tallennettu vielä.
let sec = Math.round((new Date()).getTime()/1000) - last;
if (sec > raja) return data;
print("Viimeinen tallennus " + sec + " sekuntia sitten!<br>");
print("Et saa vielä yrittää uudestaan!");
data.markup.nosave = true;
data.input.nosave = true;
data.input.type = "text"; // ei aja koodia
data.markup.savedText = "Ei tallenneta";
return data;
!!
postprogram: |!!
// print("\<pre\>" + JSON.stringify(data, null, 2) + "\</pre\>"); // debug
let save_object = data.save_object;
if (!save_object || Object.keys(save_object).length === 0) {
data.allow_save = false;
return data; // koska edellinen ei antanut tallentaa
}
data.save_object.date = Math.round((new Date()).getTime()/1000);
return data;
!!
9. Arvojen laskeminen taulukossa
Tehdään "taulukkolaskentaa" vastaava yksinkertainen esimerkki.
10. Monta tehtävää joille yhteinen palautteen laskeminen
Tehtävät voit tehdä myös kunkin omaksi fieldikseen ja laskea niille palautteen.
Todo: Tutki toimiiko uudessa dokussa myös 1. kerralla?
Kun tehtävät laittaa taulukoon, on tuollaisen ylläpito helpompaa.
TIM: Voisiko samoja makroja mitenkään käyttää kaikissa alueen lohkoissa?
Laskutehtäviä 2
Paljonko on 2+2:
Please
Please
Please
Please
Please
Please
Paljonko on 1+2:
Paljonko on 3+3:
Please
These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.