Due Date: Aug 8 before midnight; No Late Submissions
Max Points: 100
Important Reminder: As per the course Academic Honesty Statement, cheating of any kind will minimally result in your letter grade for the entire course being reduced by one level.
To be turned in via the submission link on brightspace.
Please remember to justify all answers.
You are encouraged to use the web or the library but are required to cite any external sources used in your answers.
It may be the case that some questions cannot be answered as requested.
If you followed the hints for Project 4 or Project 5, validating user entry into a spreadsheet cell entails a web service call. This is not particularly efficient. Discuss changes to the implementation to remove this inefficiency. Your approach should allow reuse of the validation code developed in earlier projects. 5-points
The simplest solution is to build up a library which is a subset of the cs544-prj2-sol library with all server dependencies removed. Specifically, include spreadsheet.ts (which provides all the validation logic) and its direct and indirect dependancies expr-parser.ts, utils.ts and limits.ts. Note that none of these files have any server-side dependencies.
Considering the fact that the web services ss-ws.ts
provide the
same interface as
ss-services.ts, it would be useful to also include
ss-services.ts
, but that has a clear server-side dependancy on
spreadsheet-dao.ts. However, since an ss-services.ts
has an
instance of SpreadsheetDao
injected into it, it would be simple
enough to replace the injection with an in-memory version of
SpreadsheetDao
implemented simply as { [ ssName:
string ]: { [cellId: string] : string }}
.
The services in ss-ws.ts
can first call the corresponding
service in this modified ss-services.ts
. The web request would
be made only if that call returns success. If the call indicates
a validation error, then the web service call is never made.
This permits validation in the client using the validation code
developed in earlier projects.
Discuss the choice of HTML form widgets for each of the following situations: 15-points
Selecting one-or-more US states.
Specifying a color.
Specifying the speed of an animation.
Specifying a person's age in years.
Selecting one-or-more planets from our solar system.
The answers follow:
Selecting multiple elements from a large list is problematic on desktop devices and even worse on mobile devices. One aspect of this particular problem is that the user will be familiar with the possible US states and is not likely to get bogged down selecting from an unfamiliar list.
The ideal control would be a select
element with the
multiple
attribute set <select multiple="multiple">
.
Unfortunately, most users are not familiar with choosing
multiple options from a select box; hence if this option is
specified, it is a good idea to provide brief help text or a
tool tip.
If the maximum number of states to be selected is known and small, then multiple uni-select boxes could be used, with labels like Select First State and Select Second State.
Any kind of select
box with 50+ selections will likely be
clumsy on mobile devices.
Another possibility would be to use checkboxes <input
type="checkbox">
. However, this would have the following
disadvantages:
Would add clutter to the UI.
Would use up too much screen real-estate.
If screen real-estate is not a problem, then it is possible to hide the checkboxes behind an image map with the image of each state corresponding to a checkbox. This would remove some of the clutter from the UI.
If the maximum number of states to be selected is known and
small, then another possibility is to use an input
with a
datalist
.
<datalist id="states"> <option>AL</option> <option>AK</option> ... </datalist <label for="state1">Enter first state</label> <input name="state1" list="states"> <label for="state2">Enter second state</label> <input name="state2" list="states">
In summary, though the above alternatives can be used, there does not appear to be any good solution which does not use JavaScript.
In modern browsers, we would use <input type="color">
.
Since the animation speed need not be precise, in modern
browsers we can use a slider <input type="range">
.
In modern browsers, we can use something like
<input type="number" min="0" max="120" step="1">
Since the number of planets is relatively small,
<input type="checkbox">
would probably suffice.
Discuss the tradeoffs between choosing the DOMContentLoaded
browser event versus the load
browser event. Give examples of
situations where you would prefer one over the other. 10-points
The DomContentLoaded
event is fired after the DOM
tree is
built but external resources like stylesheets and images may not
have loaded, whereas the load
event fires only after the DOM
tree is built and external resources have loaded. It follows
that:
The DomContentLoaded
event handler may run before the load
event handler, leading to a more zippy UX.
DomContentLoaded
should be preferred over load
except
for situations where the event handler requires access
to the external resources.
Hence if we just need to attach event handlers to DOM elements, we
would use an event handler triggered by DomContentLoaded
event.
OTOH, if we needed to resize portions of a page based on the size
of an external image, we would use an event handler triggered by
the load
event.
A shopping cart is displayed using HTML structured as follows:
<div class="cart" id="cart123"> <ul class="cart-items"> <li> <span class="item-descr" data-product-id="2347"> Beefsteak tomatoes </span> <span class="item-quantity" data-unit="pound"> 2.34 pounds </span> <span class="item-price">$ 5.63</span> </li> <!-- more <li> items with similar structure --> ... </ul> </div>
Using only the DOM API, provide JavaScript code for each of the following:
Show code for a function cartItems(cardId)
which returns a
list containing all the items in the cart having id
cartId
. For example, given the above data, the returned
list for cartItems('cart123')
should start out as [
'Beefsteak tomatoes', ... ]
.
Show code for a function itemPrice(cartId, itemIndex)
which returns
a number giving the price of the item at index itemIndex
in the
cart with id
cartId
. For example, given the above data,
itemPrice('cart123', 0)
should return 5.63
.
Show code for a function itemUnit(cartId, itemIndex)
which
returns the data-unit
attribute of the item at index itemIndex
.
For example, itemUnit('cart123', 0)
should return 'pound'
.
Show code for a function cartPrice(cartId)
which returns the
total price of the cart (each item contributes unit-price *
item-quantity
to the total price of the cart).
Using the function from the previous part, show code which
appends the total price of the cart between the closing
</ul>
and the closing </div>
using any reasonable HTML
formatting.
If any of the above order-items do not exist, then the above
functions should return null
. 15-points
Working code (not required). The relevant parts are reproduced below:
The code for the function follows:
/** return list of all order items */ function cartItems(cartId) { const sel = `#${cartId} .item-descr`; const nodes = [...document.querySelectorAll(sel)]; return nodes.map(e => e.innerHTML.trim()); }
The code for the function follows:
/** return number giving price of item */ function itemPrice(cartId, itemIndex) { const sel = `#${cartId} .item-price`; const nodes = document.querySelectorAll(sel); const price = nodes[itemIndex]?.innerHTML; return price ? Number(price.replace(/[^\d\.]/g, '')) : null; }
The code for the function follows:
/** return # of units for item in cart */ function itemUnit(cartId, itemIndex) { const sel = `#${cartId} .item-quantity`; const el = document.querySelectorAll(sel)[itemIndex]; return (el) ? el.getAttribute('data-unit') : null; }
The code for the function follows:
/** return total price of cart */ function cartPrice(cartId) { const innerHTMLNumber = el => Number(el.innerHTML.replace(/[^\d\.]/g, '')); const sel1 = `#${cartId} .item-price`; const unitPrices = [...document.querySelectorAll(sel1)].map(innerHTMLNumber); const sel2 = `#${cartId} .item-quantity`; const quantities = [...document.querySelectorAll(sel2)].map(innerHTMLNumber); console.assert(unitPrices.length === quantities.length, `failed ${unitPrices.length} === ${quantities.length}`); const total = unitPrices .reduce((acc, p, i) => acc + (p * quantities[i]), 0); return Number(total.toFixed(2)); }
The code for a function which does the append follows:
/** append total cost */ function appendTotalPrice(cartId) { const total = this.cartPrice(cartId); const cart = document.querySelector(`#${cartId}`); const text = document.createTextNode(`Total price: $ ${total}`); cart.appendChild(text); return ''; }
The management of a large multi-national corporation is aware of the need to keep its programmers well caffeinatated. Being a woke, environmentally conscious corporation, the management prefers to not use single-brew solutions like K-cups but uses old fashioned coffee pots instead. In the absence of widespread adoption of RFC 2324, the company decides to use HTTP web services with the following services:
GET /coffee
Returns a JSON list of all the company locations having coffee pots which are hooked into the web services.
Example output: [ "Boston", "Johannesburg", "London",
"Mumbai", "San Diego", "Sao Paulo" ]
GET /coffee/
LOC Returns a JSON list of office areas at LOC having coffee pots.
For example, GET /coffee/San%20Diego
returns
[ "Kitchen 1", "Kitchen 2", "Lobby" ]
.
GET /coffee/
LOC/
AREA Returns the status of the coffee pot in area AREA at
location LOC. Specifically, it returns a JSON pair
[
FULL_PERCENT,
BREW_MINUTES ]
where FULL_PERCENT
is a percentage between 0
and 100
specifying how full
the coffee pot is and BREW_MINUTES specifies how many
minutes ago the coffee was freshly brewed.
For example, GET /coffee/San%20Diego/Kitchen%202
may
return [ 67, 18 ]
.
Ignoring errors, these web services can be accessed in a browser using the following wrapper API:
async function getLocations() : string[]; async function getLocationAreas(loc: string) : string[]; async function getAreaCoffeePotStatus(loc: string, area: string) : [ number, number ];
with a simulated implementation.
Discuss how you would use the DOM API to implement a single page app (SPA) which allows any employee of the corporation to check the status of coffee pots. Specifically, the UI should allow the employee to specify a location. Then at one second intervals, the page should continuously cycle through all the areas at that location, displaying the coffee pot status for that area. A typical UI is shown in this brief video.
Your discussion should minimally include the following:
The events which would be caught along with a brief description of the operation of the corresponding event handler.
The initial setup of the SPA.
[If you decide to actually write code and submit a .html
file,
then please be aware of the fact that brightspace silently
replaces all <script>
sections of HTML with its own content. A
workaround for this totally unacceptable behavior is to add a
.text
extension to the file so that it is submitted as
.html.text
] 20-points
On startup in reponse to the DOMContentLoaded
event, the SPA will:
Instantiate the coffee web services.
Call the getLocations()
web service to get the locations
for the company with coffee pots participating in the web
service.
Render a select widget listing those locations as its options. Choose the first location as the current location.
Get the coffee pot areas for the currently selected location
using the getLocationAreas()
web service.
Create an interval timer to run every second.
A change listener for the select widget will use the
getLocationAreas()
web service to update the coffee pot areas
for the currently selected location.
Each tick of the interval timer will use the
getAreaCoffeePotStatus()
web service for the current area and
cycle the areas. The coffee pot status will be displayed on the
page.
A working version (not required) is available in dom-coffee.html.
Repeat the previous question, but use React hooks instead of the
DOM API. Specify the hooks you would use; for useEffect()
be sure to specify the dependancy array and cleanup function
(if needed). 20-points
We can use a single App
component having the following state
constituents set up using the useState()
hook:
locs
the set of locations.
loc
the currently selected location.
areas
the set of areas for loc
.
index
the index of the currently displayed area.
status
the coffee pot status pair for the currently displayed area.
For initialization we call the useEffect()
hook with an empty
dependancy array. It makes a getLocations()
web service call
and sets the locs
and loc
state constituents. Subsequently,
it makes an additional getLocationAreas()
to get and set the
areas
state constituent for loc
.
The initialization effect also sets up an interval timer which bumps up the index state constituent. The hook returns a function which cleans up the interval timer.
When the select box rendering the locs
is changed, the loc
and
areas
state constituents are updated (the latter requires a call
to the getLocationAreas()
web service).
We use an effect which depends on index
and areas
to respond
to changes in the areas
(caused by changing the select box) and
the index
(caused by the tick of the interval timer) state
constituents. This effect updates the coffee pot status using
the getAreaCoffeePotStatus()
web service.
A working version (not required) is available in react-coffee.html.
Explain precisely why the JavaScript expression '2' * 3 + 4 + '5' + 6
results in '1056'
. 5-points
Making the associativity and precedence of the operators explicit
by inserting parentheses, the expression is equivalent to
((('2' * 3) + 4) + '5') + 6
. In '2' * 3
, the '2'
is converted
to a number resulting in number 6
. Hence in 6 + 4
the +
is
interpreted as addition, result in number 10
. Next, 10 + '5'
,
the string '5'
causes the +
to be interpreted as concatentation,
resulting in string '105'
. Finally, in '105' + 6
, the string
'105'
causes the +
to be interpreted as concatentation resulting
in final result string '1056'
.
Two programmers Bill and Mary are working at a company bigcorp
.
Mary has a cool web service running on her workstation such that a
GET
to the URL http://mary.bigcorp.com/company-photos
returns a
random png
image corresponding to a photo taken at a company
event. Bill would like to use this web service to display a
random company event photo whenever his home page at
http://bill.bigcorp.com
is accessed. He inserts a call to
access Mary's web service in the JavaScript contained on his
homepage but it does not work. Give possible reasons for this
problem. 10-points
Some possible reasons:
Since http://mary.bigcorp.com
is a different origin
from http://bill.bigcorp.com
, the same-origin policy
will not allow JavaScript from Bill's domain to make
a call to Mary's domain. A fix for that would be to
have Mary set up CORS exceptions to whitelist Bill's
domain.
Since the domain is bigcorp.com
, it is possible that
the company is a large corporation. Large corporations
run rather restrictive firewall policies. Since most
employees use their workstations purely as web clients,
the corporation's security policies probably do not
allow use of a workstation as a server by another
workstation. So it may be impossible for Bill's
homepage to access Mary's web services without
some modification of the company firewall rules.