markdown-note/markdown-notes

View on GitHub
src/browser/notes/note-client.js

Summary

Maintainability
F
3 days
Test Coverage
/*****************************************************************
 * Acts like a controller and contains code to handle notes in the
 * application. It calls and coordinates the activities of other
 * note-related modules such as the notes, note editor and events.
 *
 * @author : Abijeet Patro
 ****************************************************************/

'use strict';
var _i18n = require('i18n');
var _marked = require('mark-it-down');

var _appConfig = require(__dirname + '/../../../config.js');
var _notes = require(_appConfig.browserSrcPath + 'notes/note.js');
var _noteEvents = require(_appConfig.browserSrcPath + 'notes/note-events.js');
var _noteEditor = require(_appConfig.browserSrcPath + 'notes/note-editor.js');
var _appError = require(_appConfig.commonsPath + 'app-error.js');
var _appUtil = require(_appConfig.commonsPath + 'utility.js');
var _ee = _appConfig.getEventEmitter();

var NoteClient = function() {
  var currentlyFocusedNote = null;

  function _init() {
    // Initialize note events with callback events.
    _noteEvents.init({
      saveNote: saveNote,
      markNoteAsComplete: markNoteAsComplete,
      saveAndCreateNote: saveAndCreateNote,
      deleteNote: deleteNote,
      makeNoteEditable: makeNoteEditable,
      displayNoteDateDlg: displayNoteDateDlg,
      modifyNoteDate: modifyNoteDate
    });
  }

  /**
   * Used to generate the note elements inside a notebook. Whenever the user
   * clicks on a notebook to be displayed this function will be called to
   * generate the notes. It also adds events to the notes generated.
   * @param  {Array} notes              The notes present inside the notebook
   * @param  {String} notebookDbID      The ID of the notebook
   * @param  {Object} notebookContainer The notebook container HTML element
   * @return {undefined}                No return type.
   */
  var buildNotes = function(notes, notebookDbID, notebookContainer, isEditable) {
    if (!notebookContainer) {
      notebookContainer =
        document.getElementById(_appConfig.getNotebookContentID(notebookDbID));
    }

    if (isEditable === undefined) {
      isEditable = true;
    }

    var notebooksContainer = notebookContainer.querySelector('.notes-container');
    if (!notebooksContainer) {
      throw new Error(_i18n.__('error.notebook_container_not_found'));
    }
    if (notes.length === 0) {
      return;
    }
    for (var i = 0, len = notes.length; i !== len; ++i) {
      appendNoteElement(notebookDbID, notes[i], notebooksContainer, isEditable);
    }
  };

  /**
   * Adds a new note element and calls addNoteEvents to add the events
   * to the new note element.
   * @param  {String} notebookDbID      ID of the notebook to which the note is
   * being added
   * @param  {Object} notebookContainer HTML element containing the notes.
   * @return {undefined}                No return type.
   */
  var addNewNote = function(notebookDbID, notebookContainer) {
    if (!notebookDbID) {
      throw new ReferenceError('Notebook ID not provided!');
    }
    if (!notebookContainer) {
      var notebookID = _appConfig.getNotebookContentID(notebookDbID);
      notebookContainer = document.getElementById(notebookID);
    }

    var notesContainer = notebookContainer.querySelector('.notes-container');

    // Create the note
    var currNote = appendNoteElement(notebookDbID, null, notesContainer);

    makeNoteEditable(currNote);
  };

  var removeNotesFromNotebook = function(notebookDbID) {
    if (!notebookDbID) {
      throw new ReferenceError('Notebook ID not provided!');
    }
    var notebookID = _appConfig.getNotebookContentID(notebookDbID);
    var notebookContainer = document.getElementById(notebookID);
    _noteEvents.removeAllEvents(notebookContainer);
    var notesContainer = notebookContainer.querySelector('.notes-container');
    while (notesContainer.firstChild) {
      notesContainer.removeChild(notesContainer.firstChild);
    }
  };

  /**
   * Saves and creates a note. This is called when the user presses
   * Shift + Enter. Calls `saveNote` and the calls `addNewNote`
   * @param  {Object} note The note object
   * @return {undefined}
   */
  function saveAndCreateNote(note) {
    var notebookID = note.dataset.notebookid;
    try {
      if (_noteEditor.isEditable(note)) {
        saveNote(note, true);
      }
      addNewNote(notebookID);
    } catch (e) {
      var errObj = new _appError(e, _i18n.__('error.save_and_create_note'));
      errObj.display();
    }
  }

  /**
   * Calls the method to update or create a note, based on the note element.
   * @param  {Object}  note   HTML note element that needs to be saved.
   * @param  {Boolean} isBlur Is this triggered as part of blur event.
   * @return {undefined}      No return type.
   */
  function saveNote(note, isBlur, isNoteComplete) {
    var noteText = note.innerText;
    if (noteText) {
      try {
        var noteID = note.dataset.noteid;
        var notebookID = note.dataset.notebookid;
        var noteObj = createNoteObjFromElement(note, isBlur, isNoteComplete);
        if (noteID && notebookID) {
          // Update
          _notes.modifyNote(noteObj, false, cbModifiedNote);
        } else if (notebookID) {
          // Insert
          _notes.modifyNote(noteObj, true, function(err, noteObj) {
            if (noteObj) {
              noteObj.noteElem.dataset.noteid = noteObj._id;
            }
            cbModifiedNote(err, noteObj);
          });
        } else {
          throw new Error(_i18n.__('error.savenote_invalid_call'));
        }
        if (isBlur) {
          // If its' a blur event, save the note and remove the editable property
          // In addition also remove the unnecessary blur event.
          _noteEditor.turnOffEditing(note);
          _noteEvents.addNonEditableEvents(note);
        }
      } catch (e) {
        var errObj = new _appError(e, _i18n.__('error.notes_modification_err'));
        errObj.display();
      }
    }
    note = null;
  }

  /**
   * Calls the method to delete a note, based on the note element.
   * @param  {Object} note HTML note element
   * @return {undefined}      No return type.
   */
  function deleteNote(note) {
    try {
      var respConfirm = window.confirm(_i18n.__('note.deletion_confirmation_text'),
        _i18n.__('note.deletion_confirmation_title'));
      if (!respConfirm) {
        return;
      }
      var noteID = note.dataset.noteid;
      if (noteID) {
        _notes.deleteNote(noteID, function(err) {
          if (err) {
            var errObj = new _appError(err, _i18n.__('error.notes_deletion_err'));
            errObj.display();
            return;
          }
          // Remove the events
          _noteEvents.removeAllEvents(note);

          // Remove the note from DOM.
          _noteEditor.removeNote(note);
          _ee.trigger('note.deleted', [note.dataset.notebookid]);
        });
      } else {
        // Remove the note from DOM.
        _noteEditor.removeNote(note);
        _ee.trigger('note.deleted', [note.dataset.notebookid]);
      }
    } catch (e) {
      var appErrObj = new _appError(e, _i18n.__('error.notes_deletion_err'));
      appErrObj.display();
    }
  }

  /**
   * Adds the necessary class depending on whether the note is complete or
   * incomplete. It then calls the `saveNote` method to save the note.
   * @param  {Object} note HTML note element
   * @return {undefined}      No return type.
   */
  function markNoteAsComplete(note) {
    var isComplete = _noteEditor.isComplete(note);
    if (!_noteEditor.markAsComplete(note)) {
      return;
    }
    // It's not editable, we don't want to save the whole thing,
    // just update the isComplete flag.
    _notes.updateCompletion(note.dataset.noteid, !isComplete, function(err) {
      if (err) {
        err.display();
      }
      _noteEditor.toggleMove(note);
      _ee.trigger('note.saved', [note.dataset.notebookid]);
    });

  }

  function makeNoteEditable(note) {
    if (_noteEditor.isEditable(note)) {
      return;
    }
    _noteEvents.addEditableEvents(note);
    currentlyFocusedNote = note;
    // Completed notes are not editable.
    if (_noteEditor.isComplete(currentlyFocusedNote)) {
      return;
    }
    if (!currentlyFocusedNote.dataset.noteid) {
      // Note has not been saved before, no need to fetch the data.
      _noteEditor.turnOnEditing(currentlyFocusedNote);
      return;
    }
    // Fetch the content of the note.
    _notes.getNoteByID(currentlyFocusedNote.dataset.noteid,
      function(err, noteObj) {
        if (err) {
          return;
        }
        if (currentlyFocusedNote.dataset.noteid === noteObj._id) {
          // the note is still selected.
          currentlyFocusedNote.innerHTML = "";
          currentlyFocusedNote.innerText = noteObj.text;
          _noteEditor.turnOnEditing(currentlyFocusedNote);
        }
      });
  }

  /**
   * Creates a note object from the given note element.
   * This note object can then be stored in the database.
   * @param  {object}  note   Note HTML Element
   * @param  {Boolean} isBlur Whether this function was called on blur of note
   * element.
   * @return {object}         Note object to be stored in the database
   */
  function createNoteObjFromElement(note, isBlur, isNoteComplete) {
    var noteObj = {
      text: note.innerText,
      notebookID: note.dataset.notebookid,
      noteElem: note,
      isBlur: isBlur
    };

    if (note.dataset.noteid) {
      noteObj._id = note.dataset.noteid;
    }

    if (_noteEditor.isComplete(note)) {
      noteObj.isComplete = true;
    } else {
      noteObj.isComplete = false;
    }

    if (isNoteComplete === true) {
      noteObj.completedOn = new Date();
    } else {
      noteObj.completedOn = null;
    }

    var notebookElemID = _appConfig.getNotebookContentID(note.dataset.notebookid);
    var notebookDate = jQuery('#' + notebookElemID).find('.notebook-date').datepicker('getDate');

    // We need to store this to filter future date!
    noteObj.targetDate = notebookDate;

    return noteObj;
  }

  /**
   * Creates and appends a note element to the notebook container and
   * calls `addNoteEvents`.
   * defaults.
   * @param  {String} notebookDbID       Notebook ID
   * @param  {[type]} note               The note object from the database
   * @param  {Object} notebooksContainer HTML notes container element
   * @return {Object} HTML note element that was added.
   */
  function appendNoteElement(notebookDbID, noteObj, notesContainer, isEditable) {
    // Create the note
    var note = document.createElement('div');
    note.setAttribute('class', 'note-container');

    if (isEditable === undefined) {
      isEditable = true;
    }

    // Create the inner elements.
    note.innerHTML = _noteEditor.getNoteHTML(notebookDbID, noteObj, isEditable);

    // Add it to the notes container
    notesContainer.insertBefore(note, notesContainer.firstChild);

    // Return the newly added note
    var currNote = note.querySelector('.note');

    // Keyup event - Perform action according to the
    // key's pressed.
    if (isEditable) {
      _noteEvents.addEvents(currNote);
    } else {
      // Not editable, but still allowed to move.
      _noteEvents.addNonEditableEvents(currNote);
    }

    return currNote;
  }

  // START of CALLBACKS
  /**
   * Callback triggered after a note is saved in the database.
   * @param  {AppError} err   App Error object or null if no error
   * @param  {Object} noteObj The note object that is inserted or modified.
   * @return {undefined}      No return type.
   */
  function cbModifiedNote(err, noteObj) {
    if (err) {
      var errObj = new _appError(err, _i18n.__('error.notes_modification_err'));
      errObj.display();
      return;
    }
    checkIfBlur(noteObj);
    _ee.trigger('note.saved', [noteObj.notebookID]);
    noteObj = null;
  }
  // END of CALLBACKS

  /**
   * Triggered after a note is saved in the database, checks if it was a blur
   * event, if so converts the text inside the note to HTML using **marked**.
   * @param  {Object} noteObj The note object that is inserted or modified
   * @return {undefined}      No return type.
   */
  function checkIfBlur(noteObj) {
    if (noteObj.isBlur && noteObj.noteElem) {
      var noteHtml = _marked(noteObj.text);
      noteObj.noteElem.innerHTML = noteHtml;
      _noteEditor.turnOffEditing(noteObj.noteElem);
    }
  }

  function displayNoteDateDlg(note) {
    // Show the dialog box to handle the note update.
    _appUtil.loadDialog('change-note-date.html', {
      note: note
    }, function(err, html) {
      if (!_appUtil.checkAndInsertDialog(err, html, _i18n.__('error.note_dlg_change_open'))) {
        return;
      }
      var $dlg = jQuery('#dlgMoveNote_88');
      // Set the value in the hidden field
      var noteID = note.dataset.noteid;
      if (!noteID) {
        // TODO User tried to move an unsaved note.
        return;
      }
      $dlg.find('#hdnNoteID_88').val(noteID);

      let isComplete = _noteEditor.isComplete(note);
      _noteEvents.evtNoteDateChangeOpen($dlg[0], isComplete);

      $dlg.on('shown.bs.modal', function() {
        // Focus the textbox on dialog open
        this.querySelector('#txtTargetDate_88').focus();
      });

      $dlg.modal('show');
      _appUtil.addCloseEvent($dlg, function() {
        _noteEvents.evtNoteDateChangeClose($dlg[0]);
        $dlg = null;
      });
    });
  }

  function modifyNoteDate(noteID, newDate, isComplete, $dlg) {
    _notes.changeDate(noteID, newDate, isComplete, function(err) {
      if (err) {
        var errObj = new _appError(err, _i18n.__('error.notes_modification_err'));
        errObj.display();
      } else {
        var note = _noteEditor.getNoteByID(noteID);
        if (!note) {
          return;
        }
        // Remove the events
        _noteEvents.removeAllEvents(note);
        $dlg.modal('hide');
        // Remove the note from DOM.
        _noteEditor.removeNote(note);
        _ee.trigger('note.saved', [note.dataset.notebookid]);
      }
    });
  }

  return {
    buildNotes: buildNotes,
    addNewNote: addNewNote,
    removeNotesFromNotebook: removeNotesFromNotebook,
    init: _init
  };
};

module.exports = new NoteClient();