Разработка с Flask, ReactJS, gulp.js, bower, Browserify. Часть 1.

main-logo
image-4292

Давайте посмотрим на небольшую, но мощную JavaScript UI библиотеку ReactJS в действии. Это приложение работает на Python 3 и фремворке Flask, на back-end. Так же мы будем использовать на front-end: gulp.js (для выполнения задач), bower(front-end менеджер пакетов), и Browserify(пакетирование зависимостей JavaScript).

ReactJS

Реагировать библиотека, а не фреймворк. В отличие от клиентских MVC фреймворков таких как: Backbone, Ember, и AngularJS, React не делает никаких предположений о вашей технологии стека, так что вы можете легко интегрировать его в новый или унаследованного кода. Это часто используется, чтобы управлять конкретных областей UI приложение, а не всем пользовательским интерфейсом.

React заботится только о пользовательском интерфейсе, который определяется иерархией модульных views компонентов. Если вы знакомы с Anmgular эти компоненты аналогичны директивам. Компоненты используют синтаксис XML, именуемый JSX, который собирает в JavaScript.

Так компоненты определяются в иерархическом порядке, вам не придется повторно рендерить весь DOM при изменении состояния. Вместо этого, он использует виртуальную DOM, что только повторное оказывает индивидуальные компоненты после изменения состояния, в невероятно быстрых скоростях!

Настройка проекта

Давайте начнем с того, что мы знаем: Flask.

Скачать шаблонный кода из репозитория, извлеките файлы, создавайте и активируйте virtualenv, и установите необходимые модули: pip install -r requirements.txt

Теперь давайте запустим приложение:

 $ sh run.sh 

React — первый раунд

Давайте посмотрим на простой компонент.

Компонент: переход от статичного к React

Мы добавим этот скрипт JSX в наш hello.html. Остановитесь на минутку, чтобы проверить его.

<script type="text/jsx">
  /*** @jsx React.DOM */
  var realPython = React.createClass({
    render: function() {
      return (
<h2>Greetings, from Real Python!</h2>
)
    }
  });
  React.render(
    React.createElement(realPython, null),
    document.getElementById('content')
  );
</script>

Что происходит?

  1. Мы создаем компонент с помощью вызова createClass(), и дали ему имя realPython. React.createClass() принимает один аргумент, объект.
  2. Внутри этого объекта мы добавили функцию render, которая декларативно обновляет DOM во время вызова.
  3. Затем идет возвращение значения Greetings, from Real Python!, в JSX, который представляет актуальный HTML элемент, который будет добавлен к DOM.
  4. В конце, React.render() создает экземпляр компонента realPython и вводит элемент в DOM с ID селектором content.

Трансформация

Что дальше? Мы должны «трансформировать», или скомпилировать, JSX в JavaScript. Это делается очень просто. Обновляем hello.html:

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="UTF-8">
    <title>Flask React</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- styles -->
  </head>
  <body>
<div class="container">
<h1>Flask React</h1>
<div id="content"></div>
    </div>
    <!-- scripts -->
    <script src="http://cdnjs.cloudflare.com/ajax/libs/react/0.12.0/react.js"></script>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/react/0.12.0/JSXTransformer.js"></script>
    <script type="text/jsx">
      /*** @jsx React.DOM */
      var helloWorld = React.createClass({
        render: function() {
          return (
<h2>Greetings, from Real Python!</h2>
)
        }
      });
      React.render(
        React.createElement(helloWorld, null),
        document.getElementById('content')
      );
    </script>
  </body>
</html>

Здесь мы добавили скрипты react.js и JSXTransformer.js. Последний из них используется для «преобразования» синтаксис JSX в обычный JavaScript в браузере.

Обратите внимание, мы не добавили JQuery, так как это не требуется для React.

Вот и все. Запустите сервер Flask и проверьте результат в браузере по адресу: http://localhost:5000/hello

flask-react-hello-world
image-4293

Bower

Вместо того чтобы вручную скачивать JavaScript файлы или подключать их с CDN, давайте использовать Bower для лучшего управлениями этими зависимостями. Bower это мощный менеджер пакетов для фронтальных зависимостей, таких как: JQuery, Bootstrap, React, Angular, Backbone.

Убедитесь, что у вас уже установлен Node и npm. Если его нет то установите его.

Инициализация

Установим Bower с помощью npm:

$ npm install -g bower

npm это менеджер пакетов используется для управления модулями Node. В отличие от PyPi/pip, npm по умолчанию устанавливает зависимости локально. Флаг -g используется для переопределения, чтобы установить Bower глобально.

bower.json

Bower использует файл bower.json для определения зависимостей проекта, который похож на файл requirements.txt. Выполните следующую команду, чтобы в интерактивном режиме создать этот файл:

$ bower init

Просто нажимайте enter что бы установить значения по умолчанию. После этого файл bower.json должен выглядеть примерно так:

