├── .gitignore ├── Learning Journal HOW-TO.docx ├── .eslintrc ├── CHANGELOG.md ├── Learningjournal.css ├── README.md └── Learningjournal.js /.gitignore: -------------------------------------------------------------------------------- 1 | # notes # 2 | ################### 3 | *.txt* 4 | /node_modules 5 | ~* 6 | -------------------------------------------------------------------------------- /Learning Journal HOW-TO.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikeamelang/learning-journal/HEAD/Learning Journal HOW-TO.docx -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | /* "extends": "google", */ 4 | "extends": "eslint:recommended", 5 | 6 | "env": { 7 | "es6": true, /* added when working with babel. can remove if babel install fails */ 8 | /* "node": true, 9 | "browser": true */ 10 | }, 11 | 12 | "globals": { 13 | "$": false 14 | }, 15 | 16 | "rules": { 17 | "linebreak-style": 0, /* because this is Windows enviro */ 18 | "max-len": ["error", { "code": 120 , 19 | "ignoreComments": true }], 20 | "no-undef": 0, /* because otherwise functions/variables from a different js file throw errors */ 21 | "no-useless-escape": 0, /* because fixField has regex that causes this error */ 22 | "no-console": 1, /* because dumpObject function's purpose is console.log */ 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.1] - 2020-12-10 2 | ### Changed 3 | - Fixed bug where all notes are removed 4 | 5 | 6 | ## [2.0] - 2020-06-17 7 | ### Changed 8 | - Notes selector changed after an Articulate update 9 | 10 | 11 | ## [1.5] - 2020-02-27 12 | ### Added 13 | - Firefox compatibility added 14 | 15 | 16 | ## [1.4] - 2020-01-21 17 | ### Added 18 | - Email My Journal button functionality added 19 | 20 | 21 | ## [1.3] - 2020-01-20 22 | ### Added 23 | - Sections can be ordered with "Section Order:#" 24 | 25 | 26 | ## [1.2] - 2019-6-24 27 | ### Changed 28 | - added initialProcessNotes to run processNotes multiple times when page loads 29 | 30 | 31 | ## [1.1] - 2018-12-10 32 | ### Fixed 33 | - Moved printed section header png file to a more evergreen location 34 | 35 | 36 | ## [1.0] - 2018-10-9 37 | ### Added 38 | - Initial Commit 39 | -------------------------------------------------------------------------------- /Learningjournal.css: -------------------------------------------------------------------------------- 1 | 2 | /* set the background color of journal entries */ 3 | .block-statement--note-journalentry { 4 | background-color: #4b4b4b; 5 | } 6 | 7 | 8 | .journalentry-container{ 9 | margin-left: 8.33%; 10 | width: 83.333% 11 | } 12 | 13 | .journalentry-prompt { 14 | color:white !important; 15 | } 16 | 17 | .journalentry-prompt strong { 18 | font-weight: bold; 19 | } 20 | 21 | .journalentry-response { 22 | width:100%; 23 | height:5em; 24 | } 25 | 26 | .journalbuttons-container{ 27 | text-align: center; 28 | } 29 | 30 | .journalprintbutton { 31 | padding: 5px 10px; 32 | background-color: black; 33 | display: inline-block; 34 | margin-bottom: 15px; 35 | color: white; 36 | margin-right: 20px; 37 | padding-left: 20px; 38 | padding-right: 20px; 39 | } 40 | 41 | .journalprintbutton:hover { 42 | opacity:.7; 43 | cursor:pointer; 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learning Journal for Articulate Rise 2 | 3 | The Learning Journal allows a learner of an Articulate Rise course to enter text responses to journal prompts throughout a Rise module. At the end of the module, the learner can print their learning journal of all their responses. The responses are saved to the browser so that they persist on future visits to the Rise course. 4 | 5 | See the example Rise course at http://amelangrise.s3.amazonaws.com/learningjournal/index.html 6 | 7 | 8 | ## Use 9 | 10 | ### 1. Edit the Rise Course 11 | 12 | For detailed instructions on how to add the Learning Journal to the Rise course, see the [HOW-TO](https://github.com/mikeamelang/learning-journal/raw/master/Learning%20Journal%20HOW-TO.docx). 13 | 14 | ### 2. Add the Learning Journal files 15 | 16 | After outputting the Rise course, the following two files must be included alongside the index.html file of the Rise course: 17 | * Learningjournal.js 18 | * Learningjournal.css 19 | 20 | ### 3. Edit the index.html 21 | 22 | These two files must be linked in the index.html by including the following three lines in the `` of the index.html file of the Rise course: 23 | 24 | ``` 25 | 26 | 27 | 28 | ``` 29 | 30 | ## Notes 31 | 32 | The formatting can be customized by using the Learningjournal.css file. 33 | -------------------------------------------------------------------------------- /Learningjournal.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Custom Learning Journal in Rise 4 | ------------------------------- 5 | 6 | version: 2.2 7 | Project page: https://github.com/mikeamelang/learning-journal 8 | 9 | 10 | The Learning Journal allows a learner to enter text responses to 11 | journal prompts throughout a Rise module. At the end of the module, the learner 12 | can print their “learning journal” of all their responses. The responses are saved 13 | to the computer so that they persist on future visits to the Rise module. 14 | 15 | HOW TO ADD JOURNAL PROMPTS: 16 | Wherever a Journal Entry is needed in the Rise module, add a new block of type 17 | “NOTE” from the “STATEMENT” area and enter the following text: 18 | 19 | Journal Entry 20 | Section: 21 | Prompt: 22 | Take Action: yes 23 | 24 | HOW TO ADD AN INTRO TO A SECTION ON THE PRINTED JOURNAL: 25 | Wherever an intro to a section is needed in the Rise module, add a new block of type 26 | “NOTE” from the “STATEMENT” area and enter the following text: 27 | 28 | Section Intro 29 | Section: 30 | Section Order: 32 | Intro Text: 33 | 34 | HOW TO ADD PRINT BUTTONS OR PROVIDE A CUSTOM TITLE TO THE LEARNING JOURNAL: 35 | Two print buttons will be shown: Print all journal items and Print take 36 | action items only. (The actual text of these buttons is customized with the variables below: 37 | PrintAllButton_Text, PrintTakeActionsOnly_Text and EmailButton_Text) 38 | Wherever the print buttons are desired in the Rise module, add a new block of type 39 | “NOTE” from the “STATEMENT” area and enter the following text: 40 | 41 | Journal Buttons 42 | Course Title: 43 | Include Email Button: (This is not required. Default is no.) 44 | Email Address: (This is only required 45 | if the above "Include Email Button" is set to true.) 46 | 47 | */ 48 | 49 | // These css selectors select the Notes and select the contents of each Note 50 | var noteSelector = ".block-statement--note .block-statement__row"; // "[aria-label='Note']"; 51 | var noteContentsSelector = '.fr-view'; 52 | 53 | // These are the flags that must appear at the first line of the Note or the 54 | // Note will not be successfully processed 55 | var flagEntry = "Journal Entry"; 56 | var flagButtons = "Journal Buttons"; 57 | var flagIntro = "Section Intro"; 58 | 59 | // These are the labels that accompany the data. These must be entered exactly 60 | // correct or the Note will not be successfully processed 61 | var sectionlabel = "Section:"; 62 | var promptlabel = "Prompt:"; 63 | var takeactionlabel = "Take Action:"; 64 | var coursetitlelabel = "Course Title:"; 65 | var includeEmailButtonLabel = "Include Email Button:"; 66 | var emailAddressLabel = "Email Address:"; 67 | var introsectionlabel = "Section:"; 68 | var introSectionOrderLabel = "Section Order:"; 69 | var introtitlelabel = "Intro Title:"; 70 | var introtextlabel = "Intro Text:"; 71 | 72 | // These are the text for the Print buttons 73 | var PrintAllButton_Text = "Print My Journal"; 74 | var PrintTakeActionsOnly_Text = "Print My Actions"; 75 | var EmailButton_Text = "Email My Journal"; // text for the Email button, if active 76 | 77 | 78 | // These are the data storage variables. When the course loads, these are filled 79 | // with any existing journal entries found in localStorage. Likewise, when any entries are 80 | // updated, these data storage variables are updated AND the localStorage is updated. 81 | var UserData = {}; 82 | UserData.Sections = []; 83 | var courseTitle = ''; 84 | 85 | // localStorageItem is the item in localStorage where the journal entry data is stored. 86 | // a unique identifier is formed by concatenating 87 | // localStorageItem_prefix and the URL path up to the html file. 88 | var localStorageItem_prefix = 'LearningJournal_'; 89 | var localStorageItem = ''; 90 | 91 | // image in the printed journal header 92 | var imageIntroHeader = 'http://amelangrise.s3.amazonaws.com/SharedAssets/images/Reflection_Dark.png'; 93 | 94 | // These are the settings used by the autosave of journal entries 95 | var typingTimer; // timer identifier 96 | var doneTypingInterval = 400; // time in ms 97 | 98 | // Test if browser is firefox (used in printEntries) 99 | var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; 100 | 101 | /* ------------------------------------------------------------------------------------------------ */ 102 | 103 | 104 | $(document).ready(function() { 105 | setlocalStorageItem(); 106 | getSectionsfromLocalStorage(); 107 | initialProcessNotes(); 108 | addEvents(); 109 | 110 | }); 111 | 112 | 113 | /** 114 | * @desc sets the value for the variable localStorageItem by concatenating 115 | * localStorageItem_prefix and and the URL path up to the html file 116 | * @param none 117 | * @return string 118 | */ 119 | function setlocalStorageItem() { 120 | var loc = document.location; 121 | var uniqueURL = loc.origin + loc.pathname.substring(0, loc.pathname.lastIndexOf("/")); 122 | localStorageItem = localStorageItem_prefix + encodeURIComponent(uniqueURL); 123 | } 124 | 125 | 126 | 127 | /** 128 | * @desc Run processNotes several times when the page first loads 129 | * @return none 130 | */ 131 | function initialProcessNotes( ) { 132 | var MAX_INSTANCES = 5; 133 | var instances = 0; 134 | var myInterval = setInterval(myTimerProcessNotes, 300); 135 | function myTimerProcessNotes() { 136 | instances++; 137 | if (instances === MAX_INSTANCES ) { 138 | clearInterval(myInterval); 139 | } 140 | if (processNotes()) { clearInterval(myInterval) } 141 | } 142 | } 143 | 144 | 145 | 146 | /** 147 | * @desc add eventlisteners so that the func processNotes is fired when appropriate 148 | * @param none 149 | * @return none 150 | */ 151 | function addEvents() { 152 | 153 | // fire processNotes when the url changes 154 | function hashchanged(){ 155 | processNotes(); 156 | } 157 | window.addEventListener("hashchange", hashchanged, false); 158 | 159 | // fire processNotes when the CONTINUE button is clicked and new blocks are dynamically added 160 | function nodeadded(event) { 161 | if( event.relatedNode.nodeName == "SECTION" ) { 162 | if ( event.relatedNode.className == "blocks-lesson" ) { 163 | processNotes(); 164 | } 165 | } 166 | 167 | } 168 | window.addEventListener("DOMNodeInserted", nodeadded, false); 169 | } 170 | 171 | 172 | 173 | /** 174 | * @desc Create Section object 175 | * @param string title - title of section 176 | * @param string introtitle - title of the section intro that appears in printed journal 177 | * @param string introtext - text of the section intro that appears in printed journal 178 | * @return none 179 | */ 180 | function Section( title, order, introtitle, introtext ) { 181 | if (!order) { 182 | order = 999 183 | } 184 | this["title"] = title; 185 | this["order"] = order; 186 | this["entries"] = []; 187 | introtitle = (introtitle) ? introtitle : ''; 188 | this["introtitle"] = introtitle; // optional 189 | introtext = (introtext) ? introtext : ''; 190 | this["introtext"] = introtext; // optional 191 | } 192 | 193 | 194 | /** 195 | * @desc Create Entry object 196 | * @param string section - which section does this entry belong in (linked to a Section object) 197 | * @param string prompt - text of the prompt 198 | * @param string response - text of the response (blank if new) 199 | * @param bool isTakeAction - is this a Take Action? 200 | * @return none 201 | */ 202 | function Entry( section, prompt, response, isTakeAction ) { 203 | this["section"] = section; 204 | this["prompt"] = prompt; 205 | this["response"] = response; 206 | this["isTakeAction"] = isTakeAction; 207 | // another data element is entryid, added after the entry is created 208 | // another data element is sectionid, added after the entry is created 209 | } 210 | 211 | 212 | /** 213 | * @desc these functions either copy localStorageItem to UserData.Sections or vice versa 214 | * @param none 215 | * @return none 216 | */ 217 | function setSectionstoLocalStorage() { 218 | localStorage.setItem(localStorageItem, JSON.stringify(UserData.Sections)); 219 | } 220 | function getSectionsfromLocalStorage() { 221 | var retrievedString = localStorage.getItem(localStorageItem); 222 | if ( retrievedString == null || retrievedString == '' ) { 223 | localStorage.setItem(localStorageItem, ''); 224 | var emptyarray = []; 225 | return emptyarray; 226 | } else { 227 | UserData.Sections = JSON.parse(retrievedString); 228 | } 229 | } 230 | 231 | 232 | /** 233 | * @desc This is the workhorse of the learning journal. It finds all the Notes on the page 234 | * and processes them depending on what type of Note it is 235 | * @param none 236 | * @return true if Notes were found 237 | */ 238 | function processNotes() { 239 | 240 | var $notes = $( noteSelector); 241 | var returnValue = ($notes.length > 0) ? true : false ; 242 | 243 | $notes.each( function() { 244 | switch (this.querySelector(noteContentsSelector).firstChild.innerText.trim()) { 245 | case flagEntry: 246 | processEntry( this ); 247 | this.parentNode.removeChild(this); 248 | break; 249 | 250 | case flagButtons: 251 | processButtons( this); 252 | this.parentNode.removeChild(this); 253 | break; 254 | 255 | case flagIntro: 256 | processIntro( this ); 257 | this.parentNode.removeChild(this); 258 | break; 259 | 260 | default: 261 | break; 262 | } 263 | 264 | }); 265 | setSectionstoLocalStorage(); 266 | return returnValue; 267 | } 268 | 269 | 270 | /** 271 | * @desc This processes an Entry. If successful, it updates UserData 272 | * and renders the entry to DOM 273 | * @param jQueryObject note - the note to be processed 274 | * @return none 275 | */ 276 | function processEntry( note ) { 277 | 278 | var entry = createEntryfromNote( note ); 279 | if ( entry ) { 280 | 281 | // use indexSection and indexEntry to determine if this is a new section and entry 282 | var indexSection = -1; indexEntry = -1; 283 | for (var i = 0; i < UserData.Sections.length; i++) { 284 | var currentSection = UserData.Sections[i]; 285 | if ( currentSection.title == entry.section ) { indexSection = i; } 286 | for (var j = 0; j < currentSection.entries.length; j++ ) { 287 | if ( currentSection.entries[j].section == entry.section && 288 | currentSection.entries[j].prompt == entry.prompt ) { 289 | indexEntry = j; 290 | } 291 | } 292 | } 293 | 294 | // New section, new entry 295 | if (indexSection == -1 && indexEntry == -1 ) { 296 | indexSection = UserData.Sections.length; 297 | indexEntry = 0; 298 | var newsection = new Section( entry.section ); 299 | newsection.entries.push( entry ); 300 | UserData.Sections.push( newsection ); 301 | } 302 | 303 | // Existing section, new entry 304 | if (indexSection > -1 && indexEntry == -1 ) { 305 | indexEntry = UserData.Sections[indexSection].entries.length; 306 | UserData.Sections[indexSection].entries.push( entry ); 307 | } 308 | 309 | // Existing section, existing entry 310 | if (indexSection > -1 && indexEntry > -1 ) { 311 | entry.response = UserData.Sections[indexSection].entries[indexEntry].response; 312 | } 313 | 314 | renderEntrytoDOM( note.parentNode, entry, indexSection, indexEntry ); 315 | } 316 | } 317 | 318 | 319 | /** 320 | * @desc renders an Entry to DOM. 321 | * @param DOMElement parentcontainer - entry's parent container 322 | * @param Entry entry - the entry 323 | * @param string sectionid - the id of the corresponding section in UserData.Sections 324 | * @param string entryid - the id on the entry within UserData.Sections 325 | * @return none 326 | */ 327 | function renderEntrytoDOM( parentcontainer, entry, sectionid, entryid ) { 328 | 329 | // create container 330 | var container = document.createElement("div"); 331 | container.className = "journalentry-container"; 332 | container.dataset.sectionid = sectionid; 333 | container.dataset.entryid = entryid; 334 | 335 | // create prompt 336 | var prompt = document.createElement("div"); 337 | prompt.className = "journalentry-prompt"; 338 | prompt.innerText = entry.prompt; 339 | container.appendChild( prompt ); 340 | 341 | // create response 342 | var response = document.createElement("textarea"); 343 | response.className = "journalentry-response"; 344 | response.value = entry.response; 345 | container.appendChild(response); 346 | parentcontainer.appendChild(container); 347 | 348 | $( ".block-statement--note:has( .journalentry-container)").addClass("block-statement--note-journalentry"); 349 | } 350 | 351 | 352 | /** 353 | * @desc creates an Entry object from a Note. 354 | * @param DOMElement note - note from which to create the entry 355 | * @return Entry object or null if fail (section or prompt is empty) 356 | */ 357 | function createEntryfromNote( note ) { 358 | 359 | var section = '', prompt = '', isTakeAction = false; 360 | var notecontents = note.querySelector(noteContentsSelector); 361 | for (var i = 0; i< notecontents.childNodes.length; i++ ) { 362 | var a = notecontents.childNodes[i]; 363 | 364 | // set the section 365 | if ( a.innerText.substring(0,sectionlabel.length) == sectionlabel ) { 366 | section = a.innerText.substring(sectionlabel.length).trim(); 367 | } 368 | // set the prompt 369 | if ( a.innerText.substring(0,promptlabel.length) == promptlabel ) { 370 | prompt = a.innerText.replace(promptlabel, "").trim(); 371 | } 372 | // set the takeaction 373 | if ( a.innerText.substring(0,takeactionlabel.length) == takeactionlabel ) { 374 | var TakeActiontext = a.innerText.replace(takeactionlabel, "").trim(); 375 | if ( TakeActiontext.toLowerCase() == "yes" ) { isTakeAction = true } 376 | } 377 | } 378 | 379 | if (section != '' && prompt != '') { 380 | return new Entry( section, prompt, '', isTakeAction); // response is added later 381 | } else { 382 | return null; 383 | } 384 | } 385 | 386 | 387 | /** 388 | * @desc This processes the Buttons. It updates sets the courseTitle variable 389 | * and renders the buttons to DOM 390 | * @param DOMElement note - note 391 | * @return none 392 | */ 393 | function processButtons( note ) { 394 | 395 | var includeEmailButton = false; 396 | var emailAddress = ''; 397 | 398 | // Set Course Title 399 | var notecontents = note.querySelector(noteContentsSelector); 400 | for (var i = 0; i< notecontents.childNodes.length; i++ ) { 401 | var a = notecontents.childNodes[i]; 402 | 403 | // Set the Course Title 404 | if ( a.innerText.substring(0,coursetitlelabel.length) == coursetitlelabel ) { 405 | courseTitle = a.innerText.substring(coursetitlelabel.length).trim(); 406 | } 407 | 408 | // Include an Email button 409 | if ( a.innerText.substring(0,includeEmailButtonLabel.length) == includeEmailButtonLabel ) { 410 | var emailButtonSetting = a.innerText.replace(includeEmailButtonLabel, "").trim(); 411 | if ( emailButtonSetting.toLowerCase() == "yes" ) { includeEmailButton = true } 412 | } 413 | 414 | // Email address to which the journals will be emailed 415 | if ( a.innerText.substring(0,emailAddressLabel.length) == emailAddressLabel ) { 416 | emailAddress = a.innerText.substring(emailAddressLabel.length).trim(); 417 | } 418 | } 419 | 420 | // Render buttons to DOM 421 | var container = document.createElement("div"); 422 | container.className = "journalbuttons-container"; 423 | 424 | var button1 = document.createElement("div"); 425 | button1.className = "journalprintbutton"; 426 | button1.innerText = PrintAllButton_Text; 427 | button1.addEventListener("click", function() { printEntries(false)} ); 428 | container.appendChild(button1); 429 | 430 | var button2 = document.createElement("div"); 431 | button2.className = "journalprintbutton"; 432 | button2.innerText = PrintTakeActionsOnly_Text; 433 | button2.addEventListener("click", function() { printEntries(true)} ); 434 | container.appendChild(button2); 435 | note.parentNode.appendChild(container); 436 | 437 | if ( includeEmailButton ) { 438 | var button3 = document.createElement("div"); 439 | button3.className = "journalprintbutton"; 440 | button3.innerText = EmailButton_Text; 441 | button3.addEventListener("click", function() { emailEntries( emailAddress )} ); 442 | container.appendChild(button3); 443 | note.parentNode.appendChild(container); 444 | } 445 | } 446 | 447 | 448 | /** 449 | * @desc This processes a Section Intro, saving the intro information to UserData 450 | * @param DOMElement note - note 451 | * @return none 452 | */ 453 | function processIntro( note ) { 454 | 455 | var notecontents = note.querySelector(noteContentsSelector); 456 | var introsection = '', introSectionOrder = 999, introtitle = '', introtext = ''; 457 | for (var i = 0; i< notecontents.childNodes.length; i++ ) { 458 | var a = notecontents.childNodes[i]; 459 | 460 | // set the intro section 461 | if ( a.innerText.substring(0,introsectionlabel.length) == introsectionlabel ) { 462 | introsection = a.innerText.substring(introsectionlabel.length).trim(); 463 | } 464 | // set the intro section index 465 | if ( a.innerText.substring(0,introSectionOrderLabel.length) == introSectionOrderLabel ) { 466 | introSectionOrder = parseInt(a.innerText.substring(introSectionOrderLabel.length).trim()); 467 | if ( introSectionOrder !== introSectionOrder ) { // is not a number 468 | introSectionOrder = 999 469 | } 470 | } 471 | // set the intro title 472 | if ( a.innerText.substring(0,introtitlelabel.length) == introtitlelabel ) { 473 | introtitle = a.innerText.substring(introtitlelabel.length).trim(); 474 | } 475 | // set the intro text 476 | if ( a.innerText.substring(0,introtextlabel.length) == introtextlabel ) { 477 | introtext = a.innerText.replace(introtextlabel, "").trim(); 478 | 479 | // grab the rest of the Note for the text also 480 | i++; 481 | while (i < notecontents.childNodes.length) { 482 | introtext += "

