Reference Source

src/components/models/home-model.js

import Model from './model';
import Database from '../../util/database';
import TimeUtil from '../../util/timeutility';

/**
 * Class for the home/notification model to be used by the Home Presenter
 * @extends Model 
 */
class HomeModel extends Model {
	/**
	 * Creates an instance of HomeModel. Initializes , creates an observerlist,
	 * and registers an on read from the database.
	 *
	 * @constructor
	 */
	constructor() {
		super();
		
		this.listener = null;
		this._data = {data: []};
		this._activeBookmarks = [];

		this._callback = this._defaultCallback;
		this._createObserverList();
		this._registerDBReadListener();
	}

	/**
	 * Get method for presenters to get data.
	 *
	 * @return {Object} data stored in the model
	 */
	get() {
		return {...this._data} // immutable
	}

	/**
	 * Default callback
	 */
	_defaultCallback(message) {
		console.log(message);
	}

	/**
	 * Set the model's callback to a new callback. This callback can be used anywhere and is usually passed in from a presenter.
	 *
	 * @param {Function} callback - A callback to run when certain code is executed
	 */
	setCallback(callback) {
		this._callback = callback;
	}

	/**
	 * Register an 'on' read from the database, supplying the callback when the database has changed.
	 */
	_registerDBReadListener() {
		this.listener = Database.readBikeDataOn((snapshot) => {
			// console.log(snapshot.val());
			this._insertDataOnRead(snapshot.val());
			this.moveBookmarkedDataToFront();
		});
	}

	/**
	 * Toggle the database listener off and then on again to get the data again.
	 * TODO : Better method to do this?
	 */
	toggleListeners() {
		if (this.listener != null) {
			Database.readBikeDataOff(this.listener);
			this._registerDBReadListener();
		}
	}

	/**
	 * Insert data into the data object when data has changed from the database
	 *
	 * @param {Object} databaseData - Each data item is an object within the overall object
	 */
	_insertDataOnRead(databaseData) {
		let tempData = {data:[]};
		let dataID = 0;
		
		if (databaseData != null) { // Check if there are objects in the database
			for (let val in databaseData) {
				if (!databaseData[val].hasOwnProperty('id')) { // Make sure id exists, otherwise skip
					continue;
				}
				// if (!databaseData[val].hasOwnProperty('stolen') || !databaseData[val].stolen) {
				// 	continue;
				// }

				if (databaseData[val].hasOwnProperty('stolen') && databaseData[val].stolen) {
					databaseData[val].dataID = dataID++;
					// Add timeago and datetime formatted info
					databaseData[val].timeago = TimeUtil.getTimeAgoFromMilliseconds(databaseData[val].milliseconds);
					databaseData[val].datetime = TimeUtil.getDateFormatFromDateTime(databaseData[val].milliseconds);
					tempData.data.push(databaseData[val]);
				}
			}
			this._data = tempData;
			// console.log(this._data)
		}
	}

	/**
	 * Recalculates the 'timeago' property of the object, based on the milliseconds.
	 */
	recalculateTimeAgo() {
		let tempData = Object.assign(this._data.data);
		let dataID = 0;
		for (let i=0; i < tempData.length; i++) {
			tempData[i].dataID = dataID++;
			// Convert back to timeago from milliseconds
			tempData[i].timeago = TimeUtil.getTimeAgoFromMilliseconds(tempData[i].milliseconds);
		}
		this._data.data = Object.assign(tempData);
	}


	/**
	 * Moves the bookmarked data to the front of the list.
	 */
	moveBookmarkedDataToFront() {
		if (typeof this._data !== "undefined" && this._data != undefined) {
			const temp = this._data.data;

			const nonBookmarkedData = this.getBookmarkedData(temp, false);
			const bookmarkedData	= this.getBookmarkedData(temp, true);

			// Reverse the lists because we want latest time first
			const sortedBookmarkedData 		= TimeUtil.sortOnTime(bookmarkedData).reverse();
			const sortedNonBookmarkedData 	= TimeUtil.sortOnTime(nonBookmarkedData).reverse();
		
			// console.log(sortedNonBookmarkedData);

			const totalTempData = sortedBookmarkedData.concat(sortedNonBookmarkedData);

			this._data.data = totalTempData;
			this._notifyAll(this._data);
		}
	}

	/**
	 * Gets the bookmarked or unbookmarked data from based on the toggle value.
	 *
	 * @param {List} data - A list of objects with property id
	 * @param {Boolean} toggle - true: Look for bookmarked data; false: Look for unbookmarked data
	 * @return {List} A list of bookmarked or unbookmarked data depending on the toggle value
	 */
	getBookmarkedData(data, toggle) {
		return data.filter(obj => this.isBookmarked(obj.id) === toggle);
	}

	/**
	 * Update method for presenters to update the model's data.
	 *
	 * @param {Object} newData - New data to add
	 */
	update(newData) {
		newData.data.found_milliseconds = TimeUtil.getDateTime();

		if (newData.hasOwnProperty('foundTriggered') && newData.foundTriggered && newData.data.found) {
			this._editExistingInDatabase(newData.data, (result) => {
				this._callback(true); 
				this._removeFromData(newData.data); 
				this._notifyAll(this._data);
			});
		}
	}

	/**
	 * @private
	 * TEST CASE USE ONLY
	 * Function for tests only to inject data.
	 */
	testUpdateInjection(newData) {
		this._data.data.push(newData.data);
		this._notifyAll(this._data);
	}

	/**
	 * Remove data from the home model's data.
	 * 
	 * @param {Object} data - Data to remove
	 */
	_removeFromData(data) {
		this._data.data = this._data.data.filter((el) => el != data);
	}

	/**
	 * Returns the bookmarked state for a bike ID
	 *
	 * @param {Number} id - A bike notification ID
	 * @return {Boolean} true: if ID is bookmarked by user; false: otherwise
	 */
	isBookmarked(id) {
		return this._activeBookmarks.includes(id);
	}

	/**
	 * Sets a bookmark for a specific ID
	 *
	 * @param {Number} id - A bike notification ID to bookmark
	 */
	setBookmark(id) {
		this._activeBookmarks.push(id);
	}

	/**
	 * Unsets a bookmark for a specific ID
	 *
	 * @param {Number} id - A bike nofication ID to unbookmark
	 */
	unsetBookmark(id) {
		this._activeBookmarks = this._activeBookmarks.filter(bid => {return bid != id;})
	}

	/**
	 * @private
	 * DON'T USE THIS FUNCTION IF YOU DON'T HAVE TO.
	 * I hope you know what you're doing.
	 * We force a notifyAll here because if a presenter subscribes too late and needs data on startup, it won't
	 * receive anything because it mounts after data is received. Force a notifyAll so it can start with data.
	 */
	forceNotifyAll() {
		this._notifyAll(this._data);
	}

	/**
	 * Overwrite existing data in database and call the function callback depending on if it was successful or not.
	 *
	 * @param {Object} newData - Data to be written to the database
	 * @param {Function} callback - A function to call on the success or failure of the call
	 */
	_editExistingInDatabase(newData, callback) {
		return Database.editBikeData(newData, (data) => {
			// console.log(data);
			callback(typeof data !== 'undefined' && data !== undefined);
			// return typeof data !== 'undefined' && data !== undefined;
			// this._callback(typeof data !== 'undefined' && data !== undefined);
		},(error) => {
			console.log(error);
			callback(false);
			// this._callback(false);
		});
	}
}

export default HomeModel;