Hoping to get some scrutiny from experienced programmers

(Edit on 9-Jun-21: Post title changed from “Hoping to get some expert’s scrutiny” to “Hoping to get some scrutiny from experienced programmers”. People are really modest here…)

This isn’t asking for full on project feedback (my CSS code is far from done), but more asking for experienced programmers’ critical opinions about certain aspects of my JS code. If anyone’s willing to spend the time, I’d appreciate it very much.

Challenge: Build a Random Quote Machine

While coding for this challenge I did my best to follow these rules:

  • Use module pattern.
  • Write loosely coupled functions.
  • Use abstraction.
  • Keep it shadow.
  • Don’t repeat yourself.
  • Don’t reinvent the wheel.

I also added a few challenges on top of merely getting it to work:

  • Make no assumptions about the remote server’s configurations.
  • Make no assumptions about the structure of the received data payload.
  • Make no assumptions about the frontend’s HTML.

Here’s what I ended up with: FCC Project: Random Quote Machine (codepen.io)


I would appreciate some expert’s scrutiny in terms of how well my JS code has adhered to the above principles, as well as any glaring issues you’d hate to see in the real world.


Should you find anything confusing:

Module usage explained

Basic logic


The module has an entry function randomQuoteMachine(). You call this function to request and store (in an internal state) data from a remote server. Simultaneously, the function returns an object with several exposed methods one of which is .afterDocReady().

You then use .afterDocReady() to:

  • map parts of the received data to DOM elements;
  • attach event handlers to DOM elements; and
  • specify functions to run as soon as the above are done.

Additionally:

  • The module has a built-in array index randomiser, or you can supply your own.
  • The module provides a built-in tweet intent builder, or you can supply your own.


Methods references


randomQuoteMachine( { url | settings } )

See “Basic logic” above for usage.

  • url
    A string specifying the address of the resource to request. If the function is called with url, it will assume that dataType is 'json'.

  • settings
    A jQuery ajax settings object.

  • The above two are mutually exclusive and one of them must be supplied.

Returns an object which exposes all methods listed below.

IMPORTANT:
The resource payload, upon successful storage, will be logged in the console along with its type for observation. You need to make sure that it’s a JS array or object. If not, you need to use the .data() method (see below) to manually parse it.



.afterDocReady( mapping [, onLoadHandler1] [, onLoadHandler2]... )

See “Basic logic” above for usage. Returns undefined.

  • mapping
    Mandatory. An object with key-value pairs any of which can take one of these forms:
    selector: path
    selector: callback
    selector: [path, prefix, suffix]
    selector: [callback, prefix, suffix]
    selector: [event, handler]
    The first four forms will map text content to the inner HTML of a DOM element. The last form will attach an event handler to a DOM element.

    • selector
      A valid jQuery selector. You may need to wrap it in quotes.

    • path
      A string which takes the style of JS accessors and specifies which part of the received data you’d like to extract, e.g.:
      "quotes[flexiIndex1][0].source info[flexiIndex2].text", or
      ".quotes.meta[flexiIndex1][flexiIndex2].details[101].author"
      They also follow these rules:

      • Dot notations are treated as fixed object locations, e.g. .source info will be assumed as trying to access the 'source info' property of an object. With the exception of dots and brackets, there is no need to worry about other illegal characters for dot notation such as space.

      • If the full path starts with a dot notation, the leading . can be omitted.

      • Bracket notations enclosing only numbers are treated as fixed Array locations, e.g. [101] will be assumed as trying to access an array at index 101.

      • Bracket notations enclosing not only numbers are treated as “flexible” array locations on which the built-in index randomiser can be applied, e.g. [flexiIndex1] will result in an internal state name “flexiIndex1” being created and initialised to a random & valid index number; later the index may be used to extract data, or it may regenerate, depending on the relevant method call.

      • You can have the same name for flexible array locations in different paths; they will remain equal to each other. If their parent arrays have different lengths, the shorter length will be respected.

      • You cannot have the same name for flexible array locations in a single path.

      • If differently named flexible array locations in one path also both appear in another path, the order they appear in the two paths must be the same, e.g. you cannot have:
        {
        '#text': 'quotes[flexidex1][flexidex2].quote',
        '#author': 'quotes[flexidex2][flexidex1].quote'
        }

      • DO NOT use flexible index locations on arrays of length 1 or 0. This will result in infinite loops.

    • callback
      A function which takes the stored data as it’s argument. You can use this to implement your own data extraction algorithm.

    • prefix
      A string with which the mapped text content will start.

    • suffix
      A string with which the mapped text content will end. If you want to supply a suffix but not a prefix, use an empty string for the prefix.

    • event
      A string specifying any of the browser’s standard JavaScript event type. One of the most often used ones is "click". This and handler below should follow the exact same syntax as the jQuery .on() method.

    • handler
      A function for handling the specified event. This and event above should follow the exact same syntax as the jQuery .on() method.

  • onLoadHandler1, onLoadHandler2...
    Optional. Functions which will be called (in the order they are supplied) once the DOM tree is ready and if the resource request was successful.



.data( [ argument ] )

As a getter, this method can be called without an argument to return the requested and stored data.

