0

I am confused on how to work with module pattern (and design patterns in general) in JavaScript.

I already wrote some functioning code in my application using module pattern that does what I want to, but it doesn't seem to be very modular to me, and I keep having this feeling that I am doing it wrong. I didn't manage to find any concrete and complete application example with any design pattern.

Here is how I work with it :

Let's say I have forms in my application that I'll use for different modules (post a thread, reply to a thread, comment the guests book), with some JavaScript I'll give users some functionalities, as such as popping a smiley bubble and handling insertion of them in my forms, sending data posts to my server code to return the HTML code in order to add the message without reloading the page, I'll do something like that:

    let Form = function (selector_form, selector_textarea, selector_emoticonsButton, selector_postButton) {

      let form, textarea, emoticonsButton, postButton;
      let emoticonsBubble = new EmoticonsBubble()

      return {
        selectors: function () {
          return {
            form: function () { return selector_form },
            sendButton: function () { return selector_sendButton }
          }
        }

        setElements: function (obj) {
          form = $(obj).get(0);
          textarea = $(form).find(selector_textarea).get(0);
          emoticonsButton = $(form).find(emoticonsButton).get(0);
          postButton = $(form).find(selector_postButton).get(0);

          emoticonsBubble.setElements(form, emoticonsButton);
        },

        get: function () {
          return {
            form: function () { return form },
            //...
            emoticonsBubble: function () { return emoticonsBubble }
          }
        },

        post: function (moduleId, callback) {
          $.ajax({
          //parameters
          }).done(function (data) {
            callback(data);
          });
        }
      }
    }

    let EmoticonsBubble = function () {

      let thisContainerToAppendTo, thisTextarea;

      return {
        setElements: function (container, textarea) {
          thisContainerToAppendTo = container;
          thisTextarea = textarea;
        },

        pop: function () {
          this.ajax().pop(function (data) {
            $(thisContainerToAppendTo).append(data);
          });
        }

        insert: function (emoticon) {
          $(thisTextarea).append(emoticon);
        },

        ajax: function () {
          return {
            pop: function (callback) {
              $.ajax({
              //parameters
              }).done(function (data) {
                callback(data);
              });
            }
          }
        }
      }
    }

    // Events part

    let form = new Form('#threadForm', '.textarea', 'button[name="emoticons"]', 'button[name="send"]');
    let emoticonsBubble = form.get().emoticonsBubble();

    $(form.selectors().form()).on('click', function (e) {
      form.setElements(this);
    });

    $(form.selectors().sendButton()).on('click', function (e) {
      let moduleId = // retrieve module id, if it belongs to guests book, thread creation module or reply module
      form.post(moduleId, function (data) {
        // append data to something
      });
    });

    // etc for emoticons handling

The fact that I have to rewrite the event part for every different form I have in my application while keeping everything the same but variables name, annoys me a lot.

Could you guys tell me how you would handle those functionalities and what may be wrong with my way of coding?

Lin Du
  • 88,126
  • 95
  • 281
  • 483