" + notecontents.childNodes[i].innerText; 483 | i++; 484 | } 485 | } 486 | } 487 | 488 | if (introsection != '' && introtitle != '' && introtext != '') { 489 | var sectionMatch = -1; 490 | for (var j = 0; j < UserData.Sections.length; j++) { 491 | if ( UserData.Sections[j].title == introsection ) { sectionMatch = j; } 492 | } 493 | 494 | if (sectionMatch == -1) { 495 | // new section 496 | UserData.Sections.push( new Section( introsection, introSectionOrder, introtitle, introtext ) ); 497 | } else { 498 | // existing section 499 | UserData.Sections[sectionMatch].order = introSectionOrder; 500 | UserData.Sections[sectionMatch].introtitle = introtitle; 501 | UserData.Sections[sectionMatch].introtext = introtext; 502 | } 503 | UserData.Sections.sort( compareOrders ) 504 | } 505 | 506 | // SUB function 507 | // Sorts an array of objects on a particular property 508 | function compareOrders( a, b ) { 509 | if ( a.order < b.order ){ 510 | return -1; 511 | } 512 | if ( a.order > b.order ){ 513 | return 1; 514 | } 515 | return 0; 516 | } 517 | } 518 | 519 | 520 | // Set up autosave of journal entries to UserData and to localStorage 521 | // see https://stackoverflow.com/questions/4220126/run-javascript-function-when-user-finishes-typing-instead-of-on-key-up?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa 522 | $(document).on('keyup', '.journalentry-response', function(){ 523 | clearTimeout(typingTimer); 524 | var myentrycontainer = this.parentNode; 525 | typingTimer = setTimeout(function() { 526 | var response = myentrycontainer.querySelector('.journalentry-response').value; 527 | var sectionid = myentrycontainer.dataset.sectionid; 528 | var entryid = myentrycontainer.dataset.entryid; 529 | UserData.Sections[sectionid].entries[entryid].response = response; 530 | setSectionstoLocalStorage(); 531 | }, doneTypingInterval); 532 | }); 533 | 534 | 535 | /** 536 | * @desc prints the entries by opening a new browser window with a print button on it 537 | * @param bool TakeActionsOnly - are we printing all or simply Take Actions? 538 | * @return none 539 | */ 540 | function printEntries( TakeActionsOnly ) { 541 | 542 | var printtitle = ( TakeActionsOnly ) ? "Take Action Items" : "Learning Journal"; 543 | var printCommand = (isFirefox) 544 | ? 'window.print()' 545 | : 'document.execCommand(\"print\", false, null);'; 546 | var date = getDate(); 547 | 548 | var contents = "" 549 | contents+= "
"; 551 | contents+="
" + courseTitle + " " + printtitle + "
"; 552 | contents+="
"+date+"
"; 553 | 554 | // print each entry if applicable 555 | for (var i = 0; i< UserData.Sections.length; i++ ) { 556 | var currentSection = UserData.Sections[i]; 557 | 558 | var sectionheader = "
Section: " + currentSection.title + "
"; 559 | if ( currentSection.introtitle ) { 560 | sectionheader += 561 | "
" + 562 | "" + 563 | "
" + 564 | "
" + currentSection.introtitle + "
" + 565 | "
" + currentSection.introtext + "
" + 566 | "
"; 567 | } 568 | 569 | 570 | var sectioncontents = ''; 571 | for (var j = 0; j< currentSection.entries.length; j++ ) { 572 | if ( (!TakeActionsOnly || currentSection.entries[j].isTakeAction == true) && 573 | currentSection.entries[j].response != '' ) { 574 | sectioncontents+="
" + currentSection.entries[j].prompt + "
"; 575 | sectioncontents+="
" + currentSection.entries[j].response + "
"; 576 | } 577 | } 578 | if (sectioncontents != '' ) { 579 | contents+= "
" + sectionheader + sectioncontents + "
"; 580 | if (i != UserData.Sections.length - 1 ) { contents+= "
" } 581 | } 582 | //} 583 | } 584 | 585 | contents+= "" 586 | 587 | var myWindow = window.open("","Print " + getTimestamp(),"width=810,height=610,scrollbars=1,resizable=1"); 588 | myWindow.document.write(contents); 589 | 590 | var myStringOfstyles = "@media print { .no-print, .no-print * { display: none !important; } }" + 591 | "body { width:650px;padding:20px;font-family:sans-serif }" + 592 | ".printbutton { height:20px;padding:10px;margin-bottom:20px;text-align:center; }" + 593 | ".headertext { text-transform: uppercase;text-align:center;font-size:22px; " + 594 | " font-weight:bold;margin-bottom:20px; background-color: #4c4c4c !important; " + 595 | " -webkit-print-color-adjust: exact;color: white; padding: 15px 20px; }" + 596 | ".date { font-size:16px;font-weight:bold;text-align: center;margin-bottom: 30px }" + 597 | ".sectionarea { margin-bottom:80px;}" + 598 | ".sectionintrocontainer { margin-bottom: 5px; color: black; padding: 25px 20px;}" + 599 | ".sectionintroicon { height: 160px; display: inline-block; padding: 0px 20px}" + 600 | ".sectionintrotextcontainer { display: inline-block; width: 330px; vertical-align: top;" + 601 | " padding-left:20px}" + 602 | ".sectionintrotitle { font-weight: bold; font-size: 15pt;margin-bottom: 12px;}" + 603 | ".sectionintrotext { line-height: 18pt;}" + 604 | ".sectiontitle { font-weight: bold; margin-bottom: 10px;}" + 605 | ".pagebreak { page-break-before: always; }" + 606 | ".response { font-size: 11pt;border: 1.5px gray solid;padding: 15px;" + 607 | " margin-bottom: 20px;white-space: pre-wrap; margin-top: 0px; }" + 608 | ".prompt { font-size: 16px; background-color: #4c4c4c !important; " + 609 | " -webkit-print-color-adjust: exact;color: white; font-weight: bold; " + 610 | " padding: 8px 10px;line-height:15pt; }"; 611 | //".section { font-size: 18px;font-weight:bold;margin-top: 50px;text-align: center;margin-bottom: 15px }"; 612 | var linkElement = myWindow.document.createElement('link'); 613 | linkElement.setAttribute('rel', 'stylesheet'); 614 | linkElement.setAttribute('type', 'text/css'); 615 | linkElement.setAttribute('href', 'data:text/css;charset=UTF-8,' + encodeURIComponent(myStringOfstyles)); 616 | myWindow.document.head.appendChild(linkElement); 617 | 618 | var titleElement = myWindow.document.createElement('title'); 619 | var t = myWindow.document.createTextNode("Print " + printtitle); 620 | titleElement.appendChild(t); 621 | myWindow.document.head.appendChild(titleElement); 622 | 623 | // sub-function 624 | function getDate() { 625 | var m_names = new Array("January", "February", "March", 626 | "April", "May", "June", "July", "August", "September", 627 | "October", "November", "December"); 628 | var today = new Date(); 629 | var dd = today.getDate(); 630 | var mm = today.getMonth(); 631 | var yyyy = today.getFullYear(); 632 | if(dd<10) { dd='0'+dd } 633 | return m_names[mm]+' '+dd+', '+yyyy; 634 | } 635 | } 636 | 637 | 638 | /** 639 | * @desc emails the entries 640 | * @param none 641 | * @return none 642 | */ 643 | function emailEntries( emailAddress ) { 644 | 645 | var printtitle = "Learning Journal"; 646 | var lineBreak = '%0D'; 647 | var contents = courseTitle + lineBreak + printtitle + lineBreak + lineBreak; 648 | contents+= "------------------------------" + lineBreak; 649 | 650 | // print each entry if applicable 651 | for (var i = 0; i< UserData.Sections.length; i++ ) { 652 | var currentSection = UserData.Sections[i]; 653 | 654 | var sectionheader = "Section: " + currentSection.title + lineBreak; 655 | if ( currentSection.introtitle ) { 656 | sectionheader += 657 | currentSection.introtitle + lineBreak + 658 | currentSection.introtext + lineBreak + lineBreak; 659 | } 660 | 661 | 662 | var sectioncontents = ''; 663 | for (var j = 0; j< currentSection.entries.length; j++ ) { 664 | if ( currentSection.entries[j].response != '' ) { 665 | sectioncontents+= currentSection.entries[j].prompt + lineBreak; 666 | sectioncontents+= currentSection.entries[j].response + lineBreak + lineBreak; 667 | } 668 | } 669 | if (sectioncontents != '' ) { 670 | contents+= sectionheader + sectioncontents; 671 | if (i != UserData.Sections.length - 1 ) { contents+= "------------------------------" + lineBreak } 672 | } 673 | //} 674 | } 675 | 676 | 677 | window.open('mailto:' + emailAddress + 678 | '?subject=My Learning Journal&body=' + contents); 679 | 680 | 681 | } 682 | 683 | 684 | 685 | /** 686 | * @desc returns timestamp in the form of yyyymmddhhmmss 687 | * @param none 688 | * @return string 689 | */ 690 | function getTimestamp() { 691 | var today = new Date(); 692 | var mm = today.getMonth()+1; 693 | if(mm<10) { mm='0'+mm } 694 | var dd = today.getDate(); 695 | if(dd<10) { dd='0'+dd } 696 | var hh = today.getHours(); 697 | if(hh<10) { hh='0'+hh } 698 | var min = today.getMinutes(); 699 | if(min<10) { min='0'+min } 700 | var sec = today.getSeconds(); 701 | if(sec<10) { sec='0'+sec } 702 | return today.getFullYear() + mm + dd + hh+ min + sec ; 703 | } 704 | 705 | // Polyfill for isNaN 706 | Number.isNaN = Number.isNaN || function(value) { 707 | return value !== value; 708 | } 709 | --------------------------------------------------------------------------------