CS349

A3-Vanilla

Started doing the mockup on Sat March 2.

Video demo: https://vault.cs.uwaterloo.ca/s/HB8b9MqGiE7Rj2D

  • You must create your interface using “vanilla” HTML, CSS, and DOM manipulation. This means using things like <div> and <input> tags, standard DOM events, CSS layout techniques like Flexbox, etc. See the corresponding lectures for approaches.
  • You may install the npm package “html-template-tag” to use html tagged templates for constructing your UI.

March 3:

  • Start a3
  • Need to figure out how to put the drawing of the cat, bullseye etc. all together in html and css.
  • Have the layout done
/* mockup.html */
 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Shape Editor Without UI Tools</title>
  <style>
    body {
      margin: 0;
      padding: 0;
      font-family: Arial, sans-serif;
      font-size: 12pt;
    }
    
    * {
      box-sizing: border-box;
    }
 
    /* General styles */
    div {
      border: 1px solid red; /* for visualization only */
    }
 
    /* App container */
    div#app {
      display: flex;
      min-width: 620px;
      min-height: 420px;
      height: 100vh;
    }
 
    /* Panel styles */
    div#panel {
      display: flex;
      height: 100vh;
      flex: 1;
    }
 
    /* Left panel */
    div#left {
      flex: 1;
      display: flex;
      width: 66.7%;
      background-color: white;
      flex-direction: column;
    }
 
    div#toolbar {
      padding: 10px;
      height: 50px;
      background-color: lightgrey;
      display: flex;
      align-items: center;
    }
 
    div#shape_list {
      padding: 20px;
      background-color: white;
      flex: 1;
      display: flex;
      flex-wrap: wrap;
      align-content: flex-start;
      overflow: auto;
    }
 
    div#shape_list > div {
      width: 50px;
      height: 50px;
      border: 1px solid grey;
      display: flex;
      align-items: center;
      justify-content: center;
      margin-right: 20px;
      margin-bottom: 20px;
    }
 
    div#shape_list > div:hover {
      border-color: lightblue;
      border-width: 4px;
    }
 
    div#statusbar {
      padding: 10px;
      height: 50px;
      background-color: lightgrey;
      display: flex;
      align-items: center;
    }
 
    div#statusbar > div {
      flex: 1;
    }
 
    /* Right panel */
    div#right {
      background-color: whitesmoke;
      width: 33.3%;
      margin: 10px;
      border: 2px solid lightgrey;
      display: flex;
      flex-direction: column;
    }
 
    div#right > #editor {
      margin: 10px;
      height: 67%;
      border: 1px solid lightgreen;
      background-color: whitesmoke;
    }
 
    div#right > #form {
      margin: 10px;
      display: flex;
      flex-direction: column;
      border: 1px solid black;
      background-color: whitesmoke;
      align-items: left;
      height: 33%;
    }
 
    div#right > #form > div {
      display: flex;
      margin: 5px;
      align-items: center;
    }
 
    div#right > #form > div > label {
      margin-right: 10px;
    }
 
    div#right > #form > div > input {
      height: 25px;
    }
  </style>
</head>
<body>
  <div id="app">
    <div id="panel">
      <!-- Left panel -->
      <div id="left">
        <div id="toolbar">
          <button>Add</button>
          <select id="shape_selected">
            <option value="square">Square</option>
            <option value="star">Star</option>
            <option value="cat">Cat</option>
            <option value="bullseye">Bullseye</option>
          </select>
          <button>Delete</button>
          <button>Clear</button>
        </div>
        <div id="shape_list">
          <div>1</div>
          <div>2</div>
          <div>3</div>
          <div>4</div>
          <div>5</div>
          <div>6</div>
          <div>7</div>
          <div>8</div>
          <div>9</div>
        </div>
        <div id="statusbar">
          <div># of shapes</div>
          <div>SHIFT 1 selected</div>
        </div>
      </div>
      
      <!-- Right panel -->
      <div id="right">
        <div id="editor">
          <div id="canvas">Canvas</div>
        </div>
        <div id="form">
          <div>
            <label for="hue">Hue:</label>
            <input type="text" id="hue" placeholder="0-360" />
          </div>
          <div>
            <label for="radius">Radius:</label>
            <input type="text" id="radius" placeholder="20-45" />
          </div>
          <div>
            <label for="points">Points:</label>
            <input type="text" id="points" placeholder="3-9" />
          </div>
        </div>
      </div>
    </div>
  </div>