{
"name": "ultimate-flask-front-end",
"version": "0.0.1",
"authors": [
"Michael Herman <michael@realpython.com>"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

npm

npm использует аналогичный bower.json файл, называется package.json, он определяет зависимости проекта. Вы также можете создать его в интерактивном режиме:

$ npm init

Так же установите значения по умолчанию:

{
"name": "ultimate-flask-front-end",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

Теперь, давайте добавим файл bower в npm зависимости:

$ npm install --save-dev bower

Конфигурация

Наряду с файлом bower.json, мы можем определить параметры конфигурации Bower в файле .bowerrc. Создадим такой файл в корне проекта. Структура проекта должна теперь выглядеть следующим образом:

├── .bowerrc
├── .gitignore
├── bower.json
├── package.json
├── project
│   ├── app.py
│   ├── static
│   │   ├── css
│   │   │   └── style.css
│   │   └── scripts
│   └── templates
│       ├── hello.html
│       └── index.html
├── requirements.txt
└── run.sh

По умолчанию Bower установит пакеты в каталоге «bower_components» в корне проекта. Мы должны изменить это, так как Flask необходимо что бы файлы лежали в static. Таким образом, добавьте следующий JSON код в файл .bowerrc, так что бы Bower автоматически устанавливал файл в нужную директорию:

{
"directory": "./project/static/bower_components"
}

Инициализация

Мы должны установить следующие пакеты для этого проекта:

  • Bootstrap
  • jQuery
  • React

Это можно сделать двумя способами:

  1. Запустите bower install --save для каждого из пакетов (флаг —save добавляет зависимость (название и версию) в файле bower.json.).
  2. Обновите файл bower.json добавив названию и версию пакета, а затем запустить bower install, чтобы установить все зависимости от файла.

Давайте воспользуемся вторым способом.

{
"name": "ultimate-flask-front-end",
"version": "0.0.1",
"authors": [
"Michael Herman <michael@realpython.com>"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"./project/static/bower_components",
"test",
"tests"
],
"dependencies": {
"bootstrap": "~3.3.2",
"jquery": "~2.1.3",
"react": "~0.12.2"
}
}

И запустим bower install

$ bower install
bower cached git://github.com/twbs/bootstrap.git#3.3.2
bower validate 3.3.2 against git://github.com/twbs/bootstrap.git#~3.3.2
bower cached git://github.com/jquery/jquery.git#2.1.3
bower validate 2.1.3 against git://github.com/jquery/jquery.git#~2.1.3
bower cached git://github.com/facebook/react-bower.git#0.12.2
bower validate 0.12.2 against git://github.com/facebook/react-bower.git#~0.12.2
bower install react#0.12.2
bower install jquery#2.1.3
bower install bootstrap#3.3.2

react#0.12.2 project/static/bower_components/react

jquery#2.1.3 project/static/bower_components/jquery

bootstrap#3.3.2 project/static/bower_components/bootstrap
└── jquery#2.1.3

Теперь вы можете проверить файлы в каталоге «project/static/bower_components».

Тест

Обновите hello.html:

<script src="{{ url_for('static', filename='bower_components/react/react.min.js') }}"></script>
<script src="{{ url_for('static', filename='bower_components/react/JSXTransformer.js') }}"></script>

Протестируйте приложение, чтобы убедиться, что оно все еще работает.

Валидация AngularJS форм с ngMessages

angular
image-3100

Angular всегда стремились предоставить инструменты для облегчения работы с формами.
В Angular 1.3, есть новый инструмент для создания и управления сообщениями формамы — ngMessages. Этот модуль помогает нам справиться с отображением сообщений об ошибках валидации форм.

Вывод сообщения без ngMessages

Давайте взглянем на то, как формы выглядят без использования этого модуля.

<form name="userForm">
    <input
        type="text"
        name="username"
        ng-model="user.username"
        ng-minlength="3"
        ng-maxlength="8"
        required>
    <!-- show an error if username is too short -->
    <p ng-show="userForm.username.$error.minlength">Username is too short.</p>
    <!-- show an error if username is too long -->
    <p ng-show="userForm.username.$error.maxlength">Username is too long.</p>
    <!-- show an error if this isn't filled in -->
    <p ng-show="userForm.username.$error.required">Your username is required.</p>
</form>

Мы явно показывая каждое сообщение об ошибке только если ошибка существует. Это может стать утомительным, если у нас есть много видов ошибок, которые мы хотим показать.
Тут и понадобится ngMessages. Этот модуль дает некоторый порядок к сообщениям валидации.

Быстрый взгляд на ngMessages

Давайте используем предыдущий пример и посмотрим, как это будет выглядеть с ngMessages.

<form name="userForm">
    <input
        type="text"
        name="username"
        ng-model="user.username"
        ng-minlength="3"
        ng-maxlength="8"
        required>
    <div ng-messages="userForm.name.$error">
        <p ng-message="minlength">Your name is too short.</p>
        <p ng-message="maxlength">Your name is too long.</p>
        <p ng-message="required">Your name is required.</p>
    </div>
</form>

Гораздо лучше. ngMessages будет обрабатывать, показывая и скрывая определенные сообщения, основанные на ошибках.

Использование ngMessages

Установить ngMessages очень просто. Нам просто нужно загрузить модуль после AngularJS и затем добавить его в нашем приложении.

Загрузка зависимости и добавление

<!-- load angular -->
<script src="//code.angularjs.org/1.4.0/angular.js"></script>
<!-- load ngmessages -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular-messages.js"></script>
<!-- load our custom app -->
<script src="app.js"></script>

Теперь мы можем добавить в наше приложение в app.js.

angular.module('app', ['ngMessages']);

Отображение сообщений

Просто используйте директиву ng-messages и передайте в поле то что вы хотите увидеть при возникновении ошибки.
Формат использования:

<div ng-messages="<formName>.<inputName>.$error">
    <p ng-message="<validationName>">Your message here.</p>
</div>

Заключение

С помощью этого простого модуля, валидация форм AngularJS становится гораздо проще. Попробуйте его в ваших собственных приложениях и вы сами все поймете.

Зум слайдер

Простой слайдер контента с функцией зума в каждом слайде.

ZoomSlider
image-3065

Демо

Каждый слайд имеет предопределенный области масштабирования, который будет использоваться для расчета соответствующего значения масштаба для полноэкранной заливки. После того, как значок масштабирования нажат, область увеличения, а также страницы масштабируются, создавая иллюзию, что зритель приближается к контенту.

Мы используем CSS переходы и dymanic.js для перемещения элементов слайд. Dymanic.js Майкла Виллара является библиотекой JavaScript для создания физики на основе анимации.

Пожалуйста, обратите внимание, что мы используем пару современных свойств CSS, так что только современные браузеры поддерживают его.

HTML

<!-- Main container -->
<div class="container">
	<!-- Blueprint header -->
	<header class="bp-header cf">
		<!-- Page title etc. -->
	</header>
	<!-- Grid -->
	<section class="slider">
		<div class="slide slide--current" data-content="content-1">
			<div class="slide__mover">
				<div class="zoomer flex-center">
					<img class="zoomer__image" src="images/iphone.png" alt="iPhone" />
					<div class="preview">
						<img src="images/iphone-content-preview.png" alt="iPhone app preview" />
						<div class="zoomer__area zoomer__area--size-2"></div>
					</div>
				</div>
			</div>
			<h2 class="slide__title"><span>The Classy</span> iPhone 6</h2>
		</div>
		<div class="slide" data-content="content-2">
			<!-- ... -->
		</div>
		<!-- ... -->
		<nav class="slider__nav">
			<button class="button button--nav-prev">
				<i class="icon icon--arrow-left"></i>
				<span class="text-hidden">Previous product</span>
			</button>
			<button class="button button--zoom">
				<i class="icon icon--zoom"></i>
				<span class="text-hidden">View details</span>
			</button>
			<button class="button button--nav-next">
				<i class="icon icon--arrow-right"></i>
				<span class="text-hidden">Next product</span>
			</button>
		</nav>
	</section>
	<!-- /slider-->
	<!-- content -->
	<section class="content">
		<div class="content__item" id="content-1">
			<img class="content__item-img rounded-right" src="images/iphone-content.png" alt="Apple Watch Content" />
			<div class="content__item-inner">
				<h2>The iPhone 6</h2>
				<h3>Incredible performance for powerful apps</h3>
				<p>...</p>
			</div>
		</div>
		<div class="content__item" id="content-2">
			<!-- ... -->
		</div>
		<!-- ... -->
		<button class="button button--close">
			<i class="icon icon--circle-cross"></i>
			<span class="text-hidden">Close content</span>
		</button>
	</section>
	<!-- /content -->
</div>
<script src="js/classie.js"></script>
<script src="js/dynamics.min.js"></script>
<script src="js/main.js"></script>

CSS

/* Helper classes */
html,
body {
	overflow: hidden;
	height: 100%;
}
.container {
	position: relative;
	overflow: hidden;
	overflow-y: scroll;
	width: 100%;
	height: 100%;
	-webkit-overflow-scrolling: touch;
}
.noscroll .container {
	overflow-y: hidden;
}
.slider {
	position: relative;
	z-index: 200;
	width: 100%;
	margin: 0 auto;
	padding: 0 0 7em;
	text-align: center;
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
	-webkit-touch-callout: none;
	-khtml-user-select: none;
}
.slide {
	position: absolute;
	top: 0;
	visibility: hidden;
	width: 100%;
	opacity: 0;
}
.slide--current {
	position: relative;
	z-index: 100;
	visibility: visible;
	opacity: 1;
}
.slide__mover {
	position: relative;
	z-index: 100;
}
.slide__title {
	font-size: 1.75em;
	font-weight: normal;
	margin: 0 auto;
	padding: 1em 0 0 0;
}
.slide__title span {
	font-size: 55%;
	font-weight: bold;
	display: block;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: #35303d;
}
.slider__nav {
	position: absolute;
	bottom: 2em;
	width: 100%;
	text-align: center;
}
.button {
	font-size: 1.31em;
	position: relative;
	display: inline-block;
	overflow: hidden;
	margin: 0 25px;
	padding: 0;
	cursor: pointer;
	color: #5c5edc;
	border: none;
	background: none;
}
.button:focus {
	outline: none;
}
.button:hover {
	color: #fff;
}
.text-hidden {
	position: absolute;
	top: 200%;
}
.button--close {
	font-size: 1.55em;
	position: absolute;
	top: 30px;
	right: 30px;
	margin: 0;
	opacity: 0;
	color: #50505a;
	-webkit-transition: opacity 0.3s;
	transition: opacity 0.3s;
}
.content--open .button--close {
	opacity: 1;
}
/* Zoomer */
.zoomer {
	position: relative;
	height: 360px; /* this is needed for IE10 so that vertical flexbox centering works */
}
.flex-center {
	display: -webkit-flex;
	display: -ms-flexbox;
	display: flex;
	-webkit-align-items: center;
	-ms-flex-align: center;
	align-items: center;
	-webkit-justify-content: center;
	-ms-flex-pack: center;
	justify-content: center;
}
.zoomer__image {
	display: block;
	margin: 0;
	-webkit-flex: none;
	-ms-flex: none;
	flex: none;
}
.zoomer__area,
.preview {
	position: absolute;
	top: 50%;
	left: 50%;
	-webkit-transform: translate3d(-50%,-50%,0);
	transform: translate3d(-50%,-50%,0);
}
.zoomer__area:focus {
	outline: none;
}
.zoomer__area--size-1 {
	/* Apple Watch */
	width: 96px;
	height: 118px;
}
.zoomer__area--size-2 {
	/* iPhone */
	width: 112px;
	height: 198px;
}
.zoomer__area--size-3 {
	/* MacBook */
	width: 315px;
	height: 200px;
}
.zoomer__area--size-4 {
	/* iPad */
	width: 150px;
	height: 200px;
}
.zoomer__area--size-5 {
	/* iMac */
	width: 315px;
	height: 189px;
}
.preview {
	overflow: hidden;
	background: #18191b;
}
.preview img {
	display: block;
	border-radius: inherit;
	-webkit-transform: translate3d(0,0,0);
	transform: translate3d(0,0,0);
}
.zoomer--active .preview img {
	-webkit-transform: translate3d(100%,0,0);
	transform: translate3d(100%,0,0);
}
.rounded {
	border-radius: 15px;
}
.rounded-right {
	border-radius: 0 15px 15px 0;
}
.preview__content {
	position: absolute;
	top: 0;
	left: 100%;
	width: 100%;
	height: 100%;
	border-radius: inherit;
}
/* Content */
.content {
	position: fixed;
	z-index: 1000;
	top: 0;
	left: -100%;
	overflow: hidden;
	overflow-y: scroll;
	width: 100%;
	height: 100vh;
	background: #18191b;
	-webkit-overflow-scrolling: touch;
}
.content--open {
	left: 0;
}
.content__item {
	position: absolute;
	top: 0;
	display: -webkit-flex;
	display: -ms-flexbox;
	display: flex;
	overflow: hidden;
	height: 0;
	min-height: 100%;
	margin: 0 auto;
	padding: 2em 0;
	pointer-events: none;
	opacity: 0;
	color: #fff;
	-webkit-align-items: center;
	-ms-flex-align: center;
	align-items: center;
}
.content__item--current {
	pointer-events: auto;
	opacity: 1;
}
.content__item--reset {
	height: auto;
}
.content h2 {
	font-size: 3.5em;
	font-weight: normal;
	margin: 0;
}
.content h3 {
	font-size: 1.95em;
	font-weight: normal;
	margin: 0.25em 0 0.5em;
	color: #685884;
}
.content p {
	font-size: 1.25em;
	line-height: 1.5;
}
.content__item-img {
	display: block;
	max-width: 40vw;
	max-height: 80vh;
	-webkit-transform: translate3d(-120%,0,0);
	transform: translate3d(-120%,0,0);
	-webkit-flex: none;
	-ms-flex: none;
	flex: none;
}
.content__item--current .content__item-img {
	-webkit-transform: translate3d(-10px,0,0);
	transform: translate3d(-10px,0,0);
}
.content__item-inner {
	padding: 0 10vw 0;
	opacity: 0;
	-webkit-transform: translate3d(0,50px,0);
	transform: translate3d(0,50px,0);
}
.content__item--current .content__item-inner {
	opacity: 1;
	-webkit-transform: translate3d(0,0,0);
	transform: translate3d(0,0,0);
}
/**************************/
/* All synced transitions */
/**************************/
.zoomer {
	-webkit-transition: -webkit-transform 0.5s;
	transition: transform 0.5s;
	-webkit-transition-timing-function: cubic-bezier(0.7,0,0.3,1);
	transition-timing-function: cubic-bezier(0.7,0,0.3,1);
}
.zoomer.zoomer--notrans {
	-webkit-transition: none;
	transition: none;
}
.zoomer__image {
	-webkit-transition: opacity 0.3s 0.3s;
	transition: opacity 0.3s 0.3s;
}
.zoomer--active .zoomer__image {
	opacity: 0;
	-webkit-transition-delay: 0s;
	transition-delay: 0s;
}
.preview img {
	-webkit-transition: -webkit-transform 0.6s 0.3s;
	transition: transform 0.6s 0.3s;
	-webkit-transition-timing-function: cubic-bezier(0.2,1,0.3,1);
	transition-timing-function: cubic-bezier(0.2,1,0.3,1);
}
.zoomer--active .preview img {
	-webkit-transition: -webkit-transform 0.3s;
	transition: transform 0.3s;
}
.content {
	-webkit-transition: left 0s;
	transition: left 0s;
}
.content__item {
	-webkit-transition: opacity 0s;
	transition: opacity 0s;
}
.content,
.content__item {
	/* delay for content to disappear and zoomer to start transitioning back to 0 */
	-webkit-transition-delay: 0.3s;
	transition-delay: 0.3s;
}
.content--open,
.content__item--current {
	-webkit-transition: none;
	transition: none;
}
.content__item-img {
	-webkit-transition: -webkit-transform 0.4s;
	transition: transform 0.4s;
	-webkit-transition-timing-function: cubic-bezier(0.7,1,0.8,1);
	transition-timing-function: cubic-bezier(0.7,1,0.8,1);
}
.content__item--current .content__item-img {
	-webkit-transition-timing-function: cubic-bezier(0.2,1,0.3,1);
	transition-timing-function: cubic-bezier(0.2,1,0.3,1);
	-webkit-transition-duration: 1s;
	transition-duration: 1s;
}
.content__item-inner {
	-webkit-transition: -webkit-transform 0.6s, opacity 0.3s;
	transition: transform 0.6s, opacity 0.3s;
	-webkit-transition-timing-function: cubic-bezier(0.7,1,0.8,1), ease;
	transition-timing-function: cubic-bezier(0.7,1,0.8,1), ease;
}
.content__item--current .content__item-inner {
	-webkit-transition-timing-function: cubic-bezier(0.2,1,0.3,1), ease;
	transition-timing-function: cubic-bezier(0.2,1,0.3,1), ease;
	-webkit-transition-duration: 1.7s;
	transition-duration: 1.7s;
}
/* Media Queries */
@media screen and (max-width: 50em) {
	.content__item {
		display: block;
	}
	.content__item-img {
		max-width: calc(100% - 80px);
		max-height: 70vh;
	}
	.content h2 {
		font-size: 3em;
	}
	.content__item-inner {
		font-size: 82%;
		padding: 4em 3em 2em;
	}
}

JavaScript

/**
 * main.js
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright 2015, Codrops
 * http://www.codrops.com
 */
;(function(window) {
	'use strict';
	var bodyEl = document.body,
		docElem = window.document.documentElement,
		support = { transitions: Modernizr.csstransitions },
		// transition end event name
		transEndEventNames = { 'WebkitTransition': 'webkitTransitionEnd', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'MSTransitionEnd', 'transition': 'transitionend' },
		transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ],
		onEndTransition = function( el, callback ) {
			var onEndCallbackFn = function( ev ) {
				if( support.transitions ) {
					if( ev.target != this ) return;
					this.removeEventListener( transEndEventName, onEndCallbackFn );
				}
				if( callback && typeof callback === 'function' ) { callback.call(this); }
			};
			if( support.transitions ) {
				el.addEventListener( transEndEventName, onEndCallbackFn );
			}
			else {
				onEndCallbackFn();
			}
		},
		// window sizes
		win = {width: window.innerWidth, height: window.innerHeight},
		// some helper vars to disallow scrolling
		lockScroll = false, xscroll, yscroll,
		scrollContainer = document.querySelector('.container'),
		// the main slider and its items
		sliderEl = document.querySelector('.slider'),
		items = [].slice.call(sliderEl.querySelectorAll('.slide')),
		// total number of items
		itemsTotal = items.length,
		// navigation controls/arrows
		navRightCtrl = sliderEl.querySelector('.button--nav-next'),
		navLeftCtrl = sliderEl.querySelector('.button--nav-prev'),
		zoomCtrl = sliderEl.querySelector('.button--zoom'),
		// the main content element
		contentEl = document.querySelector('.content'),
		// close content control
		closeContentCtrl = contentEl.querySelector('button.button--close'),
		// index of current item
		current = 0,
		// check if an item is "open"
		isOpen = false,
		isFirefox = typeof InstallTrigger !== 'undefined',
		// scale body when zooming into the items, if not Firefox (the performance in Firefox is not very good)
		bodyScale = isFirefox ? false : 3;
	// some helper functions:
	function scrollX() { return window.pageXOffset || docElem.scrollLeft; }
	function scrollY() { return window.pageYOffset || docElem.scrollTop; }
	// from http://www.sberry.me/articles/javascript-event-throttling-debouncing
	function throttle(fn, delay) {
		var allowSample = true;
		return function(e) {
			if (allowSample) {
				allowSample = false;
				setTimeout(function() { allowSample = true; }, delay);
				fn(e);
			}
		};
	}
	function init() {
		initEvents();
	}
	// event binding
	function initEvents() {
		// open items
		zoomCtrl.addEventListener('click', function() {
			openItem(items[current]);
		});
		// close content
		closeContentCtrl.addEventListener('click', closeContent);
		// navigation
		navRightCtrl.addEventListener('click', function() { navigate('right'); });
		navLeftCtrl.addEventListener('click', function() { navigate('left'); });
		// window resize
		window.addEventListener('resize', throttle(function(ev) {
			// reset window sizes
			win = {width: window.innerWidth, height: window.innerHeight};
			// reset transforms for the items (slider items)
			items.forEach(function(item, pos) {
				if( pos === current ) return;
				var el = item.querySelector('.slide__mover');
				dynamics.css(el, { translateX: el.offsetWidth });
			});
		}, 10));
		// keyboard navigation events
		document.addEventListener( 'keydown', function( ev ) {
			if( isOpen ) return;
			var keyCode = ev.keyCode || ev.which;
			switch (keyCode) {
				case 37:
					navigate('left');
					break;
				case 39:
					navigate('right');
					break;
			}
		} );
	}
	// opens one item
	function openItem(item) {
		if( isOpen ) return;
		isOpen = true;
		// the element that will be transformed
		var zoomer = item.querySelector('.zoomer');
		// slide screen preview
		classie.add(zoomer, 'zoomer--active');
		// disallow scroll
		scrollContainer.addEventListener('scroll', noscroll);
		// apply transforms
		applyTransforms(zoomer);
		// also scale the body so it looks the camera moves to the item.
		if( bodyScale ) {
			dynamics.animate(bodyEl, { scale: bodyScale }, { type: dynamics.easeInOut, duration: 500 });
		}
		// after the transition is finished:
		onEndTransition(zoomer, function() {
			// reset body transform
			if( bodyScale ) {
				dynamics.stop(bodyEl);
				dynamics.css(bodyEl, { scale: 1 });
				// fix for safari (allowing fixed children to keep position)
				bodyEl.style.WebkitTransform = 'none';
				bodyEl.style.transform = 'none';
			}
			// no scrolling
			classie.add(bodyEl, 'noscroll');
			classie.add(contentEl, 'content--open');
			var contentItem = document.getElementById(item.getAttribute('data-content'))
			classie.add(contentItem, 'content__item--current');
			classie.add(contentItem, 'content__item--reset');
			// reset zoomer transform - back to its original position/transform without a transition
			classie.add(zoomer, 'zoomer--notrans');
			zoomer.style.WebkitTransform = 'translate3d(0,0,0) scale3d(1,1,1)';
			zoomer.style.transform = 'translate3d(0,0,0) scale3d(1,1,1)';
		});
	}
	// closes the item/content
	function closeContent() {
		var contentItem = contentEl.querySelector('.content__item--current'),
			zoomer = items[current].querySelector('.zoomer');
		classie.remove(contentEl, 'content--open');
		classie.remove(contentItem, 'content__item--current');
		classie.remove(bodyEl, 'noscroll');
		if( bodyScale ) {
			// reset fix for safari (allowing fixed children to keep position)
			bodyEl.style.WebkitTransform = '';
			bodyEl.style.transform = '';
		}
		/* fix for safari flickering */
		var nobodyscale = true;
		applyTransforms(zoomer, nobodyscale);
		/* fix for safari flickering */
		// wait for the inner content to finish the transition
		onEndTransition(contentItem, function(ev) {
			classie.remove(this, 'content__item--reset');
			// reset scrolling permission
			lockScroll = false;
			scrollContainer.removeEventListener('scroll', noscroll);
			/* fix for safari flickering */
			zoomer.style.WebkitTransform = 'translate3d(0,0,0) scale3d(1,1,1)';
			zoomer.style.transform = 'translate3d(0,0,0) scale3d(1,1,1)';
			/* fix for safari flickering */
			// scale up - behind the scenes - the item again (without transition)
			applyTransforms(zoomer);
			// animate/scale down the item
			setTimeout(function() {
				classie.remove(zoomer, 'zoomer--notrans');
				classie.remove(zoomer, 'zoomer--active');
				zoomer.style.WebkitTransform = 'translate3d(0,0,0) scale3d(1,1,1)';
				zoomer.style.transform = 'translate3d(0,0,0) scale3d(1,1,1)';
			}, 25);
			if( bodyScale ) {
				dynamics.css(bodyEl, { scale: bodyScale });
				dynamics.animate(bodyEl, { scale: 1 }, {
					type: dynamics.easeInOut,
					duration: 500
				});
			}
			isOpen = false;
		});
	}
	// applies the necessary transform value to scale the item up
	function applyTransforms(el, nobodyscale) {
		// zoomer area and scale value
		var zoomerArea = el.querySelector('.zoomer__area'),
			zoomerAreaSize = {width: zoomerArea.offsetWidth, height: zoomerArea.offsetHeight},
			zoomerOffset = zoomerArea.getBoundingClientRect(),
			scaleVal = zoomerAreaSize.width/zoomerAreaSize.height < win.width/win.height ? win.width/zoomerAreaSize.width : win.height/zoomerAreaSize.height;
		if( bodyScale && !nobodyscale ) {
			scaleVal /= bodyScale;
		}
		// apply transform
		el.style.WebkitTransform = 'translate3d(' + Number(win.width/2 - (zoomerOffset.left+zoomerAreaSize.width/2)) + 'px,' + Number(win.height/2 - (zoomerOffset.top+zoomerAreaSize.height/2)) + 'px,0) scale3d(' + scaleVal + ',' + scaleVal + ',1)';
		el.style.transform = 'translate3d(' + Number(win.width/2 - (zoomerOffset.left+zoomerAreaSize.width/2)) + 'px,' + Number(win.height/2 - (zoomerOffset.top+zoomerAreaSize.height/2)) + 'px,0) scale3d(' + scaleVal + ',' + scaleVal + ',1)';
	}
	// navigate the slider
	function navigate(dir) {
		var itemCurrent = items[current],
			currentEl = itemCurrent.querySelector('.slide__mover'),
			currentTitleEl = itemCurrent.querySelector('.slide__title');
		// update new current value
		if( dir === 'right' ) {
			current = current < itemsTotal-1 ? current + 1 : 0;
		}
		else {
			current = current > 0 ? current - 1 : itemsTotal-1;
		}
		var itemNext = items[current],
			nextEl = itemNext.querySelector('.slide__mover'),
			nextTitleEl = itemNext.querySelector('.slide__title');
		// animate the current element out
		dynamics.animate(currentEl, { opacity: 0, translateX: dir === 'right' ? -1*currentEl.offsetWidth/2 : currentEl.offsetWidth/2, rotateZ: dir === 'right' ? -10 : 10 }, {
			type: dynamics.spring,
			duration: 2000,
			friction: 600,
			complete: function() {
				dynamics.css(itemCurrent, { opacity: 0, visibility: 'hidden' });
			}
		});
		// animate the current title out
		dynamics.animate(currentTitleEl, { translateX: dir === 'right' ? -250 : 250, opacity: 0 }, {
			type: dynamics.bezier,
			points: [{"x":0,"y":0,"cp":[{"x":0.2,"y":1}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
			duration: 450
		});
		// set the right properties for the next element to come in
		dynamics.css(itemNext, { opacity: 1, visibility: 'visible' });
		dynamics.css(nextEl, { opacity: 0, translateX: dir === 'right' ? nextEl.offsetWidth/2 : -1*nextEl.offsetWidth/2, rotateZ: dir === 'right' ? 10 : -10 });
		// animate the next element in
		dynamics.animate(nextEl, { opacity: 1, translateX: 0 }, {
			type: dynamics.spring,
			duration: 2000,
			friction: 600,
			complete: function() {
				items.forEach(function(item) { classie.remove(item, 'slide--current'); });
				classie.add(itemNext, 'slide--current');
			}
		});
		// set the right properties for the next title to come in
		dynamics.css(nextTitleEl, { translateX: dir === 'right' ? 250 : -250, opacity: 0 });
		// animate the next title in
		dynamics.animate(nextTitleEl, { translateX: 0, opacity: 1 }, {
			type: dynamics.bezier,
			points: [{"x":0,"y":0,"cp":[{"x":0.2,"y":1}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
			duration: 650
		});
	}
	// disallow scrolling (on the scrollContainer)
	function noscroll() {
		if(!lockScroll) {
			lockScroll = true;
			xscroll = scrollContainer.scrollLeft;
			yscroll = scrollContainer.scrollTop;
		}
		scrollContainer.scrollTop = yscroll;
		scrollContainer.scrollLeft = xscroll;
	}
	init();
})(window);

Демо

Использование React.js для рисования SVG

reactjs-logo-thumb
image-869

Масштабируемая векторная графика (Scalable Vector Graphics — SVG) являются отличным способом, чтобы включить векторную графики на веб-странице. Они легкие, на основе XML, и поддерживаются почти всеми современными браузерами. Так как они на основе XML, они состоят из разметки, и могут(в теории, по крайней мере) создавать все то, что может создавать корректные XML-разметки. Это включает в себя и JavaScript библиотеку, пользовательского интерфейса, React.js.

Введение

SVG находится внутри SVG тегов, так что первым шагом будет создание простого компонента, который создает SVG тег и отображает своих потомков внутри него.

var SVGComponent = React.createClass({
    render: function() {
        return this.transferPropsTo(
            <svg>{this.props.children}</svg>
        );
    }
});
React.renderComponent(
    <SVGComponent height="50" width="50" />,
    document.getElementById('svg_mount_example')
);

Так мы получили svg элемент 50 на 50, он находится ниже, можете посмотреть его через «Просмотр кода элемента»

Это обычный пустой SVG элемент.

Основные формы

Прямоугольники

var Rectangle = React.createClass({
    render: function() {
        return this.transferPropsTo(
            <rect>{this.props.children}</rect>
        );
    }
});
React.renderComponent(
    <SVGComponent height="100" width="100">
        <Rectangle height="50" width="50" x="25" y="25" />
    </SVGComponent>,
    document.getElementById('svg_rectangle')
);

Круги

var Circle = React.createClass({
    render: function() {
        return this.transferPropsTo(
            <circle>{this.props.children}</circle>
        );
    }
});
React.renderComponent(
    <SVGComponent height="100" width="100">
        <Circle cx="50" cy="50" r="25" />
    </SVGComponent>,
    document.getElementById('svg_circle')
);

Эллипсы

var Ellipse = React.createClass({
    render: function() {
        return this.transferPropsTo(
            <ellipse>{this.props.children}</ellipse>
        );
    }
});
React.renderComponent(
    <SVGComponent height="100" width="100">
        <Ellipse cx="50" cy="50" rx="25" ry="15" />
    </SVGComponent>,
    document.getElementById('svg_ellipse')
);

Работа с цветами

Цвета передаются как параметры компонентов. Вы можете использовать любой из цветовых форматов, доступных в CSS2, SVG также поддерживает расширенный набор именованных цветов CSS.

Два наиболее полезных параметров SVG цвета являются:

      fill: интерьер цвет формы
      stroke: цвет линии, образующей наружную форму

Ниже приведен пример этих цветовых свойств, указанных несколькими различными способами.

React.renderComponent(
    <SVGComponent height="100" width="230">
        <Circle
            cx="50" cy="50" r="25"
            fill="mediumorchid" />
        <Circle
            cx="125" cy="50" r="25"
            fill="#ff0099" />
        <Circle
            cx="200" cy="50" r="25"
            fill="none"
            stroke="crimson" />
    </SVGComponent>,
    document.getElementById('svg_colors')
);

SVG поддерживает огромное количество свойств цветов и рисования, и понимание как они работают и как они взаимодействуют друг с другом, это является ключом к расширенным возможностям рисования.

Линии

Прямые линии

var Line = React.createClass({
    render: function() {
        return this.transferPropsTo(
            <line>{this.props.children}</line>
        );
    }
});
React.renderComponent(
    <SVGComponent height="100" width="100">
        <Line x1="25" y1="25" x2="75" y2="75"
            strokeWidth="5"
            stroke="orange" />
    </SVGComponent>,
    document.getElementById('svg_straight_line')
);

Ломанные линии

var Polyline = React.createClass({
    render: function() {
        return this.transferPropsTo(
            <polyline>{this.props.children}</polyline>
        );
    }
});
React.renderComponent(
    <SVGComponent height="100" width="100">
        <Polyline
            points="25,25 25,75 50,75 50,50 75,25"
            strokeWidth="5"
            stroke="orange"
            fill="none" />
    </SVGComponent>,
    document.getElementById('svg_polyline')
);

Создание пошаговой формы регистрации на React

Хорошей особенностью React является как этот фреймворк обрабатывает состояние нашего приложения. Каждый раз, когда у  React  обновляются компоненты, React будет рендерить обновленный вид. Иначе: React отлично показывает пользователям то что происходит на странице — если это необходимо.

Хороший пример демонстрации этой способности будет реализация пошаговой регистрации, где мы обновляем состояние компонента, чтобы показать на каком этапе пользователь находится, и показывать поля для этого шага. Но прежде, чем мы углубимся давайте посмотрим, что мы будем создавать.

Демо

Исоходники на github

Начинаем работу

У нас будет процедура регистрации состоять из 4-х шагов. Пользователь будет:

  1. Вводить основную информацию об учетной записи
  2. Заполнит основную информацию о себе
  3. Подтвердит что информация верна
  4. Увидит сообщение об успешной регистрации

Самый простой способ, чтобы показать только необходимые поля для данного шага, это хранить контент организованный в дискретных компонентах. Затем, когда пользователь переходит к следующему шагу в процессе заполнения, мы увеличим шаг на 1. React увидит изменения и автоматически рендерит компонент, чтобы показать то, что мы хотим, показать пользователю. Вот основной код:

// file: Registration.jsx
var React         = require('react')
var AccountFields = require('./AccountFields')
var SurveyFields  = require('./SurveyFields')
var Confirmation  = require('./Confirmation')
var Success       = require('./Success')
var Registration = React.createClass({
  getInitialState: function() {
    return {
      step : 1
    }
  },
  render: function() {
    switch(this.state.step) {
      case 1:
        return <AccountFields />
      case 2:
        return <SurveyFields />
      case 3:
        return <Confirmation />
      case 4:
        return <Success />
    }
  }
}
module.exports = Registration

Когда пользователь находится на первом шаге (когда наш первый компонент загружается) мы покажем поля данных, на втором мы покажем вопросы анкеты, на третьем подтверждение пользователя, на четвертом сообщение об успешной регистрации. Мы будем подключать эти компоненты используя CommonJS паттерн; каждый из них будет React компонентом.

Далее, мы создадим объект, для хранения данных которые вводит пользователь. У нас будет поля имя, email, пароль, возраст, и любимый цвет. Давайте сохраним эту информацию в качестве FieldValues в верхней части нашего родительского компонента (Register.jsx).

// file: Registration.jsx
var fieldValues = {
  name     : null,
  email    : null,
  password : null,
  age      : null,
  colors   : []
}
// The rest of our file
...

Наш первый компонент, , содержит поля, используемые для создания новой учетной записи: имя пользователя, пароль и электронную почту. Когда пользователь нажимает «Сохранить и продолжить» Мы сохраним данные и перейдем к шагу 2.

// file: AccountFields.jsx
var React = require('react')
var AccountFields = React.createClass({
  render: function() {
    return (
      <div>
        <label>Name</label>
        <input type="text" ref="name" defaultValue={this.props.fieldValues.name} />
        <label>Password</label>
        <input type="password" ref="password" defaultValue={this.props.fieldValues.password} />
        <label>Email</label>
        <input type="email" ref="email" defaultValue={this.props.fieldValues.email} />
        <button onClick={this.saveAndContinue}>Save and Continue</button>
      </div>
    )
  },
  saveAndContinue: function(e) {
    e.preventDefault()
    // Get values via this.refs
    var data = {
      name     : this.refs.name.getDOMNode().value,
      password : this.refs.password.getDOMNode().value,
      email    : this.refs.email.getDOMNode().value,
    }
    this.props.saveValues(data)
    this.props.nextStep()
  }
})
module.exports = AccountFields

Основные моменты в этом компоненте :

  1. defaultValue будут установлены начальные значения при входе. Это необходимо React. Нажмите здесь, чтобы прочитать больше на эту тему.
  2. Мы устанавливаем defaultValue к ключу, связанному this.props.fieldValues, который передается в качестве свойств от родительского компонента (Registration.jsx). Это так, когда пользователь сохраняет и продолжает к следующему шагу, но затем возвращается к предыдущему шагу, и данные которые он ввел будут уже отображаться в полях.
  3. Мы получаем значение этих полей, ссылаясь на DOM узлов с использованием ссылок.
  4. saveValues метод для nextStep передает в качестве свойства параметры. Так как на этапе 2, мы можем вернуться к предыдущему шагу, мы должны создать previousStep.

// file: Registration.jsx

saveValues: function(fields) {
return function() {
// Remember, `fieldValues` is set at the top of this file, we are simply appending
// to and overriding keys in `fieldValues` with the `fields` with Object.assign
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
fieldValues = Object.assign({}, fieldValues, fields)
}()
},

nextStep: function() {
  this.setState({
    step : this.state.step + 1
  })
},
// Same as nextStep, but decrementing
previousStep: function() {
  this.setState({
    step : this.state.step - 1
  })
},
...

Затем мы передадим эти данные вновь, как свойства каждому из наших следующих компонентов.

// file: Registration.jsx
...
render: function() {
  switch (this.state.step) {
    case 1:
      return <AccountFields fieldValues={fieldValues}
                            nextStep={this.nextStep}
                            saveValues={this.saveValues} />
    case 2:
      return <SurveyFields fieldValues={fieldValues}
                           nextStep={this.nextStep}
                           previousStep={this.previousStep}
                           saveValues={this.saveValues} />
    case 3:
      return <Confirmation fieldValues={fieldValues}
                           previousStep={this.previousStep}
                           submitRegistration={this.submitRegistration} />
    case 4:
      return <Success fieldValues={fieldValues} />
  }
}

В нет previousStep, так как это наш первый шаг, и вы не можете вернуться назад. Кроме того, вместо передачи saveValues или с nextStep для мы передаем только что созданный метод submitRegistration, который будет обрабатывать представления для входа пользователей (FieldValues) и изменит шаг нашего процесса регистрации на 4, тем самым показывая .

Компоненты , и вы можете посмотреть на Github здесь, здесь, и здесь.

Вывод

React является хорошим выбором для обработки и вывода пользователю данных и форм. В нашем примере, мы показываем поля ввода, и когда эти поля показаны определяется текущей стадии (шаг нашего компонента регистрации). В зависимости от состояния нашего приложения, мы можем просто и предсказуемо сделать что-то другое, будь то, как маленький одно изменение характера или показывая совершенно иной компонент в целом.

Небольшое руководство по React

react-javascript-library-building-user-interfaces[1]
image-756

Эта статья даст вам краткий обзор того, как построить пользовательские интерфейсы в React JS. Код статьи доступен в репозитрии, или же вы можете все делать постепенно как в статье.

Основная концепция

React имеет довольно небольшую API. Это делает этот фреймворк приятным в использовании, легким в обучении, и простым для понимания. Однако, будучи простым, не означает, что все это вам уже знакомо. Есть несколько концепций, которые надо знать перед началом работы. Давайте рассмотрим их по очереди.

Элементы React  являются объектами JavaScript, которые представляют HTML элементы. Они не существуют в браузере. Они представляют собой элементы браузера, такие как h1, div или section.

Компоненты разработчик создает React элементы. Они, как правило, более крупные части пользовательского интерфейса, которые содержат как структура и функции. Подумайте о таких понятий, как NavBar, LikeButton или ImageUploader.

JSX представляет собой метод для создания React элементов и компонентов. Например, <h1>Hello</h1> является React элементом, записанный в JSX. Некоторые React элементы могут быть записан в виде JavaScript как React.DOM.h1 (NULL, 'Hello');. С JSX вы затратите меньше усилий, на чтение, запись и трансформацию в JavaScript перед запуском в браузере.

Виртуальный DOM является JavaScript деревом React элементов и компонентов. React создает виртуальный DOM для браузера, для вывода пользовательского интерфейса. React следить за виртуальным DOM для изменения и автоматического изменения в браузере, чтобы соответствовать виртуальному DOM.

С понимание этих понятий мы можем двигаться дальше к использованию React. Мы будем строить серию пользовательских интерфейсов, каждый из которых добавляет слой функциональности на предыдущий. Мы будем строить фотогалерею, похожую на Instagram.

Rendering

Первым делом рендерим визуальный элемент (React элементы или компоненты). Запомните, что виртуальный элемент существует только в памяти JavaScript, мы должны явно указать React, чтобы вывести его в браузере DOM.

React.render(<img src="http://tinyurl.com/lkevsb9" alt="" />, document.body);

Функция render принимает два аргумента: виртуальный элемент и реального DOM узла. React принимает визуальный элемент и вставляет его в данный DOM узлел. Изображение теперь отображается в браузере.

Компоненты

Компоненты сердце и душа React. Они настраиваемый React элементы. Они, как правило, расширены с уникальной функциональностью и структурой.

var Photo = React.createClass({
  render: function() {
    return <img src='http://tinyurl.com/lkevsb9' />
  }
});
React.render(<Photo />, document.body);

Функция createClass принимает объект, который реализует рендеринг функции.

Компонент photo создает <Photo />, и выводит его в теле документа.

Этот компонент не делает ничего, что делает предыдущий React элемент изображения, но он может быть расширен с пользовательскими функциями и структурами.

Свойства

Свойства можно рассматривать в качестве опций компонента. Они используются в качестве аргументов компонента и выглядят в точности как атрибуты HTML.

var Photo = React.createClass({
  render: function() {
    return (
      <div className='photo'>
        <img src={this.props.imageURL} />
        <span>{this.props.caption}</span>
      </div>
    )
  }
});
React.render(<Photo imageURL='http://tinyurl.com/lkevsb9' caption='New York!' />, document.body);

Внутри функции React render: два параметра передаются в компонент Photo: ImageUrl и caption.

Внутри компонента функция render свойство ImageUrl используется в качестве SRC для React элемента изображения. caption также используется как обычный текст в React элементе span.

Стоит отметить, что компонент никогда не должны изменять свои параметры, они неизменны. Если компонент имеет данные, которые надо менять, используйте состояния объекта.

Состояние

Состояние объекта является внутренним для компонента. Он содержит данные, которые могут меняться с течением времени.

var Photo = React.createClass({
  toggleLiked: function() {
    this.setState({
      liked: !this.state.liked
    });
  },
  getInitialState: function() {
    return {
      liked: false
    }
  },
  render: function() {
    var buttonClass = this.state.liked ? 'active' : '';
    return (
      <div className='photo'>
        <img src={this.props.src} />
        <div className='bar'>
          <button onClick={this.toggleLiked} className={buttonClass}>
            ♥
          </button>
          <span>{this.props.caption}</span>
        </div>
      </div>
    )
  }
});
React.render(<Photo src='http://tinyurl.com/lkevsb9' caption='New York!'/>, document.body);

Благодаря состоянию в компоненте, можно реализовать более сложные вещи.

У компонента появилась новая функция getInitialState. React вызывает эту функцию, когда компонент инициализируется. Возвращаемый объект установлен в качестве начального состояния компонента (как имя функции).

Компонент имеет еще один новый функцию toggleLiked. Эта функция вызывает SetState для компонента, и меняет значение liked.

В визуализации функции компонента переменной buttonClass присваивается либо «активый» или стирается — в зависимости от liked состояния.

buttonClass используется в качестве имени класса для кнопки React элемента. Кнопка также имеет обработчик событий OnClick и настроена на работу в toggleLiked.

Вот что происходит, когда компонент оказывается в DOM браузера:

  • Когда кнопка компонента нажата, вызывается toggleLiked
  • liked состояние изменяется
  • React повторно рендерит компонент в виртуальный DOM
  • Новая виртуальная DОМ  сравнивается с предыдущим виртуальной DOM
  • React изолирует, что изменилось и обновляет DOM браузера

В этом случае, React изменяет имя класса на кнопке.

Состав

Состав означает объединение более мелких компонентов с образованием большего целого. Например компонент фотография может быть использовано в качестве компонента фотогалерии, например, так:

var Photo = React.createClass({
  toggleLiked: function() {
    this.setState({
      liked: !this.state.liked
    });
  },
  getInitialState: function() {
    return {
      liked: false
    }
  },
  render: function() {
    var buttonClass = this.state.liked ? 'active' : '';
    return (
      <div className='photo'>
        <img src={this.props.src} />
        <div className='bar'>
          <button onClick={this.toggleLiked} className={buttonClass}>
            ♥
          </button>
          <span>{this.props.caption}</span>
        </div>
      </div>
    )
  }
});
var PhotoGallery = React.createClass({
  getDataFromServer: function() {
    return [{
      url: 'http://tinyurl.com/lkevsb9',
      caption: 'New York!'
    },
    {
      url: 'http://tinyurl.com/mxkwh56',
      caption: 'Cows'
    },
    {
      url: 'http://tinyurl.com/nc7jv28',
      caption: 'Scooters'
    }];
  },
  render: function() {
    var data = this.getDataFromServer();
    var photos = data.map(function(photo) {
      return <Photo src={photo.url} caption={photo.caption} />
    });
    return (
      <div className='photo-gallery'>
        {photos}
      </div>
    )
  }
});
React.render(<PhotoGallery />, document.body);

Компонент Photo точно такой же, как и раньше.

Новый компонент PhotoGallery, который генерирует Photo компоненты. В этом случае есть некоторые поддельные данные сервер, который возвращает массив 3 объектов, содержащие url и caption.

Данные создают 3 компонента Photo, которые вставляются в возвращаемом значении render функции компонента.

Вывод

Этого должно быть достаточно, чтобы начать создавать пользовательские интерфейсы с React. React документация можно более подробно узнать об этом фреймворке. Я настоятельно рекомендую прочитать ее.

Создаем десктопное приложение с помощью HTML, JS и Node-WebKit

html5-node-webkit-retina-preview
image-532

Сейчас  вы можете создать очень многое с использование только JavaScript и HTML. Благодаря Node-WebKit, мы можем даже создать десктопные приложения которые имеют полный доступ к любой части операционной системы. В этой небольшой статье мы покажем вам, как создать простое приложение, используя Node-WebKit, который сочетает в себе JQuery и несколько Node.js модулей.

Node-WebKit является комбинацией Node.js и встроенным WebKit-браузером. Код JavaScript, который вы пишете выполняется в особой среде и имеет доступ к интерфейсу браузера и Node.js. Звучит интересно? Продолжайте читать!

Установка Node-Webkit

Для разработки приложений, вам нужно будет загрузить Node-WebKit исполняемый файл и вызывать его из вашего терминала, если вы хотите запустить свой код. (Позже вы можете упаковать все в одной программе, чтобы пользователи могли легко его запускать).

Зайдем на страницу проекта и загрузим исполняемый файл, который подходит для вашей операционной системы. Распакуйте архив куда-нибудь на вашем компьютере.

Первое приложение

Скачаем пример приложения. Запускаем через терминал приложение

# linux/osx
/path/to/node-webkit/nw /your/project/folder
# Iwindows
C:\path\to\node-webkit\nw.exe C:\your\project\folder

Структура пприложения

nw-app-tree
image-533

 

В итоге мы получаем готовое приложение.

Bower — быстрый старт

Сейчас очень много библиотек и фреймворков для френтед разработчиков, и уже не редкость когда в одном проекте пять или больше таких библиотек. И уследить за всеми, за новыми обновлениями становится все сложнее. Bower — пакетный менеджер от Twitter, он позволяет легко управлять всеми пакетами и зависимостями вашего проекта.

bower
image-356

В этом посте вы узнаете, как установить и работает с Bower. Мы начнем с его установки, а затем посмотрим на команды с помощью которых можно управлять библиотеками вашего проекта.

Установка Bower

Bower может быть установлен с помощью npm, менеджер пакетов Node.js. Если у вас еще не установлен npm или Node.js. Установите их, npm входит в состав Node.js.

После того как вы установите npm, откройте терминал и введите следующую команду:

npm install -g bower

Это позволит установить Bower на вашу систему.

Теперь, когда у вас установлен Bower, мы можем рассмотреть команды, которые используются для управления пакетами.

Поиск пакетов

Есть два способа, с помощью которых которые можно найти Bower пакеты. Либо с помощью онлайн каталога, или при помощи команды search.

bower search <имя пакета>

Например, для поиска пакетов, содержащих слово ‘JQuery’ вы могли бы сделать следующее:

bower search jquery

Эта команда вернет список результатов, некоторые из которых представлены в расположенной ниже фрагменте.

Search results:
    jquery git://github.com/components/jquery.git
    jquery-ui git://github.com/components/jqueryui
    jquery.cookie git://github.com/carhartl/jquery-cookie.git
    jquery-placeholder git://github.com/mathiasbynens/jquery-placeholder.git
    jquery-file-upload git://github.com/blueimp/jQuery-File-Upload.git
    jasmine-jquery git://github.com/velesin/jasmine-jquery
    jquery.ui git://github.com/jquery/jquery-ui.git
    ...

Каждый результат отображается в виде имя пакета и репозитория Git, они вам понадобятся для установки пакета.

Установка пакетов

Чтобы добавить новый пакет, для вашего проекта вы должны использовать команду install и имя пакета.

bower install <package>

Также вместо имени пакета, вы можете установить его, использууя один из следующих способов:

  • Адресс репозитория Git: //github.com/components/jquery.git
  • Путь в локальное хранилище Git
  • Сокращение components/jquery. Bower будет предполагать, что GitHub используется по умолчанию
  • URL к архивам. Содержание файлы будут извлечены автоматически.

Вы можете установить определенную версию пакета, добавив знак решётки (#) после имени пакета, за которым следует номер версии.

bower install <package>#<version>

Установленные пакеты будут размещены в каталоге bower_components. Вы можете изменить папку с помощью параметров конфигурации в файле .bowerrc.

  • bower_components
    • jquery
      • jquery.js
      • jquery.min.js
      • jquery.min.map
    • modernizr
      • modernizr.js

После установки, вы можете использовать пакет, просто добавив script или link теги в ваш HTML файл. Хотя Bower пакеты чаще всего содержат файлы JavaScript, они также могут содержать CSS или даже изображения.

<script src="path/to/bower_components/jquery/jquery.min.js"></script>

Установка пакетов с использованием bower.json файла

Если вы используете несколько пакетов в вашем проекте это то удобно их перечислить в файле bower.json. Это позволит вам устанавливать и обновлять несколько пакетов с помощью одной команды.

{
"name": "app-name",
"version": "0.0.1",
"dependencies": {
"sass-bootstrap": "~3.0.0",
"modernizr": "~2.6.2",
"jquery": "~1.10.2"
},
"private": true
}

Вот простой пример bower.json файла, который определяет некоторую информацию о проектах, а также список зависимостей. Файл bower.json фактически используется, чтобы определить пакет Bower, так что по сути вы создаете свой собственный пакет, который содержит все зависимости для вашего приложения.

Основные команды Bower

Список установленных пакетов в проекте

bower list

Обновление пакетов

bower update

Обновление пакетов

bower update

или обновление конкретного пакета

bower update <название пакета>

Удаление пакетов

bower uninstall <название пакета>

Удаление пакетов

bower uninstall <название пакета>

Языки и фреймворки, которые вы должны изучить в 2015м году

Языки / платформы

Node.js является победителем в этом списке,  PHP второй, и JavaScript — третий. Существует большое сообщество вокруг Node.js, так что это не удивительно. Если вы знаете JavaScript, вы уже наполовину способны к создания веб-приложений в Node.js.

Фреймворки

AngularJS занимает первое место среди фреймворков. Крупные компании и предприятия приняли Angular, и вырос спрос на специалистов знающих данный фреймворк. Дело в том, что он опирается на некоторые из лучших инженеров Google, также помогает ему достичь первое место.

Чтобы помочь вам сделать свой выбор, ниже небольшой обзор из вышеперечисленных технологий!

Вот то, чему вы должны научиться в 2015 году

Библиотеки и фреймворки приходят и уходят, поэтому рискованно приложить усилия для того, чтобы узнать все то новое, что приходит вместе. Но вот предложения для языков и фреймворков, которые мы считаем будет придерживаться вокруг в долгосрочной перспективе и стоит обучению. Они все популярные, есть большие сообщества, и дают много возможностей для карьерного роста.

1. JavaScript везде

офмфыскшзе
image-323

Если вы занимаетесь веб-разработкой, JavaScript является языком, который вы должны знать, независимо от того, какой язык программирования используете. Сегодня вы можете использовать JS в браузере, на сервере, в мобильных приложениях и даже на программируемом оборудовании. ES6 принесет необходимых улучшений и сделает язык еще более мощным и легко в написании. Это также хорошая идея, чтобы узнать о Bower и npm, а также инструменты, такие как jshint и jscs.

2. AngularJS

angular
image-324

AngularJS является JavaScript фреймворком от Google, который быстро зарекомендовал себя как инструмент для создания мощных веб-приложений. При этом признании приходит повышенный спрос на программистов умеющих работать с ним, по этому вы найдете упоминание о нем в списке требований почти в каждом вакансии JavaScript разработчика. Причин не изучать его просто нет.

3. React

react
image-325

React является новичком в этом списке. Библиотека разработана Facebook и обеспечивает очень быструю работу благодаря производительности своей виртуальной DOM, и может быть легко подключен в существующие проекты. Она также имеет очень активное сообщество, которое развивается все виды компонентов. На наш взгляд, React имеет большой потенциал и является фреймворком, который стоит чтобы посмотреть (и учить) в 2015 году.

4. Node.js

nodejs
image-326

С Node.js вы можете разрабатывать сетевые серверные приложения на JavaScript. Он может быть использован для простых движков веб-сайтов с использованием такой структуры, как Express, конечных точек API, WebSocket серверов или даже торрент-клиентов. Node имеет невероятно активное сообщество и превысил всех других языков по количеству модулей в этом году.

5. NoSQL databases

nosql
image-327

Базы данных, которые не нужны ни таблицы, ни SQL высоко ценятся современные веб-разработчики, и мы считаем, что эти базы данных станут только более популярным в следующем году. Два примечательных варианты MongoDB и Redis. Это гораздо проще начать с одного из этих баз данных, чем с MySQL и Postgres. Но не ведитесь, думая, что базы данных NoSQL являются идеальной заменой — в некоторых ситуациях классический реляционная база данных сделает ваше развитие легче даже если это займет больше усилий.

6. Less/Sass/Stylus

lesscss
image-328

Существует много что не нравится в CSS. Это слишком просто, в конечном итоге получаются громоздкие файлы с 1000 линиями, который трудно ориентироваться и измененять. Чтобы решить эту проблему, есть языки, такие как Less, Sass и Styles, которые составляются для CSS и предлагают переменные, макросы и другие вкусности, которые помогут вам улучшить ваш код. Вы можете изучить один из них за один день.

7. Потрясающие новые фреймворки

exciting-new-frameworks
image-329

Meteor принципиально новый подход к развитию веб-приложений, которые размывает границы между фронтендом и бекендом. Это позволяет писать Reall-time приложения, и имеет быстро растущее сообщество. Hood.ie является небольшим его соперником, и предлагает новый подход. Он обрабатывает серверную часть для вас, так что вы можете полностью сосредоточиться на фронтенде вашего приложения.

8. Потрясающие новые языки

exciting-new-languages
image-330

Для языковых ботаников, вот некоторые новые языки. Golang, Rust и Elixir набирают обороты в программировании и используются в ситуациях, когда требуется очень высокая производительность.

9. Классические популярные фремворки

full-stack-frameworks
image-331

Несмотря на то что одностраничные приложения набирает популярность, до сих пор огромный спрос имеют на классические серверных веб-приложения. Ruby on Rails, Django, Laravel, Play, ASP.NET являются наиболее популярными фреймворками в данный момент.

10. Старая гвардия

the-old-guard
image-332

Существует большая коллекция установленных языков и платформ, которые по-прежнему востребованы — Java, .NET, Python, Ruby. Они имеют большие сообщества и будет хорошо смотреться в любом резюме. Все они имеют свои плюсы и минусы.