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