The hints below might help when you are stuck with one of the exercises in this book. They don’t give away the entire solution, but rather try to help you find it yourself.
You can start with a program that simply prints out the numbers 1 to 7, which you can derive by making a few modifications to the even number printing example given earlier in the chapter, where the for
loop was introduced.
Now consider the equivalence between numbers and strings of hash characters. Similarly, you can go from 1 to 2 by adding 1 (+= 1
). You can go from "#"
to "##"
by adding a character (+= "#"
). Thus, your solution can closely follow the number printing program.
Going over the numbers is clearly a looping job, and selecting what to print is a matter of conditional execution. Remember the trick of using the remainder (%
) operator for checking whether a number is divisible by another number (has a remainder of zero).
In the first version, there are three possible outcomes for every number, so you’ll have to create an if
/else if
/else
chain.
The second version of the program has a straightforward solution and a clever one. The simple way is to add another “branch” to precisely test the given condition. For the clever method, build up a string containing the word or words to output and print either this word or the number if there is no word, potentially by making elegant use of the ||
operator.
The string can be built by starting with an empty one (""
) and repeatedly adding characters. A newline character is written "\n"
.
Use console.log
to inspect the output of your program.
To work with two dimensions, you will need a loop inside of a loop. Put braces around the bodies of both loops to make it easy to see where they start and end. Try to properly indent these bodies. The order of the loops must follow the order in which we build up the string (line by line, left to right, top to bottom). So the outer loop handles the lines, and the inner loop handles the characters on a line.
You’ll need two variables to track your progress. To know whether to put a space or a hash sign at a given position, you could test whether the sum of the two counters is even (% 2
).
Terminating a line by adding a newline character happens after the line has been built up, so do this after the inner loop but inside of the outer loop.
If you have trouble putting braces and parentheses in the right place to get a valid function definition, start by copying one of the examples in this chapter and modifying it.
A function may contain multiple return
statements.
Your function will likely look somewhat similar to the inner find
function in the recursive findSolution
example in this chapter, with an if
/else if
/else
chain that tests which of the three cases applies. The final else
, corresponding to the third case, makes the recursive call. Each of the branches should contain a return
statement or in some other way arrange for a specific value to be returned.
When given a negative number, the function will recurse again and again, passing itself an ever more negative number, thus getting further and further away from returning a result. It will eventually run out of stack space and abort.
A loop in your function will have to look at every character in the string by running an index from zero to one below its length (< string.length
). If the character at the current position is the same as the one the function is looking for, it adds 1 to a counter variable. Once the loop has finished, the counter can be returned.
Take care to make all the variables used in the function local to the function by using the var
keyword.
Building up an array is most easily done by first initializing a variable to []
(a fresh, empty array) and repeatedly calling its push
method to add a value. Don’t forget to return the array at the end of the function.
Since the end boundary is inclusive, you’ll need to use the <=
operator rather than simply <
to check for the end of your loop.
To check whether the optional step argument was given, either check arguments.length
or compare the value of the argument to undefined
. If it wasn’t given, simply set it to its default value (1) at the top of the function.
Having range
understand negative step values is probably best done by writing two separate loops—one for counting up and one for counting down—because the comparison that checks whether the loop is finished needs to be >=
rather than <=
when counting down.
It might also be worthwhile to use a different default step, namely, –1, when the end of the range is smaller than the start. That way, range(5, 2)
returns something meaningful, rather than getting stuck in an infinite loop.
There are two obvious ways to implement reverseArray
. The first is to simply go over the input array from front to back and use the unshift
method on the new array to insert each element at its start. The second is to loop over the input array backward and use the push
method. Iterating over an array backward requires a (somewhat awkward) for
specification like (var i = array.length - 1; i \textgreater{}= 0; i--)
.
Reversing the array in place is harder. You have to be careful not to overwrite elements that you will need later. Using reverseArray
or otherwise copying the whole array (array.slice(0)
is a good way to copy an array) works but is cheating.
The trick is to swap the first and last elements, then the second and second-to-last, and so on. You can do this by looping over half the length of the array (use Math.floor
to round down—you don’t need to touch the middle element in an array with an odd length) and swapping the element at position i
with the one at position array.length - 1 - i
. You can use a local variable to briefly hold on to one of the elements, overwrite that one with its mirror image, and then put the value from the local variable in the place where the mirror image used to be.
Building up a list is best done back to front. So arrayToList
could iterate over the array backward (see previous exercise) and, for each element, add an object to the list. You can use a local variable to hold the part of the list that was built so far and use a pattern like list = {value: X, rest: list}
to add an element.
To run over a list (in listToArray
and nth
), a for
loop specification like this can be used:
for (var node = list; node; node = node.rest) {}
Can you see how that works? Every iteration of the loop, node
points to the current sublist, and the body can read its value
property to get the current element. At the end of an iteration, node
moves to the next sublist. When that is null, we have reached the end of the list and the loop is finished.
The recursive version of nth
will, similarly, look at an ever smaller part of the “tail” of the list and at the same time count down the index until it reaches zero, at which point it can return the value
property of the node it is looking at. To get the zeroeth element of a list, you simply take the value
property of its head node. To get element N + 1, you take the N th element of the list that’s in this list’s rest
property.
Your test for whether you are dealing with a real object will look something like typeof x == "object" && x != null
. Be careful to compare properties only when both arguments are objects. In all other cases you can just immediately return the result of applying ===
.
Use a for
/in
loop to go over the properties. You need to test whether both objects have the same set of property names and whether those properties have identical values. The first test can be done by counting the properties in both objects and returning false
if the numbers of properties are different. If they’re the same, then go over the properties of one object, and for each of them, verify that the other object also has the property. The values of the properties are compared by a recursive call to deepEqual
.
Returning the correct value from the function is best done by immediately returning false
when a mismatch is noticed and returning true
at the end of the function.
Because not all elements in the ancestry
array produce useful data (we can’t compute the age difference unless we know the birth date of the mother), we will have to apply filter
in some manner before calling average
. You could do it as a first pass, by defining a hasKnownMother
function and filtering on that first. Alternatively, you could start by calling map
and in your mapping function return either the age difference or null
if no mother is known. Then, you can call filter
to remove the null
elements before passing the array to average
.
The essence of this example lies in grouping the elements of a collection by some common aspect. Here, we want to split the array of ancestors into smaller arrays, grouping ancestors by century.
During the grouping process, keep an object that associates century names (numbers) with arrays of either person objects or ages. Since we do not know in advance what categories we will find, we’ll have to create them on the fly. For each person, after computing their century, we test whether that century was already known. If not, add an array for it. Then add the person (or age) to the array for the proper century.
Finally, a for
/in
loop can be used to print the average ages for the individual centuries.
The functions can follow a similar pattern to the definition of forEach
at the start of the chapter, except that they must return immediately (with the right value) when the predicate function returns false
—or true
. Don’t forget to put another return
statement after the loop so that the function also returns the correct value when it reaches the end of the array.
Your solution can follow the pattern of the Rabbit
constructor from this chapter quite closely.
Adding a getter property to the constructor can be done with the Object.defineProperty
function. To compute the distance from (0, 0) to (x, y), you can use the Pythagorean theorem, which says that the square of the distance we are looking for is equal to the square of the x-coordinate plus the square of the y-coordinate. Thus, is the number you want, and
Math.sqrt
is the way you compute a square root in JavaScript.
You’ll have to store all three constructor arguments in the instance object. The minWidth
and minHeight
methods should call through to the corresponding methods in the inner
cell but ensure that no number less than the given size is returned (possibly using Math.max
).
Don’t forget to add a draw
method that simply forwards the call to the inner cell.
One way to solve this is to give the sequence objects state, meaning their properties are changed in the process of using them. You could store a counter that indicates how far the sequence object has advanced.
At least, your interface will need to expose a way to get the next element and to find out whether the iteration has reached the end of the sequence yet. It is tempting to roll these into one method, next
, which returns null
or undefined
when the sequence is at its end. But now you have a problem when a sequence actually contains null
. So a separate method (or getter property) to find out whether the end has been reached is probably preferable.
Another solution is to avoid changing state in the object. You can expose a method for getting the current element (without advancing any counter) and another for getting a new sequence that represents the remaining elements after the current one (or a special value if the end of the sequence is reached). This is quite elegant—a sequence value will “stay itself” even after it is used and can thus be shared with other code without worrying about what might happen to it. It is, unfortunately, also somewhat inefficient in a language like JavaScript because it involves creating a lot of objects during iteration.
The greediness problem can be attacked in several ways. The critters could stop eating when they reach a certain energy level. Or they could eat only every N turns (by keeping a counter of the turns since their last meal in a property on the critter
object). Or to make sure plants never go entirely extinct, the critters could refuse to eat a plant unless they see at least one other plant nearby (using the findAll
method on the view). A combination of these, or some entirely different strategy, might also work.
Making the critters move more effectively could be done by stealing one of the movement strategies from the critters in our old, energyless world. Both the bouncing behavior and the wall-following behavior showed a much wider range of movement than completely random staggering.
Making critters breed more slowly is trivial. Just increase the minimum energy level at which they reproduce. Of course, making the ecosystem more stable also makes it more boring. If you have a handful of fat, immobile critters forever munching on a sea of plants and never reproducing, that makes for a very stable ecosystem. But no one wants to watch that.
Many of the same tricks that worked for the previous exercise also apply here. Making the predators big (lots of energy) and having them reproduce slowly is recommended. That’ll make them less vulnerable to periods of starvation when the herbivores are scarce.
Beyond staying alive, keeping its food stock alive is a predator’s main objective. Find some way to make predators hunt more aggressively when there are a lot of herbivores and hunt more slowly (or not at all) when prey is rare. Since herbivores move around, the simple trick of eating one only when others are nearby is unlikely to work—that’ll happen so rarely that your predator will starve. But you could keep track of observations in previous turns, in some data structure kept on the predator objects, and have it base its behavior on what it has seen recently.
The call to primitiveMultiply
should obviously happen in a try
block. The corresponding catch
block should rethrow the exception when it is not an instance of MultiplicatorUnitFailure
and ensure the call is retried when it is.
To do the retrying, you can either use a loop that breaks only when a call succeeds—as in the look
example earlier in this chapter—or use recursion and hope you don’t get a string of failures so long that it overflows the stack (which is a pretty safe bet).
This exercise calls for a finally
block, as you probably guessed. Your function should first unlock the box and then call the argument function from inside a try
body. The finally
block after it should lock the box again.
To make sure we don’t lock the box when it wasn’t already locked, check its lock at the start of the function and unlock and lock it only when it started out locked.
The most obvious solution is to only replace quotes with a nonword character on at least one side. Something like /\W'|'\W/
. But you also have to take the start and end of the line into account.
In addition, you must ensure that the replacement also includes the characters that were matched by the \W
pattern so that those are not dropped. This can be done by wrapping them in parentheses and including their groups in the replacement string ($1
, $2
). Groups that are not matched will be replaced by nothing.
First, do not forget the backslash in front of the dot.
Matching the optional sign in front of the number, as well as in front of the exponent, can be done with [+\-]?
or (\+|-|)
(plus, minus, or nothing).
The more complicated part of the exercise is the problem of matching both "5."
and ".5"
without also matching "."
. For this, a good solution is to use the |
operator to separate the two cases—either one or more digits optionally followed by a dot and zero or more digits or a dot followed by one or more digits.
Finally, to make the e case insensitive, either add an i
option to the regular expression or use [eE]
.
This follows the weekDay
module almost exactly. A function expression, called immediately, wraps the variable that holds the array of names, along with the two functions that must be exported. The functions are put in an object and returned. The returned interface object is stored in the month
variable.
Here is what I came up with. I’ve put parentheses around internal functions.
Module "grid" Vector Grid directions directionNames Module "world" (randomElement) (elementFromChar) (charFromElement) View World LifelikeWorld directions [reexported] Module "simple_ecosystem" (randomElement) [duplicated] (dirPlus) Wall BouncingCritter WallFollower Module "ecosystem" Wall [duplicated] Plant PlantEater SmartPlantEater Tiger
I have reexported the directions
array from the grid
module from world
so that modules built on that (the ecosystems) don’t have to know or worry about the existence of the grid
module.
I also duplicated two generic and tiny helper values (randomElement
and Wall
) since they are used as internal details in different contexts and do not belong in the interfaces for these modules.
The trick is to add the exports
object created for a module to require
’s cache before actually running the module. This means the module will not yet have had a chance to override module.exports
, so we do not know whether it wants to export some other value. After loading, the cache object is overridden with module.exports
, which may be a different value.
But if in the course of loading the module, a second module is loaded that asks for the first module, its default exports
object, which is likely still empty at this point, will be in the cache, and the second module will receive a reference to it. If it doesn’t try to do anything with the object until the first module has finished loading, things will work.
The easiest way to do this is to represent Egg arrays with JavaScript arrays.
The values added to the top environment must be functions. Array.prototype.slice
can be used to convert an arguments
array-like object into a regular array.
Again, we are riding along on a JavaScript mechanism to get the equivalent feature in Egg. Special forms are passed the local environment in which they are evaluated so that they can evaluate their subforms in that environment. The function returned by fun
closes over the env
argument given to its enclosing function and uses that to create the function’s local environment when it is called.
This means that the prototype of the local environment will be the environment in which the function was created, which makes it possible to access variables in that environment from the function. This is all there is to implementing closure (though to compile it in a way that is actually efficient, you’d need to do some more work).
Make sure your solution handles multiple comments in a row, with whitespace potentially between or after them.
A regular expression is probably the easiest way to solve this. Write something that matches “whitespace or a comment, zero or more times.” Use the exec
or match
method and look at the length of the first element in the returned array (the whole match) to find out how many characters to slice off.
You will have to loop through one scope at a time, using Object.getPrototypeOf
to go the next outer scope. For each scope, use hasOwnProperty
to find out whether the variable, indicated by the name
property of the first argument to set
, exists in that scope. If it does, set it to the result of evaluating the second argument to set
and then return that value.
If the outermost scope is reached (Object.getPrototypeOf
returns null) and we haven’t found the variable yet, it doesn’t exist, and an error should be thrown.
Use document.createElement
to create new element nodes, document.createTextNode
to create text nodes, and the appendChild
method to put nodes into other nodes.
You should loop over the key names once to fill in the top row and then again for each object in the array to construct the data rows.
Don’t forget to return the enclosing <table>
element at the end of the function.
The solution is most easily expressed with a recursive function, similar to the talksAbout
function defined earlier in this chapter.
You could call byTagname
itself recursively, concatenating the resulting arrays to produce the output. For a more efficient approach, define an inner function that calls itself recursively and that has access to an array variable defined in the outer function to which it can add the matching elements it finds. Don’t forget to call the inner function once from the outer function.
The recursive function must check the node type. Here we are interested only in node type 1 (document.ELEMENT_NODE
). For such nodes, we must loop over their children and, for each child, see whether the child matches the query while also doing a recursive call on it to inspect its own children.
The solution to this exercise involves preventing the default behavior of key events. You can handle either "keypress"
or "keydown"
. If either of them has preventDefault
called on it, the letter will not appear.
Identifying the letter typed requires looking at the keyCode
or charCode
property and comparing that with the codes for the letters you want to filter. In "keydown"
, you do not have to worry about lowercase and uppercase letters, since it identifies only the key pressed. If you decide to handle "keypress"
instead, which identifies the actual character typed, you have to make sure you test for both cases. Here’s one way to do that:
/[qwx]/i.test(String.fromCharCode(event.charCode))
Creating the elements is best done in a loop. Append them to the document to make them show up. Store the trail elements in an array, so you can access them later to change their position.
Cycling through them can be done by keeping a counter variable and adding 1 to it every time the "mousemove"
event fires. The remainder operator (% 10
) can then be used to get a valid array index to pick the element you want to position during a given event.
Another interesting effect can be achieved by modeling a simple physics system. Use only the "mousemove"
event to update a pair of variables that track the mouse position. Then use requestAnimationFrame
to simulate the trailing elements being attracted to the position of the mouse pointer. At every animation step, update their position based on their position relative to the pointer (and optionally, a speed that is stored for each element). Figuring out a good way to do this is up to you.
One pitfall you’ll probably run into is that you can’t directly use the node’s childNodes
property as a collection of tab nodes. For one thing, when you add the buttons, they will also become child nodes and end up in this object because it is live. For another, the text nodes created for the whitespace between the nodes are also in there and should not get their own tabs.
To work around this, start by building up a real array of all the children in the wrapper that have a nodeType
of 1.
When registering event handlers on the buttons, the handler functions will need to know which tab element is associated with the button. If they are created in a normal loop, you can access the loop index variable from inside the function, but it won’t give you the correct number because that variable will have been further changed by the loop.
A simple workaround is to use the forEach
method and create the handler functions from inside the function passed to forEach
. The loop index, which is passed as a second argument to that function, will be a normal local variable there and won’t be overwritten by further iterations.
The most obvious solution would be to make lives
a variable that lives in runGame
and is thus visible to the startLevel
closure.
Another approach, which fits nicely with the spirit of the rest of the function, would be to add a second parameter to startLevel
that gives the number of lives. When the whole state of a system is stored in the arguments to a function, calling that function provides an elegant way to transition to a new state.
In any case, when a level is lost, there should now be two possible state transitions. If that was the last life, we go back to level zero with the starting amount of lives. If not, we repeat the current level with one less life remaining.
An animation can be interrupted by returning false
from the function given to runAnimation
. It can be continued by calling runAnimation
again.
To communicate that the animation should be interrupted to the function passed to runAnimation
so that it can return false
, you can use a variable that both the event handler and that function have access to.
When finding a way to unregister the handlers registered by trackKeys
, remember that the exact same function value that was passed to addEventListener
must be passed to removeEventListener
to successfully remove a handler. Thus, the handler
function value created in trackKeys
must be available to the code that unregisters the handlers.
You can add a property to the object returned by trackKeys
, which contains either that function value or a method that handles the unregistering directly.
The trapezoid (1) is easy to draw using a path. Pick suitable center coordinates and add each of the four corners around that.
The diamond (2) can be drawn the easy way, with a path, or the interesting way, with a rotate
transformation. To use rotation, you will have to apply a trick similar to what we did in the flipHorizontally
function. Because you want to rotate around the center of your rectangle and not around the point (0,0), you must first translate
to there, then rotate, and then translate back.
For the zigzag (3) it becomes impractical to write a new call to lineTo
for each line segment. Instead, you should use a loop. You can have each iteration draw either two line segments (right and then left again) or one, in which case you must use the evenness (% 2
) of the loop index to determine whether to go left or right.
You’ll also need a loop for the spiral (4). If you draw a series of points, with each point moving further along a circle around the spiral’s center, you get a circle. If, during the loop, you vary the radius of the circle on which you are putting the current point and go around more than once, the result is a spiral.
The star (5) depicted is built out of quadraticCurveTo
lines. You could also draw one with straight lines. Divide a circle into eight pieces, or a piece for each point you want your star to have. Draw lines between these points, making them curve toward the center of the star. With quadraticCurveTo
, you can use the center as the control point.
You will need to call fillText
and set the context’s textAlign
and textBaseline
properties in such a way that the text ends up where you want it.
A sensible way to position the labels would be to put the text on the line going from the center of the pie through the middle of the slice. You don’t want to put the text directly against the side of the pie but rather move the text out to the side of the pie by a given number of pixels.
The angle of this line is currentAngle + 0.5 * sliceAngle
. The following code finds a position on this line, 120 pixels from the center:
var middleAngle = currentAngle + 0.5 * sliceAngle; var textX = Math.cos(middleAngle) * 120 + centerX; var textY = Math.sin(middleAngle) * 120 + centerY;
For textBaseline
, the value "middle"
is probably appropriate when using this approach. What to use for textAlign
depends on the side of the circle we are on. On the left, it should be "right"
, and on the right, it should be "left"
so that the text is positioned away from the pie.
If you are not sure how to find out which side of the circle a given angle is on, look to the explanation of Math.cos
in the previous exercise. The cosine of an angle tells us which x-coordinate it corresponds to, which in turn tells us exactly which side of the circle we are on.
A box is easy to draw with strokeRect
. Define a variable that holds its size or define two variables if your box’s width and height differ. To create a round ball, start a path, call arc(x, y, radius, 0, 7)
, which creates an arc going from zero to more than a whole circle, and fill it.
To model the ball’s position and speed, you can use the Vector
type from Chapter 15. Give it a starting speed, preferably one that is not purely vertical or horizontal, and every frame, multiply that speed by the amount of time that elapsed. When the ball gets too close to a vertical wall, invert the x component in its speed. Likewise, invert the y component when it hits a horizontal wall.
After finding the ball’s new position and speed, use clearRect
to delete the scene and redraw it using the new position.
The key to the solution is the fact that we can use a canvas element as a source image when using drawImage
. It is possible to create an extra <canvas>
element, without adding it to the document, and draw our inverted sprites to it once. When drawing an actual frame, we just copy the already inverted sprites to the main canvas.
Some care would be required because images do not load instantly. We do the inverted drawing only once, and if we do it before the image loads, it won’t draw anything. A "load"
handler on the image can be used to draw the inverted images to the extra canvas. This canvas can be used as a drawing source immediately (it’ll simply be blank until we draw the character onto it).
Look at the various examples of using an XMLHttpRequest
in this chapter to see the method calls involved in making a request. You can use a synchronous request (by setting the third parameter to open
to false
) if you want.
Asking for a bogus media type will return a response with code 406, “Not acceptable,” which is the code a server should return when it can’t fulfill the Accept
header.
The function passed to the Promise
constructor will have to call then
on each of the promises in the given array. When one of them succeeds, two things need to happen. The resulting value needs to be stored in the correct position of a result array, and we must check whether this was the last pending promise and finish our own promise if it was.
The latter can be done with a counter, which is initialized to the length of the input array and from which we subtract 1 every time a promise succeeds. When it reaches 0, we are done. Make sure you take the situation where the input array is empty (and thus no promise will ever resolve) into account.
Handling failure requires some thought but turns out to be extremely simple. Just pass the failure function of the wrapping promise to each of the promises in the array so that a failure in one of them triggers the failure of the whole wrapper.
Use document.querySelector
or document.getElementById
to get access to the elements defined in your HTML. An event handler for "click"
or "mousedown"
events on the button can get the value
property of the text field and call new Function
on it.
Make sure you wrap both the call to new Function
and the call to its result in a try
block so that you can catch exceptions that it produces. In this case, we really don’t know what type of exception we are looking for, so catch everything.
The textContent
property of the output element can be used to fill it with a string message. Or if you want to keep the old content around, create a new text node using document.createTextNode
and append it to the element. Remember to add a newline character to the end so that not all output appears on a single line.
The best event for updating the suggestion list is "input"
since that will fire immediately when the content of the field is changed.
Then loop over the array of terms and see whether they start with the given string. For example, you could call indexOf
and see whether the result is zero. For each matching string, add an element to the suggestions <div>
. You should probably also empty that each time you start updating the suggestions, for example, by setting its textContent
to the empty string.
You could either add a "click"
event handler to every suggestion element or add a single one to the outer <div>
that holds them and look at the target
property of the event to find out which suggestion was clicked.
To get the suggestion text out of a DOM node, you could look at its textContent
or set an attribute to explicitly store the text when you create the element.
To solve the problem of having the changes conceptually happen at the same time, try to see the computation of a generation as a pure function, which takes one grid and produces a new grid that represents the next turn.
Representing the grid can be done in any of the ways shown in Chapter 7 and Chapter 15. Counting live neighbors can be done with two nested loops, looping over adjacent coordinates. Take care not to count cells outside of the field and to ignore the cell in the center, whose neighbors we are counting.
Making changes to checkboxes that will take effect on the next generation can be done in two ways. An event handler could notice these changes and update the current grid to reflect them, or you could generate a fresh grid from the values in the checkboxes before computing the next turn.
If you choose to go with event handlers, you might want to attach attributes that identify the position that each checkbox corresponds to so that it is easy to find out which cell to change.
To draw the grid of checkboxes, you either can use a <table>
element (see Chapter 13) or simply put them all in the same element with <br>
(line break) elements between the rows.
You can use relativePos
to find the corner corresponding to the start of the mouse drag. Figuring out where the drag ends can be done with trackDrag
or by registering your own event handler.
When you have two corners of the rectangle, you must somehow translate these into the arguments that fillRect
expects: the top-left corner, width, and height of the rectangle. Math.min
can be used to find the leftmost x-coordinate and topmost y-coordinate. To get the width or height, you can call Math.abs
(the absolute value) on the difference between two sides.
Showing the rectangle during the mouse drag requires a similar set of numbers but in the context of the whole page rather than relative to the canvas. Consider writing a function findRect
, which converts two points into an object with top
, left
, width
, and height
properties so that you don’t have to write the same logic twice.
You can then create a <div>
node and set its style.position
to absolute
. When setting positioning styles, do not forget to append "px"
to the numbers. The node must be added to the document (you can append it to document.body
) and also removed again when the drag ends and the actual rectangle is drawn onto the canvas.
You’ll again need to use relativePos
to find out which pixel was clicked. The pixelAt
function in the example demonstrates how to get the values for a given pixel. Putting those into an rgb
string merely requires some string concatenation.
Make sure you verify that the exception you catch is an instance of SecurityError
so that you don’t accidentally handle the wrong kind of exception.
Given a pair of starting coordinates and the image data for the whole canvas, this approach should work:
Create an array to hold information about already colored coordinates.
Create a work list array to hold coordinates that must be looked at. Put the start position in it.
When the work list is empty, we are done.
Remove one pair of coordinates from the work list.
If those coordinates are already in our array of colored pixels, go back to step 3.
Color the pixel at the current coordinates and add the coordinates to the array of colored pixels.
Add the coordinates of each adjacent pixel whose color is the same as the starting pixel’s original color to the work list.
Return to step 3.
The work list can simply be an array of vector objects. The data structure that tracks colored pixels will be consulted very often. Searching through the whole thing every time a new pixel is visited will take a lot of time. You could instead create an array that has a value in it for every pixel, using again the x + y – width scheme for associating positions with pixels. When checking whether a pixel has been colored already, you could directly access the field corresponding to the current pixel.
You can compare colors by running over the relevant part of the data array, comparing one field at a time. Or you can “condense” a color to a single number or string and compare those. When doing this, ensure that every color produces a unique value. For example, simply adding the color’s components is not safe since multiple colors will have the same sum.
When enumerating the neighbors of a given point, take care to exclude neighbors that are not inside of the canvas or your program might run off into one direction forever.
Don’t forget to call the end
method on the object returned by http.request
in order to actually fire off the request.
The response object passed to http.request
’s callback is a readable stream. This means that it is not entirely trivial to get the whole response body from it. The following utility function reads a whole stream and calls a callback function with the result, using the usual pattern of passing any errors it encounters as the first argument to the callback:
function readStreamAsString(stream, callback) { var data = ""; stream.on("data", function(chunk) { data += chunk; }); stream.on("end", function() { callback(null, data); }); stream.on("error", function(error) { callback(error); }); }
It is enough to strip out all occurrences of two dots that have a slash, a backslash, or the end of the string on both sides. Using the replace
method with a regular expression is the easiest way to do this. Do not forget the g
flag on the expression, or replace
will replace only a single instance, and people could still get around this safety measure by including additional double dots in their paths! Also, make sure you do the replace after decoding the string, or it would be possible to foil the check by encoding a dot or a slash.
Another potentially worrying case is when paths start with a slash, which are interpreted as absolute paths. But because urlToPath
puts a dot character in front of the path, it is impossible to create requests that result in such a path. Multiple slashes in a row, inside the path, are odd but will be treated as a single slash by the filesystem.
You can use the function that implements the DELETE
method as a blueprint for the MKCOL
method. When no file is found, try to create a directory with fs.mkdir
. When a directory exists at that path, you can return a 204 response so that directory creation requests are idempotent. If a nondirectory file exists here, return an error code. The code 400 (“bad request”) would be appropriate here.
You can create a <textarea>
element to hold the content of the file that is being edited. A GET
request, using XMLHttpRequest
, can be used to get the current content of the file. You can use relative URLs like index.html, instead of http://localhost:8000/index.html, to refer to files on the same server as the running script.
Then, when the user clicks a button (you can use a <form>
element and "submit"
event or simply a "click"
handler), make a PUT
request to the same URL, with the content of the <textarea>
as request body, to save the file.
You can then add a <select>
element that contains all the files in the server’s root directory by adding <option>
elements containing the lines returned by a GET
request to the URL /
. When the user selects another file (a "change"
event on the field), the script must fetch and display that file. Also, make sure that when saving a file, you use the currently selected filename.
Unfortunately, the server is too simplistic to be able to reliably read files from subdirectories since it does not tell us whether the thing we fetched with a GET
request is a regular file or a directory. Can you think of a way to extend the server to address this?
The simplest solution I can come up with is to encode the whole talks
object as JSON and dump it to a file with fs.writeFile
. There is already a function (registerChange
) that is called every time the server’s data changes. It can be extended to write the new data to disk.
Pick a filename, for example, ./talks.json
. When the server starts, it can try to read that file with fs.readFile
, and if that succeeds, the server can use the file’s contents as its starting data.
Beware, though. The talks
object started as a prototype-less object so that the in
operator could be sanely used. JSON.parse
will return regular objects with Object.prototype
as their prototype. If you use JSON as your file format, you’ll have to copy the properties of the object returned by JSON.parse
into a new, prototype-less object.
The ad hoc approach is to simply store the state of a talk’s comment field (its content and whether it is focused) before redrawing the talk and then reset the field to its old state afterward.
Another solution would be to not simply replace the old DOM structure with the new one but recursively compare them, node by node, and update only the parts that actually changed. This is a lot harder to implement, but it’s more general and continues working even if we add another text field.
You could change instantiateTemplate
so that its inner function takes not just a node but also a current context as an argument. You can then, when looping over a node’s child nodes, check whether the child has a template-repeat
attribute. If it does, don’t instantiate it once but instead loop over the array indicated by the attribute’s value and instantiate it once for every element in the array, passing the current array element as context.
Conditionals can be implemented in a similar way, with attributes called, for example, template-when
and template-unless
, which cause a node to be instantiated only when a given property is true (or false).
Two central aspects of the approach taken in this chapter—a clean HTTP interface and client-side template rendering—don’t work without JavaScript. Normal HTML forms can send GET
and POST
requests but not PUT
or DELETE
requests and can send their data only to a fixed URL.
Thus, the server would have to be revised to accept comments, new talks, and deleted talks through POST
requests, whose bodies aren’t JSON but rather use the URL-encoded format that HTML forms use (see Chapter 17). These requests would have to return the full new page so that users see the new state of the site after they make a change. This would not be too hard to engineer and could be implemented alongside the “clean” HTTP interface.
The code for rendering talks would have to be duplicated on the server. The index.html
file, rather than being a static file, would have to be generated dynamically by adding a handler for it to the router. That way, it already includes the current talks and comments when it gets served.
The work list can be an array, and you can add paths to it with the push
method. You cannot use the built-in forEach
method to loop over the work items since that uses the initial length of the array to bound the loop. We will be adding new items to the array from inside the loop, so its length has to be checked again on every iteration.
If you use arrays to represent paths, you can extend them with the concat
method, as in path.concat([node])
.
To find out whether a node has already been seen, you can loop over the existing work list normally or use the some
method.
The main opportunity for macro-optimization is to get rid of the inner loop that figures out whether a node has already been looked at. Looking this up in an object is much faster than iterating over the work list to search for the node. But since JavaScript map objects require strings, not objects, as property names, we need a trick like withIDs
to be able to use a map to associate information with a given object. (The next version of JavaScript defines an object type Map
, which is a real map, whose keys can be any JavaScript values, not just strings.)
Another improvement can be made by changing the way paths are stored. Extending an array with a new element without modifying the existing array requires copying the whole array. A data structure like the list from Chapter 4 does not have this problem—it allows multiple extensions of a list to share the data that they have in common.
You can make your function internally store paths as objects with last
and via
properties, where last
is the last node in the path and via
is either null
or another such object. This way, extending a path only requires creating an object with two properties, rather than copying a whole array. Make sure you convert the list to an actual array before returning it.