Snyte
  • 93
  • 6
  • You are confusing (understandably) the well-known [JavaScript Module Pattern](https://stackoverflow.com/questions/17776940/javascript-module-pattern-with-example), which I don't see you using anywhere in this code, with the code that goes into the module. – Scott Marcus Apr 19 '19 at 12:53
  • Am I not? I have private variables and public methods... I thought this was the module pattern, it looks a lot like the code showed in the linked you sent me. I am definitly missing something here – Snyte Apr 19 '19 at 13:01
  • Nope. Setting up functions to have private vs. public members is not the module pattern. The module pattern would mean that you surround all of the code for a particular module with `(function(){ ... module code here...})()` and within the module, you would expose a single namespace as the access point for the module. You aren't doing that anywhere in this code. `Form` and `EmoticonsBubble` are global variable. – Scott Marcus Apr 19 '19 at 13:10
  • ... I'd say that the module pattern is generally a thing od tze past though, nowadays one uses webpack to bundle real modules. – Jonas Wilms Apr 19 '19 at 13:12
  • @JonasWilms True, but that doesn't change the fact that there is a well-known JavaScript pattern called the Module Pattern, and that pattern isn't going away anytime soon. Also, WebPack is just one of many ways to implement modules of code. – Scott Marcus Apr 19 '19 at 13:15
  • Not the least of which is [standard ES6 modules](http://exploringjs.com/es6/ch_modules.html#_ecmascript-6-modules) – Heretic Monkey Apr 19 '19 at 13:24

3 Answers3

1

The repetition in your code basically comes from the selection of elements and their helpers, and that can easily be abstracted into a function:

  function Elements(selectors, children, options) {
    let elements = { ...children };

    return {
      selectors, 
      elements,
      setElements(obj) {                
        for(const [name, selector] of Object.entries(selectors)) 
           elements[name] = $(obj).find(selector).get(0);
        for(const child of Object.values(child))
           child.parent && child.parent(this, obj);
       },
       ...options
    }
 }

That can then be used as:

  function Form(form, textarea, emoticonsButton, postButton) {
     const emoticonsBubble = EmoticonsBubble();

     return Elements({ form, textarea, emoticonButtons }, { emoticonsBubble }, {
       post() {
         //...
       }
    });
 }

 function EmoticonsBubble() {
   return Elements({ /*...*/ }, {}, {
      parent(parent, obj) {
        this.setElements(parent);
      }
   });
 }

But you are basically reinventing a lot of wheels here, have you thought about using one of the MVCs that are out there (React, Vue, ...) ?

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • This doesn't address the question in any way. It's just an optimization of the OPs code. – Scott Marcus Apr 19 '19 at 13:26
  • `The fact that I have to rewrite the event part for every different form I have in my application, while keeping everything the same but variables name, annoys me a lot.` Maybe you should read the question more carefully? – Jonas Wilms Apr 19 '19 at 13:29
  • `Confusion on how to work with module pattern - I am confused on how to work with module pattern (and design patterns in general) in JavaScript.` I tend to answer the question that the main title is asking about. You've just addressed an ancillary comment made during the larger question. Maybe you should start reading at the top of the post? – Scott Marcus Apr 19 '19 at 13:31
  • Don't worry, this is good. I understand the principle of this code but it looks a bit unusual to me so I need to study it. But this definitly helps and gives me some food for thoughts. I know I tend to reinvent the wheel, but I tried to use some JS frameworks at first, while not being able to do anything in this language, it was a failure. So I figured that the best way to learn would be hard way, by just using JQuery and basic Javascript. Thanks for your time – Snyte Apr 19 '19 at 13:38
  • @scott so much about that. – Jonas Wilms Apr 19 '19 at 13:40
  • 1
    @snyte for sure this is neither complete nor the best way to adress the overall architecture, it's just a hint in which ways it could be abstracted ... – Jonas Wilms Apr 19 '19 at 13:41
  • Yes, I think so. – Scott Marcus Apr 19 '19 at 14:23
1

Ok the boilerplate for some common tasks that you have in the event part is driving you crazy right ?

So checking your code you can fix them in many ways.

A. Encapsulate your code in real modules I mean this.

const Form = (function(/*receive here dependencies as arguments */){
  // your code module goes here 
})(/*inject dependencies here to module*/);

B. You can create a event pattern module, to drive your internal and externals events for module.

C. You know what are the listener that the module needs , so apply them into your module.

That way should be more reusable than now

Juorder Gonzalez
  • 1,642
  • 1
  • 8
  • 10
  • So if I understand this correctly, I can only use this once? I am not quite sure how to work with that since this creates an IIFE and gives it immediate arguments, how can I reuse it? I'll read about the event pattern, thank you – Snyte Apr 19 '19 at 14:07
  • @Snyte This answer just shows that you ***can*** pass arguments into the IIFE. You are right that the IIFE will execute only once so the argument(s) you pass wouldn't be for creating objects ***within*** the module (those are created as often as you like through the namespace I showed in my answer). They would be for one-time initializations. – Scott Marcus Apr 19 '19 at 14:13
1

The Module Pattern is about keeping units of code from colliding with other scopes (usually the Global scope).

As we know, in JavaScript, variables defined with:

  • let and const are scoped to their parent block
  • var are scoped to their containing function (or Global if not in a function)

So, if you were to take your Form function:

let Form = function (x,y,z) {

  let form, textarea, emoticonsButton, postButton;
  let emoticonsBubble = new EmoticonsBubble()

  return {
        . . . 
    }

    setElements: function (obj) {
        . . . 
    },

    get: function () {
        . . . 
    },

    post: function (moduleId, callback) {
        . . . 
    }
  }
}

The variable Form is Global because there is no containing block. This is a problem because what if there is already another Global called Form (which there very well could be because of the generic nature of the word "Form"). So, this code doesn't cut off your code from being exposed. To use the Module Pattern on it, we'd wrap it with an IIFE (Immediately Invoked Function Expression) and within that IIFE, we'd create a custom namespace in the Global scope that we're sure doesn't exist (thereby avoiding name collisions):

(function(){
  // This is going to be exposed as publicly available via the module namespace
  function Form(x,y,z) {
    . . .
  }

  // This will remain private within the module
  function helper(){

  }

  // **********************************************************************    
  let temp = {};    // Create a temporary object to bind only the public API
  temp.Form = Form; // Bind the public members to the object

  // Expose the module to the Global scope by creating a custom namespace 
  // and mapping the temp object to it
  window.myCustomAPI = temp;
})();

// Now, outside of the module (in some higher scope), your public portions
// of the Module are accessible:
let myForm = new myCustomAPI.Form(arg, arg, arg);
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • Thanks for those clarifications, I understand module pattern better. Though, I am still a bit confused. If I want to use my EmoticonsBubble object in my Form object, do I have to write a new module for the EmoticonsBubble (so I can use it in another module) and then pass it to my Form object arguments in the global scope ? Like this `let bubble = new myCustomAPI.EmoticonsBubble(arg...); let myForm = new myCustomAPI.Form(bubble, arg, arg, arg); ` ? Does the events part belongs to the module scope? I guess so, so it allows me to write it only once – Snyte Apr 19 '19 at 13:48
  • @Snyte Here's why it's called the Module Pattern - - it's up to you. You decide what objects & functionality comprise a "module". You could set up `EmoticonsBubble` as a separate object within the `myCustomAPI` module and instantiate it separate from `Form` or you could make a `generateEmoticonsBubble` method of the `Form` object that returns a new object (ie.after instantiating a `Form`.... `myForm.generateEmoticonsBubble()`). If you felt like you might want the `EmoticonsBubble` object apart from a `Form`, you could make an entirely new module (API) for it. It's entirely up to you. – Scott Marcus Apr 19 '19 at 13:55
  • @Snyte Just to clarify, your last question now gets us into the realm of "coupling". In OOP, objects that are "loosely coupled" make for less brittle and more flexible designs. Examples of tight-coupling (and brittle solutions) include inheritance patterns. Examples of loose-coupling (and flexible solutions) are object composition. These two approaches are often termed as "is-a" (inheritance) and "has-a" (composition) relationships. To enable composition, we pass objects to other objects for them to use. – Scott Marcus Apr 19 '19 at 14:02
  • Ok, I get it. I will try to work with that, things are clearer to me. Thank you for those explanations, if you have some useful links about inheritance vs composition approaches for more informations, I am interested – Snyte Apr 19 '19 at 14:12
  • 1
    @Snyte I'd start with [loose coupling](https://en.wikipedia.org/wiki/Loose_coupling) and then [inheritance vs. composition](https://en.wikipedia.org/wiki/Composition_over_inheritance). – Scott Marcus Apr 19 '19 at 14:19