From Spiral11 To Spiral12

Code

The code for the Model Concepts project is at model_concepts on GitHub.

The code for the Magic Boxes project is at mb_spirals on GitHub. For teaching or learning purposes, each spiral in Magic Boxes is decomposed into several sub-spirals.

Overview

In Spiral 11, a model may be transformed to [JSON] (http://www.json.org/) and back from JSON to a model. JSON (JavaScript Object Notation) is a lightweight data-interchange format. JSON is easy for humans to understand and for software to parse it. It is based on two data structures: a collection of name/value pairs {} and an ordered list of values [].

In Spiral 12, a model may be saved in a [local storage] (http://en.wikipedia.org/wiki/Web_storage) under a model name and later opened by using the model name. A type of a data attribute may be changed from default String to another type. Based on the current item obtained by the Get 1 button (where 1 is determined by the name in the item input field), the next item may be selected by the Get +1 (Next) button.

Model

The WebLinks model from Figure 11-12.01, is recreated from the JSON representation kept in the WebLinks.txt file in the model folder. The model is then updated to include types for concept attributes. This update is accelerated by the Get +1 (Next) item button in the tool bar. The updated model is then transformed into a new JSON representation that is then copied and pasted in the WebLinks text file. In addition, the model is saved in a local storage of a browser under the WebLinks key.

![Alt Figure 11-12.01: WebLinks model] (/img/s11s12/WebLinksWithToolBar.png)

Figure 11-12.01: WebLinks model.

Note the Date type for the creationDate attribute.

Steps

The save model button is added to the MenuBar class.

Code 11-12.01: Save model button.

class MenuBar {
  
  final Board board;
  
  // File
  ButtonElement saveModelButton;

The save model button is initialized in the constructor of the MenuBar class.

Code 11-12.02: Initialize the save model button.

MenuBar(this.board) {  
    saveModelButton = document.querySelector('#save-model');

In the constructor of the MenuBar class, the button click event is defined.

Code 11-12.03: Save model button event.

saveModelButton.onClick.listen((MouseEvent e) {
      board.saveModel();
    });

In the Web page, the button element is added.

Code 11-12.04: Save model button element.

<li>File
         <ul>
           <li><button id="save-model">Save Model</button></li>
         </ul>  
       </li>

In the Board class, the save model method obtains the JSON representation of the model and then uses the local storage of a browser to set under the model key the JSON text.

Code 11-12.05: Save model method.

void saveModel() {
    String json = toJson();
    if (json != null) {
      window.localStorage['model'] = json;
    }
  }

The open model button is added to the MenuBar class.

Code 11-12.06: Open model button.

class MenuBar {
  
  final Board board;
  
  // File
  ButtonElement openModelButton;

The open model button is initialized in the constructor of the MenuBar class.

Code 11-12.07: Initialize the open model button.

MenuBar(this.board) {  
    openModelButton = document.querySelector('#open-model');

A click on the button opens the model.

Code 11-12.08: Open model button event.

openModelButton.onClick.listen((MouseEvent e) {
      board.openModel();
    });

In the File menu there are now two menu items.

Code 11-12.09: Open model button element.

<li>File
         <ul>
           <li><button id="open-model">Open Model</button></li>
           <li><button id="save-model">Save Model</button></li>
         </ul>  
       </li>

In the Board class, the open model method obtains the JSON text from the local storage of a browser by using the model key. The JSON text is then passed to the fromJson method to recreate the model from the JSON representation.

Code 11-12.10: Open model method.

void openModel() {
    String json = window.localStorage['model'];
    if (json != null) {
      fromJson(json);
    }
  }

Up to now, only one model may be saved in the local storage under the model key. The model name input is added to the MenuBar class in order to allow a user to define the key value as a specific model name.

Code 11-12.11: Model name input.

class MenuBar {
  
  final Board board;
  
  // File
  InputElement modelNameInput;

The model name input is initialized in the constructor of the MenuBar class.

Code 11-12.12: Initialize the model name input.

MenuBar(this.board) { 
    modelNameInput = document.querySelector('#model-name');

In the constructor of the MenuBar class, for both open and save events, the model name is first obtained and then the model name is passed as an argument to the corresponding method.

Code 11-12.13: Open and save model events.

openModelButton.onClick.listen((MouseEvent e) {
      String modelName = modelNameInput.value.trim();
      board.openModel(modelName);
    });
    saveModelButton.onClick.listen((MouseEvent e) {
      String modelName = modelNameInput.value.trim();
      board.saveModel(modelName);
    });

In the HTML code, the save model button element is added after the open model button element.

Code 11-12.14: HTML elements.

<li>File
         <ul>
           <li><input type="text" id="model-name"/></li>
           <li><button id="open-model">Open Model</button></li>
           <li><button id="save-model">Save Model</button></li>
         </ul>  
       </li>

In the Board class, the open and save model methods are updated to include a model name parameter. Now, different models with different names may be saved in the local storage.

Code 11-12.15: Open and save model methods.

void openModel(String name) {
    String json = window.localStorage[name];
    if (json != null) {
      fromJson(json);
    }
  }

  void saveModel(String name) {
    String json = toJson();
    if (json != null) {
      window.localStorage[name] = json;
    }
  }

An item may be typed. The item type option is added to the ToolBar class, after the item category option.

Code 11-12.16: Item type option.

class ToolBar {
  ...
  SelectElement itemCategoryOption;
  SelectElement itemTypeOption;

In the constructor of the ToolBar class, the HTML element with the itemType id is assigned to the item type option. A change of its value is, including the item name, is assigned to the current item and the input name is then selected, so that a user may visually recognize that the selected item is now the current item.

Code 11-12.17: Initialize the item type option.

itemTypeOption = document.querySelector('#itemType');
    itemTypeOption.onChange.listen((Event e) {
      if (currentItem != null) {
        currentItem.name = itemNameInput.value;
        currentItem.type = itemTypeOption.value;
        itemNameInput.select();
      }
      itemInitInput.value = '';
    });

There is a new type attribute in the Item class.

Code 11-12.18: Item type attribute.

class Item {
  ...
  String type; // String, num, int, double, bool, Date, Other

Different type options are part of the select element with the itemType id.

Code 11-12.19: Item type options.

<select id="itemType">
          <option>String</option>
          <option>num</option>
          <option>int</option>
          <option>double</option>
          <option>bool</option>
          <option>Date</option>
          <option>Other</option>
        </select>

In the Item class, the toJson method is updated to include the type attribute.

Code 11-12.20: Mapping from an item type.

Map<String, Object> toJson() {
    Map<String, Object> itemMap = new Map<String, Object>();
    itemMap["sequence"] = sequence;
    itemMap["name"] = name;
    itemMap["category"] = category; 
    itemMap["type"] = type;
    itemMap["init"] = init;
    return itemMap;
  }

In the Board class, the itemFromJson method is updated to include the type attribute.

Code 11-12.21: Mapping to an item type.

Item itemFromJson(Box box, Map<String, Object> itemMap) {
    String name = itemMap["name"];
    String category = itemMap["category"];
    Item item = new Item(box, name, category);
    int sequence = itemMap["sequence"];
    item.sequence = sequence;
    item.type = itemMap["type"];
    item.init = itemMap["init"];
  }

In the ToolBar class, the focus event of the box name input is updated to present the String type as the default type for box items.

Code 11-12.22: Box name input.

boxNameInput = document.querySelector('#boxName');
    boxNameInput.onFocus.listen((Event e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        boxNameInput.value = box.title;
        boxEntryCheckbox.checked = box.entry;
        currentItem = null;
        itemNameInput.value = '';
        itemCategoryOption.value = 'attribute';
        itemTypeOption.value = 'String';
        itemInitInput.value = '';
      }
    });

The click event on the add item button is updated to include the type attribute.

Code 11-12.23: Add item.

addItemButton = document.querySelector('#addItem');
    addItemButton.onClick.listen((MouseEvent e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        String itemName = itemNameInput.value.trim();
        if (itemName != '') {
          Item otherItem = box.findItem(itemName);
          if (otherItem == null) {
            Item item = new Item(box, itemName, itemCategoryOption.value);
            item.type = itemTypeOption.value;
            item.init = itemInitInput.value.trim();
          }
        }
      }
    });

The click event on the get item button is updated to include the type attribute.

Code 11-12.24: Get item.

getItemButton = document.querySelector('#getItem');
    getItemButton.onClick.listen((MouseEvent e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        Item item = box.findItem(itemNameInput.value);
        if (item != null) {
          currentItem = item;
          itemNameInput.value = item.name;
          itemCategoryOption.value = item.category;
          itemTypeOption.value = item.type;
          itemInitInput.value = item.init;
          itemNameInput.select();
        } else {
          currentItem = null;
        }
      }
    });

The click event on the up item button is updated to include the type attribute.

Code 11-12.25: Up item.

upItemButton = document.querySelector('#upItem');
    upItemButton.onClick.listen((MouseEvent e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        if (currentItem != null) {
          Item previousItem = box.findPreviousItem(currentItem);
          if (previousItem != null) {
            int previousSequence = previousItem.sequence;
            int currentSequence = currentItem.sequence;
            currentItem.sequence = previousSequence;
            previousItem.sequence = currentSequence;
            itemNameInput.select();
          } else {
            currentItem = null;
            itemNameInput.value = '';
            itemCategoryOption.value = 'attribute';
            itemTypeOption.value = 'String';
            itemInitInput.value = '';
          }
        }
      }
    });

The click event on the down item button is updated to include the type attribute.

Code 11-12.26: Down item.

downItemButton = document.querySelector('#downItem');
    downItemButton.onClick.listen((MouseEvent e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        if (currentItem != null) {
          Item nextItem = box.findNextItem(currentItem);
          if (nextItem != null) {
            int nextSequence = nextItem.sequence;
            int currentSequence = currentItem.sequence;
            currentItem.sequence = nextSequence;
            nextItem.sequence = currentSequence;
            itemNameInput.select();
          } else {
            currentItem = null;
            itemNameInput.value = '';
            itemCategoryOption.value = 'attribute';
            itemTypeOption.value = 'String';
            itemInitInput.value = '';
          }
        }
      }
    });

The click event on the set item button is updated to include the type attribute.

Code 11-12.27: Set item.

setItemButton = document.querySelector('#setItem');
    setItemButton.onClick.listen((MouseEvent e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        if (currentItem != null) {
          String itemName = itemNameInput.value.trim();
          if (itemName != '') {
            Item otherItem = box.findItem(itemName);
            if (otherItem == null) {
              currentItem.name = itemName;
            }
          }
          currentItem.category = itemCategoryOption.value;
          currentItem.type = itemTypeOption.value;
          currentItem.init = itemInitInput.value;
          itemNameInput.select();
        }
      }
    });

The click event on the remove item button is updated to include the type attribute.

Code 11-12.28: Remove item.

removeItemButton = document.querySelector('#removeItem');
    removeItemButton.onClick.listen((MouseEvent e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        if (currentItem != null) {
          if (box.removeItem(currentItem)) {
            currentItem = null;
            itemNameInput.value = '';
            itemCategoryOption.value = 'attribute';
            itemTypeOption.value = 'String';
            itemInitInput.value = '';
          }
        }
      }
    });

The next item, based on the current item, may be obtained.

Code 11-12.29: Get next item button.

class ToolBar {
  ...
  ButtonElement getItemButton;
  ButtonElement getNextItemButton;

If there is a selected box and the current item of the box exists, the findNextItem method returns the next item.

Code 11-12.30: Get next item button event.

getNextItemButton = document.querySelector('#getNextItem');
    getNextItemButton.onClick.listen((MouseEvent e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        if (currentItem != null) {
          Item nextItem = box.findNextItem(currentItem);
          if (nextItem != null) {
            currentItem = nextItem;
            itemNameInput.value = nextItem.name;
            itemCategoryOption.value = nextItem.category;
            itemTypeOption.value = nextItem.type;
            itemInitInput.value = nextItem.init;
            itemNameInput.select();
          } else {
            currentItem = null;
            itemNameInput.value = '';
            itemCategoryOption.value = 'attribute';
            itemTypeOption.value = 'String';
            itemInitInput.value = '';
          }
        }
      }
    });

The next item button is placed after the get item button.

Code 11-12.31: Get next item button element.

<button class="button" id="getItem">Get</button>
        <button class="button" id="getNextItem">Get Next</button>

In the Box class, the findNextMethod accepts the current item and finds the next item, if it exists.

Code 11-12.32: Find next item.

Item findNextItem(Item currentItem) {
    sortItemsBySequence();
    for (Item item in items) {
      if (item == currentItem) {
        int ix = items.indexOf(item, 0);
        if (ix < items.length - 1) {
          return items[ix + 1];
        } 
      }
    }
    return null;
  }

In this step, the type options are updated.

Code 11-12.33: Item type options.

<select id="itemType">
          <option>String</option>
          <option>num</option>
          <option>int</option>
          <option>double</option>
          <option>bool</option>
          <option>Date</option>
          <option>Email</option>
          <option>Url</option>
          <option>Dynamic</option>
          <option>Other</option>
        </select>

If there is no current item, the next item will become the first item.

Code 11-12.34: Get first item.

getNextItemButton = document.querySelector('#getNextItem');
    getNextItemButton.onClick.listen((MouseEvent e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        if (currentItem != null) {
          Item nextItem = box.findNextItem(currentItem);
          if (nextItem != null) {
            currentItem = nextItem;
            itemNameInput.value = nextItem.name;
            itemCategoryOption.value = nextItem.category;
            itemTypeOption.value = nextItem.type;
            itemInitInput.value = nextItem.init;
            itemNameInput.select();
          } else {
            currentItem = null;
            itemNameInput.value = '';
            itemCategoryOption.value = 'attribute';
            itemTypeOption.value = 'String';
            itemInitInput.value = '';
          }
        } else {
          if (!box.items.isEmpty) {
            Item firstItem = box.findFirstItem();
            currentItem = firstItem;
            itemNameInput.value = firstItem.name;
            itemCategoryOption.value = firstItem.category;
            itemTypeOption.value = firstItem.type;
            itemInitInput.value = firstItem.init;
            itemNameInput.select();
          }
        }
      }
    });

In the Box class, the findFirstItem method finds the first item, if it exists.

Code 11-12.35: Find first item.

Item findFirstItem() {
    if (items.isEmpty()) {
      return null;
    } else {
      return items[0];
    }
  }

A model may now be closed.

Code 11-12.36: Close model button.

class MenuBar {  
  ...
  // File
  InputElement modelNameInput;
  ButtonElement openModelButton;
  ButtonElement saveModelButton;
  ButtonElement closeModelButton;

The close button is initialized in the constructor of the MenuBar class.

Code 11-12.37: Initialize the close model button.

MenuBar(this.board) {  
    modelNameInput = document.querySelector('#model-name');
    openModelButton = document.querySelector('#open-model');
    saveModelButton = document.querySelector('#save-model');
    closeModelButton = document.querySelector('#close-model');

The close model button calls the closeModel method.

Code 11-12.38: Close model button event.

closeModelButton.onClick.listen((MouseEvent e) {
      modelNameInput.value = '';
      board.closeModel();
    });

The close model button element is added to the HTML code.

Code 11-12.39: Close model button element.

<li>File
         <ul>
           <li><input type="text" id="model-name"/></li>
           <li><button id="open-model">Open Model</button></li>
           <li><button id="save-model">Save Model</button></li>
           <li><button id="close-model">Close Model</button></li>
         </ul>  
       </li>

When a model is transformed to its JSON representation, the JSON text in the JSON text area is selected, so that a copy of the text may be immediately made and then pasted in a text file. In addition, the JSON text may be cleared from the text area.

Code 11-12.40: Select and clear.

class JsonPanel {

  final Board board;

  TextAreaElement modelJsonTextArea;
  ButtonElement fromModelToJsonButton;
  ButtonElement fromJsonToModelButton;
  ButtonElement clearButton;

  JsonPanel(this.board) {
    modelJsonTextArea = document.querySelector('#modelJson');
    fromModelToJsonButton = document.querySelector('#fromModelToJson');
    fromModelToJsonButton.onClick.listen((MouseEvent e) {
      modelJsonTextArea.value =  board.toJson();
      modelJsonTextArea.select();
    });
    fromJsonToModelButton = document.querySelector('#fromJsonToModel');
    fromJsonToModelButton.onClick.listen((MouseEvent e) {
      board.fromJson(modelJsonTextArea.value);
    });
    clearButton = document.querySelector('#clearJson');
    clearButton.onClick.listen((MouseEvent e) {
      clear();
    });
  }

  void clear() {
    modelJsonTextArea.value = '';
  }

}

The clear button element is added to the HTML code.

Code 11-12.41: Clear button element.

<section> 
      <button class="button" id="fromModelToJson">To JSON</button>
      <button class="button" id="fromJsonToModel">From JSON</button>
      <button class="button" id="clearJson">Clear</button>  
      <textarea name="modelJson" rows=20 cols=120 id="modelJson"></textarea>
    </section>

The PNG image of a model may be now hidden and the shown again. The clear button hides the model image. A removal of an image might have been a better approach to clear the model image.

Code 11-12.42: Hide and show model image.

class PngPanel {

final Board board;

  ImageElement modelImage;
  ButtonElement fromModelToPngButton;
  ButtonElement clearButton;

  PngPanel(this.board) {
    modelImage = document.querySelector('#modelImage');
    fromModelToPngButton = document.querySelector('#fromModelToPng');
    fromModelToPngButton.onClick.listen((MouseEvent e) {
      modelImage.src = board.canvas.toDataUrl("image/png");
      show();
    });
    clearButton = document.querySelector('#clearImage');
    clearButton.onClick.listen((MouseEvent e) {
      hide();
    });
  }

  void hide() {
    modelImage.hidden = true;
  }

  void show() {
    modelImage.hidden = false;
  }

}

The clear button is added to the HTML code.

Code 11-12.43: Clear model image.

<section>
      <button class="button" id="fromModelToPng">To PNG</button>
      <button class="button" id="clearImage">Clear</button>
      <img alt="Your model as PNG." id="modelImage"/>
    </section>

In the Board class, the close method deletes the model and then clears the JSON panel and clears the PNG panel.

Code 11-12.44: Close model.

void closeModel() {
    delete();
    jsonPanel.clear();
    pngPanel.hide();
  }

This is the old version of the selection methods In the Box class.

Code 11-12.45: Old selection methods.

void select() {
    _selected = true;
    board.lastBoxSelected = this;
  }
  
  void deselect() {
    _selected = false;
    board.lastBoxSelected = null;
  }
  
  void toggleSelection() { 
    _selected = !_selected;
    if (_selected) {
      board.lastBoxSelected = this;
    } else {
      board.lastBoxSelected = null;
    }
  }

The new version of the toggleSelection method delegates its task to the select and deselect methods. The select method brings the selected box to the tool bar, so that a user does not need to click on the Get box button. The Get box button is removed from the tool bar.

Code 11-12.46: New selection methods.

void select() {
    _selected = true;
    board.lastBoxSelected = this;
    board.toolBar.bringSelectedBox();
  }

  void deselect() {
    _selected = false;
    board.lastBoxSelected = null;
  }

  void toggleSelection() {
    if (isSelected()) {
      deselect();
    } else {
      select();
    }
  }

In the ToolBar class, the focus event has been removed.

Code 11-12.47: Box name input focus event.

boxNameInput = document.query('#boxName');
    /*
    boxNameInput.onFocus.listen((Event e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        boxNameInput.value = box.title;
        boxEntryCheckbox.checked = box.entry;
        currentItem = null;
        itemNameInput.value = '';
        itemCategoryOption.value = 'attribute';
        itemTypeOption.value = 'String';
        itemInitInput.value = '';
      }
    });
    */

The bringSelectedBox method is added to the ToolBar class.

Code 11-12.48: Bring selected box.

void bringSelectedBox() {
    Box box = board.lastBoxSelected;
    if (box != null) {
      boxNameInput.value = box.title;
      boxEntryCheckbox.checked = box.entry;
    }
  }

Similarly, in the Line class, the new version of the toggleSelection method delegates its task to the select and deselect methods. The select method brings the selected line to the tool bar, so that a user does not need to click on the Get line button. The Get line button is removed from the tool bar.

Code 11-12.49: Line selection methods.

void select() {
    _selected = true;
    board.lastLineSelected = this;
    board.toolBar.bringSelectedLine();
  }

  void deselect() {
    _selected = false;
    board.lastLineSelected = null;
  }

  void toggleSelection() {
    if (isSelected()) {
      deselect();
    } else {
      select();
    }
  }

The bringSelectedLine method is added to the ToolBar class.

Code 11-12.50: Bring selected line.

void bringSelectedLine() {
    Line line = board.lastLineSelected;
    if (line != null) {
      lineOption.value = line.category;
      lineInternalCheckbox.checked = line.internal;

      line12Box1Label.text = line.box1.title;
      line12Box2Label.text = line.box2.title;
      line12MinInput.value = line.box1box2Min;
      line12MaxInput.value = line.box1box2Max;
      line12IdCheckbox.checked = line.box1box2Id;
      line12NameInput.value = line.box1box2Name;

      line21Box2Label.text = line.box2.title;
      line21Box1Label.text = line.box1.title;
      line21MinInput.value = line.box2box1Min;
      line21MaxInput.value = line.box2box1Max;
      line21IdCheckbox.checked = line.box2box1Id;
      line21NameInput.value = line.box2box1Name;
    }
  }

There is no more Set item button in the tool bar.

Code 11-12.51: No more Set item button.

class ToolBar {
  ...
  InputElement itemNameInput;
  SelectElement itemCategoryOption;
  SelectElement itemTypeOption;
  InputElement itemInitInput;
  ButtonElement addItemButton;
  ButtonElement getPreviousItemButton;
  ButtonElement getItemButton;
  ButtonElement getNextItemButton;
  ButtonElement moveUpItemButton;
  ButtonElement moveDownItemButton;
  ButtonElement removeItemButton;

An item name of the current item may be updated, without using the Set item button.

Code 11-12.52: Item name input event.

itemNameInput = document.querySelector('#itemName');
    itemNameInput.onInput.listen((Event e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        if (currentItem != null) {
          String itemName = itemNameInput.value.trim();
          if (itemName != '') {
            Item otherItem = box.findItem(itemName);
            if (otherItem == null) {
              currentItem.name = itemName;
            }
          }
          itemNameInput.select();
        }
      }
    });

The item category of the current item may be changed. The commented code is actually removed.

Code 11-12.53: Item category option event.

itemCategoryOption = document.querySelector('#itemCategory');
    itemCategoryOption.onChange.listen((Event e) {
      if (currentItem != null) {
        //currentItem.name = itemNameInput.value;
        currentItem.category = itemCategoryOption.value;
        itemNameInput.select();
      }
    });

The item type of the current item may be changed. The commented code is actually removed.

Code 11-12.54: Item type option event.

itemTypeOption = document.querySelector('#itemType');
    itemTypeOption.onChange.listen((Event e) {
      if (currentItem != null) {
        //currentItem.name = itemNameInput.value;
        currentItem.type = itemTypeOption.value;
        itemNameInput.select();
      }
      itemInitInput.value = '';
    });

There is a new input event for the item init input.

Code 11-12.55: Item init input event.

itemInitInput = document.querySelector('#itemInit');
    itemInitInput.onInput.listen((Event e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        if (currentItem != null) {
          currentItem.init = itemInitInput.value.trim();
          itemNameInput.select();
        }
      }
    });

There is no more Set line button in the tool bar.

Code 11-12.56: No more Set line button.

class ToolBar {
  ...
  SelectElement lineOption;
  InputElement lineInternalCheckbox;
  //ButtonElement setLineButton;

The following code snippets (Code 11-12.57 – Code 11-12.61) show updated line events to accommodate the absence of the Set button. Only the code for the 12 line direction is shown. The code for the 21 line direction is similar.

Code 11-12.57: Line category option event.

lineOption = document.querySelector('#lineCategory');
    lineOption.onChange.listen((Event e) {
      Line line = board.lastLineSelected;
      if (line != null) {
        line.category = lineOption.value;
      }
    });

Code 11-12.58: Line 12 min input event.

line12MinInput = document.querySelector('#line12Min');
    line12MinInput.onInput.listen((Event e) {
      Line line = board.lastLineSelected;
      if (line != null) {
        line.box1box2Min = line12MinInput.value.trim();
      }
    });

Code 11-12.59: Line 12 max input event.

line12MaxInput = document.querySelector('#line12Max');
    line12MaxInput.onInput.listen((Event e) {
      Line line = board.lastLineSelected;
      if (line != null) {
        line.box1box2Max = line12MaxInput.value.trim();
      }
    });

Code 11-12.60: Line 12 id checkbox event.

line12IdCheckbox = document.querySelector('#line12Id');
    line12IdCheckbox.onChange.listen((Event e) {
      Line line = board.lastLineSelected;
      if (line != null) {
        if (line.box1box2Min == '1' && line.box1box2Max == '1') {
          line.box1box2Id = line12IdCheckbox.checked;
        } else {
          line12IdCheckbox.checked = false;
          line.box1box2Id = false;
        }
      }
    });

Code 11-12.61: Line 12 name input event.

line12NameInput = document.querySelector('#line12Name');
    line12NameInput.onInput.listen((Event e) {
      Line line = board.lastLineSelected;
      if (line != null) {
        line.box1box2Name = line12NameInput.value.trim();
      }
    });

In the Board class, there is a new method to count boxes that contain a certain point.

Code 11-12.62: Count boxes that contain a point.

int countBoxesContain(int pointX, int pointY) {
    int count = 0;
    for (Box box in boxes) {
      if (box.contains(pointX, pointY)) {
        count++;
      }
    }
    return count;
  }

In the Box class, the countBoxesContain method is used in the new version of the onMouseMove method. The commented code shows the old version where only the selected box was moved.

Code 11-12.63: Move a box.

void onMouseMove(MouseEvent e) {
    //if (contains(e.offset.x, e.offset.y) && isSelected() && _mouseDown && 
    //    board.countSelectedBoxesContain(e.offset.x, e.offset.y) < 2) {
    if (contains(e.offset.x, e.offset.y) && _mouseDown && 
        board.countBoxesContain(e.offset.x, e.offset.y) < 2) {
      x =  e.offset.x - width / 2;
      if (x < 0) {
        x = 1;
      }
      if (x > board.width - width) {
        x = board.width - width - 1;
      }
      y = e.offset.y - height / 2;
      if (y < 0) {
        y = 1;
      }
      if (y > board.height - height) {
        y = board.height - height - 1;
      }
    }
  }

Based on the current item, the previous item may be selected.

Code 11-12.64: Get previous item button.

class ToolBar {
  ...
  ButtonElement getPreviousItemButton;
  ButtonElement getItemButton;
  ButtonElement getNextItemButton;
  ButtonElement moveUpItemButton;
  ButtonElement moveDownItemButton;

If there is no current item, the previous item will become the last item.

Code 11-12.65: Get previous item button event.

getPreviousItemButton = document.querySelector('#getPreviousItem');
    getPreviousItemButton.onClick.listen((MouseEvent e) {
      Box box = board.lastBoxSelected;
      if (box != null) {
        if (currentItem != null) {
          Item previousItem = box.findPreviousItem(currentItem);
          if (previousItem != null) {
            currentItem = previousItem;
            itemNameInput.value = previousItem.name;
            itemCategoryOption.value = previousItem.category;
            itemTypeOption.value = previousItem.type;
            itemInitInput.value = previousItem.init;
            itemNameInput.select();
          } else {
            currentItem = null;
            itemNameInput.value = '';
            itemCategoryOption.value = 'attribute';
            itemTypeOption.value = 'String';
            itemInitInput.value = '';
          }
        } else {
          if (!box.items.isEmpty) {
            Item lastItem = box.findLastItem();
            currentItem = lastItem;
            itemNameInput.value = lastItem.name;
            itemCategoryOption.value = lastItem.category;
            itemTypeOption.value = lastItem.type;
            itemInitInput.value = lastItem.init;
            itemNameInput.select();
          }
        }
      }
    });

The findLastItem method is added to the Box class.

Code 11-12.66: Find last item.

Item findLastItem() {
    if (items.isEmpty()) {
      return null;
    } else {
      return items.last();
    }
  }

There are new names for some of the item buttons. The current item is obtained by the Get 1 button, based on the item name in the item input field. The next item is selected by clicking on the Get +1 button. The previous item is obtained by the Get -1 button. The current item is moved one position down or up by the Move +1 or Move -1 buttons.

Code 11-12.67: New names for item button elements.

<button class="button" id="addItem">Add</button>
        <button class="button" id="getPreviousItem">Get -1</button>
        <button class="button" id="getItem">Get 1</button>
        <button class="button" id="getNextItem">Get +1</button>
        <button class="button" id="moveUpItem">Move -1</button>
        <button class="button" id="moveDownItem">Move +1</button>
        <button class="button" id="removeItem">Remove</button>

An external line gets a softer gray color, so that a visual difference between internal and external lines is more obvious.

Code 11-12.68: Softer gray color for external lines.

class Board {
  ...
  static const int DEFAULT_LINE_WIDTH = 1;
  static const String DEFAULT_LINE_COLOR = '#000000'; // black
  static const String SOFT_LINE_COLOR = '#999493'; // gray; old: 736f6e

In the Board class, the onMouseDown method allows now a creation of maximum of two lines between the same two boxes.

Code 11-12.69: Maximum two lines between the same two boxes.

void onMouseDown(MouseEvent e) {
    ...
    if (!clickedOnBox) {
      if (toolBar.isSelectToolOn()) {
        ...
      } else if (toolBar.isBoxToolOn()) {
        ...
      } else if (toolBar.isLineToolOn()) {
        // Create a line between the last two clicked boxes.
        if (beforeLastBoxClicked != null && lastBoxClicked != null &&
            _boxExists(beforeLastBoxClicked) && _boxExists(lastBoxClicked) &&
            countLinesBetween(beforeLastBoxClicked, lastBoxClicked) < 2) {
            Line line = new Line(this, beforeLastBoxClicked, lastBoxClicked);
            lines.add(line);
          }
      }
      toolBar.backToFixedTool();
    }
  }

It is the countLinesBetween method that counts the number of lines between the two given boxes.

Code 11-12.70: Count lines between the same two boxes.

int countLinesBetween(Box box1, Box box2) {
    int count = 0;
    for (Line line in lines) {
      if ((line.box1 == box1 && line.box2 == box2) || 
          (line.box1 == box2 && line.box2 == box1)) {
        count++;
      }
    }
    return count;
  }

In the constructor of the Board class, when two empty lists are constructed, the type of the List element is specified.

Code 11-12.71: List element Box type and list element Line type.

Board(this.canvas) {
    ...
    boxes = new List<Box>();
    lines = new List<Line>();

Similar, in the constructor of the Box class, the type of the List element is specified.

Code 11-12.72: List element Item type.

Box(this.board, this.x, this.y, this.width, this.height) {
    items = new List<Item>();
blog comments powered by Disqus