diff --git a/collection.js b/collection.js new file mode 100644 index 0000000..2ba6438 --- /dev/null +++ b/collection.js @@ -0,0 +1,53 @@ +var Collection = (function () { + 'use strict'; + var Collection = function (items) { + var item; + this.items = []; + for (item in items) { + if (items.hasOwnProperty(item)) { + if (items[item].validate().valid) { + this.items.push(items[item]); + } + } + } + }; + + /** + * @return {Collection} + */ + Collection.prototype = + { + add : function (model) { + this.items.push(model); + }, + /** + * Фильтрация коллекции по правилам, определенным в функции selector + * + * @param {Function} selector + * + * @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/filter + * + * @return {Collection} + */ + filter : function (selector) { + var tmp = this.items.filter(selector); + return new this.constructor(tmp); + }, + /** + * Сортировка коллекции по правилам, определенным в функции selector + * + * @param {Function} selector + * @param {Boolean} desc + * + * @return {Collection} + */ + sortBy : function (selector, desc) { + this.items.sort(selector); + if (desc) { + this.items.reverse(); + } + return this; + } + }; + return Collection; +}()); \ No newline at end of file diff --git a/event-collection.html b/event-collection.html new file mode 100644 index 0000000..c23371e --- /dev/null +++ b/event-collection.html @@ -0,0 +1,28 @@ + + + + + + EVents test + + + + + +
+ + + + + +
+ + + + + + + + + + diff --git a/event-collection.js b/event-collection.js new file mode 100644 index 0000000..6cf0687 --- /dev/null +++ b/event-collection.js @@ -0,0 +1,74 @@ +var Events = (function () { + 'use strict'; + var Events = function (items) { + Collection.apply(this, arguments); + }; + inherits(Events, Collection); + + Events.prototype.constructor = Events; + /** + * Возвращает новую коллекцию, содержащую только прошедшие события + * @return {Events} + */ + Events.prototype.findPastEvents = function () { + return this.filter(function (event) { + return event.endDate < new Date(); + }); + }; + /** + * Возвращает новую коллекцию, содержащую только будущие события + * @return {Events} + */ + Events.prototype.findFutureEvents = function () { + return this.filter(function (event) { + return event.startDate > new Date(); + }); + }; + /** + * Возвращает новую коллекцию, содержащую только события этой недели + * @return {Events} + */ + Events.prototype.findThisWeekEvents = function () { + return this.filter(function (event) { + return event.startDate > Utils.getWeekStartDate() && event.startDate < Utils.getWeekEndDate(); + }); + }; + /** + * Возвращает новую коллекцию, содержащую только события 'Drunken feast' + * @return {Events} + */ + Events.prototype.findPartyEvents = function () { + return this.filter(function (event) { + return event.title === 'Drunken feast'; + }); + }; + /** + * Возвращает новую коллекцию, содержащую только те события, напоминание которых сработает ночью + * @return {Events} + */ + Events.prototype.findNightAlarms = function () { + return this.filter(function (event) { + var alarm = event.getNextAlarmTime(); + return alarm.getHours() > 0 && alarm.getHours() < 8; + }); + }; + /** + * Сортирует коллекцию по дате начала события + * @return {Events} + */ + Events.prototype.sortByStartDate = function (asc) { + return this.sortBy(function (a, b) { + return a.startDate - b.startDate; + }, asc); + }; + /** + * Сортирует коллекцию по следующей дате периодического события + * @return {Events} + */ + Events.prototype.sortByNextHappenDate = function (asc) { + return this.sortBy(function (a, b) { + return a.getNextHappenDate() - b.getNextHappenDate(); + }, asc); + }; + return Events; +}()); \ No newline at end of file diff --git a/event-model.js b/event-model.js new file mode 100644 index 0000000..b6608f7 --- /dev/null +++ b/event-model.js @@ -0,0 +1,144 @@ +var Event = (function () { + 'use strict'; + function checkStartDate(date, validator) { + if (date === null) { + date = new Date(); + } else if (!(date instanceof Date && isFinite(date))) { + validator.addError("startDate", "Start date is invalid, check syntax"); + date = null; + } + return date; + } + + function checkEndDate(endDate, startDate, validator) { + var date; + if (endDate === null) { + date = startDate; + if (date !== null) { + date.setHours(startDate.getHours() + 1); + } + } else if (endDate instanceof Date && isFinite(endDate)) { + if (endDate < startDate) { + validator.addError("endDate", "End date should be after start date"); + date = null; + } else { + date = endDate; + } + } else { + validator.addError("endDate", "End date is invalid, check syntax"); + date = null; + } + return date; + } + + function checkRepeat(repeat, validator) { + if (repeat === null) { + repeat = Const.REPEAT.NEVER; + } else if (!(repeat.title && repeat.value)) { + validator.addError("repeat", "Unknown type of 'repeat' variable"); + repeat = null; + } else if (!Utils.checkAddTime(repeat.value)) { + validator.addError("repeat", "Add time in 'repeat' variable must have format '+ dd.MM.YY hh:mm'"); + repeat = null; + } + return repeat; + } + + function checkAlert(alert, validator) { + if (alert === null) { + alert = Const.ALERT.NONE; + } else if (!(alert.title && alert.value)) { + validator.addError("alert", "Unknown type of 'alert' variable"); + alert = null; + } else if (!Utils.checkAddTime(alert.value)) { + validator.addError("alert", "Add time in 'alert' variable must have format '+ dd.MM.YY hh:mm'"); + alert = null; + } + return alert; + } + /** + * Создает объект Event + * + * @param {String} [title="New Event"] Имя события + * @param {String} [location] Место события + * @param {Number|Date} [starts="new Date()"] Начало события + * @param {Number|Date} [ends="starts + 1"] Конец события + * @param {Object} [repeat="Const.REPEAT.NEVER"] Периодичность события + * @param {Object} [alert="Const.ALERT.NONE"] Предупреждение + * @param {String} [notes] Заметки + * + * @example + * new Event({title: "Лекция JavaScript", + * location: "УРГУ", + * startDate: new Date('2011-10-10T14:48:00'), + * endDate: new Date('2011-10-10T15:48:00'), + * repeat: REPEAT.WEEK, + * alert: ALERT.B30MIN, + * notes: "Вспомнить, что проходили на прошлом занятии"}) + * + * @return {Event} + */ + var Event = function (data) { + Model.apply(this, arguments); + }; + inherits(Event, Model); + + Event.prototype.constructor = Event; + + /** + * Функция, валидирующая объект Event + * + * @return {ValidationResult} + */ + Event.prototype.validate = function () { + var result = new ValidationResult(true); + this.startDate = checkStartDate(this.startDate, result); + this.endDate = checkEndDate(this.endDate, this.startDate, result); + this.repeat = checkRepeat(this.repeat, result); + this.alert = checkAlert(this.alert, result); + result.log(); + return result; + }; + /** + * Вычисляет когда в следующий раз случится периодическое событие + * + * @return {Date} + */ + Event.prototype.getNextHappenDate = function () { + var nhd, today; + if (!this.nextHappenDate) { + today = new Date(); + nhd = this.startDate; + while (nhd < today) { + nhd = Utils.addDateTime(nhd, this.repeat.value); + } + this.nextHappenDate = nhd; + } + return this.nextHappenDate; + }; + /** + * Вычисляет следующее время напоминания для периодических событий + * + * @param {Event} event Событие + * + * @return {Date} + */ + Event.prototype.getNextAlarmTime = function () { + var nhd = this.getNextHappenDate(); + return Utils.addDateTime(nhd, this.alert.value); + }; + /** + * Функция проверяет, нужно ли напомнить о событии + * + * @param {Event} event Событие + * + * @return {Boolean} + */ + Event.prototype.isAlertTime = function () { + var today, diff; + today = new Date(); + diff = today - this.getNextAlarmTime(); + return diff > -500 && diff < 500; + }; + return Event; +}()); \ No newline at end of file diff --git a/model.js b/model.js new file mode 100644 index 0000000..f5c43d7 --- /dev/null +++ b/model.js @@ -0,0 +1,52 @@ +var Model = (function () { + "use strict"; + var Model = function (data) { + var propName; + for (propName in data) { + if (data.hasOwnProperty(propName)) { + this[propName] = data[propName]; + } + } + + }; + + Model.prototype = + { + /** Мутатор + * @param {Object} attributes + * + * @example + * item.set({title: "March 20", notes: "In his eyes she eclipses..."}); + */ + set : function (attributes) { + var attribute; + for (attribute in attributes) { + if (attributes.hasOwnProperty(attribute)) { + this[attribute] = attributes[attribute]; + } + } + }, + /** Аксессор + * @param {String} attribute + */ + get : function (attribute) { + if (this.hasOwnProperty(attribute)) { + return this[attribute]; + } + }, + /** Валидатор + * @param {Object} attributes + */ + validate : function (attributes) { + console.log('this is Abstract method'); + } + }; + return Model; +}()); + +function inherits(Constructor, SuperConstructor) { + "use strict"; + var F = function () {}; + F.prototype = SuperConstructor.prototype; + Constructor.prototype = new F(); +} \ No newline at end of file diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..8eeba66 --- /dev/null +++ b/utils.js @@ -0,0 +1,247 @@ +/*jslint plusplus: true*/ +var Const = { + ACTIVITIES : ["Studying", "Drunken feast", "Karaoke singing", "Hanging around"], + LOCATIONS : ["My home", "My work", "My friend's house", "1905 Sq.", "UrFU", "Unknown"], + REPEAT : { + NEVER: {title: "None", value: "+ 0.0.0 0:0"}, + DAY: {title: "Every Day", value: "+ 1.0.0 0:0"}, + WEEK: {title: "Every Week", value: "+ 7.0.0 0:0"}, + TWOWEEK: {title: "Every 2 weeks", value: "+ 14.0.0 0:0"}, + MONTH: {title: "Every month", value: "+ 0.1.0 0:0"}, + YEAR: {title: "Every year", value: "+ 0.0.1 0:0"} + }, + ALERT : { + NONE: {title: "None", value: "+ 0.0.0 0:0"}, + B5MIN: {title: "5 minutes before", value: "- 0.0.0 0:5"}, + B15MIN: {title: "15 minutes before", value: "- 0.0.0 0:15"}, + B30MIN: {title: "30 minutes before", value: "- 0.0.0 0:30"}, + B1HOUR: {title: "1 hour before", value: "- 0.0.0 1:0"}, + B1DAY: {title: "1 day before", value: "- 0.0.0 24:0"} + } +}; +var Utils = (function () { + 'use strict'; + var Random = function (e) { + this.activities = e.ACTIVITIES; + this.locations = e.LOCATIONS; + this.repeat = e.REPEAT; + this.alert = e.ALERT; + }; + + Random.prototype = + { + /** + * Возвращает случайное число в интервале [min, max] + * + * @param {Number} min нижний предел + * @param {Number} max верхний предел + * + * @return {Number} + */ + getRandomInt : function (min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + }, + + /** + * Возвращает случайный элемент массива + * + * @param {Array} arr массив + * + * @return {Object} + */ + getRandomElement : function (arr) { + return arr[this.getRandomInt(0, arr.length - 1)]; + }, + + /** + * Возвращает сгенерированную строку из случайных символов + * + * @return {String} + */ + getRandomString : function () { + var chars, length, str, i; + chars = "01234 56789 ABCDE FGHIJ KLMNO PQRST UVWXT Zabcd efghi klmno pqrst uvwxy z"; + chars = chars.split(''); + length = this.getRandomInt(4, chars.length); + str = ''; + for (i = 0; i < length; i++) { + str += this.getRandomElement(chars); + } + return str; + }, + + /** + * Возвращает случайное собственное свойство объекта + * + * @param {Object} obj объект + * + * @return {Object} + */ + getRandomPropertyVal : function (obj) { + var keys, prop; + keys = []; + for (prop in obj) { + if (obj.hasOwnProperty(prop)) { + keys.push(prop); + } + } + return obj[this.getRandomElement(keys)]; + }, + + /** + * Возвращает сгенерированную дату начала события + * Вычисляется как текущая дата начала события + случайное число дней от -28 до 28 + * + * @return {Date} + */ + getRandomStartDate : function () { + var startDate = new Date(); + startDate.setDate(startDate.getDate() + this.getRandomInt(-28, 28)); + startDate.setHours(this.getRandomInt(0, 23)); + startDate.setMinutes(this.getRandomInt(0, 59)); + return startDate; + }, + + /** + * Возвращает сгенерированную дату окончания события + * Вычисляется как дата начала события + случайное число часов от 1 до 23 + * @param {Number|Date} startDate Начало события + * + * @return {Date} + */ + getRandomEndDate : function (startDate) { + var endDate = new Date(startDate.getTime()); + endDate.setHours(startDate.getHours() + this.getRandomInt(1, 23)); + endDate.setMinutes(this.getRandomInt(0, 59)); + return endDate; + }, + + /** + * Возвращает сгенерированный случайным образом объект Event + * + * @return {Event} + */ + getRandomEvent : function () { + var title = this.getRandomElement(this.activities), + location = this.getRandomElement(this.locations), + starts = this.getRandomStartDate(), + ends = this.getRandomEndDate(starts), + repeat = this.getRandomPropertyVal(this.repeat), + alert = this.getRandomPropertyVal(this.alert), + notes = this.getRandomString(); + return new Event({ + title: title, + location: location, + startDate: starts, + endDate: ends, + repeat: repeat, + alert: alert, + notes: notes + }); + }, + + getRandomEventCollection : function (size) { + var i, events; + events = new Events(); + for (i = 0; i < size; i++) { + events.add(this.getRandomEvent()); + } + return events; + } + }; + + function checkAddTime(addTime) { + var re, splitted; + re = "([+-]) (\\d?\\d.\\d?\\d.\\d?\\d) (\\d?\\d:\\d?\\d)"; + splitted = addTime.match(re); + if (splitted === null || splitted.length !== 4) { + splitted = null; + } + return splitted; + } + + /** + * Добавляет к дате кол-во лет, месяцев, дней и т.д определенных в параметре + * Параметр добавления даты должен иметь формат + dd.MM.YY hh:mm + * @param {Number|Date} date Дата + * @param {String} dateTimeStr Строка в формате '+ dd.MM.YY hh:mm' + * + * @return {Date} + */ + function addDateTime(date, dateTimeStr) { + var result, splitted, sign, addDate, dd, mm, yy, addTime, hh, min; + splitted = checkAddTime(dateTimeStr); + result = new Date(date.getTime()); + if (splitted) { + if (splitted[1] === '-') { + sign = -1; + } else { + sign = 1; + } + addDate = splitted[2].split('.'); + addTime = splitted[3].split(':'); + result.setDate(result.getDate() + sign * addDate[0]); + result.setMonth(result.getMonth() + sign * addDate[1]); + result.setYear(result.getYear() + sign * addDate[2]); + result.setHours(result.getHours() + sign * addTime[0]); + result.setMinutes(result.getMinutes() + sign * addTime[1]); + } else { + console.log("Add time in 'addDateTime' function must have format '+ dd.MM.YY hh:mm'"); + } + return result; + } + + /** + * Вычисляет дату начала недели + * + * @param {Date} [date="new Date()"] Дата + * + * @return {Date} + */ + function getWeekStartDate(date) { + var startOfWeek; + date = date || new Date(); + startOfWeek = new Date(date.getTime()); + startOfWeek.setDate(date.getDate() - date.getDay() + 1); + startOfWeek.setHours(0); + startOfWeek.setMinutes(0); + return startOfWeek; + } + /** + * Вычисляет дату конца недели + * + * @param {Date} [date="new Date()"] Дата + * + * @return {Date} + */ + function getWeekEndDate(date) { + var startOfWeek, endOfWeek; + startOfWeek = getWeekStartDate(date); + endOfWeek = new Date(startOfWeek.getTime()); + endOfWeek.setDate(startOfWeek.getDate() + 7); + return endOfWeek; + } + + return { + randomEvent: function () { + return new Random(Const).getRandomEvent(); + }, + randomEventCollection: function (size) { + return new Random(Const).getRandomEventCollection(size); + }, + checkAddTime: function (addTime) { + return checkAddTime(addTime); + }, + addDateTime: function (date, dateTimeStr) { + return addDateTime(date, dateTimeStr); + }, + getWeekStartDate: function (date) { + return getWeekStartDate(date); + }, + getWeekEndDate: function (date) { + return getWeekEndDate(date); + } + }; +}()); + +var events = Utils.randomEventCollection(20); \ No newline at end of file diff --git a/validation.js b/validation.js new file mode 100644 index 0000000..40cfac0 --- /dev/null +++ b/validation.js @@ -0,0 +1,30 @@ +/*jslint devel: true */ +var ValidationResult = (function () { + "use strict"; + var ValidationError = function (fieldName, errorText) { + this.fieldName = fieldName; + this.errorText = errorText; + }, ValidationResult = function (valid) { + this.valid = valid; + this.errors = []; + }; + + ValidationResult.prototype = + { + addError : function (fieldName, errorText) { + this.errors.push(new ValidationError(fieldName, errorText)); + this.valid = false; + }, + log : function () { + var error; + if (!this.valid) { + for (error in this.errors) { + if (this.errors.hasOwnProperty(error)) { + console.log(this.errors[error].errorText); + } + } + } + } + }; + return ValidationResult; +}()); \ No newline at end of file