Trying to write a "more declarative" JavaScript code, an experiment

Hi all,
A while ago, I wanted to create a blog to practice English, HTML and write about coding. Something simple was good enough, so I used a Jekyll theme.

I made some changes to the theme and wrote some articles, it was good, but not what I wanted (I didn’t practice HTML at all).

So, I decided to write a very simple blog. I don’t like those “flashes” when I click an article and then other page is opened, something smoother is better and that was the first “feature” that I wanted in my blog.

At first, I thought about using JavaScript, the createElement method:

function addElement () {
  // create a new div element
  var newDiv = document.createElement("div");
  // and give it some content
  var newContent = document.createTextNode("Hi there and greetings!");
  // add the text node to the newly created div
  newDiv.appendChild(newContent);

  // add the newly created element and its content into the DOM
  var currentDiv = document.getElementById("div1");
  document.body.insertBefore(newDiv, currentDiv);
}

But that didn’t look right to me, that seems a good amount of JavaScript per article (to write and maintain). Maybe(?) I can try to automate the process and create a JSON[0] file, and process it with some kind of parser[1], but the initial idea was practice HTML. Also, is not like the articles are some kind of arbitrary data that I will receive, the articles are part of the blog (not something different).

I kept thinking and thought that a good way to avoid the “flashes” is:
write the entire blog including the articles in the same file, and only show the selected article (and hide the other ones). I have a first draft of this idea and I want to show it :slight_smile: .

I know the code is ugly, basic and simple, but I also believe that is OK to write something like this because is better than nothing (you need to start somewhere).

I used a statechart[2] because is a personal project and I find the native control flow/structure[3] of JavaScript more difficult to use than a statechart . For the animations I used GreenSock .

About the ugly code and bad names

I will rewrite all the code (at the moment the project is only a draft) and rethink the statechart (states, inputs, etc.).

The customary recommendation when working with a statechart is to design and
then write the code … I wanted to know how difficult can be to explore the problem
and the solution (design) while coding the implementation … and it was harder than
I thought … I ended hacking all my way to the end of the draft. Pen and paper is
faster than a keyboard.

The Diagram

img

The HTML part

  • content
       <div class="content" id="content">
        <div class="content__header">
         <h1 id="recentPostsHeader" data-state="home page">Recent Posts</h1>
        </div>  
        <div class="page__container" data-state="home page">
        <<page1>>
        <<page2>>
        </div>
        <<about>>
        <<aboutrecursion>>
        <<aboutCommentsAndFunctionsignatures>>
        <<howToTakeScreenshots>>
        <<ELN>>
        <<AsciiUnicode>>
       </div> <!-- ./content  -->
  • main
    <!doctype html>
    <html lang="en">
     <<head>>
        <body>
         <div class="container" id="container">
         <<content>>
         <<aside>>
         <<pagination>>
         <<backbtn>>
         <footer id="footer" class="footer"></footer>
         <<scripts>>
         </div>
        </body>
    </html>

The code above is from the literate document[4] that I use, is easy see the structure of the final HTML.

The interesting part is this:

    <h1 id="recentPostsHeader" data-state="home page">Recent Posts</h1>

The data-state= part:

  • Specific HTML elements have a “data-state” attribute

  • The data attribute[5] value of those HTML elements will be one or more ‘states’

  • An HTML element is only displayed (visible) if one of his states matches the current state

Another example:

    <div id="about" data-state="about">
      <h2>About</h2>
        ... 
    </div>

The “more declarative” part, JavaScript code

    const aboutAnimation = ctx => {
    /* This is setup */
      let state = blogService.state.value;
      let currentState =
        typeof state === "string" ? state : Object.values(state)[0];
      let duration = 0.6;
      let delay = duration;
    /* This is setup */
    
    
    // this is the interesting part 
      /* ANIMATIONS */
      let hideElements = new Promise((resolve, reject) =>
        TweenMax.fromTo(
          `[data-state]:not([data-state~=${currentState}])`,
          duration,
          { opacity: 1 },
          { opacity: 0, display: "none", onComplete: () => resolve() }
        )
      );
    
      let showElements = new Promise((resolve, reject) =>
        TweenMax.fromTo(
          `[data-state~=${currentState}]`,
          duration,
          { opacity: 0 },
          {
    	opacity: 1,
    	display: "inline-block",
    	delay: delay,
    	onComplete: () => resolve()
          }
        )
      );
    
      // xstate expects a promise
      return Promise.all([hideElements, showElements]);
    };

The statechart controls the flow, so, I don’t need to write all the if…else code necessary without it. Basically it says:

  • if not current state => hide

  • if it is current state => display

