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.exports
to selectively export objects from a file andrequire
to assign those exported objects to variable names of our choosing. - Browser: Everything in a file's global (top-level) scope is exported onto the
window
object 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 thefoo
variable 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).