</body>
</html>

March 4:

  • Start implementing functionalities, mostly done left view

March 5:

  • Complete rightView functionalities (form and editor views) attach with MVC model

March 8:

  • Undo bonus
  • Fix randomized bullseye and stars
  • Fix drawing bullseye

March 9:

  • Undo fix bug for Add
  • Fix Bullseye rings and scale on the right side view

TODO:

  • CSS red border when it goes out of range for the form
  • Star randomly generat outer radius??
  • Undo?
  • README.md file
  • 24. Toolbar buttons are disabled under certain conditions. A disabled button is drawn in lighter grays, there are no hover or down effects, and it won’t send any bound events. “Add” is disabled when there are 25 shapes in the list. “Delete” is disabled when no shapes are selected. “Clear” is disabled when there are no shapes in the list.
  • [ ]
  • 16. The hue textfield accepts only numeric input. There are “up/down” buttons that only permit a range of 0 to 360, but the user can enter any number. If the number is out of range or the textfield is empty, the textfield has a 2px red outline.

Things I didn’t quite understand

get root(): HTMLDivElement {
  return this.container;
}
  • This is a getter method named root that returns the value of the container property. By using a getter, you can access container as if it were a property of the class (view.root), but internally, it’s actually invoking the root() method to retrieve the value.
  • To provide external access to the container property without allowing direct modification of the property itself. This is a common pattern in TypeScript and other object-oriented programming languages to encapsulate the internal state of an object while providing controlled access to it.

A2 Example Solution Video

Video Link  password: ksk6p5cyxc

This video is only for students enrolled in W24 CS349.
Do not share or download this video

Note that the Model is almost exactly the same as A2, the general structure of views is the same as A2, and the way views work internally is very similar.

  1. General Structure in main.ts
    • 4 views: toolbar, list, status, and edit
    • layout is all flexbox
  2. Model in model.ts
    • Shape type and createRandomShape extended for Bullseye and Cat
    • select and deselect now also set the editing id (in A2 the edit id was set in the list controller, better to doo this in model since shape to be edited is always related to overall list selection state)
  3. ToolbarView
    • layout is flexbox row
    • I use nesting for CSS rules that apply to children, but this is the same as using a selector like div#toolbar>button (you’ll see this nesting in all CSS)
    • Add button gets ref to select in controller
    • used html tagged template to create <select>, but rest is imperative
    • update sets button enabled/disabled state like A2
  4. StatusView
    • layout is flexbox row
    • span for shape number and span for selected messages
    • update sets text for spans depending on model state
    • (“FULL” and “SHIFT” added for debugging and demo, not requirements)
  5. ListView
    • layout is flexbox with wrap
    • update clears all children, then adds in canvas elements for each shape
      • drawShape using canvas graphics context
      • sets class on canvas if selected
      • CSS sizes the 100x100 canvas to 50x50
      • select and hover states created with CSS
    • each shape widget gets a “click” controller:
      • model call to select or deselect the shape (multiselect and edit shape state handled in model )
      • stops propagation
    • ”click” controller on listView div container deselects all shapes and clears editor state (by calling methods on model)
  6. EditView
    • layout is flexbox
    • swaps EditMessageView or EditShapeView depending on edit state
    • removes old view from Model observer list
    • re-uses the child view if they don’t change (re-using EditShapeView maintains input focus too)
  7. EditMessageView
    • layout is flexbox with centred paragraph element
  8. EditShapeView This view is quite different than A2
    • constructor creates canvas and form for shape type (can re-use them if edit shape stays the same)
    • the canvas is 100x100
    • in update the drawShape function is called with shape props from Model
    • the form is an HTML form
    • a switch adds fields required by a shape type to a field object, each time calling
    • makeRow creates and adds the HTML label (with prop name) and adds the HTML input element
    • in update, the fields object is used to set current shape props form the model
    • makeInput creates the HTML number input element for the prop
      • sets up validation using min, max, and build-in HTML methods
      • creates a controlled with the props object to update the model
    • makeSelect creates the HTML select for the Cat
    • layout is flexbox
      • canvas is inside a div, sized using the object-fit CSS property
      • form also uses flexbox for rows and even in each row
      • invalid red outline set with pseudo selector