/*
 * Base daily goal item.
 */
class DailyGoalNode {

  constructor() {
    this.children = [];
    this.wrapper = $(`<div class='dg-lists'></div>`);
  }

  addChild(dailyGoalNode) {
    // Bind child logically.
    this.children.push(dailyGoalNode);
    dailyGoalNode.parent = this;

    // Update DOM to show child.
    this.wrapper.append(dailyGoalNode.wrapper);
  }
}

/*
 * Real/useful item in daily goal list.
 *
 * It could be nested or contains many levels of children.
 */
class DailyGoalRealNode extends DailyGoalNode {

  // MAX_WIDTH of Bootstrap is 12 columns.
  get MAX_WIDTH() {
   return 12;
  }

  // Maximum level.
  get MAX_LEVEL() {
    return 4;
  }

  /*
   * level: control indentation
   * value: text content in this item
   */
  constructor(level, value) {
    super();

    this.level = level;
    this.value = value || '';
    this.placeholder = 'Add a daily goal...';

    // HTML elements.
    this.insideWrapper = $(`<div class='row my-1 dg-keep'></div>`);
    this.input = $(`<input class='form-control mb-2 col-${this.getWidthByLevel(level) - 1} offset-${this.getOffsetByLevel(level)} border-0' type='text' placeholder='${this.placeholder}' value='${this.value}'>`);
    // this.done = $(`<span class="glyphicon glyphicon-ok-sign col-1 offset-${this.getOffsetByLevel(level) + this.getWidthByLevel(level) - 1}" aria-hidden="true"></span>`);
    this.saveButton = $(`<button type='button' class='dg-btn-save btn btn-success col-2 offset-${this.getOffsetByLevel(level)} mr-2'>Save</button>`);
    this.sublistButton = $(`<button type='button' class='dg-btn-sub-list btn btn-info col-2 mr-2'>Add sub-list</button>`);
    this.cancelButton = $(`<button type='button' class='dg-btn-cancel btn btn-warning col-2 mr-2'>Cancel</button>`);
    this.deleteButton = $(`<button type='button' class='dg-btn-delete btn btn-danger col-2'>Delete</button>`);

    // Put HTML elements together.
    this.insideWrapper.append(this.input, this.saveButton);
    // No sublist at last level.
    if (level < this.MAX_LEVEL - 1) {
       this.insideWrapper.append(this.sublistButton);
    }
    this.insideWrapper.append(this.cancelButton, this.deleteButton);
    this.wrapper.append(this.insideWrapper);

    // Register actions.

    // Focus on this daily goal item.
    this.input.focus(this.focus.bind(this));
    // Unfocus on current item.
    this.input.blur(this.blur.bind(this));
    // Click save button. Save text value to daily goal image node.
    this.saveButton.click(this.save.bind(this));
    // Click sublist button. Create another daily goal item, and attach to itself.
    this.sublistButton.click(this.sublist.bind(this));
    // Cancel edit.
    this.cancelButton.click(this.cancel.bind(this));
    // Delete this item.
    this.deleteButton.click(this.delete.bind(this));

  }

  getWidthByLevel(level) {
    return this.MAX_WIDTH - level;
  };

  getOffsetByLevel(level) {
    return level;
  };

  // Add focus class tag, so that button could appear.
  focus() {
    this.insideWrapper.addClass('dg-focus');
    this.input.removeClass('border-0');
  }

  // Remove focus class tag, so that button would disappear.
  unfocus() {
    this.insideWrapper.removeClass('dg-focus');
  }

  // Save current value and remove focus class tag.
  save() {
    // Remove focus class tag.
    this.unfocus();

    // Empty item.
    const isEmpty = !this.input.val();

    // If current modifying item is the last one.
    const parent = this.parent;
    const index = parent.children.indexOf(this);
    const isLast = parent.children.length === index + 1;

    if (!isEmpty && isLast) {
      // Last but not empty, then append new node.
      this.insideWrapper.removeClass('dg-keep');
      const next = new DailyGoalRealNode(this.level);
      parent.addChild(next);
    } else if (isEmpty && !isLast) {
      // Is empty, but not last, keep it and set to default value.
      this.input.val('Empty');
    } else if (isEmpty && isLast) {
      // Is empty, and is last one, remove border.
      this.input.addClass('border-0');
      if (parent.children.length === 1 && this.level !== 0) {
        // Is empty, is last, and not at top level, then remove it.
        this.removeSelf();
      }
    }
  }

  // If click/focus on any other element besides current inside wrapper,
  // then save current element.
  blur() {
    // When blur, automatically save, except just clicked a button.
    if(this.insideWrapper.is(':active')) {
      return;
    }
    this.save();
  }

  // Save current element and append sublist.
  sublist() {
    if (!this.input.val()) {
      this.input.val('Empty');
    }
    this.save();

    const child = new DailyGoalRealNode(this.level + 1);
    this.addChild(child);
    child.input.focus();

  }

  // TODO Not really implemented.
  cancel() {
    this.unfocus();
  }

  // Remove this item.
  delete() {
    this.removeSelf();

    // If only remaining element is a dg-keep element, then remove it.
    const parent = this.parent;
    if (parent.children.length === 1
      && parent.children[0].insideWrapper.hasClass('dg-keep')
      && this.level !== 0) {
      parent.children[0].wrapper.remove();
    }
  }

  removeSelf() {
    const parent = this.parent;

    // Remove itself from parent's children list.
    const index = parent.children.indexOf(this);
    parent.children.splice(index, 1);

    // Update DOM to remove child.
    this.wrapper.remove();
  }
}

/**
 * Render daily goal list.
 */
const init = function init() {
  // Outmost wrapper for all daily goal nodes. The root itself has no
  // text value.
  const root = new DailyGoalNode();

  // First/default daily goal node to be appended, so that user could
  // start editing.
  const first = new DailyGoalRealNode(0);

  // Render view.
  root.addChild(first);
  $('.container').append(root.wrapper);
};

$(document).ready(init);

/*
// Localforage module is local simple database.
const localforage = require('localforage');

const loadDailyGoalListFromDatabase = function loadDailyGoalListFromDatabase(key) {
  // Load daily goal data from database, then render to front end.
  localforage.getItem(key).then((value) => {
    // If the value is empty or null, then leave it as empty list.
    if (!value) {
      dailyGoalData = [];
    } else {
      dailyGoalData = value;
    }

    loadDailyGoalList();
  }).catch((err) => {
    console.log(err);
  });
};
*/

@@ -1619,137 +1619,15 @@
 -2'%3E
-Add sub-list%3C/button%3E%60);%0A    this.cancelButton = $(%60%3Cbutton type='button' class='dg-btn-cancel btn btn-warning col-2 mr-2'%3ECancel
+Subtask
 %3C/bu
@@ -2007,27 +2007,8 @@
 end(
-this.cancelButton, 
 this
@@ -2453,16 +2453,16 @@
 itself.%0A
+
     this
@@ -2512,81 +2512,8 @@
 ));%0A
-    // Cancel edit.%0A    this.cancelButton.click(this.cancel.bind(this));%0A
     
@@ -4541,80 +4541,8 @@
  %7D%0A%0A
-  // TODO Not really implemented.%0A  cancel() %7B%0A    this.unfocus();%0A  %7D%0A%0A
   //

Remove cancel buttons.