User:Zuhui/SI26/Storybook/Code to work 2: Difference between revisions

From XPUB & Lens-Based wiki
No edit summary
 
(7 intermediate revisions by the same user not shown)
Line 18: Line 18:
     </p>  
     </p>  


     <div id="stage">
     <div id="space">
         <div class="do"></div>
         <div class="do"></div>
         <div class="tigers object"></div>
         <div class="tigers"></div>
         <div class="melt object"></div>
         <div class="melt"></div>
         <div class="like"></div>
         <div class="like"></div>
         <div class="butter object"></div>
         <div class="butter"></div>
         <div class="in"></div>
         <div class="in"></div>
         <div class="the"></div>
         <div class="the"></div>
         <div class="forest object"></div>
         <div class="forest"></div>
         <div class="questionmark"></div>
         <div class="questionmark"></div>
     </div>
     </div>
Line 56: Line 56:
     if (words.length > 1) {
     if (words.length > 1) {
         let pickRandom = Math.floor(Math.random() * words.length);
         let pickRandom = Math.floor(Math.random() * words.length);
         let remove = words.splice(pickRandom,1)[0]; //had to add "[0]" to pull the word out of the array and turn it into node. switching between nodelists and arrays requires extra steps
         let remove = words.splice(pickRandom,1)[0]; //had to add "[0]" to pull the first word out from the array
         removedWords.push(remove);
         removedWords.push(remove);
         remove.remove(); //.remove() method removes the element from the DOM.
         remove.remove(); //.remove() method removes the element from the DOM.
Line 112: Line 112:
<br>
<br>
<br>
<br>
===two innerWidth variables and "resize" eventlistener===
===two innerWidth variables and "resize" eventlistener===
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
Line 145: Line 146:
<br>
<br>
<br>
<br>
===synchronizing spans and divs 1===
===synchronizing spans and divs===
'''Goal:'''
added basic variables for the divs
connect each <code>span</code> and <code>div</code> as some sort of a single unit, so they'd move together
<syntaxhighlight lang="javascript">
:: ↳ when <code>span</code> is moved, added, or pulled out, its matching <code>div</code> should sync with it
let sentence = document.getElementById("sentence");
:: ↳ this mechanism should work in a way that CSS selectors and combinators can pick up
let space = document.getElementById("space");
<br>
 
<br>
let words = Array.from(document.querySelectorAll("#sentence span"));
let figures = Array.from(document.querySelectorAll("#space div"));
 
let removedWords = [];
let removedDivs = [];
</syntaxhighlight>
<br><br>
 
===adding the matching div under function removeRandom()===
with Joseph's help,
<syntaxhighlight lang="javascript">
function removeRandom () {
    if (words.length > 1) {
        let pickRandom = Math.floor(Math.random() * words.length);
        let removingWords = words.splice(pickRandom,1)[0];
        removedWords.push(removingWords);
        removingWords.remove();
       
 
        var findSameName = removingWords.classList[0].replace("word_","");
        let matchingDiv = document.querySelector("#space div." + findSameName);
        removedDivs.push(matchingDiv);
        matchingDiv.remove();
    }
}
</syntaxhighlight>
<div style="float:right; text-align:right;">
<div style="float:right; text-align:right;">
[https://stackoverflow.com/questions/25544516/how-to-keep-html-dom-elements-synchronized-with-the-associated-javascript-object How to keep html DOM elements synchronized with the associated javascript object? reddit]<br>
[[User:Zuhui//Prototyping/Javascript#DOMTokenList |DOMTokenList]]<br>
[https://www.w3schools.com/js/js_objects.asp#gsc.tab=0 js Objects w3c]<br>
[https://developer.mozilla.org/en-US/docs/Web/API/Element/classList classList mdn]<br>
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_objects Working with Objects mdn]
[https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList classList.replace() mdn]<br>  
<br>
<br>
[https://www.reddit.com/r/learnprogramming/comments/11azbne/how_to_associate_a_dom_element_with_an_instance/ how to associate a DOM element with an instance variable? reddit]<br>
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map Map objects mdn]<br>
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_classes#instance_methods Using classes, Instance methods mdn]<br>
''"You can use the Map class to store your class instances, stored and retrieved by some key."''<br><br>
''"Objects vs. Maps:<br>Object is similar to Map—both let you set keys to values, retrieve those values, delete keys, and detect whether something is stored at a key.<br> For this reason (and because there were no built-in alternatives), Object has been used as Map historically."''
<br>
<br>
[https://www.altcademy.com/blog/what-is-an-instance-in-javascript/ What is an instance in javascript]<br>
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new new mdn]<br>
</div>
</div>
<br>
<br>
Line 173: Line 188:
<br>
<br>
<br>
<br>
'''code explained:'''<br><br>
just like how <code>Array.from()</code> converts a NodeList to an array, which allowing the use of array methods,<code>classList</code> provides DOMToeknList, which allows the use of tokenlist methods.<br>
<syntaxhighlight lang="javascript">
var findSameName = removingWords.classList[0].replace("word_","");
</syntaxhighlight>
: ↳ <code>element.classList[0]</code> takes the first class name out from the DOMTokenList, and then use the <code>classList.replace()</code> method to modify the name.
<br>
<br>
<br>
<br>
<syntaxhighlight lang="javascript">
let matchingDiv = document.querySelector("#space div." + findSameName);
</syntaxhighlight>
: ↳ <code>document.querySelector</code> to find a single, specific element in the DOM
:: ↳ using CSS selector <code>+</code> inside the parentheses
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
===adding the matching div under function bringBackRandom()===
<syntaxhighlight lang="javascript">
function bringBackRandom () {
    if (removedWords.length > 0) {
        let pickRandom = Math.floor(Math.random() * removedWords.length);
        let bringBackWord = removedWords.splice(pickRandom, 1)[0];
        words.push(bringBackWord);
        sentence.appendChild(bringBackWord);
        sentence.appendChild(document.createTextNode(" "));
        var findSameName = bringBackWord.classList[0].replace("word_","");
        let matchingDiv = removedDivs.find(div => div.classList.contains(findSameName));
        figures.push(matchingDiv);
        space.appendChild(matchingDiv);
    }
}
</syntaxhighlight>
<div style="float:right; text-align:right;">
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find find() mdn]<br>
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex findIndex() mdn]<br>
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#iterative_methods iterative methods (using callbacks) mdn]<br>
[https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/contains classList.contains() mdn]<br>
[[User:Zuhui//Prototyping/Javascript#Callback |Callback function]]<br>
</div>
<br>
<br>
<br>
<br>
Line 186: Line 238:
<br>
<br>
<br>
<br>
* I could to use either <code>Object</code> or <code>Map</code> (i'll see the the difference later) to save my spans and divs as a paired units.
'''code explained:'''
: ↳ but this doesn't mean that the <code>div</code> will actually move in a same way the <code>span</code> moves, it's mostly for convenience to find the matching <code>div</code> for the <code>span</code>.
<syntaxhighlight lang="javascript">
:: ↳ just like using an array doesn't automatically change DOM nodes, saving <code>span</code> and <code>div</code> in an <code>Object</code> doens't mean they will update in the DOM by themselves. I need methods.
let matchingDivv = removedDivs.find(div => div.classList.contains(findSameName));
</syntaxhighlight>
* <code>find()</code> is one of the ''' ''iterative methods'' '''
: ↳ "Iterative methods always use a '''callback function to loop through each item in an array and process it'''."
: ↳ but find() doesn't always use callback function
* <code>findIndex()</code> also works, but you need an extra line to remove/retrieve the element from the array using <code>splice()[0]</code>
<br>
<br>
'''how callback works here:'''<br>
* in <code>removedDivs</code> array, find the <code>divs</code> that contains <code>findSameName</code> '''token'''
* if the syntax returns <code>false</code>, it moves on to the next <code>div</code>
* if the syntax returns <code>true</code>, it returns that <code>div</code> and stops.
<br>
<br>
'''instead of "=>"'''<br>
seems like the arrow function is mostly used for convenience because it omits <code>function</code> and <code>return</code> when there's only one return, but it's still confusing to write it myself
<syntaxhighlight lang="javascript">
let matchingDiv = removedDivs.find(function(div) {return div.classList.contains(findSameName)});
</syntaxhighlight>
<div style="float:right; text-align:right;">
<div style="float:right; text-align:right;">
[https://plainenglish.io/blog/how-to-watch-for-dom-changes-with-javascript How to watch for DOM changes with Javascript]<br>
[https://www.w3schools.com/js/js_arrow_function.asp arrow function w3c]<br>
[https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver MutationObserver mdn]<br>
<br>
<br>
<br>
</div>
</div>
<br>
<br>
<br>
<br>
<br>
<br>
===Working code===
<syntaxhighlight lang="javascript">
let sentence = document.getElementById("sentence");
let space = document.getElementById("space");
let words = Array.from(document.querySelectorAll("#sentence span"));
let figures = Array.from(document.querySelectorAll("#space div"));
let removedWords = [];
let removedDivs = [];
function removeRandom () {
    if (words.length > 1) {
        let pickRandom = Math.floor(Math.random() * words.length);
        let removingWords = words.splice(pickRandom,1)[0];//had to add "[0]" to pull the word out from the array
        //console.log(removingWords);
        removedWords.push(removingWords);
        removingWords.remove(); //.remove() method removes the element from the DOM.
       
       
        //now find the same named divs
        var findSameName = removingWords.classList[0].replace("word_","");
        //console.log(findSameName);
        let matchingDiv = document.querySelector("#space div." + findSameName);//only need one matching div
        //console.log(matchingDiv);
        removedDivs.push(matchingDiv);
        matchingDiv.remove();
        //console.log(removedDivs);
    }
}
function bringBackRandom () {
    if (removedWords.length > 0) {
        let pickRandom = Math.floor(Math.random() * removedWords.length);
        let bringBackWord = removedWords.splice(pickRandom, 1)[0];
        words.push(bringBackWord);
        sentence.appendChild(bringBackWord);
        sentence.appendChild(document.createTextNode(" ")); //this works, needed a method that directly manipulates the dom tree
        //now bring back the matching divs
        var findSameName = bringBackWord.classList[0].replace("word_","");
        let matchingDiv = removedDivs.find(div => div.classList.contains(findSameName));
        //console.log(matchingDiv);
        figures.push(matchingDiv);
        space.appendChild(matchingDiv);
        //console.log(removedDivs);
    }
}
//save the innerWidths into two variables
let prevWidth = window.innerWidth;
//when the innerWidth is reduced by more than 100px, remove one random word
window.addEventListener("resize", () => {
    let currWidth = window.innerWidth;
    let widthChange = Math.abs(prevWidth - currWidth);
    //console.log(widthChange)
    if (currWidth < prevWidth && widthChange > 100) {
        removeRandom();
        prevWidth = currWidth;
    }
    else if (prevWidth < currWidth && widthChange > 100) {
        bringBackRandom();
        prevWidth = currWidth;
    }
    //instead of updating prevWidth at the very end of the function once, update it inside each if statement
});
</syntaxhighlight>
<br>
<br>
<br>
<br>
<br>
* ok, now I want to punch myself in the face, this is an unnecessarily difficult and messy way to do this. I assumed it should be a complex process syncing divs to the moving spans but I just realized that I can basically work the divs the same way I worked the spans. they have the same class names...I just need to figure out for it to get the same named divs.
<br>
<br>
===synchronizing spans and divs 2===
<br>
<br>
<br>
=CSS=
<br>
<br>
<div style="float:right; text-align:right;">
[https://developer.mozilla.org/en-US/docs/Web/CSS/Next-sibling_combinator CSS + Next sibling combinator mdm]<br>
[https://developer.mozilla.org/en-US/docs/Web/CSS/Subsequent-sibling_combinator CSS ~ Subsequent sibling combinator mdn]<br>
[https://stackoverflow.com/questions/31442051/css-adjacent-siblings-overwriting-each-other CSS adjacent siblings overwriting each other stackoverflow]<br>
</div>

Latest revision as of 09:25, 15 March 2025

Html structure


previous version

<body>
    <p id="sentence">
        <span class="word_do">do</span>
        <span class="word_tigers">tigers</span>
        <span class="word_melt">melt</span>
        <span class="word_like">like</span>
        <span class="word_butter">butter</span>
        <span class="word_in">in</span>
        <span class="word_the">the</span>
        <span class="word_forest">forest</span>
        <span class="word_questionmark">?</span>
    </p> 

    <div id="space">
        <div class="do"></div>
        <div class="tigers"></div>
        <div class="melt"></div>
        <div class="like"></div>
        <div class="butter"></div>
        <div class="in"></div>
        <div class="the"></div>
        <div class="forest"></div>
        <div class="questionmark"></div>
    </div>
   
    <script src="java1.js"></script>
</body>

now all the divs are also siblings, so they will listen to same command that controls the spans(words)


Javascript


previous version

basic variables

let sentence = document.getElementById("sentence")//this is added from the first version, in the first version, I didn’t define "sentence", so there was no way to know where to put the words back from removedWords in "bringBack"function.
let words = Array.from(document.querySelectorAll("#sentence span"));
let removedWords = [];



randomly removing, bringing words back function

function removeRandomWord () {
    if (words.length > 1) {
        let pickRandom = Math.floor(Math.random() * words.length);
        let remove = words.splice(pickRandom,1)[0]; //had to add "[0]" to pull the first word out from the array
        removedWords.push(remove);
        remove.remove(); //.remove() method removes the element from the DOM.
    }
}

function bringbackRandomWord () {
    if (removedWords.length > 0) {
        let pickRandom = Math.floor(Math.random() * removedWords.length);
        let bringBack = removedWords.splice(pickRandom,1)[0];
        words.push(bringBack);
        sentence.innerHTML = sentence.innerHTML + " "; //try to add space between the spans but this doesn't work at the moment.
        sentence.appendChild(bringBack); //opposite of remove() is appendChild()
    }
}
↳ how do I add a space (" ") after each word that is brought back?
sentence.innerHTML += " ";
↳ this works only the first time shrinking the browser, but when i try again, it stops working. then at some point, it starts adding more words. as if the words are duplicating itself





  • I think appendChild() should be a way to go. Just like how I brought the spans back with that method. But using it with properties like innerHTML, innerText, textContent, doesn’t work.
↳ throwing spaghetties on the wall approach using those 3 properties around the line did work at some point in terms of adding spaces in between my spans, but the browser rendered all my spans as [object HTMLSpanElement]. technically doesn’t work, but it’s something.
↳ so [object HTMLSpanElement] means it broke the structure of my spans and turned them into raw text within sentence.innerHTML
↳ this means the original span elements are no longer treated as elements, but instead they got converted into plain text.
↳ my spans are DOM node/elements, they should not be rendered as raw text. I need to use DOM methods again for appendchild() instead of DOM properties to control the elements directly with js.







sentence.appendChild(document.createTextNode(" "));

it works.



two innerWidth variables and "resize" eventlistener

let prevWidth = window.innerWidth;

first define prevWidth as innerWidth outside of the eventlistener

window.addEventListener("resize", () => {
    let currWidth = window.innerWidth;
    let widthChange = Math.abs(prevWidth - currWidth);
    console.log(widthChange)

    if (currWidth < prevWidth && widthChange > 100) {
        removeRandomWord();
        prevWidth = currWidth;
    }
    else if (prevWidth < currWidth && widthChange > 100) {
        bringbackRandomWord();
        prevWidth = currWidth;
    }
     //instead of updating prevWidth at the very end of the function, update it inside each if statement
     //previous version was updating currWidth to prevWidth, i thought i was saving the currWidth to prevWidth but it works other way around.
});




break

Undefined butter.png



synchronizing spans and divs

added basic variables for the divs

let sentence = document.getElementById("sentence");
let space = document.getElementById("space");

let words = Array.from(document.querySelectorAll("#sentence span"));
let figures = Array.from(document.querySelectorAll("#space div"));

let removedWords = [];
let removedDivs = [];



adding the matching div under function removeRandom()

with Joseph's help,

function removeRandom () {
    if (words.length > 1) {
        let pickRandom = Math.floor(Math.random() * words.length);
        let removingWords = words.splice(pickRandom,1)[0];
        removedWords.push(removingWords);
        removingWords.remove();
        
   
        var findSameName = removingWords.classList[0].replace("word_","");
        let matchingDiv = document.querySelector("#space div." + findSameName);
        removedDivs.push(matchingDiv);
        matchingDiv.remove();
    }
}






code explained:

just like how Array.from() converts a NodeList to an array, which allowing the use of array methods,classList provides DOMToeknList, which allows the use of tokenlist methods.

var findSameName = removingWords.classList[0].replace("word_","");
element.classList[0] takes the first class name out from the DOMTokenList, and then use the classList.replace() method to modify the name.



let matchingDiv = document.querySelector("#space div." + findSameName);
document.querySelector to find a single, specific element in the DOM
↳ using CSS selector + inside the parentheses





adding the matching div under function bringBackRandom()

function bringBackRandom () {
    if (removedWords.length > 0) {
        let pickRandom = Math.floor(Math.random() * removedWords.length);
        let bringBackWord = removedWords.splice(pickRandom, 1)[0];
        words.push(bringBackWord);
        sentence.appendChild(bringBackWord); 
        sentence.appendChild(document.createTextNode(" "));


        var findSameName = bringBackWord.classList[0].replace("word_","");
        let matchingDiv = removedDivs.find(div => div.classList.contains(findSameName));
        figures.push(matchingDiv);
        space.appendChild(matchingDiv);
    }
}








code explained:

let matchingDivv = removedDivs.find(div => div.classList.contains(findSameName));
  • find() is one of the iterative methods
↳ "Iterative methods always use a callback function to loop through each item in an array and process it."
↳ but find() doesn't always use callback function
  • findIndex() also works, but you need an extra line to remove/retrieve the element from the array using splice()[0]


how callback works here:

  • in removedDivs array, find the divs that contains findSameName token
  • if the syntax returns false, it moves on to the next div
  • if the syntax returns true, it returns that div and stops.


instead of "=>"
seems like the arrow function is mostly used for convenience because it omits function and return when there's only one return, but it's still confusing to write it myself

let matchingDiv = removedDivs.find(function(div) {return div.classList.contains(findSameName)});




Working code

let sentence = document.getElementById("sentence");
let space = document.getElementById("space");

let words = Array.from(document.querySelectorAll("#sentence span"));
let figures = Array.from(document.querySelectorAll("#space div"));

let removedWords = [];
let removedDivs = [];


function removeRandom () {
    if (words.length > 1) {
        let pickRandom = Math.floor(Math.random() * words.length);
        let removingWords = words.splice(pickRandom,1)[0];//had to add "[0]" to pull the word out from the array
        //console.log(removingWords);
        removedWords.push(removingWords);
        removingWords.remove(); //.remove() method removes the element from the DOM.
        
        

        //now find the same named divs
        var findSameName = removingWords.classList[0].replace("word_","");
        //console.log(findSameName);
        let matchingDiv = document.querySelector("#space div." + findSameName);//only need one matching div
        //console.log(matchingDiv);
        removedDivs.push(matchingDiv);
        matchingDiv.remove();
        //console.log(removedDivs);
    }
}

function bringBackRandom () {
    if (removedWords.length > 0) {
        let pickRandom = Math.floor(Math.random() * removedWords.length);
        let bringBackWord = removedWords.splice(pickRandom, 1)[0];
        words.push(bringBackWord);
        sentence.appendChild(bringBackWord); 
        sentence.appendChild(document.createTextNode(" ")); //this works, needed a method that directly manipulates the dom tree


        //now bring back the matching divs
        var findSameName = bringBackWord.classList[0].replace("word_","");
        let matchingDiv = removedDivs.find(div => div.classList.contains(findSameName));
        //console.log(matchingDiv);
        figures.push(matchingDiv);
        space.appendChild(matchingDiv);
        //console.log(removedDivs);
    }
}

//save the innerWidths into two variables
let prevWidth = window.innerWidth;
//when the innerWidth is reduced by more than 100px, remove one random word
window.addEventListener("resize", () => {
    let currWidth = window.innerWidth;
    let widthChange = Math.abs(prevWidth - currWidth);
    //console.log(widthChange)

    if (currWidth < prevWidth && widthChange > 100) {
        removeRandom();
        prevWidth = currWidth;
    }
    else if (prevWidth < currWidth && widthChange > 100) {
        bringBackRandom();
        prevWidth = currWidth;
    }
    //instead of updating prevWidth at the very end of the function once, update it inside each if statement
});