Разработка веб-приложения - ежедневника (менеджер задач, "список дел") с функцией голосового управления
Описание файловой структуры веб-приложения - ежедневника с функцией голосового управления. Интерфейс с изображениями, отображающими процесс работы приложения. Примеры кода с пояснениями. Общее описание иерархии и взаимодействия компонентов приложения.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | дипломная работа |
Язык | русский |
Дата добавления | 17.05.2018 |
Размер файла | 989,2 K |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
id: id
}
Листинг 6.3.5
Они используются, чтобы абстрагировать код интерфейса от того, как устроено хранилище. В коде клиентского приложения, в описании интерфейса, можно просто использовать эти функции так:
import * as act from './TodoStoreActions';
this.store.dispatch(act.todosLoad(this.store.getState().date));
Листинг 6.3.6
То есть, наше представление не знает, какие названия у команд. Ему это и не нужно знать. Оно отвечает только за отображение данных. В примере выше «act» - это тот самый «TodoStoreActions» - который хранит способы взаимодействия с хранилищем, предоставляя функции с говорящими названиями вроде «todosLoad». «this.store.getState()» - это получение текущего состояния хранилища. То есть получение данных. И далее через точку - свойство «date» - текущая дата. В этом участке кода инициируется запрос в хранилище на получение списка дел за текущий день. Дата считывается из хранилища и ему же передается.
5.4 Код интерфейса
В главном компоненте «TodoList» после конструктора следует метод «componentDidMount»:
componentDidMount() {
this.todosLoad();
this.activeDaysLoad();
}
Листинг 6.4.1
Он будет вызван автоматически библиотекой React каждый раз, когда будет создан. То есть как только он будет загружен и отображен на странице. В нем вызываются два других метода класса «TodoList» - «todosLoad()» и «activeDaysLoad()». Первый инициирует в хранилище загрузку списка задач, второй - сам выполняет запрос к серверу для получения списка «активных дней» (дней, на которые назначена хотя бы одна задача), для того, чтобы подсветить их в календаре. Для хранения массива «активных дней» не используется хранилище и «редьюсеры», хотя это возможно было реализовать. Но это не принципиально, поэтому данный массив хранится во внутреннем «состоянии» компонента «TodoList». При изменении этого состояния компонент также будет перерисован, как если бы использовалось хранилище Redux. Но только в данном случае все это будет сделано силами библиотеки React.
Вот так выглядит функция «todosLoad()»:
todosLoad() {
this.store.dispatch(act.todosLoad(this.store.getState().date));
this.activeDaysLoad();
}
Листинг 6.4.2
Функция «activeDaysLoad()», которая предназначена для загрузки списка «активных» дней, выглядит так:
activeDaysLoad() {
let month = this.store.getState().date.getMonth() + 1;
fetch('/days/get/active', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({month: month})
})
.then((response) => {
return response.json();
})
.then((data) => {
let res = [];
for (let key in data) {
res.push(data[key].date);
}
this.setState({activeDays: res});
})
.catch(alert);
}
Листинг 6.4.3
Сначала в переменную «month» записывается значение текущего месяца, которое извлекается из хранилища. Затем при помощи функции «fetch» создается POST-запрос к серверу для получения данных. В теле запроса передается номер текущего месяца. Как только ответ получен, он первым делом проходит через JSON-парсер, затем после разбора будет предоставлен объект «data», с которым можно работать. Извлекаются все номера дней и сохраняются в массив «res», который затем заносится в «state» (состояние) компонента «TodoList». Хранилище здесь не используется. Используется внутреннее состояние React-компонента. При его изменении также будет вызван метод «render()» и части компонента, в которых произошли изменения, будут перерисованы.
Ниже будет показан код метода «todoToggle()», который будет вызван, когда пользователь кликнет по элементу списка задач, чтобы переключить его состояние (выполнено / не выполнено). Все остальные методы для манипуляций с элементами списка, такие как «удалить элемент», «изменить», «добавить» работают по такому же принципу и разительных отличий в коде нет.
todoToggle(item) {
item.done = !item.done;
this.store.dispatch(act.todosChange(item));
if (item.done) this.showSuccessMessage("Задача выполнена! Вы молодец!");
else this.showSuccessMessage("Задача не выполнена обратно");
Листинг 6.4.4
В первой строке метода переключается значение переменной типа «Boolean» - «item.done». Ее значение меняется на противоположное. Далее в хранилище вызывается событие изменения элемента, и весь элемент передается в хранилище. Далее вызывается метод «showSuccessMessage()», который показывает сообщение о том, что задача была выполнена, если это соответствует новому состоянию элемента. В противном случае выводится сообщение о том, что задача снова стала отмечена как невыполненная.
Метод «render()» компонента «TodoList» занимает довольно много места. Его условно можно разделить на две части. В первой происходит предварительная подготовка, вычисление нужных значений, подготовка массивов с элементами. Вторая часть - оператор «return». Он использует круглые скобки, чтобы вернуть многострочное значение. «return» занимает даже больший объем, чем подготовительная часть. В нем можно рассмотреть всю структуру компонента, которая будет отображена на экране пользователя.
Вот так в подготовительной части выглядит составление массива с элементами списка:
const { todos } = this.store.getState();
let items = [];
for (let i = 0; i < todos.length; i++) {
let item = todos[i];
if (item.id !== this.state.editableItemID) {
items.push(
<TodoListItem item={item} key={item.id}
onClick={this.todoToggle.bind(this)}
onDeleteClick={this.todosDeleteItem.bind(this)}
onEditClick={this.todosSetEditableItem.bind(this)}
/>
)
}
else {
items.push(
<TodoListItemEdit item={item} key={item.id}
onCancel={this.todosSetEditableItem.bind(this, null)}
onSave={this.todosChangeItem.bind(this)}
/>
)
}
}
if (!items.length) {
items.push (
<TodoListItem item={{name: "Нет задач"}} key={0} empty />
)
}
Листинг 6.4.5
Сначала был получен массив со списком дел из хранилища при помощи «this.store.getState()». Далее создается пустой массив «items», в который будут добавляться элементы списка, созданные на основе полученных элементов массива. Каждый элемент списка будет представлять собой либо компонент «TodoListItem», либо компонент «TodoListItemEdit» (используется, если есть текущий редактируемый элемент), описание которогых импортируется в начале кода файла «TodoList.jsx». Для каждого элемента передаются обработчики событий для взаимодействия с пользователем.
Переменная «editableItemID» хранится в «state» компонента и содержит номер элемента, который сейчас редактируется (если пользователь нажимает на кнопку редактирования элемента, этой переменной присваивается значение, и так как она находится в «state», то компонент «TodoList» будет перерисован, так как это происходит каждый раз при изменении «state» компонента или при выполнении действий с хранилищем).
Если элементов в массиве со списком дел вдруг не окажется, то будет создан всего один пустой элемент «TodoListItem» с именем «Нет задач». Его можно увидеть, переключившись в календаре на любой день, на который не запланировано ни одной задачи.
Оператор «render» выглядит следующим образом:
return (
// таблица, содержащая разметку страницы
<table id="app"><tbody>
<tr>
<td>
// здесь отображается календарь. Используется сторонний модуль «react-day-picker»
<div className="calendar">
<DayPicker
modifiers={activeDays}
onDayClick={this.onDayChange.bind(this)}
onMonthChange={this.onMonthChange.bind(this)}
initialMonth={ this.store.getState().date }
selectedDays={ day => DateUtils.isSameDay(this.store.getState().date, day) }
/>
</div>
// навигация, которая находится под календарем
<div className="calendar-date-nav">
<div className="btn btn-default btn-block"
onClick={() => {
let day = new Date();
day.setDate(day.getDate() - 1);
this.onDayChange(null, day);
}}>
Вчера
</div>
<div className="btn btn-primary btn-block"
onClick={() => this.onDayChange(null, new Date())}>
Сегодня
</div>
<div className="btn btn-default btn-block"
onClick={() => {
let day = new Date();
day.setDate(day.getDate() + 1);
this.onDayChange(null, day);
}}>
Завтра
</div>
<div className="btn btn-default btn-block"
onClick={() => {
let day = new Date();
day.setDate(day.getDate() + 2);
this.onDayChange(null, day);
}}>
Послезавтра
</div>
</div>
</td>
// Средняя колонка, в которой находится таблица со «списком дел»
<td width="100%">
<div className="to-do-list">
<table className="table-curved">
<thead>
<tr>
<th className="todo-item-time">Time</th>
<th width="100%">{this.store.getState().date.toDateString()}</th>
</tr>
</thead>
<tbody>
{items}
</tbody>
</table>
<TodoListAdd handleTaskAdd={this.todosAddItem.bind(this)} />
</div>
</td>
// Панель управления с правой стороны
<td>
<div className="right-sidebar">
<a href="/logout" className="btn btn-default btn-block">Выход</a>
<div className="btn btn-default btn-block"
onClick={this.todosDeleteDone.bind(this)}>
Удалить выполненные
</div>
<div className="btn btn-default btn-block"
onClick={this.todosDeleteOld.bind(this)}>
Удалить просроченные
</div>
<div className="btn btn-default btn-block"
onClick={this.todosDeleteOnDay.bind(this, this.store.getState().date)}>
Удалить задачи на этот день
</div>
<div className="btn btn-default btn-block"
onClick={this.todosDeleteAll.bind(this)}>
Удалить все
</div>
<a className="btn btn-default btn-block"
href="/todos/export">
Экспорт
</a>
// Элемент голосового управления
<TodoVoiceControl onControl={this.handleVoiceControl.bind(this)} />
</div>
// Всплывающее сообщение
{alertBox}
</td>
</tr>
</tbody>
</table>
)
Листинг 6.4.6
В нем, по большому счету, можно увидеть все, что будет отображено в приложении. Пусть и не всегда подробно (когда используются внешние компоненты), но, по крайней мере, на уровне абстракций структуру приложения разглядеть возможно.
Так как массив с элементами списка дел был создан заранее, что было рассмотрено выше, то для его вывода в операторе «return» используется всего одна небольшая строка - «{items}». Фигурные скобки используются для вывода JavaScript-переменных и объектов внутри JSX-разметки.
Подобным же образом выводится текущая дата в заголовке таблицы: «<th width="100%">{this.store.getState().date.toDateString()}</th>». Значение просто извлекается из хранилища и приводится к типу «String». Далее оно будет выведено внутри элемента «th».
5.5 Компонент голосового управления
Для голосового управления используется технология «Yandex Speech Kit». Компания предоставляет бесплатное API на один месяц для использования. Нужно только получить API-ключ на сайте и добавить на страницу с приложением JavaScript-файл со всем нужным функционалом, который можно использовать в приложении.
Сам компонент голосового управления в данном приложения - это React-компонент. Простая кнопка. Его описание находится в отдельном файле - «TodoVoiceControl». Вот так выглядит метод «render» этого компонента:
render() {
return (
<div>
<div className="voice-button" onClick={this.handleClick.bind(this)}>
<i className="glyphicon glyphicon-volume-up"></i>
</div>
<span ref="text"></span>
</div>
)
}
Листинг 6.5.1
Весь компонент состоит из кнопки - это блок «div» с иконкой «i» внутри. Ниже располагается тег «span», в который будет записан результат разбора текста из голосового файла. На кнопку назначен обработчик события «onClick», который использует функционал скрипт от «Яндекс» для того, чтобы записать голосовую команду и отправить ее на разбор.
Ниже приведен код обработчика:
handleClick() {
var self = this;
ya.speechkit.recognize({
doneCallback: function (text) {
self.refs.text.innerHTML = "Вы сказали: " + text;
self.props.onControl(text);
},
initCallback: function () {
self.refs.text.innerHTML = "Говорите...";
},
errorCallback: function (err) {
self.refs.text.innerHTML = "Ошибка: " + err;
},
model: 'freeform', // Model name for recognition process
lang: 'ru-RU', //Language for recognition process
apiKey: '570fd671-7f9a-4555-be3a-3af792c9d4b2'
});
}
Листинг 6.5.2
В обработчике происходит вызов функции «ya.speechkit.recognize()», которая описана в упомянутом выше JavaScript-файле от компании «Яндекс». В функцию передается объект с параметрами. В числе прочих там находятся callback-функции для разных стадий выполнения запроса. «initCallback» будет вызвана при инициации процесса распознавания голоса, перед тем, как непосредственно начнется процесс записи голоса. В ней просто выводится предложение пользователю произнести команду.
«doneCallback» будет вызвана, если ответ с серверов «Яндекса» пришел и разбор голосовой команды был выполнен успешно. В этом случае будет выведено текст произнесенной команды под самой кнопкой голосового управления. И также будет вызвана callback-функция, которая была передана в компонент «TodoVoiceControl» из главного компонента «TodoList». Это функция разбора голосовой команды и она описана в файле «TodoList.jsx».
Функция «errorCallback» будет вызвана в случае возникновения ошибки. Текст ошибки также будет выведен под кнопкой.
Ниже приведен код из файла «TodoList.jsx», а именно - метод «handleVoiceControl», который, собственно, и передается как callback-функция в компонент «TodoVoiceControl» и вызывается при успешном распознавании голосовой команды.
handleVoiceControl(text) {
let arr = text.split(" ");
if (arr[0].toLowerCase() == "добавить" && arr[1].toLowerCase() == "задачу") {
let task = "", time = "";
if (arr.length > 2) {
let i;
for (i = 2; i < arr.length; i++) {
if (arr[i].toLowerCase() != "время") task += arr[i] + " ";
else break;
}
if (arr[i] == "время") {
if (arr[i+1]) time = arr[i+1];
if (arr[i+2]) time += ":" + arr[i+2];
else time += ":00";
}
}
this.todosAddItem({
name: task,
done: false,
time: time
});
}
else if (arr[0] == "удалить") {
if (arr[1] == "выполненные" && arr[2] == "задачи") {
this.todosDeleteDone();
}
else if (arr[1] == "все" && arr[2] == "задачи") {
this.todosDeleteAll();
}
else if (arr[1] == "задачи" && arr[2] == "на" && arr[3] == "этот" && arr[4] == "день") {
this.todosDeleteOnDay(this.store.getState().date);
}
else if (arr[1] == "задачи" && arr[2] == "до" && arr[3] == "этого" && arr[4] == "дня") {
this.todosDeleteOld();
}
}
}
Листинг 6.5.3
Здесь при помощи простых разветвлений операторов if и else происходит проверка разобранной строки на наличие допустимых команд. Если какое-либо из условий выполняется полностью, происходит либо вызов одного из методов класса «TodoList», таких как «todosDeleteOld()» или «todosDeleteAll()», либо, в случае с командой «добавить задачу», выполняются дополнительные действия.
При выполнении голосовой команды «добавить задачу» дальнейшая часть строки, следующая за самой командой, разделяется на «название задачи» и «время». После чего вызывается метод «todosAddItem()».
5.6 Компонент элемента списка задач
Для отображения элементов списка задач также используется отдельный React-компонент. Он описан в файле «TodoListItem.jsx». Компонент получает в качестве параметра элемент массива списка задач (который получен от сервера). А также несколько callback-функций для взаимодействия с пользователем.
Сам компонент не хранит кода, отвечающего за поведение при манипуляциях пользователя. Нужные методы для обработчиков событий передаются «сверху», из компонента «TodoList» в качестве параметров. Эти методы будут доступны внутри компонента «TodoListItem» через его свойство «props». Это можно увидеть в примере ниже:
handleDelete() {
this.props.onDeleteClick(this.props.item.id);
Листинг 6.6.1
При нажатии кнопки удаления элемента вызывается внутренний метод компонента «TodoListItem», в котором уже происходит вызов callback-функции, переданной компоненту из «TodoList».
Ниже приведен полный код метода «render» компонента «TodoListItem»:
render() {
let item = this.props.item;
let timeCellContent = (item.done) ? <span className="glyphicon glyphicon-ok"></span>:
(item.time) ? item.time.slice(0, 5): <span className="glyphicon glyphicon-time"></span>;
let trClass = ((!!item.done) ? "success": "");
if (!this.props.empty)
return (
<tr className={trClass}>
<td className="todo-item-time">{timeCellContent}</td>
<td width="100%" onClick={this.handleTodoClick}>
{item.name}
<div className="todo-item-control">
<span className="glyphicon glyphicon-pencil" onClick={this.handleEdit}></span>
<span className="glyphicon glyphicon-remove" onClick={this.handleDelete}></span>
</div>
</td>
</tr>
)
else return (
<tr>
<td className="todo-item-time"></td>
<td width="100%">{item.name}</td>
</tr>
)
Листинг 6.6.2
В качестве кнопок «Удалить» и «Редактировать» используются иконки css-фреймворка Bootstrap «GlythIcons».
Каждый элемент списка заключен в HTML-тег <tr> (строка таблицы). Каждая такая строка состоит из двух ячеек - одна для времени, другая - для названия «задачи» и для функциональных кнопок («удалить» и «редактировать»).
В методе «render()» происходит проверка свойства «empty» на случай, если объект должен быть «пустым». Тогда у него не будет функциональных кнопок и иконки времени, а заголовком будет являться «Нет задач» (этот текст задается при создании пустого объекта в файле «TodoList.jsx» в методе «render()»).
5.7 Кратко о взаимодействии компонентов
Итак, основа всего интерфейса приложения - класс «TodoList». В нем описывается основная структура и поведение приложения, а также подключаются и используются другие компоненты данного проекта, а именно: «TodoListItem» (элемент списка задач), «TodoListAdd» (форма добавления нового элемента), «TodoListItemEdit» (форма редактирования элемента, которая отображается вместо редактируемого элемента в таблице списка задач), а также «TodoVoiceControl» (компонент-кнопка, используемая для активации функции голосового управления приложением).
Все эти компоненты отвечают за отображение данных и взаимодействие с пользователем.
Также в клиентской части проекта есть два файла - «TodoStore.js» и «TodoStoreActions.js», реализующие функционал хранилища данных для приложения (именно на стороне клиента). Хранилище отвечает за загрузку данных с сервера, их хранение в памяти и обработку, отправку изменений на сервер. Оно предоставляет всему остальному приложению возможность получать и изменять данные.
На этом описание архитектуры приложения заканчивается.
Заключение
Все задачи, указанные в пункте «Постановка задачи», были выполнены в полной мере. Приложение получилось простым, удобным и функциональным. Пользователю будет легко и комфортно взаимодействовать с программой.
Сложностей с поддержкой и развитием проекта также не должно возникнуть. Код получился вполне логичным и читаемым для глаз как опытного разработчика, так и новичка.
Было разработано веб-приложение, предоставляющее пользователю функционал «ежедневника» или «списка задач». В приложении есть календарь, с помощью которого можно переключаться на различные даты и добавлять задачи на нужный день. Также можно указать время начала «выполнения задачи».
В приложении реализована функция голосового управления, которая позволяет пользователю добавлять задачи в список без необходимости набирать текст на клавиатуре.
Интерфейс приложения выглядит интуитивно понятно, поэтому проблем у пользователя при взаимодействии с программой возникнуть не должно.
Весь изначально задуманный функционал реализован полностью. План выполнен, пользователи получат удовольствие при пользовании данным продуктом.
Список использованных источников
1 Wikipedia, the free encyclopedia [Электронный ресурс]: Cвободная общедоступная многоязычная универсальная энциклопедия. - Режим доступа: http://en.wikipedia.org/wiki/Main_Page. - Загл. с экрана. - яз. англ.
2 Современный учебник JavaScript [Электронный ресурс]: Ресурс, содержащий множество информации по языку JavaScript. - Режим доступа: https://learn.javascript.ru.- Загл. с экрана. - яз. рус.
3 React - a JavaScript library for building user interfaces. [Электронный ресурс]: Официальная документация от компании «Facebook» по JavaScript-фреймворку React. - Режим доступа: https://facebook.github.io/react.- Загл. с экрана. - яз. англ.
4 Habrahabr [Электронный ресурс]: Множество полезных статей по информационным технологиям в общем и веб-технологиям в частности. - Режим доступа: https://habrahabr.ru.- Загл. с экрана. - яз. рус.
5 NPM [Электронный ресурс]: Каталог модулей менеджера пакетов NPM для NodeJS. - Режим доступа: https://www.npmjs.com.- Загл. с экрана. - яз. англ.
6 Mozilla Developer Network [Электронный ресурс]: Большой справочник по веб-разработке. Множество полезной и просто необходимой информации - Режим доступа: https://developer.mozilla.org/ru.- Загл. с экрана. - яз. рус.
7 GitHub [Электронный ресурс]: Большое количество проектов с исходными кодами и инструкциями по их использованию. - Режим доступа: https://github.com/.- Загл. с экрана. - яз. англ.
8 Google [Электронный ресурс]: Замечательный поисковик по интернету. Помогает найти всю нужную информацию. - Режим доступа: https://www.google.ru.- Загл. с экрана. - яз. рус.
Приложение
Исходные коды приложения:
server.js:
var fs = require('fs');
var path = require('path');
var url = require('url');
var express = require('express');
var bodyParser = require('body-parser');
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
var ensureLogin = require('connect-ensure-login');
var ICS = require('ics-js');
var moment = require('moment');
var app = express();
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123',
database: 'todolist',
charset: 'UTF8_UNICODE_CI'
});
function twoDigits(d) {
if(0 <= d && d < 10) return "0" + d.toString();
if(-10 < d && d < 0) return "-0" + (-1 * d).toString();
return d.toString();
}
function toMysqlFormat (date) {
return date.getUTCFullYear() + "-" + twoDigits(1 + date.getUTCMonth()) + "-" + twoDigits(date.getUTCDate()) + " "
+ twoDigits(date.getUTCHours()) + ":" + twoDigits(date.getUTCMinutes()) + ":" + twoDigits(date.getUTCSeconds());
}
connection.connect(function(err){
if(!err) {
console.log("Database is connected... nn");
} else {
console.log("Error connecting database... nn", err);
}
});
passport.use(new Strategy(
function(username, password, cb) {
connection.query('SELECT * FROM user WHERE name = "' + username + '";', function(err, rows) {
var user = rows[0];
if (err) { return cb(err); }
if (!user) { return cb(null, false); }
if (user.pass != password) { return cb(null, false); }
return cb(null, user);
});
})
);
passport.serializeUser(function(user, cb) {
cb(null, user.user_id);
});
passport.deserializeUser(function(id, cb) {
connection.query('SELECT * FROM user WHERE user_id = ' + id + ';', function (err, rows) {
var user = rows[0];
if (err) { return cb(err); }
cb(null, user);
});
});
app.set('port', (process.env.PORT || 3000));
app.use('/styles', express.static(path.join(__dirname, 'public/styles')));
app.use('/scripts', express.static(path.join(__dirname, 'public/scripts')));
app.use('/fonts', express.static(path.join(__dirname, 'public/fonts')));
app.use(require('cookie-parser')());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(require('express-session')({ secret: 'keyboard cat', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
var siteUrls = [
{pattern:'^/login/?$', restricted: false},
{pattern:'^/$', restricted: true},
{pattern:'^/todos/\\w+/?$', restricted: true},
{pattern:'^/todos/delete/\\w+/?$', restricted: true},
{pattern:'^/todos/get/\\w+/?$', restricted: true},
{pattern:'^/days/get/\\w+/?$', restricted: true},
{pattern:'^/scripts/[\\w+.]*?$', restricted: true},
{pattern:'^/logout/?$', restricted: true}
];
function authorizeUrls(urls) {
function authorize(req, res, next) {
var requestedUrl = url.parse(req.url).pathname;
for (var ui in urls) {
var pattern = urls[ui].pattern;
var restricted = urls[ui].restricted;
if (requestedUrl.match(pattern)) {
if (restricted) {
if (req.isAuthenticated()) {
// если все хорошо, просто переходим к следующим правилам
next();
return;
}
else{
// пользователь не авторизирован, отправляем его на страницу логина
res.writeHead(303, {'Location': '/login'});
res.end();
return;
}
}
else {
next();
return;
}
}
}
// сюда мы попадаем, только если в цикле не нашлось совпадений
console.log('common 404 for ', req.url);
res.end('404: there is no ' + req.url + ' here');
}
return authorize;
}
app.use('/', authorizeUrls(siteUrls));
app.get('/', function(req, res) {
res.sendFile(__dirname + '/public/index.html');
});
function sentTODOS(req, res) {
var date = moment(req.body.date);
var q = 'select id, name, time, done, DATE_FORMAT(date, "%Y-%m-%d") AS date from todos WHERE user_id = ' + req.user.user_id +
' and date = "' + date.format("YYYY-MM-DD") + '";';
connection.query(q, function(err, rows, fields) {
if (!err)
res.json(rows);
else
console.log('Error while performing READ Query.');
});
}
app.post('/todos/get', sentTODOS);
app.get('/login', function(req, res){
res.sendFile(__dirname + '/public/login.html');
});
app.get('/todos/export', function(req, res){
res.sendFile(__dirname + '/public/export.html');
});
app.post('/login', passport.authenticate('local', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/');
});
app.post('/todos/change', function (req, res) {
item = req.body;
if (item.time == 0 || item.time == null) item.time = 'NULL';
else item.time = '"' + item.time + '"';
var q = 'UPDATE todos SET name="' + item.name + '", description="' + item.description +
'", date="' + item.date + '", time=' + item.time + ', done=' + item.done +
' WHERE id = ' + item.id + ' and user_id = ' + req.user.user_id + ';';
console.log(q);
connection.query(q,
function (err, rows, fields) {
if (err) console.log('Error while performing UPDATE Query: ' + err);
else res.send("Ok");
});
});
app.post('/todos/add', function (req, res) {
item = req.body;
if (item.time == 0 || item.time == null) item.time = 'NULL';
else item.time = '"' + item.time + '"';
item.date = new Date(item.date).toISOString().slice(0, 10);
var q = 'INSERT INTO todos (name, description, date, time, user_id, done) values ("' +
item.name + '", "' + item.description + '", "' + item.date + '", ' + item.time + ', ' +
req.user.user_id + ', ' + item.done + ');';
console.log(q);
connection.query(q,
function (err, rows, fields) {
if (err) console.log('Error while performing ADD Query: ' + err);
else res.send("Ok");
});
});
app.post('/todos/delete', function (req, res) {
item = req.body;
console.log(item);
connection.query('DELETE FROM todos WHERE id = ' + item.id + ' and user_id = ' + req.user.user_id + ';',
function (err, rows, fields) {
if (err) console.log('Error while performing DELETE Query: ' + err);
else res.send("Ok");
});
});
app.post('/todos/delete/all', function (req, res) {
connection.query('DELETE FROM todos WHERE user_id = ' + req.user.user_id,
function (err, rows) {
if (err) console.log('Error while performing DELETE ALL Query: ' + err);
else res.send("Ok");
});
});
app.post('/todos/delete/done', function (req, res) {
connection.query('DELETE FROM todos WHERE done = true and user_id = ' + req.user.user_id,
function (err, rows) {
if (err) console.log('Error while performing DELETE DONE Query: ' + err);
else res.send("Ok");
});
});
app.post('/todos/delete/old', function (req, res) {
var date = moment(req.body.date);
var q = 'DELETE FROM todos WHERE user_id = ' + req.user.user_id +
' and date < "' + date.format('YYYY-MM-DD') + '"';
connection.query(q, function (err, rows) {
if (err) console.log('Error while performing DELETE OLD Query: ' + err);
else res.send("Ok");
});
});
app.post('/todos/delete/onday', function (req, res) {
var date = moment(req.body.date);
var q = 'DELETE FROM todos WHERE user_id = ' + req.user.user_id +
' and date = "' + date.format('YYYY-MM-DD') + '"';
connection.query(q, function (err, rows) {
if (err) console.log('Error while performing DELETE ON DAY Query: ' + err);
else res.send("Ok");
});
});
app.post('/days/get/active', function (req, res) {
var month = req.body.month;
var q = 'select DISTINCT DAY(date) as date from todos where user_id = ' + req.user.user_id + ' and MONTH(date) = ' + month + ";";
connection.query(q, function (err, rows) {
if (err) console.log('Error while performing GET ACTIVE DAYS Query: ' + err);
else res.send(rows);
});
});
app.get('/todos/get/all', function (req, res) {
var q = 'select * from todos where user_id = ' + req.user.user_id + ";";
connection.query(q, function (err, rows) {
if (err) console.log('Error while performing GET ALL TODOS Query: ' + err);
else res.send(rows);
});
});
app.get('/logout',
function(req, res){
req.logout();
res.redirect('/');
});
app.use(function(req, res, next) {
res.setHeader('Cache-Control', 'no-cache');
next();
});
app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.get('port'), app.settings.env);
app.js:
import TodoList from './scripts/TodoList';
import TodoStore from './scripts/TodoStore';
import ReactDOM from 'react-dom';
import React from 'react';
import 'styles/main';
const render = () => {
ReactDOM.render(
<TodoList store={TodoStore} />,
document.getElementById("toDoListApp")
);
};
TodoStore.subscribe(render);
render();
TodoList.jsx:
import React from 'react';
import 'styles/todolist'
import TodoListItem from './TodoListItem';
import TodoListAdd from './TodoListAdd';
import TodoListItemEdit from './TodoListItemEdit';
import TodoVoiceControl from './TodoVoiceControl';
import DayPicker, { DateUtils } from "react-day-picker";
import 'react-day-picker/lib/style.css';
import * as act from './TodoStoreActions';
class TodoList extends React.Component {
constructor(props) {
super(props);
this.store = this.props.store;
this.state = {editableItemID: null};
this.state = {messageToShow: "", messageTimer: null};
this.state = {activeDays: []};
}
componentDidMount() {
this.todosLoad();
this.activeDaysLoad();
}
activeDaysLoad() {
let month = this.store.getState().date.getMonth() + 1;
fetch('/days/get/active', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({month: month})
})
.then((response) => {
return response.json();
})
.then((data) => {
let res = [];
for (let key in data) {
res.push(data[key].date);
}
this.setState({activeDays: res});
})
.catch(alert);
}
todosLoad() {
this.store.dispatch(act.todosLoad(this.store.getState().date));
this.activeDaysLoad();
}
todoToggle(item) {
item.done = !item.done;
this.store.dispatch(act.todosChange(item));
if (item.done) this.showSuccessMessage("Задача выполнена! Вы молодец!");
else this.showSuccessMessage("Задача не выполнена обратно");
}
todosAddItem(newItem) {
newItem.date = this.store.getState().date.toISOString();
this.store.dispatch(act.todosAdd(newItem));
this.showSuccessMessage("Задача добавлена в список");
this.todosLoad();
}
showSuccessMessage(msg) {
this.setState({messageToShow: msg});
clearTimeout(this.state.messageTimer);
this.setState({messageTimer: setTimeout(() => {
this.setState({messageToShow: ""});
}, 5000)});
}
todosDeleteItem(id) {
this.store.dispatch(act.todosDelete(id));
this.showSuccessMessage("Задача удалена из списка");
this.todosLoad();
}
todosSetEditableItem(id) {
this.setState({editableItemID: id});
}
todosChangeItem(item) {
this.store.dispatch(act.todosChange(item));
this.showSuccessMessage("Изменения успешно внесены");
this.todosLoad();
}
todosDeleteDone() {
this.store.dispatch(act.todosDeleteDone());
this.showSuccessMessage("Выполненные задачи удалены из списка");
this.todosLoad();
}
todosDeleteAll() {
this.store.dispatch(act.todosDeleteAll());
this.showSuccessMessage("Все задачи были успешно удалены");
this.todosLoad();
}
todosDeleteOld() {
this.store.dispatch(act.todosDeleteOld(this.store.getState().date.toISOString()));
this.showSuccessMessage("Старые задачи были успешно удалены");
this.todosLoad();
}
todosDeleteOnDay(day) {
this.store.dispatch(act.todosDeleteOnDay(day));
this.showSuccessMessage("Задачна этот день удалены");
this.todosLoad();
}
handleVoiceControl(text) {
let arr = text.split(" ");
if (arr[0].toLowerCase() == "добавить" && arr[1].toLowerCase() == "задачу") {
let task = "", time = "";
if (arr.length > 2) {
let i;
for (i = 2; i < arr.length; i++) {
if (arr[i].toLowerCase() != "время") task += arr[i] + " ";
else break;
}
if (arr[i] == "время") {
if (arr[i+1]) time = arr[i+1];
if (arr[i+2]) time += ":" + arr[i+2];
else time += ":00";
}
}
this.todosAddItem({
name: task,
done: false,
time: time
});
}
else if (arr[0] == "удалить") {
if (arr[1] == "выполненные" && arr[2] == "задачи") {
this.todosDeleteDone();
}
else if (arr[1] == "все" && arr[2] == "задачи") {
this.todosDeleteAll();
}
else if (arr[1] == "задачи" && arr[2] == "на" && arr[3] == "этот" && arr[4] == "день") {
this.todosDeleteOnDay(this.store.getState().date);
}
else if (arr[1] == "задачи" && arr[2] == "до" && arr[3] == "этого" && arr[4] == "дня") {
this.todosDeleteOld();
}
}
}
onDayChange(e, day) {
this.store.dispatch(act.dateSet(day));
this.todosLoad();
}
onMonthChange(date) {
this.store.dispatch(act.dateSetMonth(date.getMonth()));
this.todosLoad();
}
todosExportToICS() {
}
render() {
let iconTime = <span className="glyphicon glyphicon-time"></span>;
let alertBox = (this.state.messageToShow) ?
<div className="alert alert-success todo-alert">{this.state.messageToShow}</div>: "";
const { todos } = this.store.getState();
let items = [];
for (let i = 0; i < todos.length; i++) {
let item = todos[i];
if (item.id !== this.state.editableItemID) {
items.push(
<TodoListItem item={item} key={item.id}
onClick={this.todoToggle.bind(this)}
onDeleteClick={this.todosDeleteItem.bind(this)}
onEditClick={this.todosSetEditableItem.bind(this)}
/>
)
}
else {
items.push(
<TodoListItemEdit item={item} key={item.id}
onCancel={this.todosSetEditableItem.bind(this, null)}
onSave={this.todosChangeItem.bind(this)}
/>
)
}
}
if (!items.length) {
items.push (
<TodoListItem item={{name: "Нет задач"}} key={0} empty />
)
}
const activeDays = {
active: day => { return (this.state.activeDays.indexOf(day.getDate()) != -1 &&
this.store.getState().date.getMonth() == day.getMonth())}
}
return (
<table id="app"><tbody>
<tr>
<td>
<div className="calendar">
<DayPicker
modifiers={activeDays}
onDayClick={this.onDayChange.bind(this)}
onMonthChange={this.onMonthChange.bind(this)}
initialMonth={ this.store.getState().date }
selectedDays={ day => DateUtils.isSameDay(this.store.getState().date, day) }
/>
</div>
<div className="calendar-date-nav">
<div className="btn btn-default btn-block"
onClick={() => {
let day = new Date();
day.setDate(day.getDate() - 1);
this.onDayChange(null, day);
}}>
Вчера
</div>
<div className="btn btn-primary btn-block"
onClick={() => this.onDayChange(null, new Date())}>
Сегодня
</div>
<div className="btn btn-default btn-block"
onClick={() => {
let day = new Date();
day.setDate(day.getDate() + 1);
this.onDayChange(null, day);
}}>
Завтра
</div>
<div className="btn btn-default btn-block"
onClick={() => {
let day = new Date();
day.setDate(day.getDate() + 2);
this.onDayChange(null, day);
}}>
Послезавтра
</div>
</div>
</td>
<td width="100%">
<div className="to-do-list">
<table className="table-curved">
<thead>
<tr>
<th className="todo-item-time">Time</th>
<th width="100%">{this.store.getState().date.toDateString()}</th>
</tr>
</thead>
<tbody>
{items}
</tbody>
</table>
<TodoListAdd handleTaskAdd={this.todosAddItem.bind(this)} />
</div>
</td>
<td>
<div className="right-sidebar">
<a href="/logout" className="btn btn-default btn-block">Выход</a>
<div className="btn btn-default btn-block"
onClick={this.todosDeleteDone.bind(this)}>
Удалить выполненные
</div>
<div className="btn btn-default btn-block"
onClick={this.todosDeleteOld.bind(this)}>
Удалить просроченные
</div>
<div className="btn btn-default btn-block"
onClick={this.todosDeleteOnDay.bind(this, this.store.getState().date)}>
Удалить задачи на этот день
</div>
<div className="btn btn-default btn-block"
onClick={this.todosDeleteAll.bind(this)}>
Удалить все
</div>
<a className="btn btn-default btn-block"
href="/todos/export">
Экспорт
</a>
<TodoVoiceControl onControl={this.handleVoiceControl.bind(this)} />
</div>
{alertBox}
</td>
</tr>
</tbody>
</table>
)
}
}
export default TodoList;
TodoListAdd.jsx:
import React from 'react';
class TodoListAdd extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.refs.name.focus();
}
handleSubmit(event) {
event.preventDefault();
let newItem = {
name: this.refs.name.value,
time: this.refs.time.value,
done: false
};
event.target.reset();
this.refs.name.focus();
this.props.handleTaskAdd(newItem);
}
render() {
return (
<form method="post" onSubmit={this.handleSubmit} className="task-add-block form-horizontal">
<input type="text" defaultValue="" ref="name" className="form-control" placeholder="Имя..."></input>
<div className="form-group">
<div className="col-sm-6"><input type="time" defaultValue="" ref="time" className="form-control"></input></div>
<div className="col-sm-6"><input type="submit" defaultValue="" className="form-control"></input></div>
</div>
</form>
)
}
}
export default TodoListAdd;
TodoListItem.jsx:
import React from 'react';
class TodoListItem extends React.Component {
constructor(props) {
super(props);
this.handleTodoClick = this.handleTodoClick.bind(this);
this.handleDelete = this.handleDelete.bind(this);
this.handleEdit = this.handleEdit.bind(this);
}
handleTodoClick(event) {
if (event.target.tagName != "TD") return;
this.props.onClick(this.props.item);
}
handleDelete() {
this.props.onDeleteClick(this.props.item.id);
}
handleEdit() {
this.props.onEditClick(this.props.item.id);
}
render() {
let item = this.props.item;
let timeCellContent = (item.done) ? <span className="glyphicon glyphicon-ok"></span>:
(item.time) ? item.time.slice(0, 5): <span className="glyphicon glyphicon-time"></span>;
let trClass = ((!!item.done) ? "success": "");
if (!this.props.empty)
return (
<tr className={trClass}>
<td className="todo-item-time">{timeCellContent}</td>
<td width="100%" onClick={this.handleTodoClick}>
{item.name}
<div className="todo-item-control">
<span className="glyphicon glyphicon-pencil" onClick={this.handleEdit}></span>
<span className="glyphicon glyphicon-remove" onClick={this.handleDelete}></span>
</div>
</td>
</tr>
)
else return (
<tr>
<td className="todo-item-time"></td>
...Подобные документы
Разработка консольного приложения: описание и сценарий использования программы, интерфейс пользователя. Поэтапное описание создание кода компьютерной игры "Крестики нолики". Функциональные и нефункциональные требования, описание исключительных ситуаций.
методичка [819,6 K], добавлен 12.05.2013Описание технологии asp.net. Страницы веб-приложения, тестирование системы. Описание функциональной, динамической модели системы. Диаграммы вариантов использования, последовательности, база данных приложения. Реализация программы, интерфейс, тестирование.
курсовая работа [3,2 M], добавлен 30.01.2013Мультимедийное представление информации. Разработка структуры сайта, макетов страниц, серверной логики и компьютерного кода, интерфейса. Описание шагов для размещения презентации в сети интернет. Затраты на разработку приложения и экономический эффект.
дипломная работа [539,0 K], добавлен 18.10.2015Создание многоуровневого приложения с Web-интерфейсом выставления оценки фильму и просмотра оценок других пользователей. Клиентская часть приложения. Разработка многопользовательского веб-приложения на ASP.NET MVC 3 с разграничением доступа к данным.
курсовая работа [949,7 K], добавлен 22.02.2015Назначение и возможности разработанного приложения для контроля активности сетевых и периферийных устройств предприятия. Язык программирования Java. Распределенные многоуровневые приложения. Структура базы данных, интерфейс разработанного приложения.
курсовая работа [1,0 M], добавлен 16.12.2012Описание визуальных компонентов. Использование чужеродных компонентов-CTIVEX, компонент Grid. Набор свойств, которые имеет каждый визуальный компонент, их установка программно или при проектировании приложения. Примеры приложения с компонентами.
реферат [976,6 K], добавлен 19.10.2008Основные преимущества и возможности объектно-ориентированного языка программирования С#. Руководство пользователя: установка приложения, эксплуатация ежедневника, назначение полей, кнопок и пунктов меню. Руководство программиста. Событие элемента Timer.
курсовая работа [4,5 M], добавлен 16.08.2012Основные инструменты построения Web-приложения. Язык сценариев PHP. Системный анализ предметной области базы данных. Коды SQL запросов на создание таблиц. Разработка Web-приложения. Описание функциональности модулей. Система управления содержимым статей.
курсовая работа [4,8 M], добавлен 28.04.2014Общее определение JavaScript-библиотеки, виды библиотек. Создание клиентского приложения с использованием одного из существующий JS-фреймворков. Значение, виды и выбор фреймворка. Выбор приложения и его тематики. Написание программного кода, итоги работы.
курсовая работа [545,8 K], добавлен 21.12.2013Создание приложения для получения информации о расписании движения междугороднего транспорта Владимирской области. Параметры совместимости приложения с различными версиями Android. Схема взаимодействия между классами. Описание внешнего вида интерфейса.
контрольная работа [2,5 M], добавлен 17.02.2016Общая схема работы приложения Android. Разработка обучающего приложения для операционной системы Android, назначение которого - развитие речи посредством произнесения скороговорок. Описание компонентов разработанного приложения, его тестирование.
дипломная работа [1,2 M], добавлен 04.02.2016Спецификация требований к разрабатываемому приложению. Разработка структурной схемы интерфейса. Описание алгоритма шифрования DES. Разработка программного кода приложения "DES". Проведение исследования основных шагов для генерации ключей и шифрования.
курсовая работа [398,4 K], добавлен 13.12.2022Основные концепции разработки приложения в трёхуровневой архитектуре. Проектное решение, реализующее модель реляционной БД. Спецификация на разработку интерфейса. Описание выполнения транзакций прибытия и убытия судна. Инсталляционные файлы приложения.
курсовая работа [4,0 M], добавлен 26.12.2011Проектирование вариантов использования приложения. Анализ существующей версии приложения. Обоснование выбора инструментальных программных средств. Проектирование интерфейса пользователя. Адаптация под мобильные устройства. Описание программного продукта.
курсовая работа [2,8 M], добавлен 25.06.2017Анализ моделируемого приложения и постановка задачи. Диаграмма прецедентов, деятельности объектов и состояния классов. Разработка приложения-игры, выбор языка программирования и среды для разработки проекта, интерфейс приложения и ресурсы проекта.
курсовая работа [308,5 K], добавлен 14.10.2011Реализация проекта по оптимизации отделений почтовой связи. Направления деятельности в области кадровой политики. Автоматизация обработки получаемой техническим отделом информации. Разработка приложения клиент-сервер. Описание клиентского приложения.
курсовая работа [34,3 K], добавлен 07.08.2013Общее описание разрабатываемого приложения, его актуальность и сферы практического применения. Выбор среды разработки и языка программирования, 3D-движка. Архитектура приложения, интерфейса и его главных элементов, взаимодействие с пользователем.
дипломная работа [317,5 K], добавлен 10.07.2017Разработка приложения, которое будет выполнять функции показа точного времени и точной даты. Определение дополнительных функций разработанного приложения. Рассмотрение основных этапов создания программного продукта. Результаты тестирования приложения.
курсовая работа [2,2 M], добавлен 14.04.2019Описание приложения в виде пользовательского сценария. Проектирование обмена сообщениями между модулями. Разработка общей структуры приложения. Обзор структуры файлов. Разработка получения данных со страницы. Характеристика результата работы программы.
дипломная работа [1,5 M], добавлен 22.03.2018Описание предметной области. Характеристика программных средств. Описание компонентов, интерфейс программы. Описание процедур и функций. Вызов и загрузка программы. Испытание методом белого и черного ящика на ошибки кода программного приложения.
курсовая работа [2,2 M], добавлен 26.04.2015