Objects in the Browser
Making our objects browser friendly
Sorry for this step - it's all fun after this I promise.
The next thing we want to do is to render some ports.
Lets start by importing our port.js file into the index.html file. Do this in the same way you imported the controller.js file.
Now look in the browser console... you should see an error similar to the following:
Taking a look at line 15 of port.js we see the following line:
Why is module not defined?
Until now we've been using our objects (Port, Itinerary and Ship) inside a Node.js environment. In Node.js, each file is a module, and we use a combination of module.exports and require to pass objects between files.
The problem we now face is that browser works a bit differently to Node, in that module isn't a concept.
When we include a JS file into the window environment, everything in the global scope of that file is imported into the global scope of the window object. If we import various different files that happen to have the same variable names used in their global scopes then they will clash.
In a nutshell:
- Node.js: We can use
module.exportsto selectively export objects from a file andrequireto assign those exported objects to variable names of our choosing. - Browser: Everything in a file's global (top-level) scope is exported onto the
windowobject as a globally-accessible variable. If we have multiple files being included with<script>tags and two of those files sets a global variable with the same name, they will clash and the later one override the earlier other. (If we import two JS files into our HTML, and both of the JS files have a top level variable calledfoo, in the HTML, the value of thefoovariable will be the value from the last file to be imported).
We have two problems:
Firstly, we have no constraints on what our files do to the window object. That is we are effectively exporting everything from the global scope of file, which breaks the encapsulation of our module.
Secondly, the module object doesn't exist in the browser, so our module.exports statements in our constructor files will error.
The first problem can be solved by wrapping our code inside an Immediately-Invoked Function Expression (otherwise referred to as an IIFE). An IIFE is what it says on the tin - a function which is invoked as soon as it is declared. It looks like this:
We have our function:
Which we immediately proceed to call (()):
Finally, we have to wrap the whole declaration in parentheses for JavaScript to treat it as an expression (otherwise it will throw a syntax error):
The benefit of using an IIFE is that in JavaScript, variables/function declarations are scoped to functions so:
won't attach Port to the window object (because it isn't on the top level scope), whereas:
would attach a Port property to the window object, as Port is now on the top level scope.
Let's make a start by encapsulating our Port object:
Now nothing from the Port.js file is exported, which is a problem. We'll be failing our tests now for a start, but also we will need Port in our GUI soon. We can fix this by adding the following at the end of the IIFE:
This checks to see if module and module.exports both exist (a.k.a. we are in the Node.js environment) and if they do then we export Port from the module, otherwise we attach the Port object to the window object (which exists in the browser environment).
Overall, Port.js now looks like this:
Challenge
Do the same as above for the other constructor files (including your new Controller constructor).