As a setter, this method can be called with an argument to replace the stored data with the passed argument. This also returns the stored data



.refreshTextMapping()

This method literally calls the .safeRandom(), .runMappingCallbacks() and .applyTextMapping() methods described below.

Returns undefined.



.safeRandom()

Calling this method will refresh all flexible array locations, if any. It’s guaranteed they will be different from the previous locations. Data extracted from the new locations will not be reflected in the DOM until the .applyTextMapping() method (described below) is called.

Returns undefined.

The “safe” part of the name refers to the fact that, if arrays of different lengths use the same flexible index location, the shortest length will be respected.



.runMappingCallbacks()

This method will call all callback functions supplied for DOM-data mapping when .afterDocReady() was called (event handlers will not be called). The returned value of these callbacks will not be reflected in the DOM until the .applyTextMapping() method (described below) is called.

Returns undefined.



.applyTextMapping()

This method will update DOM using the latest flexible array locations as well as the latest values returned by callbacks supplied for DOM-data mapping.



.readyTweetIntent( anchorSelector [, mappedElement1 [, option1 ] ]
[ [, separator1 ], mappedElement2 [, option2 ] ]
[ [, separator2 ], mappedElement3 [, option3 ] ]
... )

(This method is available because the FCC project has some Twitter related requirements.)

This method will change the href attribute of an <a> element to a tweet intent url with the text parameter prepopulated using the contents of any specified mapped DOM elements.

  • anchorSelector
    Mandatory. A string representing a valid jQuery selector. This will be used to select the anchor element.

  • mappedElement
    Optional. A string representing a valid jQuery selector which you used for DOM-data mapping when calling .afterDocReady(). The content of the element will be retrieved from an internal state, not from DOM directly.

  • option
    Optional; must not appear without a preceding mappedElement.
    One of three numbers representing three options:
    0 - Do not include the preceding mappedElement's prefix or suffix in tweet text.
    1 - Only include the preceding mappedElement's prefix in tweet text.
    2 - Only include the preceding mappedElement's suffix in tweet text.
    If no supplied, both prefix and suffix will be included in tweet text.

  • separator
    Optional; should not appear without a succeeding mappedElement.
    A string starting with '@', the characters following the leading '@' will be inserted between the previous and the following mappedElements, e.g. if '@ ' (@ followed by a space) is supplied, a space will be inserted.

I didn’t look at the code in detail and your post is fairly long which means it requires time to digest.

I will say this. There is such a thing as over-abstracting and over-engineering. For such a simple project this might be the case here. Ask yourself this, if in 6 months you came back to this code how long do you think it would take you to get reacquainted with it? Let’s say you found a bug and wanted to fix it, how easy would it be to jump back into the code?

Also, abstractions need to be handled with care.

I do realize that parts of the code would likely go inside some utility functions that you just import and use. Which would make the main code less mentally taxing. But as it is now with everything inside one “file” (I realize that’s just how Codepen works) it is a lot of code for such a simple app. I understand you may be using this project as an opportunity to try out different ideas and paradigms. I’m also not saying the code is bad, just that its complexity may outweigh its functionality.

Like I said I haven’t really read the code just skimmed it so don’t take this as a review or anything.

1 Like

That’s one heck of a tech spec you’ve put together here, thorough and informative. And i do tend to build something like this before i begin to code, and evolve it as i go - it keeps my thoughts clean and my eyes on the goal.

It is, however, very easy to get bogged down with over-detail. There’s a lot here. And with good reason, you’re explaining concepts that feel fairly intricate. Which may or may not be a good thing.

I don’t know if I’d qualify as an “expert” dev, i honestly would think that someone who does either has an overblown opinion of themselves or a limited knowledge of the language. The more i learn, the more i realize i don’t really know.

But I’ve been at this a while, seen a lot of codebases and constructions and frameworks come and go. You reference jQuery, and i remember when it first emerged on the scene - it was viewed as this tiny, lightweight, deceptive powerhouse that would knock its larger, bulkier, klunkier competitors aside easily. And it did - by providing what was needed in an easy to use package, and letting us get on with our project. It didn’t take a ton of research, its interface was intuitive.

jQuery has largely become outdated, because much of it’s functionality has been rolled into the javascript core. It served its purpose, to provide a single interface, regardless of platform, and be consistent. But with es6, and the evolution of javascript, jQuery and other tools of the time aren’t really as necessary.

Now, the concepts or rules you’re trying to adhere to are good ones, and from where i stand, it seems you’re feeling around in the dark for the doorknob that will open to the “next level.” You’re asking good questions, whether you know the actual question or not. “My code is getting really complex - how can i provide the same functionality while making my code simpler?”

It seems to me you are looking for “next steps.” This might be a good time to explore functional programming and what it means to compose complex programs from smaller, simpler bits. A book recommendation that might heartily benefit you, Composing Software by Eric Elliott.

Not only looking at modules and DRY, but things like variable and function naming and a “javascript style guide.” It really breaks down both the why and the how of the rules you’re referencing.

3 Likes

Thank you. I will keep this in mind.

Thank you. I will explore your recommendations and particularly thank you for the book recommendation.