diff --git a/Week1/homework/js-exercises/common-files/api-axios.js b/Week1/homework/js-exercises/common-files/api-axios.js new file mode 100644 index 000000000..adf85c0b9 --- /dev/null +++ b/Week1/homework/js-exercises/common-files/api-axios.js @@ -0,0 +1,17 @@ + + function requestByAxios (theURL,onSuccess,onFailure) { + axios.get(theURL) + .then(function(aResponse) { + // console.log('Axios call success! '); + // console.log(aResponse); + onSuccess(aResponse.data); + }) + .catch(function(anError) { + // console.log('Axios call failed with following message: '); + // console.log(anError); + onFailure('Axios',undefined,anError); + }); + }; + +; + diff --git a/Week1/homework/js-exercises/common-files/api-xhr.js b/Week1/homework/js-exercises/common-files/api-xhr.js new file mode 100644 index 000000000..5ca0ce102 --- /dev/null +++ b/Week1/homework/js-exercises/common-files/api-xhr.js @@ -0,0 +1,22 @@ + + function requestByXHR (theURL,onSuccess,onFailure) { + let myXHR=new XMLHttpRequest(); + myXHR.onreadystatechange=function() { + if (myXHR.readyState===XMLHttpRequest.DONE){ + if (myXHR.status===200) { + // console.log('XHR call success! '); + // console.log(JSON.parse(myXHR.responseText)); + onSuccess(JSON.parse(myXHR.responseText)); + } else { + // console.log(`XHR call failed with status=[${myXHR.status}] `); + // if (myXHR.statusText) {console.log(myXHR.statusText)}; + onFailure('XHR',myXHR.status,myXHR.statusText); + }; + }; + }; + myXHR.open("GET",theURL,true); + myXHR.send(); + }; + +; + diff --git a/Week1/homework/js-exercises/ex01-random-user/index.html b/Week1/homework/js-exercises/ex01-random-user/index.html new file mode 100644 index 000000000..401112b91 --- /dev/null +++ b/Week1/homework/js-exercises/ex01-random-user/index.html @@ -0,0 +1,28 @@ + + + + + + + + + + +
+ +
+
+ +
+
+
+ +
+ + + + + + + + diff --git a/Week1/homework/js-exercises/ex01-random-user/main.css b/Week1/homework/js-exercises/ex01-random-user/main.css new file mode 100644 index 000000000..489920058 --- /dev/null +++ b/Week1/homework/js-exercises/ex01-random-user/main.css @@ -0,0 +1,67 @@ + +* { + box-sizing: border-box; + margin: 0px; + padding: 0px; +} + +body { + margin: 0px 18px; + font-family: sans-serif; + font-size: 1.5em; +} + +table { + padding: 0px 0px 10px; +} + +td { + padding: 0px 12px 3px 0px; +} + +hr { + display: block; + margin: 12px auto 12px; + color: navy; + border-style: outset; + border-width: 2px; +} + +.cls_text_align_left { + text-align: left; +} + +.cls_text_align_right { + text-align: right; +} + +.cls_text_align_center { + text-align: center; +} + +.cls_unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +#id_page_header { + padding: 12px 16px 8px 16px; + margin: 20px 0 0; +} + +.cls_api_data_container { + background: linear-gradient(to bottom right, orange, gold); + border-radius: 12px; + font-size: 1.2rem; + padding: 20px; + margin-top: 15px; + margin-left: 10px; + display: inline-block; +} + +; + diff --git a/Week1/homework/js-exercises/ex01-random-user/main.js b/Week1/homework/js-exercises/ex01-random-user/main.js new file mode 100644 index 000000000..2c32e0ca9 --- /dev/null +++ b/Week1/homework/js-exercises/ex01-random-user/main.js @@ -0,0 +1,60 @@ + +{ + 'use strict'; + + let lmntRef=document.getElementById('id_page_header'); + lmntRef.style.backgroundColor='gold'; + document.getElementById('id_page_title').innerHTML=lmntRef.innerHTML= + `(Homework: Javascript 3 - week 1) - (Exercise 01: randomuser API with XHR & Axios)`; + lmntRef.style.borderRadius='12px'; + document.body.style.backgroundColor='#102030'; + + const symbolEuro='€'; + const symbolApostrophe='’'; /* ’ or ´ or ‘ */ + + const xhrListRef=document.getElementById('id_list_xhr_data'); + const axiosListRef=document.getElementById('id_list_axios_data'); + const targetURL='https://www.randomuser.me/api'; + + apiCallThroughXHR(); + apiCallThroughAxios(); + + function apiCallThroughXHR(){ + xhrListRef.innerHTML=createProgressHTML(); + requestByXHR(targetURL,(apiData)=>{xhrListRef.innerHTML=createDataHTML(apiData.results[0])} + ,(apiMethod,errorStatus,errorMsg)=>{xhrListRef.innerHTML=createErrorHTML(errorStatus,errorMsg)}); + }; + + function apiCallThroughAxios() { + axiosListRef.innerHTML=createProgressHTML(); + requestByAxios(targetURL,(apiData)=>{axiosListRef.innerHTML=createDataHTML(apiData.results[0])} + ,(apiMethod,errorStatus,errorMsg)=>{axiosListRef.innerHTML=createErrorHTML(errorStatus,errorMsg)}); + }; + + function createDataHTML(theData) { + return `Name(${theData.name.title}) ${ + theData.name.first} ${theData.name.last} ` + + `Gender${theData.gender} ` + + `Born${theData.dob.date.substr(0,10)} ` + + `Age${theData.dob.age} ` + + `Email${theData.email} ` + + `Country${theData.location.country} ` + + `City${theData.location.city} `; + } + + function createErrorHTML(theStatus,theMessage) { + return 'ResultUnsuccessful ' + + ' ' + + `Status code${theStatus} ` + + ' ' + + `Message${theMessage} `; + } + + function createProgressHTML() + {return 'RequestSending in progress... ';} + +} + + +; + diff --git a/Week1/homework/js-exercises/ex02-random-humor/index.html b/Week1/homework/js-exercises/ex02-random-humor/index.html new file mode 100644 index 000000000..e8740c1bd --- /dev/null +++ b/Week1/homework/js-exercises/ex02-random-humor/index.html @@ -0,0 +1,30 @@ + + + + + + + + + + +
+ +
+ +
+ + +
+ +
+
+
+ + + + + + + + diff --git a/Week1/homework/js-exercises/ex02-random-humor/main.css b/Week1/homework/js-exercises/ex02-random-humor/main.css new file mode 100644 index 000000000..7feef5b12 --- /dev/null +++ b/Week1/homework/js-exercises/ex02-random-humor/main.css @@ -0,0 +1,87 @@ + +* { + box-sizing: border-box; + margin: 0px; + padding: 0px; +} + +body { + margin: 0px 18px; + font-family: sans-serif; + font-size: 1.5em; +} + +table { + border-spacing: 0; +} + +tr { + background: linear-gradient(to bottom right, gold, orange); +} + +td { + vertical-align: top; + padding: 1px 4px; + max-width: 58vw; + overflow: hidden; + text-overflow: ellipsis; +} + +img { + margin: 0 10px 10px 0; + height: 100%; + border-width: 2px; + border-style: outset; +} + +hr { + display: block; + margin: 12px auto 12px; + color: navy; + border-style: outset; + border-width: 2px; +} + +.cls_text_align_left { + text-align: left; +} + +.cls_text_align_right { + text-align: right; +} + +.cls_text_align_center { + text-align: center; +} + +.cls_unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.cls_page_header { + padding: 12px 16px 8px 16px; + margin: 20px 0px; +} + +.cls_api_container { + background-color: orange; + font-size: 1.2rem; + padding: 12px 16px; + margin-top: 10px; + border-radius: 16px; +} + +.cls_api_data_container { + padding-top: 10px; + display: flex; + flex-wrap: wrap; +} + + +; + diff --git a/Week1/homework/js-exercises/ex02-random-humor/main.js b/Week1/homework/js-exercises/ex02-random-humor/main.js new file mode 100644 index 000000000..1c900154a --- /dev/null +++ b/Week1/homework/js-exercises/ex02-random-humor/main.js @@ -0,0 +1,84 @@ + +{ + 'use strict'; + + let lmntRef=document.getElementById('id_page_header'); + lmntRef.style.backgroundColor='gold'; + document.getElementById('id_page_title').innerHTML=lmntRef.innerHTML= + `(Homework: Javascript 3 - week 1) - (Exercise 02: Load image given by API with XHR & Axios)`; + lmntRef.style.borderRadius='12px'; + document.body.style.backgroundColor='#102030'; + lmntRef=document.getElementById('id_page_info'); + lmntRef.style.backgroundColor='gold'; + lmntRef.innerHTML='Using randomuser’s api for images since xkcd’s api is throwing CORB'; + lmntRef.style.borderRadius='12px'; + + const symbolEuro='€'; + const symbolApostrophe='’'; /* ’ or ´ or ‘ */ + + const dataTableRef=document.getElementById('id_api_data_table'); + const dataImageRef=document.getElementById('id_api_data_image'); + + const targetURL='https://www.randomuser.me/api'; + + apiCallThroughXHR(); + + function apiCallThroughXHR(){ + showRequestStart('XHR'); + requestByXHR(targetURL,showRequestData,showRequestError); + }; + + function apiCallThroughAxios() { + showRequestStart('Axios'); + requestByAxios(targetURL,showRequestData,showRequestError); + }; + + function showRequestStart(theMethod) { + resetInfo(); + dataTableRef.innerHTML= + 'Requestin progress... ' + + ' ' + + `from${targetURL} ` + + ' ' + + `with${theMethod} `; + }; + + function showRequestError(theMethod,theStatus,theMessage) { + resetInfo(); + dataTableRef.innerHTML= + `RequestUnsuccessful ` + + ' ' + + `from${targetURL} ` + + ' ' + + `with${theMethod} ` + + ' ' + + `Status code${theStatus} ` + + ' ' + + `Message${theMessage} `; + }; + + function showRequestData(theData) { + const myData=theData.results[0]; + resetInfo(); + dataTableRef.innerHTML= + `Name(${myData.name.title}) ${ + myData.name.first} ${myData.name.last} ` + + `Gender${myData.gender} ` + + `Born${myData.dob.date.substr(0,10)} ` + + `Age${myData.dob.age} ` + + `Email${myData.email} ` + + `Country${myData.location.country} ` + + `City${myData.location.city} `; + dataImageRef.setAttribute('src',myData.picture.large); + dataImageRef.hidden=false; + }; + + function resetInfo() { + dataTableRef.innerHTML=''; + dataImageRef.hidden=true; + }; +}; + + +; + diff --git a/Week1/homework/js-exercises/ex03-dog-photo-gallery/index.html b/Week1/homework/js-exercises/ex03-dog-photo-gallery/index.html new file mode 100644 index 000000000..08675e679 --- /dev/null +++ b/Week1/homework/js-exercises/ex03-dog-photo-gallery/index.html @@ -0,0 +1,24 @@ + + + + + + + + + + +
+ + + +

id_api_status_text

+ + + + + + + + + diff --git a/Week1/homework/js-exercises/ex03-dog-photo-gallery/main.css b/Week1/homework/js-exercises/ex03-dog-photo-gallery/main.css new file mode 100644 index 000000000..589e4eda3 --- /dev/null +++ b/Week1/homework/js-exercises/ex03-dog-photo-gallery/main.css @@ -0,0 +1,78 @@ + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background-color: #102030; + font-size: 1.2rem; + color: gold; + margin: 0px 18px; + font-family: sans-serif; +} + +ul { + list-style-type: none; + display: grid; + grid-template-columns:repeat(auto-fit,minmax(24rem,1fr)); + grid-gap: 4px 6px; + transform: rotate(180deg); +} + +li { + transform: rotate(180deg); + max-width: 84vmin; +} + +img { + width: 100%; +} + +hr { + display: block; + margin: 12px auto 12px; + color: navy; + border-style: outset; + border-width: 2px; +} + +.cls_text_align_left { + text-align: left; +} + +.cls_text_align_right { + text-align: right; +} + +.cls_text_align_center { + text-align: center; +} + +.cls_unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.cls_page_header { + background-color: gold; + font-size: 1.5em; + color: black; + border-radius: 12px; + margin: 20px 0px; + padding: 12px 16px 8px 16px; +} + +#id_api_status_text { + margin-top: 10px; + margin-bottom: 10px; +} + + +; + diff --git a/Week1/homework/js-exercises/ex03-dog-photo-gallery/main.js b/Week1/homework/js-exercises/ex03-dog-photo-gallery/main.js new file mode 100644 index 000000000..b4594230f --- /dev/null +++ b/Week1/homework/js-exercises/ex03-dog-photo-gallery/main.js @@ -0,0 +1,50 @@ + +{ + 'use strict'; + + let lmntRef=document.getElementById('id_page_header'); + document.getElementById('id_page_title').innerHTML=lmntRef.innerHTML= + `(Homework: Javascript 3 - week 1) - (Exercise 03: Dog photo API with XHR & Axios)`; + + const symbolEuro='€'; + const symbolApostrophe='’'; /* ’ or ´ or ‘ */ + + const dataTextRef=document.getElementById('id_api_status_text'); + const dataListRef=document.getElementById('id_api_data_list'); + + const targetURL='https://dog.ceo/api/breeds/image/random'; + + apiCallThroughXHR(); + + function apiCallThroughXHR(){ + showRequestStart('XHR'); + requestByXHR(targetURL,showRequestData,showRequestError); + }; + + function apiCallThroughAxios() { + showRequestStart('Axios'); + requestByAxios(targetURL,showRequestData,showRequestError); + }; + + function showRequestStart(theMethod) { + dataTextRef.innerHTML=`API Request at ${targetURL} by ${theMethod}`; + }; + + function showRequestError(theMethod,theStatus,theMessage) { + dataTextRef.innerHTML=theMethod+` Request failed with status=[${ + theStatus}] and message=[${theMessage}] `; + }; + + function showRequestData(theData) { + const itemRef=document.createElement('LI'); + dataListRef.appendChild(itemRef); + const imgRef=document.createElement('IMG'); + imgRef.setAttribute('src',theData.message); + itemRef.appendChild(imgRef); + dataTextRef.innerHTML='Concluded'; + }; +}; + + +; + diff --git a/Week1/homework/js-exercises/project-hack-your-repo-I/hyf.png b/Week1/homework/js-exercises/project-hack-your-repo-I/hyf.png new file mode 100644 index 000000000..76bc5a13b Binary files /dev/null and b/Week1/homework/js-exercises/project-hack-your-repo-I/hyf.png differ diff --git a/Week1/homework/js-exercises/project-hack-your-repo-I/index.html b/Week1/homework/js-exercises/project-hack-your-repo-I/index.html new file mode 100644 index 000000000..9c8f80c1a --- /dev/null +++ b/Week1/homework/js-exercises/project-hack-your-repo-I/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + HYF-GITHUB + + + + + +
+ + + + \ No newline at end of file diff --git a/Week1/homework/js-exercises/project-hack-your-repo-I/index.js b/Week1/homework/js-exercises/project-hack-your-repo-I/index.js new file mode 100644 index 000000000..45b769764 --- /dev/null +++ b/Week1/homework/js-exercises/project-hack-your-repo-I/index.js @@ -0,0 +1,94 @@ + +'use strict'; + +{ + function fetchJSON(url, cb) { + const xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.responseType = 'json'; + xhr.onload = () => { + if (xhr.status >= 200 && xhr.status <= 299) { + cb(null, xhr.response); + } else { + cb(new Error(`Network error: ${xhr.status} - ${xhr.statusText}`)); + } + }; + xhr.onerror = () => cb(new Error('Network request failed')); + xhr.send(); + } + + function createAndAppend(name, parent, options = {}) { + const elem = document.createElement(name); + parent.appendChild(elem); + Object.entries(options).forEach(([key, value]) => { + if (key === 'text') { + elem.textContent = value; + } else { + elem.setAttribute(key, value); + } + }); + return elem; + } + + function renderRepoDetails(repo, ul) { + createAndAppend('li', ul, { text: repo.name }); + } + + function renderRepoData(repoData,ul) { + const extractDateTime=(dtString)=>dtString.substr(0,10)+' '+dtString.substr(11,8); + const rowHTML=(aLabel,aValue,aLink)=>{ + const anStr='title="Click for transition to related page." target="_blank"'; + aValue=((aValue==undefined)||(aValue==null)||(String(aValue).trim()==='')) + ?'-':aValue; + aValue=((aLink==undefined)||(aLink==null)||(String(aLink).trim()==='')) + ?aValue:`${aValue}`; + return `${aLabel}${aValue}` + }; + repoData.sort((argA,argB)=>argA.name.localeCompare(argB.name)); + for (let i=0; i<10; i++) { + const symbolExpandCollapse=(isHidden)=>isHidden?'➕':'➖'; // or '⏬ ':'⏫ ' + const listItemRef=createAndAppend('LI',ul); + const initialHidden=false; + const itemRef=createAndAppend('DIV',listItemRef,{id:'id'+'_data_item_'+i + ,class:'cls_hover_highlight',text:`(${ + symbolExpandCollapse(initialHidden)}) `+repoData[i].name + ,title:'Click to expand / collapse the details'}); + itemRef.onclick=function(e) { + const trgtRef=document.getElementById(this.id+'_detail'); + trgtRef.hidden=!trgtRef.hidden; + this.textContent='('+symbolExpandCollapse(trgtRef.hidden) + +this.textContent.substr(2); + } + const detailRef=createAndAppend('DIV',listItemRef + ,{id:itemRef.id+'_detail',class:'cls_alt_color'}); + detailRef.hidden=initialHidden; + const tableRef=createAndAppend('TABLE',detailRef); + tableRef.innerHTML= rowHTML('Name (and link):',repoData[i].name,repoData[i].html_url) + + rowHTML('Description:',repoData[i].description) + + rowHTML('Forks:',repoData[i].forks_count) + + rowHTML('Created:',extractDateTime(repoData[i].created_at)) + + rowHTML('Updated:',extractDateTime(repoData[i].updated_at)); + }; + }; + + function main(url) { + const root=document.getElementById('root'); + createAndAppend('DIV',root,{class:'cls_colored_header',text:'HYF Repositories'}); + root.setAttribute('class','cls_unselectable'); + fetchJSON(url, (err, repos) => { + if (err) { + createAndAppend('div',root,{text:err.message,class:'alert-error'}); + return; + } + renderRepoData(repos,createAndAppend('UL',root)); + }); + } + + const HYF_REPOS_URL = + 'https://api.github.com/orgs/HackYourFuture/repos?per_page=100'; + window.onload = () => main(HYF_REPOS_URL); +}; + + +; + diff --git a/Week1/homework/js-exercises/project-hack-your-repo-I/style.css b/Week1/homework/js-exercises/project-hack-your-repo-I/style.css new file mode 100644 index 000000000..31425126a --- /dev/null +++ b/Week1/homework/js-exercises/project-hack-your-repo-I/style.css @@ -0,0 +1,138 @@ + + +:root { + --normal_back_color: wheat; + --normal_text_color: black; + --attention_back_color: lightblue; + --alternate_back_color: #0a1a3e; // dark blue-black + --alternate_text_color: #efefef; + --interactive_back_color: silver; + --interactive_text_color: black; + --hover_back_color: orange; + --hover_text_color: black; + --focus_back_color: yellow; + --focus_text_color: black; + --alert_back_color: red; + --alert_text_color: gold; +} + + +* { + vertical-align: middle; + padding: 0; + margin: 0; + cursor: default; + box-sizing: border-box; +} + + +:focus { + background-color: var(--focus_back_color); + outline: none; +} + + +body { + background-color: var(--normal_back_color); + color: var(--alternate_back_color); + margin-top: 5px; + font-size: 1.5rem; + font-family: "Roboto", sans-serif; +} +body:before { + background: url("./hyf.png"); + background-size: cover; + content: " "; + position: fixed; + z-index: -1; + width: 42vmin; + height: 42vmin; + top: 1rem; + right: 1rem; + opacity: 0.24; +} + + +div { + padding: 16px 20px; +} + + +li { + list-style-type: none; +} + + +td { + padding: 0px 12px 3px 0px; + vertical-align: top; +} + + +a { + background-color: var(--interactive_back_color); + padding: 0 12px; +} +a:hover { + background-color: var(--hover_back_color); + text-decoration: none; +} + + +.cls_colored_header { + background: linear-gradient(to right, var(--attention_back_color) , rgba(128,128,128,0) ); + font-weight: bold; + font-size: 1.7rem; + font-style: italic; +} + + +.cls_alt_color { + background: linear-gradient(to right, var(--attention_back_color) , rgba(128,128,128,0) ); +} + + +.cls_hover_highlight { + background: linear-gradient(to right, var(--interactive_back_color) , rgba(128,128,128,0) ); + margin-top: 8px; + font-weight: bold; + z-index: 1; + position: relative; +} +.cls_hover_highlight::before { + background: linear-gradient(to right, var(--hover_back_color) , rgba(128,128,128,0.05) ); + content: ""; + position: absolute; + opacity: 0; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: -1; + transition: opacity 203ms linear; +} +.cls_hover_highlight:hover::before { + opacity: 1; +} + + +.alert-error { + background: linear-gradient(to right, var(--alert_back_color) , rgba(128,128,128,0) ); + color: var(--alert_text_color); + font-size: 2rem; + font-weight: bold; +} + + +.cls_unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + + +; + diff --git a/Week2/homework/README.md b/Week2/homework/README.md new file mode 100644 index 000000000..7aadbe9c2 --- /dev/null +++ b/Week2/homework/README.md @@ -0,0 +1,77 @@ + + +About the Project (the web page) : +==================================== +Extra effort was included to fortify the page from breaking due to bad / unexpected data. + +For this reason extra functionality was included and there exists an option to run the page in data emulation mode +(which actually is the initial page mode), +wherein the data is not requested through an API call, but is instead supplied by an internal function, that produces data with diverse errors, nonexistent, unexpected or invalid values. + +To that end, a special "options" section has been added to the top of the page, whence from the page behavior can be influenced. + +All the originally-required essential HTML elements are produced through javascript code. + +The only additions to the actual html file are those introduced for the new "options" section, and that only so they can retain their settings between page refreshes. + + +About the "options" section : +==================================== +The "options" section can be toggled to visible / hidden by tapping the cog icon on the upper right corner. + +If hidden, it will stay so until revealed by taping the same icon (or when you refresh the web page and option #4 is checked). + +It can be kept permanently hidden after page refresh, by un-checking option #4. + +By default the options are set to emulate API calls, and will do so while option #1 is checked. + +Options #1 , #2 and #3 will influence the next fetch operation. +(meaning you could mix & match "emulated" and "live" data). +An Exception is when the page gets refreshed, where there are 2 consecutive fetches, both of which are subjected to the specified options. + +Error production (option #2) is done by: +1. in case of emulated data a sample error is thrown +2. in case of real API call, the URL is slightly altered to produce an error response + + +Empty datasets (option #3) are always emulated, regardles of option #1 + +In case when options #3 and #4 are both checked, then option #3 has precedence, meaning an error result will be produced before an empty dataset. + + +About the "emulated" data : +==================================== +The emulated "repositories" result consists of a special data collection with a multitude of nonexistent, erroneous, null, undefined, empty or missing values, test values, missing names, same names with upper/lower case differences, etc... + +It is supposed to represent possible data problems a page may encounter, and has to fortify against in order to work correctly. + +The subsequent "contributors" results are semi-random, meaning that names are completely random, but images and links are real, tho inserted into random objects, and also the resulting contributor list length is randomized. + + +About the "live" API data : +==================================== +Some subsequent (contributor list) fetches fail because of bad links. +Located repos with links that fail are: +1. english-booster +2. hyfer-infra + +Those 2 hold broken values in their "contributors_url". + +Evoking the addresses of those urls in the browser yields empty pages. + +Also the repo "DataStructures" has an empty contrib list result. + + +About the data in general : +==================================== +In case of subsequent fetch errors (those for contributors) I have opted not to display an error but instead to just log the error and the link in the console, and then let the page behave as if an empty result was received. + +In order to avoid web traffic, and possible API request refusal due to frequency, "contributors" fetching is done once for each repo, and that result is stored and used until page refresh. + +That means that, once a result has been received, whether it is an actual list, an empty dataset or an error which results in an empty dataset, that will be the result displayed each time the user selects that repo from the repo selection element. + + +Bunus objective: undesired API request prevention +==================================== +Additional effort was put into the prevention of consecutive API requests when the contents of the repo SELECT item are scrolled rapidly through by the user. To that end, once a repo-change has initiated an API request, all subsequent repo-change-handling is halted until that API request has concluded. + diff --git a/Week2/homework/project-hack-your-repo-II/hyf.png b/Week2/homework/project-hack-your-repo-II/hyf.png new file mode 100644 index 000000000..76bc5a13b Binary files /dev/null and b/Week2/homework/project-hack-your-repo-II/hyf.png differ diff --git a/Week2/homework/project-hack-your-repo-II/index.html b/Week2/homework/project-hack-your-repo-II/index.html new file mode 100644 index 000000000..845356a90 --- /dev/null +++ b/Week2/homework/project-hack-your-repo-II/index.html @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + HYF-GITHUB + + + + + + + +
+

+ Due to GitHub’s strict rules on API request frequency, + you may experience a "forbidden" response. +

+

+ In that case ( or for testing, or even for fun ) + you can use the following options to "emulate" + API data fetching with sample / random Data. +

+

+ Note: set options persist even after page refresh, + thus enabling them to influence the initial auto-fetch + that creates the Repository List. +

+ + + + + + + + + + + + + + + + + +
Return emulated data on next fetch ?
Force example error on next fetch ?
Force empty result on next fetch ?
Keep Options visible on page refresh ?
+
+ +
+ + + + + diff --git a/Week2/homework/project-hack-your-repo-II/index.js b/Week2/homework/project-hack-your-repo-II/index.js new file mode 100644 index 000000000..1b9c17ae9 --- /dev/null +++ b/Week2/homework/project-hack-your-repo-II/index.js @@ -0,0 +1,367 @@ + +'use strict'; + +{ + const root=document.getElementById('root'); + // root.setAttribute('class','cls_unselectable'); + // + const hdrRef=addToDOM('DIV',root,{class:'cls_colored_header'}); + const msgRef=addToDOM('SPAN',addToDOM('DIV',hdrRef + ,{class:'cls_responsive_child cls_padded_area'}) + ,{text:'API data request in progress'}); + const dtlRef=addToDOM('DIV',root,{class:'cls_responsive_parent'}); + const contribSymbolKey=Symbol('DOT.NOT: contributor list key'); + // + const optEmulation =document.getElementById('id_opt_emulation'); + const optForceError=document.getElementById('id_opt_force_error'); + const optForceEmpty=document.getElementById('id_opt_force_empty'); + const optKeepVisible=document.getElementById('id_opt_keep_visible'); + const optAreaRef=document.getElementById('id_options_area'); + optAreaRef.hidden=!optKeepVisible.checked; + const optButton=document.getElementById('id_options_image'); + const optBtnTitle=()=>{return `Tap to ${optAreaRef.hidden?'show':'hide'} Options`}; + optButton.title=optBtnTitle(); + optButton.onclick=()=>{ + optAreaRef.hidden=!optAreaRef.hidden; + optButton.title=optBtnTitle(); + }; + let repoData=[],selectRef; + const rootURL='https://api.github.com/orgs/HackYourFuture/repos?per_page=100'; + // + window.onload=()=>{ + fetchWrapper('repositories',(theResponse)=>{ + repoData=clone_JSON_object(theResponse) // theResponse + .map(lmnt=>{return { + name :getSecureValue(lmnt.name,'- not named -'), + description :getSecureValue(lmnt.description,'-'), + forks_count :getSecureValue(lmnt.forks_count,'0'), + created_at :getSecureValue(lmnt.created_at,'-'), + updated_at :getSecureValue(lmnt.updated_at,'-'), + html_url :getSecureValue(lmnt.html_url), + contributors_url:getSecureValue(lmnt.contributors_url), + }}) + .sort((argA,argB)=>argA.name.localeCompare(argB.name)); + createRepoSelectionList(); + },(anError)=>{ + msgRef.textContent='API data request error.'; + addToDOM('div',root,{text:anError,class:'cls_error_alert'}); + }); + }; + + function createRepoSelectionList () { + if (repoData.length<1) { msgRef.textContent='No repositories available!'; + } else { + msgRef.textContent='HYF Repositories'; + selectRef=addToDOM('SELECT',addToDOM('DIV',hdrRef + ,{class:'cls_responsive_child cls_padded_area'})); + selectRef.innerHTML=repoData.reduce((tot,cur,idx)=> + tot+``,''); + selectRef.onchange=createRepoDetails; + createRepoDetails(); + } + }; + + function createRepoDetails() { + const had_onchange=(selectRef.onchange!==null); + if (had_onchange) {selectRef.onchange=null}; + const current_repoID=Number(selectRef.value); + removeChildrenFromDOM(dtlRef); + const repoRef=addToDOM('TABLE',addToDOM('DIV',addToDOM('DIV',dtlRef + ,{class:'cls_responsive_child'}),{class:'cls_alt_color cls_padded_area'})); + const theRepo=repoData[current_repoID]; + repoRef.innerHTML= tableRowHTML('Repository:',theRepo.name,theRepo.html_url) + + tableRowHTML('Description:',theRepo.description) + + tableRowHTML('Forks:',theRepo.forks_count) + + tableRowHTML('Created:',extractDateTime(theRepo.created_at)) + + tableRowHTML('Updated:',extractDateTime(theRepo.updated_at)); + const listRef=addToDOM('DIV',dtlRef + ,{class:'cls_responsive_child cls_alt_color cls_padded_area'}); + const titleRef=addToDOM('P',listRef,{text:'API data request in progress'}); + if (theRepo[contribSymbolKey]==undefined) { + fetchWrapper(theRepo.contributors_url,(theResponse)=>{ + theRepo[contribSymbolKey]=clone_JSON_object(theResponse) + .map(lmnt=>{return { + html_url :getSecureValue(lmnt.html_url), + avatar_url :getSecureValue(lmnt.avatar_url), + login :getSecureValue(lmnt.login,'-'), + contributions:getSecureValue(lmnt.contributions,'0') + }}); + createContribList(theRepo[contribSymbolKey]); + },(anError)=>{ + /* + // ignore any possible errors + // instead act as if contrib request returned empty list + // + // because the actual repo list contains repos + // that return an error when asked for contributors + // + // repos with error in contributor links + // english-booster + // hyfer-infra + // + // repo without contributors + // DataStructures + // + // + // BUT: make sure to console.log the link and error + // + titleRef.textContent='API data request error.'; + addToDOM('DIV',listRef,{text:anError}) + finalizeRepoDetails(); + */ + console.log('API request error for url',theRepo.contributors_url); + console.log('Error message',anError); + theRepo[contribSymbolKey]=[]; + createContribList(theRepo[contribSymbolKey]); + }); + } else { + createContribList(theRepo[contribSymbolKey]); + }; + function createContribList(theData) { + if (theData.length<1) {titleRef.textContent='No contributors available!'} + else { + titleRef.innerHTML='Contributions'; + const contRef=addToDOM('TABLE',addToDOM('DIV',listRef) + ,{class:'cls_span_full_parent_width'}); + let theHTML=''; + theData.forEach(lmnt=>{ + const theIMG=``; + theHTML+=`${tableCellHTML(theIMG)}${ + tableCellHTML(lmnt.login,lmnt.html_url)}${ + tableCellHTML(lmnt.contributions)}`; + }); + contRef.innerHTML=theHTML; + }; + finalizeRepoDetails(); + }; + function finalizeRepoDetails() { + if (current_repoID!=selectRef.value) {createRepoDetails()}; + if (had_onchange) {selectRef.onchange=createRepoDetails}; + }; + function tableRowHTML(aLabel,aValue,aLink) { + return `${tableCellHTML(aLabel,null,false,true)}${ + tableCellHTML(aValue,aLink,true)}`; + }; + function tableCellHTML(theValue,theLink=null,makeBold=false,makeItalic=false) { + let result=getValidAnchor(theValue,theLink); + if (makeBold) {result=`${result}`}; + if (makeItalic) {result=`${result}`}; + return `${result}`; + }; + function getValidAnchor(aText,aLink) { + const trg='title="Tap for transition to related page." target="_blank"'; + return isUndefinedNullEmpty(aLink)?aText:`${aText}`; + }; + function extractDateTime(dtString){ + return dtString.substr(0,10)+' '+dtString.substr(11,8); + }; + }; + + function isUndefinedNullEmpty(theData) { + return (theData==undefined)||(theData==null)||(String(theData).trim()===''); + }; + + function getSecureValue(theData,defValue='') { + return isUndefinedNullEmpty(theData)?defValue:theData; + }; + + function clone_JSON_object(theObject) {return JSON.parse(JSON.stringify(theObject))}; + + function addToDOM(theTag,theParent,theOptions={}) { + const lmnt=document.createElement(theTag); + Object.entries(theOptions).forEach(([aKey,aValue])=>{ + if (aKey==='text') {lmnt.textContent=aValue} + else {lmnt.setAttribute(aKey,aValue)}; + }); + theParent.appendChild(lmnt); + return lmnt; + }; + + function removeChildrenFromDOM (theLmnt) { + while (theLmnt.hasChildNodes()) { + removeChildrenFromDOM(theLmnt.lastChild); + theLmnt.removeChild(theLmnt.lastChild); + }; + }; + + function fetchWrapper (theResource,onData,onError) { + if (optEmulation.checked) { + if (theResource==='repositories') { + fetchTestData(optForceError.checked,optForceEmpty.checked + ,theResource,onData,onError); + } else { + fetchTestData(optForceError.checked,optForceEmpty.checked + ,'contributors',onData,onError); + }; + } else { + if (theResource==='repositories') { + if (optForceError.checked) { + const targetURL='https://api.github.com/orgs/HackYourFuture/Prepos?per_page=1'; + fetchData(targetURL,onData,onError); + } else if (optForceEmpty.checked) { + fetchTestData(false,true,theResource,onData,onError); + } else { + fetchData(rootURL,onData,onError); + }; + } else { + if (optForceError.checked) { + const targetURL='https://api.github.com/orgs/HackYourFuture/Prepos?per_page=1'; + fetchData(targetURL,onData,onError); + } else if (optForceEmpty.checked) { + fetchTestData(false,true,theResource,onData,onError); + } else { + fetchData(theResource,onData,onError); + }; + }; + }; + }; + + function fetchData (dataURL,dataCallback,errorCallback) { + fetch(dataURL) + .then((theResponse)=>{ + if (!theResponse.ok) { + const errPrefix=`Request Error: status=${theResponse.status} msg=`; + throw errPrefix+theResponse.statusText; + }; + return theResponse.json(); + }) + .then((theData)=>{dataCallback(theData)}) + .catch((anError)=>{errorCallback(anError)}); + }; + + function fetchTestData (produceError,produceEmpty,dataURL,dataCallback,errorCallback) { + function getRandomNumberBetween (minValue,maxValue){return (Math.floor(Math.pow(10,14)* + Math.random()*Math.random())%(maxValue-minValue+1))+minValue}; + function delayedCallback(argDelay,argCB,argData) {setTimeout(()=>{argCB(argData)},argDelay)}; + const repositories = [ + {name:'One Thing', description:'description of 1st One Thing', forks_count:7, + created_at:'2005-04-24T07:20:48Z', updated_at:'2019-01-11T18:21:37Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'one Thing', description:'description of 2nd one Thing', forks_count:3, + created_at:'2006-03-23T07:19:38Z', updated_at:'2017-09-05T15:52:37Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'one thing', description:'description of 3rd one thing', forks_count:1, + created_at:'2008-01-21T07:17:18Z', updated_at:'2013-11-15T11:02:37Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'One thing', description:'description of 4th One thing', forks_count:6, + created_at:'2007-02-22T07:18:28Z', updated_at:'2015-07-25T13:48:37Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'another thing', description:'another thing (missing link)', forks_count:4, + created_at:'2017-04-03T17:26:16Z', updated_at:'2020.01.11T05:46:02Z', + }, + {name:'that’s the thing', description:'description of that’s the thing', forks_count:1, + created_at:'2016-08-07T19:17:31Z', updated_at:'2017-09-14T10:34:47Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'The others', description:'description of The others', forks_count:7, + created_at:'2014-12-25T05:57:35Z', updated_at:'2020-02-13T21:31:47Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'JustLookAtThisGargantuanRepositoryNameWithEmptyDescription', description:' ', forks_count:9, + created_at:'2011-10-04T21:49:54Z', updated_at:'2013-05-10T16:01:23Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'that thing again', description:'description of that thing again', forks_count:3, + created_at:'2017-03-14T02:20:29Z', updated_at:'2020-03-01T06:34:36Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'just some more', description:'just some more (link is null)', forks_count:5, + created_at:'2015-10-12T00:14:52Z', updated_at:'2018-10-20T10:31:57Z', + html_url:null}, + {name:'everything', description:'everything (empty link)', forks_count:4, + created_at:'2016-03-30T01:25:17Z', updated_at:'2019-04-30T15:42:34Z', + html_url:' '}, + // {name:'everything else', description:'description of everything else', forks_count:4, + {name:'everything else (missing description and dates)', forks_count:4, + //created_at:'2017-09-23T21:15:20Z', updated_at:'2020-01-11T15:02:51Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + //{name:'another life', description:'description of another life', forks_count:3, + {name:'another life (null description & dates)', description:null, forks_count:3, + //created_at:'2015-07-17T22:41:40Z', updated_at:'2017-05-28T19:04:24Z', + created_at:null, updated_at:'', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'one more please', description:'description of one more please', forks_count:2, + created_at:'2014-02-28T12:54:51Z', updated_at:'2020-02-15T19:51:54Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'Another brand', description:'description of another brand', forks_count:8, + created_at:'2013-06-21T04:36:50Z', updated_at:'2018-12-16T07:11:13Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'useless things', description:'description of useless things', forks_count:6, + created_at:'2011-01-31T09:23:38Z', updated_at:'2017-11-28T10:34:16Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'good to know', description:'description of good to know', forks_count:0, + created_at:'2019-02-23T06:32:43Z', updated_at:'2020-01-05T02:09:21Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'turn around', description:'description of turn around', forks_count:2, + created_at:'2013-11-19T17:34:22Z', updated_at:'2016-07-30T16:48:14Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'turn some more', description:'description of turn some more', forks_count:0, + created_at:'2014-05-09T15:29:00Z', updated_at:'2019-09-04T12:02:45Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:' ', description:'description of (empty name)', forks_count:' ', + created_at:'2015.06.04T17:50:07Z', updated_at:'2020-02-01T01:29:33Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + { description:'description of (missing name)', + created_at:'2015.06.04T17:50:07Z', updated_at:'2020-02-01T01:29:33Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:null, description:'description of (null name)', forks_count:null, + created_at:'2015.06.04T17:50:07Z', updated_at:'2020-02-01T01:29:33Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'abusing power', description:'everything you ever wanted to know but where afraid to ask' + , forks_count:12, + created_at:'2011-11-22T05:05:18Z', updated_at:'2020-03-01T06:20:51Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'what a name', description:'description of what a name', forks_count:6, + created_at:'2017-12-10T08:37:15Z', updated_at:'2017-12-30T17:32:15Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + {name:'what goes there', description:'description of what goes there', forks_count:0, + created_at:'2012-02-14T15:01:08Z', updated_at:'2020-02-25T01:41:41Z', + html_url:'https://github.com/SocialHackersCodeSchool'}, + ]; + const contrib_img_url = [ + 'https://avatars3.githubusercontent.com/u/8708858?v=4', + 'https://avatars3.githubusercontent.com/u/969026?v=4', + 'https://avatars3.githubusercontent.com/u/33282467?v=4', + 'https://avatars0.githubusercontent.com/u/651290?v=4', + 'https://avatars2.githubusercontent.com/u/17527017?v=4', + 'https://avatars0.githubusercontent.com/u/13186712?v=4', + 'https://avatars0.githubusercontent.com/u/15912395?v=4', + 'https://avatars0.githubusercontent.com/u/1047135?v=4', + 'https://avatars3.githubusercontent.com/u/45293?v=4', + 'https://avatars3.githubusercontent.com/u/22626039?v=4', + 'https://avatars3.githubusercontent.com/u/658656?v=4', + 'https://avatars3.githubusercontent.com/u/988347?v=4', + 'https://avatars3.githubusercontent.com/u/33317975?v=4', + 'https://avatars0.githubusercontent.com/u/25272822?v=4', + 'https://avatars0.githubusercontent.com/u/6762913?v=4', + 'https://avatars2.githubusercontent.com/u/33654086?v=4', + 'https://avatars1.githubusercontent.com/u/10981911?v=4', + 'https://avatars0.githubusercontent.com/u/43432?v=4', + 'https://avatars0.githubusercontent.com/u/32513012?v=4', + 'https://avatars2.githubusercontent.com/u/7113309?v=4', + 'https://avatars0.githubusercontent.com/u/2788771?v=4', + ]; + if (produceError) { + delayedCallback(567,errorCallback,'Error: status=707 msg="Example error".'); + } else if (produceEmpty) { + delayedCallback(678,dataCallback,[]); + } else { + let theData=[]; + if (dataURL=='repositories') {theData=clone_JSON_object(repositories)} + else if (dataURL=='contributors') { + const resultLength=getRandomNumberBetween(0,20); + for (let i=0; i<=resultLength; i++) { + const theLink=contrib_img_url.splice(getRandomNumberBetween(0,contrib_img_url.length-1),1); + theData.push({ + html_url : theLink, + avatar_url : theLink, + login : Math.random().toString(36).substring(2), + contributions : getRandomNumberBetween(1,99) + }); + } + }; + delayedCallback(789,dataCallback,theData); + }; + }; + +}; + + +; + diff --git a/Week2/homework/project-hack-your-repo-II/options.png b/Week2/homework/project-hack-your-repo-II/options.png new file mode 100644 index 000000000..3703ca053 Binary files /dev/null and b/Week2/homework/project-hack-your-repo-II/options.png differ diff --git a/Week2/homework/project-hack-your-repo-II/style.css b/Week2/homework/project-hack-your-repo-II/style.css new file mode 100644 index 000000000..ff1cd88f0 --- /dev/null +++ b/Week2/homework/project-hack-your-repo-II/style.css @@ -0,0 +1,204 @@ + +:root { + /* unused interesting colors */ + /* yellow: #FFFF00 - rgb(255, 255, 0) */ + /* orange: #FFA500 - rgb(255, 165, 0) */ + /* silver: #C0C0C0 - rgb(192, 192, 192) */ + /* dark-blue #0A1A3E - rgb(10, 26, 62) */ + /* silver-white #EFEFEF - rgb(239, 239, 239) */ + + /* wheat: #F5DEB3 - rgb(245, 222, 179) */ + --color_page_normal_bg: #F5DEB3; + /* black: #000000 - rgb(0, 0, 0) */ + --color_page_normal_text: #000000; + + /* mid-blue #6EBEFE - rgb(110, 190, 254) */ + --color_attention_bg_grad_begin: #6EBEFE; + /* ^ with 70% transparency */ + --color_attention_bg_grad_end: rgba(110,190,253,0.3); + + /* lightblue: #ADD8E6 - rgb(173, 216, 230) */ + --color_list_bg_grad_begin: #ADD8E6; + /* ^ with 90% transparency */ + --color_list_bg_grad_end: rgba(173,216,230,0.1); + + /* black: #000000 - rgb(0, 0, 0) */ + --color_focus_outline: #000000; + + /* light-silver #E5E5E5 - rgb(229, 229, 229) */ + --color_interactive_bg_grad_begin: #E5E5E5; + /* light-silver #E0E0E0 - rgb(224, 224, 224) */ + --color_interactive_bg_grad_end: #E0E0E0; + + /* red: #FF0000 - rgb(255, 0, 0) */ + --color_alert_bg_grad_begin: #FF0000; + /* gold: #FFD700 - rgb(255, 215, 0) */ + --color_alert_text: #FFD700; + + /* WhiteSmoke: #F5F5F5 - rgb(245,245,245) */ + --color_hover_bg_grad_begin: #F5F5F5; + /* silver-white #F0F0F0 - rgb(240,240,240) */ + --color_hover_bg_grad_end: #F0F0F0; +} + +/* resets */ +body, div, table, tr, td, ul, li, a, p, img, span { + box-sizing: border-box; + margin: 0; + padding: 0; + cursor: default; + vertical-align: middle; +} + + +body { + background-color: var(--color_page_normal_bg); + color: var(--color_page_normal_text); + margin: 12px 12px 0; + font-size: 1.5rem; + font-family: "Roboto", sans-serif; +} +body:before { + background: url("./hyf.png"); + background-size: cover; + content: " "; + position: fixed; + z-index: -1; + width: 42vmin; + height: 42vmin; + top: 1rem; + right: 1rem; + opacity: 0.24; +} + + +li { + list-style-type: none; +} + + +table { + border-collapse: collapse; + border-spacing: 0; +} + + +td { + padding-top: 6px; + padding-bottom: 2px; +} +td:nth-child(1) { + padding-right: 8px; + vertical-align: top; +} +td:nth-child(2) { + overflow-wrap: anywhere; + padding-left: 8px; +} +td:nth-child(3) { + padding-left: 16px; + text-align: right; +} + + +select { + font-size: 1.4rem; + width: 99%; +} + + +a { + background: linear-gradient(to bottom, var(--color_interactive_bg_grad_begin) , var(--color_interactive_bg_grad_end) ); +} +a:hover { + background: linear-gradient(to bottom, var(--color_hover_bg_grad_begin) , var(--color_hover_bg_grad_end) ); +} + + +*:focus { + outline: 4px solid var(--color_focus_outline); +} + + +.cls_colored_header { + background: linear-gradient(to right, var(--color_attention_bg_grad_begin) , var(--color_attention_bg_grad_end) ); + font-weight: bold; + font-size: 1.7rem; + font-style: italic; + padding: 10px 0 8px; +} + + +.cls_responsive_parent { + display: grid; + grid-gap: 0; + grid-template-columns:repeat(auto-fit,minmax(24rem,1fr)); +} + + +.cls_responsive_child { + max-width: 90vw; + display: inline-block; +} + + +.cls_padded_area { + padding-left: 12px; + padding-right: 12px; +} + + +.cls_alt_color { + background: linear-gradient(to right, var(--color_list_bg_grad_begin) , var(--color_list_bg_grad_end) ); + margin-top: 12px; + padding-top: 8px; + padding-bottom: 6px; +} + + +#id_options_area { + background-color: var(--color_list_bg_grad_begin); + font-size: 1.4rem; + margin-bottom: 12px; + padding: 10px 12px 8px 14px; +} + + +#id_options_image { + float: right; + margin-left: 6px; + margin-bottom: 6px; +} + + +.cls_options_element { + margin-bottom: 8px; +} + + +.cls_span_full_parent_width { + width: 99%; +} + + +.cls_error_alert { + background: linear-gradient(to right, var(--color_alert_bg_grad_begin) , rgba(128,128,128,0) ); + color: var(--color_alert_text); + font-size: 2rem; + font-weight: bold; + padding: 8px 12px 6px; +} + + +.cls_unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + + +; + diff --git a/Week3/homework/README.md b/Week3/homework/README.md new file mode 100644 index 000000000..d04a26cc4 --- /dev/null +++ b/Week3/homework/README.md @@ -0,0 +1,4 @@ + +Homework folder for JS-3 - week 3 +===================== + diff --git a/Week3/homework/project-hack-your-repo-III/App.js b/Week3/homework/project-hack-your-repo-III/App.js new file mode 100644 index 000000000..8788f8b85 --- /dev/null +++ b/Week3/homework/project-hack-your-repo-III/App.js @@ -0,0 +1,56 @@ +'use strict'; + +{ + const accounts = { + hyf: { + name: 'HackYourFuture', + type: 'org', + }, + microsoft: { + name: 'Microsoft', + type: 'org', + }, + jim: { + name: 'remarcmij', + type: 'user', + }, + }; + + const { Model, HeaderView, RepoView, ContributorsView, ErrorView } = window; + const { createAndAppend } = window.Util; + + class App { + constructor(account) { + const containers = App.renderContainers(); + + const model = new Model(account); + const fetchData = model.fetchData.bind(model); + + model.subscribe(new HeaderView(account, containers.header, fetchData)); + model.subscribe(new RepoView(containers.repo)); + model.subscribe(new ContributorsView(containers.contributors)); + model.subscribe(new ErrorView(containers.error)); + + fetchData(); + } + + static renderContainers() { + const root = document.getElementById('root'); + const header = createAndAppend('header', root, { class: 'header' }); + const error = createAndAppend('div', root); + const main = createAndAppend('main', root, { + class: 'main-container', + }); + const repo = createAndAppend('section', main, { + class: 'repo-container whiteframe', + }); + const contributors = createAndAppend('section', main, { + class: 'contributors-container whiteframe', + }); + return { header, error, main, repo, contributors }; + } + } + + const ACCOUNT_KEY = 'hyf'; + window.onload = () => new App(accounts[ACCOUNT_KEY]); +} diff --git a/Week3/homework/project-hack-your-repo-III/ContributorsView.js b/Week3/homework/project-hack-your-repo-III/ContributorsView.js new file mode 100644 index 000000000..86a7f6f41 --- /dev/null +++ b/Week3/homework/project-hack-your-repo-III/ContributorsView.js @@ -0,0 +1,48 @@ +'use strict'; + +{ + // DOT.NOT:JS-3:w-3:project: include additional utility functions + const { createAndAppend , createTableRowHTML , + getValidValue , createTableCellObject } = window.Util; + + class ContributorsView { + constructor(container) { + this.container = container; + // DOT.NOT:JS-3:w-3:project: additional rendering / styling elements + const wrapper=createAndAppend('DIV',this.container + ,{id:'id_wrap_contributors'}); + this.titleRef=createAndAppend('P',wrapper,{id:'id_title_contributors'}); + this.dataList=createAndAppend('TABLE',wrapper + ,{id:'id_table_contributors'}); + } + + update(state) { + if (!state.error) { + this.render(state.contributors); + } + } + + /** + * Renders the list of contributors + * @param {Object[]} contributors An array of contributor objects + */ + render(contributors) { + // DOT.NOT:JS-3:w-3:project: implement contributors rendering + const cntrbTitle=(cnt)=>`${cnt<1?'No':cnt} Contributor${cnt===1?'':'s'}`; + this.titleRef.innerHTML=cntrbTitle((contributors.length||0)); + let theHTML=''; + if ((contributors) && (contributors.length>0)) { + theHTML=contributors.reduce((tot,cur)=>tot+createTableRowHTML([ + createTableCellObject(``), + createTableCellObject(getValidValue(cur.login), + getValidValue(cur.html_url),true), + createTableCellObject(getValidValue(cur.contributions,0)),]),''); + }; + this.dataList.innerHTML=theHTML; + }; + + } + + window.ContributorsView = ContributorsView; +} diff --git a/Week3/homework/project-hack-your-repo-III/ErrorView.js b/Week3/homework/project-hack-your-repo-III/ErrorView.js new file mode 100644 index 000000000..67ad53087 --- /dev/null +++ b/Week3/homework/project-hack-your-repo-III/ErrorView.js @@ -0,0 +1,31 @@ +'use strict'; + +{ + const { createAndAppend } = window.Util; + + class ErrorView { + constructor(container) { + this.container = container; + } + + update(state) { + this.render(state.error); + } + + /** + * Renders an error for the 'error' message type. + * @param {Error} error An Error object + */ + render(error) { + this.container.innerHTML = ''; + if (error) { + createAndAppend('div', this.container, { + text: error.message, + class: 'alert alert-error', + }); + } + } + } + + window.ErrorView = ErrorView; +} diff --git a/Week3/homework/project-hack-your-repo-III/HeaderView.js b/Week3/homework/project-hack-your-repo-III/HeaderView.js new file mode 100644 index 000000000..11f9c8971 --- /dev/null +++ b/Week3/homework/project-hack-your-repo-III/HeaderView.js @@ -0,0 +1,46 @@ +'use strict'; + +{ + const { createAndAppend } = window.Util; + + class HeaderView { + constructor(account, header, fetchData) { + this.account = account; + this.header = header; + this.fetchData = fetchData; + this.select = null; + } + + update(state) { + if (!this.select && !state.error) { + this.render(state.repos); + } + } + + /** + * Renders the data for the 'select' message type. Create a