To open an article I use a list of buttons, like this one:

    <ul id="postsList" class="page__list" data-state="home page">
    	<li class="page__item"><button type="button" class="articleBtn" id="aboutRecursionBtn">About Recursion</button>
    	</li>
    	<li class="page_list__item">
    	 <button type="button" class="articleBtn"  id="aboutCommentsAndFunctionsignaturesBtn">About comments and function signatures</button>
    	</li>
    	<li class="page_list__item">
    	 <button type="button" class="articleBtn" id="howToTakeScreenshotsBtn">How to take screenshots</button>
    	</li>
    	<li class="page_list__item">
    	 <button type="button" class="articleBtn" id="ELNBtn">ELN</button>
    	</li>
    	<li class="page_list__item">
    	 <button type="button" class="articleBtn" id="AsciiUnicodeBtn">ASCII UNICODE</button>
    	</li>
     </ul>	

The list is time consuming to write and because I used really long names in camelCase, rewriting it in the JavaScript side of the project was something I didn’t wanted, so I used querySelectorAll to add the event listener that will send the input to the statechart:

    /* ARTICLES BUTTONS and LISTENERS*/
    document.querySelectorAll(".articleBtn").forEach(elem => {
      document.getElementById(elem.id).addEventListener("click", () => {
        let articleId = elem.id.replace(/Btn/gi, "");
        blogService.send({ type: "READARTICLE", currentArticle: articleId });
      });
    });

logs

I can get some info about how the blog is running:

    const blogService = interpret(blog).onTransition(state => {
      console.log("current state:   ", state.value);
      console.log("page Number:     ", state.context.pageNumber);
      console.log("current Article: ", state.context.currentArticle);
      console.log("--------------------------------------------");
    });

Manual testing

I recorded some videos to show how it works:

  1. Loading page first time:
  1. Click “Previous” button:
  1. Click “Next” button:
  1. Click “1” button:
  1. Click “2” button:
  1. Click “1” button, Click “2” button:
  1. Click “About Recursion”:
  1. Click “ELN”:
  1. Click “About”:
  1. Demo:

Automated testing

After a while I realize that the manual testing was time consuming, so I wrote a
function to do the work for me:

    /*
       AUTOMATIC TESTING
    */
    
    // I need to test this
    var testingCaseIndex = -1;
    var automaticTestingID;
    var automaticTesting = () =>(automaticTestingID = window.setInterval(callback, 2800));
    
    function callback() {
      let testEvent = testingCase[(testingCaseIndex += 1)];
      console.log("PERFORMING AUTOMATIC TESTING");
      console.log("sending test event ", testEvent);
      blogService.send(testEvent);
    }
    
    const testingCase = [
      { type: "PREVIOUS" },
      { type: "NEXT" },
      { type: "NUMBERED", pageNumber: 1 },
      { type: "NUMBERED", pageNumber: 2 },
      { type: "NUMBERED", pageNumber: 1 },
      { type: "NUMBERED", pageNumber: 2 },
      { type: "NUMBERED", pageNumber: 1 },
      { type: "READARTICLE", currentArticle: "ELN" },
      { type: "BACK" },
      { type: "ABOUT" },
      { type: "BACK" },
      { type: "STOPTESTING" }
    ];
    
    function stopTesting() {
      console.log("stopTesting fn called");
      clearInterval(automaticTestingID);
      testingCaseIndex = -1;
      console.log("ENDING AUTOMATIC TESTING");
      console.log("--------------------------------------------");
    }

I can start the testing calling the function automaticTesting, to stop the testing I need to add to the testingCase array an the object “STOPTESTING” (maybe I can do it another way, but was easy to write and good enough for now)

That is all for now.

Cheers and happy coding :slight_smile:


Footnotes:

[0] JSON - Wikipedia

[1] Parsing - Wikipedia

[2] Concepts | XState Docs

[3] https://developer.mozilla.org/en-/docs/Web/JavaScript/Guide/Control_flow_and_error_handling

[4] Literate programming - Wikipedia

[5] Using data attributes - Learn web development | MDN

Yes, I have. Thanks for helping me :slight_smile:

I’ve been thinking that I can easily reduce the current number of states
(for the animations) to only one and reduce the number of functions at the same
time, but I’m hesitating because that can increase the complexity of the
function and also can increase the coupling with the statechart.To avoid that
I can use a compound state, multiple statecharts, or maybe in this case it’s more
useful to use an activity? what make more sense?

Also, how do you avoid DOM manipulation (with JavaScript)? what other options
are there? how would you do it to achieve the same effect?

Cheers and happy coding :slight_smile: