diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..13bbe5b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.idea/
+
diff --git a/collection.js b/collection.js
new file mode 100644
index 0000000..1705353
--- /dev/null
+++ b/collection.js
@@ -0,0 +1,91 @@
+(function (exports) {
+ "use strict";
+
+var Collection = function (items) {
+
+ this.items = [];
+ var key;
+
+ for (key in items) {
+ if (items.hasOwnProperty(key)) {
+ this.items.push(items[key]);
+ }
+ }
+};
+
+exports.Collection = Collection;
+
+Collection.prototype.constructor = Collection;
+
+/**
+ * Сериализует коллекцию
+ *
+ * @return {JSON object}
+ *
+ */
+Collection.prototype.serialise = function () {
+ return JSON.stringify(this.items, null, ' ');
+}
+
+/**
+ * Добавляет в коллекцию объект
+ *
+ * @param {object} model
+ *
+ * @return {Collection} * @example
+ *
+ */
+Collection.prototype.add = function (model) {
+
+ var temp = new this.constructor(this.items);
+ temp.items.push(model);
+ return temp;
+};
+
+/**
+ * @param {Function} selector
+ *
+ * @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/filter
+ *
+ * @example
+ * new Collection().filter(function (item) {
+ * return item.get('attendee').indexOf("me") !== -1;
+ * });
+ * @return {Collection}
+ */
+Collection.prototype.filter = function (selector) {
+
+ if (typeof selector !== "function") {
+ throw new Error('Argument must be function');
+ }
+
+ return new this.constructor(this.items.filter(selector));
+};
+
+/**
+ * Принимает функцию сортировки и сортирует на основе ее
+ *
+ * @param {function} selector
+ *
+ * @return {Collection} * @example
+ *
+ */
+Collection.prototype.sort = function (selector) {
+
+ if (typeof selector !== "function") {
+ throw new Error('Argument must be function');
+ }
+
+ return new this.constructor(this.items.sort(selector));
+};
+
+Collection.prototype.reverse = function () {
+
+ return new this.constructor(this.items.reverse());
+};
+
+Collection.prototype.length = function (selector) {
+
+ return this.items.length;
+};
+}(window));
\ No newline at end of file
diff --git a/current-event.json b/current-event.json
new file mode 100644
index 0000000..63d384b
--- /dev/null
+++ b/current-event.json
@@ -0,0 +1,56 @@
+[
+ {
+ "name": "pewpew",
+ "start": "2012-11-07T04:18:00.000Z",
+ "end": "2012-11-07T04:18:00.000Z",
+ "location": "",
+ "remindTime": 0,
+ "description": "(отсутствует)",
+ "raiting": 0
+ },
+ {
+ "name": "Пара по веб-технологиям",
+ "start": "2012-11-20T06:50:00.000Z",
+ "end": "2012-11-20T06:50:00.000Z",
+ "location": "5 этаж",
+ "remindTime": 10,
+ "description": "Взять бумагу и ручку, не брать бук!",
+ "raiting": 0
+ },
+ {
+ "name": "Событие",
+ "start": "2012-11-28T18:00:00.000Z",
+ "end": "2012-11-28T18:00:00.000Z",
+ "location": "",
+ "remindTime": 0,
+ "description": "(отсутствует)",
+ "raiting": "0"
+ },
+ {
+ "name": "Событие5",
+ "start": "2012-11-29T18:00:00.000Z",
+ "end": "2012-11-29T18:00:00.000Z",
+ "location": "",
+ "remindTime": 0,
+ "description": "(отсутствует)",
+ "raiting": "0"
+ },
+ {
+ "name": "tretr",
+ "start": "2012-11-28T18:00:00.000Z",
+ "end": "2012-11-28T18:00:00.000Z",
+ "location": "",
+ "remindTime": 0,
+ "description": "(отсутствует)",
+ "raiting": "0"
+ },
+ {
+ "name": "Событиеjnklk",
+ "start": "2012-11-29T18:00:00.000Z",
+ "end": "2012-11-29T18:00:00.000Z",
+ "location": "",
+ "remindTime": 0,
+ "description": "(отсутствует)",
+ "raiting": "0"
+ }
+]
\ No newline at end of file
diff --git a/documentAction.js b/documentAction.js
new file mode 100644
index 0000000..01dd672
--- /dev/null
+++ b/documentAction.js
@@ -0,0 +1,177 @@
+$(function (exports) {
+ "use strict";
+
+ var ListOfEvents = new Events(),
+ sortedList = new Events(),
+ queue = new Events,
+ filterOption = "all",
+ sortOption = "without";
+
+ var template = '
' +
+ "Название: <%= name %>
" +
+ "Начало: <%= start %>
" +
+ "Окончание: <%= end %>
" +
+ "Местоположение: <%= location %>
" +
+ "Напомнить за: <%= remindTime %>
" +
+ "Описание: <%= description %>
" +
+ "Рейтинг: <%= raiting %>
";
+ $(document).ready(initialise);
+
+/**
+ * Загружает свое состояние с сервера
+ * при отсутствии соединения/страницы на сервере пытается подключиться через 5 минут снова
+ *
+*/
+ function initialise() {
+
+ $('.date').datepicker();
+
+ $.getJSON('current-event.json')
+ .complete(function () { $("#notify").hide(); })
+ .error(function () { $('#notifyError').show(); })
+ .success(function (result) {
+ var i, newEvent;
+
+ for (i = 0; i < result.length; i++) {
+ newEvent = new Event(result[i]).validate();
+ ListOfEvents = ListOfEvents.add(newEvent);
+ }
+ changeDocument("sort");
+ addListener();
+ });
+ }
+/**
+ * Добавляет новое событие в список. Если установлены опции фильтрации и сортировки
+ * - то располагает элементы на странице, в с-ии с ними
+ *
+*/
+ function preventDefault() {
+
+ var name = $("#title").val().trim(),
+ start = $("#from").val(),
+ end = $("#to").val(),
+ location = $("#location").val().trim(),
+ raiting = $("#raiting").val(),
+ description = $("#description").val().trim(),
+ remindTime = $("#remindTime").val();
+
+ if (!validateTitle(name, $('#title_help'))) { alert("Событие не было добавлено. Ошибка"); return; }
+ if (!validateDate(start, $('#from_help'))) { alert("Событие не было добавлено. Ошибка"); return; }
+ if (!validateNumber(remindTime, $('#remindTime_help'))) { alert("Событие не было добавлено. Ошибка"); return; }
+
+ var element = new Event({
+ name: name,
+ start: start,
+ end: end,
+ location: location,
+ raiting: raiting,
+ description: description,
+ remindTime: remindTime
+ }).validate();
+
+ var result = ListOfEvents.add(element);
+
+ $.post('current-event.json', result.serialise())
+ .success(function () {
+ ListOfEvents = result;
+ changeDocument("sort");
+ document.forms["form"].reset();
+ //alert("Все события были успешно отправлены.");
+ })
+ .error( function () {
+ alert("Отсутсвует подключение к серверу.");
+ });
+ }
+
+ function filterEvents(listEvents) {
+ switch (filterOption) {
+ case "future":
+ return listEvents.coming();
+ case "past":
+ return listEvents.past();
+ default:
+ return listEvents;
+ }
+ }
+
+ function sortEvents() {
+ switch (sortOption) {
+ case "byName":
+ return ListOfEvents.sortByName();
+ case "byStart":
+ return ListOfEvents.sortByTime();
+ case "byRaiting":
+ return ListOfEvents.sortByRaiting();
+ default:
+ return ListOfEvents;
+ }
+ }
+
+/**
+ * Сортирует и фильтрует события в соответствии с указанными опциями.
+ *
+ * @param {string} changeType - если указана строка "sort", то события также будут отсортированы,
+ * инчае - только отфильтрованы
+ * @return коллекция объектов типа event
+*/
+
+ function changeDocument(changeType) {
+ var $removeList = $(".events");
+ $removeList.remove();
+
+ if (changeType === "sort") {
+ sortedList = sortEvents();
+ }
+ var filterList = filterEvents(sortedList),
+ length = filterList.length(),
+ i;
+
+ var $box = $("");
+ var $temp = $();
+
+ for (i = 0; i < length; i++)
+ {
+ $temp = $temp.add($(tmpl(template, filterList.items[i])));
+ }
+
+ $temp.appendTo($box);
+ $box.appendTo($("#collection"));
+}
+
+/**
+ * Навешивает обработчики событий на страницу
+*/
+ function addListener() {
+
+ $("#title").on('blur', function($event) {
+ var cur = $event.currentTarget;
+ validateTitle(cur.value, $('#title_help'));
+ });
+
+ $("#from").on('blur', function ($event) {
+ var cur = $event.currentTarget;
+ validateDate(cur.value, $('#from_help'));
+ });
+
+ $("#remindTime").on('blur', function ($event) {
+ var cur = $event.currentTarget;
+ validateNumber(cur.value, $('#remindTime_help'));
+ });
+
+ $('.filter').each(function() {
+ $(this).on('change', function () {
+ filterOption = $('input[name="filter"]:checked').val();
+ changeDocument("filter");
+ })});
+
+ $('.sort').each(function() {
+ $(this).on('change', function () {
+ sortOption = $('input[name="sort"]:checked').val();
+ changeDocument("sort");
+ })});
+
+
+ $("#addButton").on('click', preventDefault);
+ }
+
+}(window));
\ No newline at end of file
diff --git a/event.js b/event.js
new file mode 100644
index 0000000..f96a339
--- /dev/null
+++ b/event.js
@@ -0,0 +1,93 @@
+(function (exports) {
+ "use strict";
+
+ exports.isDate = function (date) {
+
+ if (typeof date === 'undefined') {
+ return false;
+ }
+ if (typeof date.getMonth !== 'function') {
+ return false;
+ }
+ if (isNaN(date.getMonth())) {
+ return false;
+ }
+ return true;
+ };
+
+ exports.inherits = function (constructor, superconstructor) {
+
+ var Func = function () { };
+
+ Func.prototype = superconstructor.prototype;
+ constructor.prototype = new Func();
+ };
+
+ exports.Event = function (data) {
+
+ Model.apply(this, arguments);
+
+ if (typeof data.start !== undefined) {
+ this.start = new Date(this.start);
+ }
+ if (typeof data.end !== undefined) {
+ this.end = new Date(this.end);
+ }
+ };
+
+ inherits(Event, Model);
+
+/**
+ * Валидирует объект event, либо undefined, если в объекте отсутвуют обязательные поля
+ * eventObject{
+ * name - название события
+ * start - начало
+ * end - окончание
+ * location - место
+ * remindTime - за сколько минут до события напомнить
+ * description - описание
+ * raiting - важность события
+ * }
+
+ * @param {object} obj Объект
+ * @example
+ * Event({
+ * name: "Пара по веб-технологиям",
+ * start: new Date("2012-10-20 10:00:00"),
+ * end: new Date("2012-10-20 12:50:00"),
+ * location: "5 этаж",
+ * remindTime: 10,
+ * raiting:5,
+ * description: "Взять бумагу и ручку, не брать бук!"
+ * })
+ *
+ * @return {Object}
+ */
+ Event.prototype.validate = function () {
+
+ var remindTime = this.remindTime || 0;
+ this.raiting = this.raiting || 0;
+
+ if (!isDate(this.get("start"))) {
+ throw new Error('Field "start" must be Date format');
+ }
+
+ if (!isDate(this.end)) {
+ this.end = this.start;
+ }
+
+ if (this.end < this.start) {
+ this.end = this.start;
+ }
+
+ return {
+ "name": this.name || "(Нет темы)",
+ "start": this.start,
+ "end": this.end,
+ "location": this.location || "",
+ "remindTime": remindTime,
+ "description": this.description || "(отсутствует)",
+ "raiting": this.raiting
+ };
+ };
+}(window));
\ No newline at end of file
diff --git a/events.js b/events.js
new file mode 100644
index 0000000..ef7fd9d
--- /dev/null
+++ b/events.js
@@ -0,0 +1,151 @@
+(function (exports) {
+ "use strict";
+
+exports.Events = function (data) {
+
+ Collection.apply(this, arguments);
+};
+
+inherits(Events, Collection);
+
+Events.prototype.constructor = exports.Events;
+
+/**
+ * Возвращает прошедшие события, из items отсортированной по дате начала
+ *
+ * @param {events} - коллекция объектов типа event
+ * @return {Collection} - коллекция объектов типа event
+*/
+Events.prototype.past = function () {
+
+ return this.filter(function (events) {
+ return events.start < new Date();
+ });
+};
+
+/**
+ * Возвращает предстоящие события,
+ * из items, отсортированной по дате начала
+ *
+ * @return {Collection} - коллекция объектов типа event
+*/
+Events.prototype.coming = function () {
+
+ return this.filter(function (events) {
+ return events.start > new Date();
+ });
+};
+
+/**
+ * Возвращает события, которые произойдут через опр период времени,
+ * из items, отсортированной по дате начала
+ *
+ * @param {number} days - период (в днях) времени
+ *
+ * @return коллекция объектов типа event
+*/
+Events.prototype.comeThrough = function (days) {
+
+ var now = new Date();
+ now.setDate(now.getDate() + days);
+
+ var result = this.coming()
+ .filter(function (events) {
+ return events.start < now;
+ });
+
+ return result;
+};
+
+Events.prototype.byEndTime = function () {
+
+ return this.sort(function (a, b) {
+ return a.end - b.end;
+ });
+};
+
+Events.prototype.byRaiting = function () {
+
+ return this.sort(function (a, b) {
+ return a.raiting - b.raiting;
+ });
+};
+
+Events.prototype.byStartTime = function () {
+
+ return this.sort(function (a, b) {
+ return a.start - b.start;
+ });
+};
+
+Events.prototype.byName = function () {
+
+ return this.sort(function (a, b) {
+ return a.name - b.name;
+ });
+};
+
+/**
+ * Возвращает события,из items отсортированной по дате начала по возр/убыв
+ * от старых обытий к новым / наоборот.
+ * По умолчанию сортирует в порядке возрастания
+ *
+ * @param {bool} isAscending - необязательный параметр - указывает порядок сортировки.
+ * при отсутсвии сортируется по возрастанию.
+ *
+ * @return {Collection} - Новый объект типа Collection
+*/
+Events.prototype.sortByName = function (isAscending) {
+
+ isAscending = isAscending || false;
+
+ if (isAscending) {
+ return this.byName();
+ }
+ return this.byName()
+ .reverse();
+};
+
+/**
+ * Возвращает события,из items отсортированной по названию по возр/убыв
+ * По умолчанию сортирует в порядке возрастания
+ *
+ * @param {bool} isAscending - необязательный параметр - указывает порядок сортировки.
+ * при отсутсвии сортируется по возрастанию.
+ *
+ * @return {Collection} - Новый объект типа Collection
+*/
+Events.prototype.sortByTime = function (isAscending) {
+
+ isAscending = isAscending || false;
+
+ if (isAscending) {
+ return this
+ .byStartTime().reverse();
+ }
+ return this.byStartTime();
+};
+
+/**
+ * Возвращает события, из items, отсортированной по рейтингу по убыв/возрастанию
+ * от событий с более высоким рейтингом к самому низко приоритетному / наоборот.
+ * По умолчанию сортирует в порядке убывания
+ *
+ * @param {bool} isAscending - необязательный параметр - указывает порядок сортировки.
+ * при отсутствии сортируется по убыванию.
+ *
+ * @return {COllection} - Новый объект типа Collection
+*/
+Events.prototype.sortByRaiting = function (isAscending) {
+
+ isAscending = isAscending || false;
+
+ if (isAscending) {
+ return this
+ .byRaiting()
+ .reverse();
+ }
+ return this
+ .byRaiting();
+};
+}(window));
\ No newline at end of file
diff --git a/index.css b/index.css
new file mode 100644
index 0000000..3acae6b
--- /dev/null
+++ b/index.css
@@ -0,0 +1,33 @@
+body{ text-align: left;}
+#frame {
+ display: inline-block;
+ float: left;
+ text-align: right;
+}
+#error {
+ display: inline-block;
+ float: left;
+ text-align: right;
+}
+.help {
+ color: red;
+ visibility : hidden;
+ min-height: 25px;
+}
+.event_item{
+}
+#collection{
+ float: left;
+ text-align: left;
+ margin-left: 50px;
+}
+.select{
+ text-align: left;
+}
+.selecter{
+ color: blue;
+}
+#notifyError{
+ visibility: hidden;
+}
+
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..2cd2498
--- /dev/null
+++ b/index.html
@@ -0,0 +1,73 @@
+
+
+
+
+ Календарь событий
+
+
+
+
+
+
+
Введите название события
+
Введите корректную дату
+
Введите корректную дату
+
D
+
D
+
D
+
D
+
Введите корректное число
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/model.js b/model.js
new file mode 100644
index 0000000..963a925
--- /dev/null
+++ b/model.js
@@ -0,0 +1,62 @@
+(function (exports) {
+ "use strict";
+
+/**
+ * Абстрактный конструктор, принимает объект и создает абстрактный объект для работы
+ *
+ * @param {Object} attributes
+ *
+ * @example
+ * item.set({title: "March 20", content: "In his eyes she eclipses..."});
+ */
+exports.Model = function (data) {
+
+ var key;
+
+ for (key in data) {
+ if (data.hasOwnProperty(key)) {
+ this[key] = data[key];
+ }
+ }
+};
+
+
+/**
+ * Сеттер - устанавливает аттрибуты и значения атрибутов, в соответсвии с принятым в качестве параметра объектом
+ *
+ * @param {Object} attributes
+ *
+ * @example
+ * item.set({title: "March 20", content: "In his eyes she eclipses..."});
+ */
+Model.prototype.set = function (attributes) {
+
+ var key;
+
+ for (key in attributes) {
+ if (attributes.hasOwnProperty(key)) {
+ this[key] = attributes[key];
+ }
+ }
+};
+
+/**
+ * Геттер - возвращает запрашиваемое свойство у объекта
+ *
+ * @param {Object} attributes
+ */
+Model.prototype.get = function (attribute) {
+
+ if (this.hasOwnProperty(attribute)) {
+ return this[attribute];
+ }
+
+ return undefined;
+};
+/**
+ * @param {Object} attributes
+ */
+Model.prototype.validate = function (attributes) {
+ throw new Error('this is Abstract method');
+};
+}(window));
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..9279c56
--- /dev/null
+++ b/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "dz-8-nodejs",
+ "description": "Using nodejs",
+ "author": {
+ "name": "Yunnii"
+ },
+ "version": "1.0.0",
+ "scripts": {
+ "test": "make test"
+ },
+ "dependencies": {
+ "express": "*"
+ },
+ "engines": {
+ "node": ">=0.6.12",
+ "npm": ">=1.1.4"
+ }
+}
\ No newline at end of file
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..c57b72a
--- /dev/null
+++ b/server.js
@@ -0,0 +1,29 @@
+var fs = require('fs'),
+ express = require('express');
+
+var app = express();
+var staticDir = __dirname;
+
+app.post('/current-event.json', function (request, response) {
+ var writeStream = fs.createWriteStream('./current-event.json');
+ request.pipe(writeStream);
+
+ request.on('end', function () {
+ writeStream.end();
+ response.send("ok");
+ response.end();
+ });
+ });
+
+app.get('/current-event.json', function (request, response) {
+ var readStream = fs.createReadStream('./current-event.json');
+ readStream.pipe(response);
+
+ readStream.on('close', function () {
+ response.end();
+ });
+});
+
+app.use(express.static(staticDir));
+
+app.listen(8080);
\ No newline at end of file
diff --git a/templating.js b/templating.js
new file mode 100644
index 0000000..70530b4
--- /dev/null
+++ b/templating.js
@@ -0,0 +1,35 @@
+// Simple JavaScript Templating
+// John Resig - http://ejohn.org/ - MIT Licensed
+$(function(exports){
+ var cache = {};
+
+ exports.tmpl = function tmpl(str, data){
+ // Figure out if we're getting a template, or if we need to
+ // load the template - and be sure to cache the result.
+ var fn = !/\W/.test(str) ?
+ cache[str] = cache[str] ||
+ tmpl(document.getElementById(str).innerHTML) :
+
+ // Generate a reusable function that will serve as a template
+ // generator (and which will be cached).
+ new Function("obj",
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+ // Introduce the data as local variables using with(){}
+ "with(obj){p.push('" +
+
+ // Convert the template into pure JavaScript
+ str
+ .replace(/[\r\t\n]/g, " ")
+ .split("<%").join("\t")
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+ .replace(/\t=(.*?)%>/g, "',$1,'")
+ .split("\t").join("');")
+ .split("%>").join("p.push('")
+ .split("\r").join("\\'")
+ + "');}return p.join('');");
+
+ // Provide some basic currying to the user
+ return data ? fn( data ) : fn;
+ };
+}(window));
\ No newline at end of file
diff --git a/validation.js b/validation.js
new file mode 100644
index 0000000..cb6352d
--- /dev/null
+++ b/validation.js
@@ -0,0 +1,47 @@
+(function (exports) {
+ "use strict";
+
+ exports.ListOfEvents = new Events();
+
+ exports.showError = function (isError, helpText) {
+
+ if (isError) {
+ helpText.show();
+ return;
+ }
+ helpText.hide();
+ };
+
+ exports.validateTitle = function (input, helpText) {
+
+ if ($.trim(input).length === 0) {
+ showError(true, helpText);
+ return false;
+ }
+
+ showError(false, helpText);
+ return true;
+ };
+
+ exports.validateDate = function (input, helpText) {
+
+ if (!isDate(new Date(input))) {
+ showError(true, helpText);
+ return false;
+ }
+
+ showError(false, helpText);
+ return true;
+ };
+
+ exports.validateNumber = function (input, helpText) {
+
+ if (! $.isNumeric(+input) || +input < 0) {
+ showError(true, helpText);
+ return false;
+ }
+
+ showError(false, helpText);
+ return true;
+ };
+}(window));
\ No newline at end of file