Результати пошуку
ITVDN: курси програмування
Відеокурси з
програмування
Підписка

300+ курсів за популярними IT-напрямками

Вибери свою IT спеціальність

Підписка
Підписка

300+ курсів за популярними IT-напрямками

Результати пошуку за запитом: mvc4 5*
10 секретів ідеального коду

Автор: Nicolas Baptiste

Взято с настоящего поля битвы: работы Спустя практически 10 лет работы на Javascript мне захотелось поделиться своими ежедневными секретами написания кода. Многие советы были позаимствованы у моих коллег, из книг или видео. Такие приёмы не должны оставаться в тайне, поэтому я ими и хочу поделиться!   1. Меньше кода = меньше багов Это может прозвучать тривиально, но всегда держите это в уме. Любую часть можно убрать, даже не сомневайтесь! Просто удалите. Неработающий код, неиспользуемые переменные, лишние скобки: каждый малейший знак учитывается. 2. Развивайте читабельность кода Это дополнение к пункту 1. Если хочется сократить код, всегда нужно помнить о том, что рано или поздно код будет прочитан другими людьми. Может, кто-то другой, может, через несколько месяцев, но будет очень неприятно, если придется потратить больше времени на разбор кода, чем было потрачено на его написание. Запомните: код всегда важнее, чем комментарии или документация. Следите за переменными и именами функций, они должны быть достаточно наглядными, чтобы потом не объяснять их в комментариях. 3. Не пугайтесь функций, возвращающих функции Когда я начал кодить на javascript, мне не нравился метод написания кода с использованием функций, возвращающих функции, потому что, казалось, этому сложно следовать в процессе выполнения. Но этот шаблон, довольно мощный и часто используемый, называется замыканием. Это действительно полезная функция и, как только вы используете её, она значительно облегчает структуру кода. И это первый шаг для погружения в функциональное программирование. 4. Извлекание частей Функция становится слишком большой? Сделайте из нескольких частей отдельные функции! Не совсем понятно? Выделите их в переменную и дайте ей понятное имя. Такой метод, конечно, схож со вторым пунктом, но это сделает ваш код более читабельным. А знаете ли Вы, что Ваша среда разработки может помочь? Ctrl + alt + v выражение извлекается в Webstorm и потом выделяется в переменную. Ctrl + alt + m сделает из него функцию. 5. Переверните мышление Это первый шаг перед TDD. Скорее всего, вы всегда знаете, чего хотите достичь. Так начните с конца, напишите ожидаемый результат и пишите код в обратном порядке, чтобы получить то, что хотите. Если будете писать в прямом порядке, вероятней всего, Вы начнете писать и проверять, работает ли оно. Поменяйте, проверьте, напишите что-то другое, проверьте и…. Возможно, это даже не особо полезно для достижения Вашей цели. Но такое мышление поможет сфокусироваться и потратить меньше времени. 6. Исключите условные операторы Если для функции у вас на уме принцип единственной ответственности, условным операторам тут не место. Они создают ветвления в потоке выполнения. И один маленький if позволит другим разработчикам добавить всё больше логики в него. Я считаю, что условные операторы if вообще не должны использоваться без else, а их размещение внутри другого условного оператора нужно полностью запретить! Опять же, такой подход делает код более сложным, и часто оказывается, что функция выполняет больше, чем нужно. Как же от этого избавиться? Смотрите следующий пункт. 7. Используйте lodash_.get вместо проверки null/undefined Очень часто, анализируя код, я вижу использование ifs для проверки null или undefined значений. Но без использования else! То есть идёт работа только с одним путём, а другой путь остаётся местом загадок (и багов!). Тут на помощь приходит Lodash (лучшее, написанное когда-либо на javascript) с его замечательной функцией get. Её первым аргументом является объект, над которым вы работаете, вторым – путь к дочерним атрибутам, которые вы хотите получить, и третьим является значение по умолчанию в случае с undefined. Два преимущества использования всего одной функции! 8. Используйте тернарные операторы Кто-то может поспорить насчёт их читабельности, но если правильно извлекать функции, они станут превосходным инструментом. Они также заставят Вас сотрудничать с else case. Плюс, они не позволяют содержать в себе больше кода. Чем меньше ваш код похож на смертельную пирамиду, тем лучше. 9. Никаких больше for loops, только map, reduce и filter Мы, наконец-то, подошли тут к теме функционального программирования, так как эти методы являются основой этого стиля. Хоть названия или теории не особо известны, они действительно помогут избежать тяжело читаемого императивного стиля для выражений for. Эти методы сейчас уже встроены в большинство браузеров, так что используйте их! Но если вам всё же нужна совместимость, можно использовать lodash аналоги. Итак, когда их использовать? Выражение for, имеющее дело со всеми точками входа – это map for + if , вероятно, filter sum или accumulation -  это просто reduce  С помощью сочетания этих методов вы можете сделать много работы в хорошей и краткой форме. 10. Читайте исходники Если документации недостаточно - читайте исходники! Это поможет Вам узнать, как их использовать и даст неплохое представление об их качестве. Плюс, некоторые библиотеки имеют исходники, встроенные в их документацию. Так с ними даже быстрее можно ознакомиться. С нашей IDE можно часто обращаться к источнику с помощью ctrl + clic на имя метода. Это отличный способ убрать магию и набраться вдохновения для собственного кода!   В заключение хотелось бы сказать, что это те принципы, которые помогают лично мне. Жаль, что я не знал их раньше, например, в школе… Зато ты знаешь их сейчас. И ими можно пользоваться практически в любом контексте. Некоторые из них распространяются не только на javascript, так что пользуйтесь ими где угодно :) Также хотелось бы обратить ваше внимание на великого Uncle Bob и его сайт . Вам определённо стоит посмотреть эти видео, даже несмотря на то, что они по большей части касаются Java, идеи применимы и в Javascript. Переведено с источника.
Введення в розробку програм під iOS. Частина 7

Автор: Volodymyr Bozhek

Здравствуйте, дорогие читатели. В этом уроке мы: выполним рефакторинг проекта “Warehouse” под “iOS”. Создадим удобную структуру расположения модулей в проекте. научимся пользоваться библиотекой “Alamofire”. научимся сохранять объекты в настройки телефона и извлекать их. реализуем функциональность для работы со всеми сервисами, созданными в данном уроке. Откройте проект “Warehouse” под iOS, который мы делали на протяжении всех уроков. Запросы на сервер можно отправлять синхронно и асинхронно. Основное требование Apple, чтобы приложение не зависало и большие операции выполнялись в фоновом режиме. Представим ситуацию: у нас мобильный интернет “GPRS EDGE”, скорость соединения около 54 кб/сек. Если отправлять запрос на получение данных на сервер синхронно, мы блокируем работу приложения и пользователь вынужден ждать. Наша задача - дать пользователю выбор, ждать или нет. Поэтому лучше всего отправлять такой запрос асинхронно. На сегодняшний день существует такая библиотека “Alamofire”, где собраны лучшие практики асинхронного программирования и все оптимизировано за нас. Откройте браузер и перейдите по адресу: “https://github.com/Alamofire/Alamofire”. На сайте нажмите кнопку “Clone of download” и в появившемся окне нажмите “Download Zip”. Когда скачается архив, распакуйте его. Выделите файл “Alamofire.xcodeproj” и папку “Source” и скопируйте их в папку проекта “Warehouse”. Перейдите в проект “Warehouse”. В панели навигации выполните контекстное меню по папке “Warehouse” и в контекстном меню выберите “Add files to 'Warehouse'...”. В открывшемся диалоговом окне выбора файлов  выберите файл “Alamofire.xcodeproj”. Выполните в меню “Product -> Build”, чтобы собрать проект. Затем выделите в панели навигации папку “Warehouse”, в области содержимого откроются свойства проекта. Откройте вкладку “General”, пролистайте область содержимого в самый низ. Установите свойство “Embedded binaries”, нажмите плюсик. В появившемся диалоговом окне, выберите “Alamofire.Framework.iOS” и нажмите кнопку “Add”. Результат должен получиться таким. Выполните в меню “Product -> Build”, чтобы собрать проект. Итак, что мы только что сделали. Мы подключили исходники библиотеки “Alamofire” в проект “Warehouse”. Можно было это сделать и отдельным проектом, но так проще для восприятия. Затем мы подключили сборку “Alamofire.framework.iOS” в приложение “Warehouse”  для возможности обращения к классам данной библиотеки из нашего проекта. Теперь нам надо создать модели данных, в которые мы будем сохранять JSON объекты, полученные с сервера. Также необходимо предусмотреть удобство сохранения и извлечения данных, поскольку запросы будут асинхронными и просто вернуть нужный экземпляр из метода будет проблематично. Именно поэтому мы поступим следующим образом. Отправляем запрос на сервер асинхронно. В метод отправки  добавляем callback метод, который вызовется  после того, как модель была успешно сохранена в настройки телефона. Ждем ответа. Когда ответ пришел, десериализуем объект JSON в нужную модель данных, объявленную в приложении. Сохраняем заполненную модель в настройки телефона. В контроллере, из которого был отправлен запрос, в callback методе  извлекаем модель из настроек телефона и инициализируем данными элементы управления. Создайте следующую структуру папок в панели навигации. В папку “Warehouse”  были добавлены папки “Controllers”, “Protocols”, “Models”, “Services”. В папку “Controllers”  были добавлены папки “Users”, “Registration”, “Authorization”, “Supplies”. В папке “Controllers”  будут храниться модули контроллеров. В папке “Protocols”  будут храниться модули протоколов. В папке “Models”  будут храниться модули моделей данных. В папке “Services”  будут храниться модули обращения к REST сервисам. Сейчас нам необходимо провести некоторую подготовку, перед тем как начать реализовывать приложение. Пока, давайте,  добавим нужные модули. Сам по себе добавленный модуль ничего не делает. Перетащите в папку “Controllers / Supplies”  модули “SuppliesViewController.swift”, “ProductViewController.swift”. Перетащите в папку “Controllers / Authorization”  модуль “ViewController.swift”. Добавьте в папку “Controllers / Registration”  модуль “RegistrationViewController.swift”. Добавьте в папку “Controllers / Supplies”  модуль “ImageCollectionViewController.swift”. Добавьте в папку “Controllers / Users”  модули “UsersViewController.swift”, “UserItemViewController.swift”. Добавьте в папку “Protocols”  модуль “SelectedImageProtocol.swift”. Добавьте в папку “Models”  модули “ProductModel.swift”, “UserModel.swift”. Добавьте в папку “Services”  модули “UserData.swift”, “ProductData.swift”, “Services.swift”, “AuthorizationService.swift”, “ProductService.swift”, “UserService.swift”. У вас должна получиться следующая структура. Как видите, проект стал больше. Он станет еще больше, как только мы заполним добавленные модули. Откройте модуль “Models / UserModel.swift”. Заполните его, как показано ниже. В этом модуле мы создаем модель пользователя, в которую будем десериализовывать JSON объект пользователя,  полученный от сервера. В классе “UserModel”  мы добавляем поля модели “_id”, “userName”, “userPwd”, “userDesc”, которые декларировали при объявлении схемы базы данных “Mongo DB” на сервере. На 9 строке  мы подключаем пространство имен “Foundation”. На 11 строке  мы объявляем класс с именем “UserModel”, который наследует класс “NSObject”, являющийся базовым классом для всех классов в iOS платформе. Наследуемся от протокола “NSCoding”, данный протокол необходимо реализовать, чтобы объект можно было сохранять в настройках телефона и извлекать оттуда. Рассмотрим протокол “NSCoding”. Протокол содержит метод “encode” и инициализатор. Экземпляр класса “NSCoder”  позволяет кодировать и раскодировать свойства класса, значения которых могут сохраняться в хранилище настроек телефона. В методе “encode”  данные извлекаются из свойств и кодируются в формат, нужный для сохранения в настройки телефона. В инициализаторе  данные извлекаются и раскодируются из настроек телефона, затем сохраняются в нужные свойства класса. Вот так это работает. Теперь вернемся и рассмотрим дальше модуль “UserModel.swift”. На 12 строке  мы объявляем поле “_id” типа “String” и инициализируем его значением по умолчанию, пустая строка. На 13 строке  мы объявляем поле “userName” типа “String” и инициализируем его значением по умолчанию, пустая строка. На 14 строке  мы объявляем поле “userPwd” типа “String” и инициализируем его значением по умолчанию, пустая строка. На 15 строке  мы объявляем поле “userDesc” типа “String” и инициализируем его значением по умолчанию, пустая строка. На 17 строке  мы переопределяем инициализатор по умолчанию для класса “NSObject”, тем самым мы говорим, что создание объекта без передачи аргументов в инициализатор будет возможно. На 18 строке  мы вызываем базовый инициализатор из класса “NSObject”. На 21 строке  мы объявляем параметризированный инициализатор. Данный инициализатор  инициализирует поля класса “_id”, “userName”, “userPwd”, “userDesc” значениями, переданными в аргументы инициализатора. На 31 строке  мы реализуем конструктор протокола “NSCoding”. Ключевое слово “required”  обозначает, что вызов этого инициализатора обязателен в первую очередь.   Ключевое слово “convenience”  гарантирует то, что после вызова этого инициализатора  будут вызваны остальные инициализаторы классов, связанных с этим классом по цепочке вверх. На 32 строке  мы раскодируем из настроек телефона  значение свойства “_id”  путем вызова на экземпляре “aDecoder” типа “NSCoder”  метода “decodeObject”, аргумент “forKey”  которого принимает название поля/свойства, которое было сохранено в настройки телефона. Этот метод возвращает значение “Any?”, т.е. неопределенный тип (аналог в C#  - это Object, в Visual Basic - это Variant). Нам необходимо это значение вручную привести к нужному типу поля. В настройках телефона содержится большой файл, который имеет древовидную  структуру словаря ключ и значение. Чтобы привести к нужному значению, мы используем конструкцию приведения к типу “as”. Причем, мы можем управлять этой конструкцией. Например, нам надо привести тип “Any?” к типу “String”. Для этого мы используем конструкцию “as!”. Если же нам надо привести к типу “String?” (nullable тип), тогда мы используем конструкцию “as?”. Зачем мы это делаем. Настройки телефона  разбиваются на разные файлы, для каждого приложения используется свой файл с настройками. Когда приложения устанавливаются на macOS или iOS платформу, они устанавливаются в виде папки и имеют тип “Bundle”. Apple довольно кардинально подошла к этому вопросу, она сделала так, что все настройки для каждого приложения хранятся внутри “Bundle” данного приложения. И когда приложение удаляется, удаляется “Bundle” со всеми настройками с диска операционной системы и не остается никаких следов после удаления. Чего не скажешь о таких платформах, как Windows и Android, которые засоряют все пространство на диске операционной системы вокруг папки, в которую было установлено приложение, и выше. И когда приложение удаляешь, есть риск, что после этого будет необходимо еще и переустановить операционную систему. Отчасти именно из-за этого подхода техника Apple  пользуется такой популярностью и их устройства выигрывают по отказоустойчивости программного обеспечения по сравнению с конкурентами. Если приложение обновляется, то файл настроек остается старый. Чтобы обновить его, надо выполнить запись в него, тогда старые конструкции удалятся, а новые добавятся.   Представим себе ситуацию, сейчас в нашей модели “UserModel”  всего 4 поля. Мы сохранили эту модель в файл настроек телефона. Отправили в магазин, пользователи скачали, установили приложение. Вышла новая версия приложения, в которой мы добавили новое поле в эту модель с именем “userRole” типа “String”. Разумеется, первая операция, которая будет выполнена как обычно - это запрос данных из настроек телефона, но для поля “userRole”  установлено значение “nil” (аналог в C# null) и произойдет ошибка и креш приложения, так как мы не обработали эту ситуацию, а пытаемся через метод “decodeObject” получить значение этого поля и еще и явно привести его к типу “String”  через, например, оператор “as!”. Но в поле “userRole” - значение “nil” и оно не приведется к типу “String” через конструкцию “as!”, но может привестись без ошибок к типу “String?”  через “as?”. Надеюсь, я прояснил понятно, зачем нужна данная проверка. Продолжим. На 32 строке мы проверяем, доступно ли нам значение поля “_id”. Чтобы проверить, равно ли возвращаемое значение “nil”, можно было бы использовать такую конструкцию: “aDecoder.decodeObject(..) == nil”, но такая конструкция не слишком информативна. Поэтому мы используем конструкцию “(aDecoder.decodeObject(..) as? String) == nil”.       Далее, если данная конструкция возвращает значение “true”, тогда мы через тернарный оператор  присваиваем  константе “_id” значение по умолчанию пустая строка, иначе присваиваем реальное значение  поля “_id” из настроек телефона. С 33 по 35 строку  мы выполняем те же операции проверок для полей “userName”, “userPwd”, “userDesc”. На 36 строке  мы вызываем параметризированный конструктор и инициализируем его аргументы объявленными ранее константами. На 47 строке  объявлен вспомогательный метод “Populate”, который переносит данные из экземпляра “dictionary” типа “NSDictionary”  в поля класса “UserModel”. Этот метод мы будем использовать, когда будем создавать запросы к серверу и получать ответы.   В ответе содержится словарь типа “NSDictionary”, в котором находятся свойства с теми же именами, что и поля нашего класса. С 48 по 51 строку мы инициализируем поля класса “UserModel”  значениями из экземпляра “dictionary” c теми же проверками, что мы рассматривали выше. На 53 строке объявлен вспомогательный метод “PopulateArray”, который переносит данные из экземпляра “array” типа “NSArray” в массив объектов типа “UserModel”. Этот метод мы будем использовать, когда в ответе от сервера  придет массив JSON объектов типа “User”, чтобы сконвертировать массив типа “[NSDictionary]” в массив типа “[UserModel]”. На 55 строке  мы объявляем пустой массив типа “[UserModel]” c именем “result”. На 56 строке  мы выполняем цикл “foreach” по элементам массива “array”. На 58 строке  мы создаем новый экземпляр типа “UserModel” с именем “newItem”. На 59 строке  мы проверяем, содержит ли элемент массива  значение “nil”. На 60 строке  мы вызываем на экземпляре “newItem”  метод “Populate”, который перенесет данные из словаря типа “NSDictionary”  в поля экземпляра “newItem”. На 62 строке  мы обращаемся к экземпляру “result” и вызываем у него метод “append”, чтобы добавить новый элемент в массив, и добавляем проинициализированный выше экземпляр “newItem”. На 64 строке  мы возвращаем сконвертированный массив типа “[UserModel]” из метода “PopulateArray”. Откройте модуль “Models / ProductModel.swift”, заполните его в соответствии с содержимым ниже. В этом модуле мы создаем модель пользователя, в которую будем десериализовывать JSON объект пользователя,  полученный от сервера. В классе “UserModel”  мы добавляем поля модели “_id”, “userName”, “userPwd”, “userDesc”, которые декларировали при объявлении схемы базы данных “Mongo DB” на сервере. На 9 строке  мы подключаем пространство имен “Foundation”. На 11 строке  мы объявляем класс с именем “UserModel”, который наследует класс “NSObject”, являющийся базовым классом для всех классов в iOS платформе. Наследуемся от протокола “NSCoding”, данный протокол необходимо реализовать, чтобы объект можно было сохранять в настройках телефона и извлекать оттуда. Рассмотрим протокол “NSCoding”. Данный модуль не нуждается в объяснении, поскольку он по образу и подобию такой же, как и модуль “UserModel.swift”, с разницей лишь в том, что он построен на модели “Product” и содержит соответствующие поля.       Итак, модели данных мы создали, теперь мы сможем с уверенностью сохранять результаты ответа от сервера в нормальном виде и потом использовать их при инициализации элементов управления в соответствующих представлениях :) Теперь приступаем к реализации классов для отправки запросов на сервер и сохранения/загрузки полученных данных в настройки телефона. Откройте модуль “Services / Services.swift”, заполните его в соответствии с содержимым ниже. На 11 строке  объявлен класс с именем “Services”. Данный класс будет содержать вспомогательные свойства и методы, которые мы будем часто использовать при работе с ответом от сервера. На 12 строке  объявлено статическое свойство “host” типа “String” и мы ограничили его функциональность только одним аксессором доступа “get”. Т.е. значение свойства получить можно, но изменить нельзя. В данном свойстве содержится адрес, по которому доступен сервер. В прошлом уроке, когда мы делали сервер, мы использовали адрес “http://localhost:3000” , внутри приложения клиента имеется свой “localhost”, именно поэтому мы используем явный IP адрес “127.0.0.1”, чтобы запрос шел именно на сервер, а не внутрь приложения. На 18 строке  объявлен статический метод “ejectJsonArray”, который принимает аргумент “data” типа “NSData?” и возвращает массив типа “NSArray?”. Данный метод конвертирует экземпляр класса “NSData” в экземпляр класса “NSArray”. В экземпляре “data” будет содержаться массив JSON объектов, полученных от сервера. На 19 строке  объявлена переменная “json” типа “NSArray?” и инициализировано значение по умолчанию “nil”. На 20 строке  мы используем конструкцию “do .. try .. catch” (аналог в C# - “try .. catch”). Символ “_” в блоке “catch” на месте аргумента используется потому, что мы не будем использовать этот аргумент в блоке “catch”. В случае возникновения исключения мы не будем завершать аварийно приложение, а просто вернем значение “nil” по умолчанию, которое будет говорить о том, что конвертация не удалась, поскольку в NSData  не содержится JSON объект. Аналог JSON объекта в Swift - это класс “NSDictionary”. На 21 строке  мы правым операндом обращаемся к классу “JSONSerialization” и вызываем от его экземпляра статический метод “jsonObject”, который первым аргументом принимает данные типа “Data” для конвертации, вторым аргументом принимает опции конвертации. Метод “jsonObject” возвращает экземпляр типа “Any”. Далее мы приводим это значение к нужному нам типу “NSArray?”, если приведение было проведено не успешно, будет возвращено значение “nil”. Результат конвертации присваивается объявленной выше переменной “json”. Обратите внимание на ключевое слово “try” перед конструкцией “JSONSerialization.jsonObject(..)”, использование этого ключевого слова является обязательным, так как метод “jsonObject” имеет в своей сигнатуре ключевое слово “throws”, это означает, что данный метод может генерировать исключения и их необходимо обрабатывать. На 23 строке в случае исключения вызванного методом “jsonObject”  возвращается значение “nil”. На 25 строке в случае успеха операции  возвращается экземпляр типа “NSArray?”. На 28 строке  объвлен метод “ejectJsonDictionary” с той же сигнатурой, что и метод “ejectJsonArray”, но с разницей в возвращаемом значении. В этом методе возвращается экземпляр типа “NSDictionary?”. Метод подразумевает, что в экземпляре “data” типа “NSData?” содержится единичный JSON объект и пытается его  сконвертировать в тип “NSDictionary” , являющийся аналогом JSON объектов в Swift.       Реализацию метода смысла описывать не вижу, она такая же, как и в методе “ejectJsonArray”. Реализуем сохранение моделей данных “UserModel” и “ProductModel” в настройки телефона. Откройте модуль “Services / UserData.swift”, заполните его в соответствии с содержимым ниже. Модуль большой, поэтому будем разбирать его, разбив на две части по функциональности. В первой его части  будут описаны методы сохранения / извлечения единичного экземпляра типа “UserModel” и экземпляра типа массив “[UserModel]”. Во второй его части  будут описаны вспомогательные методы  для обработчиков ошибок ответа от сервера и сохранения данных в настройки телефона. Данные методы были созданы для того, чтобы не копипастить один и тот же код много раз, а стандартизировать и сделать его повторно используемым. На 9 строке  подключено пространство имен “UIKit”, необходимое для классов “UserDefaults”, “NSKeyedArchiver”, “NSKeyedUnarchiver”. В пространстве имен уже содержится подключенное пространство имен “Foundation”, поэтому нет смысла его тоже  подключать для классов “NSArray”, “NSDictionary”, “NSData”. На 10 строке  подключено пространство имен “Alamofire”, необходимое для класса “DataResponse”. На 12 строке  объявлен класс с именем “UserData”. На 13 строке  объявлен метод “saveUserModel”, который принимает аргумент “userModel” типа “UserModel”. Метод будет сохранять поля модели “UserModel” в настройки телефона. На 14 строке  правым операндом мы обращаемся от экземпляра “UserDefaults” к его статическому свойству “standard”, это свойство является источником данных по отношению к файлу настроек телефона и содержит необходимые методы для сохранения / извлечения данных. На 15 строке  правым операндом мы обращаемся к экземпляру “NSKeyedArchiver” и вызываем статический метод “archivedData”. Этот метод принимает один аргумент “withRootObject” типа “Any” и возвращает экземпляр типа “Data”. Метод упаковывает экземпляр типа “UserModel” в экземпляр типа “Data” (аналог NSData) и возвращает его. На 16 строке  мы от экземпляра “userDefaults” вызываем метод “set”, который принимает два аргумента.  Первый аргумент “_” типа “Any?” принимает экземпляр, который необходимо сохранить в настройках телефона. Второй аргумент “forKey” типа “String” необходим, чтобы задать название ключа в словаре настроек  телефона. По этому ключу будет произведено сохранение экземпляра “UserModel”. На 17 строке  мы обращаемся к экземпляру “userDefaults”, вызываем от него метод “synchronize”. Данный метод выполняет сохранение экземпляра модели “UserModel” в настройки телефона (аналог в C#, например,  'using(StreamWriter sw = File.CreateText(“test.txt”)) { sw.Write(“some text”); sw.Flush(); }', из этого примера метод “Write” аналогичен методу “set”, а метод “Flush” аналогичен методу “synchronize”). На 20 строке  мы объявили статический метод “getUserModel”, который не принимает аргументов и возвращает экземпляр типа “UserModel”. В этом методе мы излекаем модель “UserModel” из файла настроек телефона по ключу, указанному в предыдущем методе при сохранении этой модели. На 21 строке  мы получаем экземпляр источника данных настроек телефона. На 22 строке  мы проверяем, если модель “UserModel” ранее не сохранялась в настройки телефона, тогда необходимо создать пустую модель по умолчанию. Вызываем на экземпляре “userDefaults” метод “object”, который принимает аргумент “forKey” типа “String” и возвращает значение типа “Any?”. В качестве аргумента мы передаем ключ, под которым сохраняется данная модель в настройках телефона. Если значение было найдено в словаре, будет возвращен экземпля , который надо будет привести у нужному типу. Если значение не было найдено, будет возвращено значение “nil”. На 23 строке  мы создаем пустой экземпляр типа “UserModel”. На 24 строке  мы возвращаем этот экземпляр из метода. На 26 строке  код будет выполнен при наличии записи по ключу в словаре. Правым операндом мы обращаемся к экземпляру “userDefaults”, вызываем метод “object” и передаем в аргумент “forKey”  ключ, по которому надо извлечь значение. Полученное значение приводим к типу “NSData”. На 27 строке  мы обращаемся к экземпляру “NSKeyedUnarchiver” и вызываем от него статический метод “unarchiveObject”, который принимает аргумент “with” типа “Data” и возвращает значение типа “Any”. Метод конвертирует экземпляр типа “Data” в экземпляр типа “Any”. Возращаемое значение метода мы явно приводим к типу “UserModel”. На 28 строке  мы возвращаем из метода полученный из словаря экземпляр типа “UserModel”. На 31 строке  объявлен метод “saveListOfUsersModel”, который принимает аргумент “listOfUsersModel” типа “[UserModel]” и ничего не возвращает. Метод сохраняет массив “[UserModel]” в файл настроек телефона. Описывать данный метод не имеет смысла, его работа аналогична методу “saveUserModel”. На 38 строке  объявлен метод “getListOfUsersModel”, который не принимает аргументов и возвращает экземпляр типа массив “[UserModel]”. Метод извлекает из настроек телефона массив типа “[UserModel]” по ключу, указанному в методе “saveListOfUsersModel”. Описывать данный метод не имеет смысла, его работа аналогична методу “getUserModel”. Откройте модуль “ProductData.swift”, заполните его в соответствии с содержимым ниже. В первой части описано сохранение / извлечение моделей типа “ProductModel”, “[ProductModel]” в настройки телефона. Описывать код не имеет смысла, его работа точно такая же, как и в модуле “UserData.swift”. Во второй части описаны вспомогательные методы для обработки запросов от сервера и сохранения данных. Описывать код не имеет смысла, его работа точно такая же, как и в модуле “UserData.swift”. Теперь, наконец то, мы можем реализовать первый класс для работы с сервисом авторизации пользователя и разобрать работу с библиотекой “Alamofire”. Откройте модуль “Services / AuthorizationService.swift”, заполните его в соответствии с содержимым ниже. На 12 строке  объявлен класс с именем “AuthorizationService”.             На 13 строке  объявлен метод “login”, который принимает три аргумента и ничего не возвращает.             Первый аргумент “userName” типа “String” -  это имя пользователя (логин).             Второй аргумент “userPwd” типа “String” -  это пароль пользователя.             Третий аргумент - это “callback” метод, который принимает аргумент типа “Bool” и ничего не возвращает.             Метод с такой сигнатурой, переданный в третий аргумент, будет вызван по завершению обработки ответа от сервера. В случае наличия ошибок  в аргумент этого метода будет передано значение “false”. В случае успешно проведенной операции - значение “true”.             Да, можно было сделать этот аргумент другим типом, например, отдельным классом ошибок, в который можно было сохранять подробно, что за ошибка произошла.             Но это учебный пример, он итак очень большой. Я решил не усложнять этим жизнь  ни себе, ни вам :)             На 14 строке  правым операндом мы вызываем метод “request” из библиотеки “Alamofire”.             Метод принимает 5 аргументов и возвращает значение типа “DataRequest”.             Данный метод используется для отправки HTTP/HTTPS запросов на сервер.             Первый аргумент “_” типа “URLConvertible” принимает адрес, по которому необходимо отправить запрос. Мы его инициализируем значением “\(Services.host)/auth”, которое после подстановки свойства “host”  будет выглядеть вот так: “http://127.0.0.1:3000/auth”.             Второй аргумент “method” типа “HTTPMethod” принимает HTTP глагол. Тип “HTTPMethod”  является перечислением и может принимать следующие значения. Мы инициализируем данный аргумент значением “.post” (это сокращенная запись, полная запись “HTTPMethod.post”)             Третий аргумент “parameters” типа “Parameters?”. Принимает словарь, ключ - значение, мы его заполняем теми же данными, что отправляли в прошлом уроке через утилиту “Postman”. Конкретно, мы передаем значение имени пользователя и его пароль.             Четвертый аргумент “encoding” типа “ParameterEncoding”. Этим параметром задается заголовок запроса “Content-Type”, т.е. указывается тип данных, который будет отправлен на сервер, сами данные задаются в третьем аргументе “parameters”. Мы инициализируем аргумент значением “JSONEncoding.default”, что аналогично записи “application/json”. Тип “ParameterEncoding” является протоколом и имеет следующие реализации себя в классах “URLEncoding”, “JSONEncoding”, “PropertyListEncoding”. Про эти классы вы можете почитать в официальной документации на сайте “Alamofire”.             Пятый аргумент “headers” типа “HTTPHeaders?” имеет значение по умолчанию “nil”, в нашем вызове метода он не используется. Данный аргумент задается, если вы желаете отправить в заголовках запроса на сервер какую- либо информацию, например, “cookie”. Если у вас сервер написан на Web API и вы используете для авторизации OWin , с настройкой надо использовать “cookie” как ключ авторизации пользователя. Тогда при отправке запросов вам будет необходимо заполнять этот аргумент. Пример его заполнения может выглядеть вот так: Этот пример взят из моего приложения “Smart Payment”,  написанного на Swift 2.2 c минимальной версией iOS 8.3  библиотекой “Alamofire” версии 2. Чтобы этот пример работал на Swift 3, надо заменить значение параметра “encoding” на “JSONEncoding.default”. Этот пример не относится к нашему приложению и его переписывать не нужно.             На 16 строке  мы вызываем от экземпляра “r” типа “DataRequest”  метод “responseJSON”. Этот метод выполняет запрос на сервер и содержит три аргумента.             Первый аргумент “queue” типа “DispatchQueue?”  по умолчанию содержит значение “nil”. Используется при переопределении потока выполнения запроса.             Второй аргумент “options” типа “JSONSerialization.ReadingOptions” по умолчанию содержит значение “.allowFragments”. Используется для задания опция отправки запроса.    Тип “ReadingOptions” является структурой и имеет такое объявление. Третий аргумент “completionHandler” типа делегат “@escaping (DataResponse) -> Void”. Данная сигнатура принимает один аргумент типа “DataResponse” и ничего не возвращает. В этом аргументе содержится ответ от сервера. Сам метод вызывается после запроса, когда сервер отправил ответ на клиент.             Обратите внимание, на 16 строке, когда мы вызываем метод “responseJSON”, у нас нет круглых скобок после метода, а идут сразу фигурные скобки, это называется “closure” подход. В фигурных скобках мы объявляем лямбда метод с сигнатурой, соответствующей третьему аргументу метода “responseJSON”.             Запись “response in” означает следующее: “response” - это аргумент лямбда метода, а само тело метода начинается после ключевого слова “in”. Аргумент “response” имеет тип “DataResponse”.             На 17 строке  мы проверяем, если ответ пришел с ошибкой, то это означает, что данных нет и пришло пустое значение типа “nil”. Мы обращаемся к экземпляру “response” и вызываем свойство “result” типа “Result” и от свойства “result” вызываем свойство “value” типа “Value?”. Если в свойстве “value” пришло пустое значение, значит запрос не был выполнен, возможно, по причине недоступности сервера или отсутствия подключения к интернету.             На 18 строке  мы вызываем “callback” метод “completion” со значением “false”.             На 21 строке, в случае если проблем с подключением к серверу не возникло, мы правым операндом вызываем от экземпляра “Services” метод “ejectJsonDictionary”, который ранее уже подробно рассматривали. В аргумент “data” мы передаем значение свойства “data”, вызванного от экземпляра “response”. Свойство “data” имеет тип “Data?”. Левому операнду “json” присваивается экземпляр “NSDictionary”, в котором содержится JSON объект пользователя, прошедшего авторизацию, который был отправлен сервером на клиент.             На 22 строке  мы проверяем, если пришел JSON объект с ошибкой, тогда возвращаем нужный флаг в “callback” методе “completion”. Помним, что в предыдущем уроке мы создавали JSON объект с ошибкой на сервере, в случае если пользователь ввел неверные данные авторизации,  JSON выглядел вот так '{ “Error”: “Authorization error”}'.             У словаря “json” есть возможность получить все ключи, для этого надо вызвать свойство “allKeys”, которое имеет тип “[Any]”. Но,  поскольку выполнить поиск строки в массиве объектов нельзя, мы оборачиваем массив объектов в массив типа “Array” и явно приводим его к нужному нам типу массива “[String]”. Мы это делаем, поскольку знаем наверняка, что ключи JSON объекта имеют тип “String”. Затем от полученного экземпляра типа “[String]”  мы вызываем метод “contains”, в аргумент которого передает название свойства JSON объекта ошибки, значение “Error”. Если такой JSON объект пришел в ответе, значит пользователь ввел неправильные логин или пароль, или ввел несуществующего пользователя.             На 23 строке  мы вызываем “callback” метод “completion” с аргументом “false”.             На 25 строке  мы создаем пустой экземпляр “userModel” типа “UserModel”.             На 26 строке  мы вызываем на экземпляре “userModel”  метод “Populate”  в аргумент “dictionary”, передаем экземпляр “json”. Метод “Populate”  перенесет данные из экземпляра “json” типа “NSDictionary” в поля экземпляра “userModel”.             На 27 строке  мы обращаемся к экземпляру “UserData” и вызываем статический метод “saveUserModel”, чтобы сохранить данные экземпляра “userModel” в настройки телефона. В аргумент “userModel”  мы передаем экземпляр “userModel”.             На 28 строке  мы вызываем “callback” метод “completion” и передаем в него результат операции “true”.             Позже в контроллере, где мы будем вызывать метод “login”, будет передан “callback” метод “completion”, объявленный в этом контроллере. Тем самым решается проблема ожидания ответа от сервера в контроллере, так как есть “callback” метод. Внутри этого метода мы проверим результат операции и затем обратимся к экземпляру “UserData” и вызовем метод “getUserModel”, который вернет нам экземпляр “UserModel”, с которым мы сможем работать на стороне контроллера.             Откройте модуль “Services / UserService.swift”, заполните его, как показано  ниже. На 12 строке  объявлен класс “UserService”.             На 13 строке  объявлен метод “get”, который принимает два аргумента и ничего не возвращает.             Первый аргумент “id” типа “String?”  принимает идентификатор пользователя “_id”. Если передать в аргумент значение “nil”, будет возвращен весь список пользователей, если передать конкретный идентификатор, будет возвращен конкретный пользователь.             Второй аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 29 строке  объявлен метод “create”, который принимает 4 аргумента и ничего не возвращает. Метод отправляет запрос на создание пользователя на сервер.             Первые три аргумента “userName”, “userPwd”, “userDesc” имеют тип “String” и содержат данные пользователя. Идентификатор пользователя в операции создания не нужен. Так как идентификатор пользователю присваивается на стороне сервера и данный метод выполняет как раз эту операцию.             Четвертый аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 37 строке  объявлен метод “update”, который принимает 5 аргументов и ничего не возвращает. Данный метод отправляет запрос на обновление данных пользователя на сервер.             Первые 4 аргумента “id”, “userName”, “userPwd”, “userDesc” имеют тип “String” и содержат данные пользователя.             Пятый аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 45 строке  мы объявляем метод “delete”, который принимает два аргумента. Этот метод отправляет запрос на удаление пользователя на сервер.             Первый аргумент “id” типа “String?”  принимает идентификатор пользователя “_id”.             Второй аргумент “completion” типа делегат “(Bool) -> Void”, используется для получения сигнала, что ответ был получен и основные операции были выполнены.               Откройте модуль “Services / ProductService.swift”, заполните его, как показано ниже На 12 строке  объявлен класс “ProductService”.             На 13 строке  объявлен метод “get”, который принимает два аргумента и ничего не возвращает.             Первый аргумент “id” типа “String?”  принимает идентификатор товара “_id”. Если передать в аргумент значение “nil”, будет возвращен весь список товаров, если передать конкретный идентификатор, будет возвращен конкретный товар.             Второй аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 29 строке  объявлен метод “create”, который принимает 4 аргумента и ничего не возвращает. Метод отправляет запрос на создание товара на сервер.             Первые три аргумента “productImage”, “productName”, “productDescription” имеют тип “String” и содержат данные товара. Идентификатор товара в операции создания не нужен. Так как идентификатор товару присваивается на стороне сервера и данный метод выполняет как раз эту операцию.             Четвертый аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 37 строке  объявлен метод “update”, который принимает 5 аргументов и ничего не возвращает. Данный метод отправляет запрос на обновление данных товара на сервер.             Первые 4 аргумента “id”, “productImage”, “productName”, “productDescription” имеют тип “String” и содержат данные пользователя.             Пятый аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 45 строке  мы объявляем метод “delete”, который принимает два аргумента. Этот метод отправляет запрос на удаление товара на сервер.             Первый аргумент “id” типа “String?”  принимает идентификатор товара “_id”.             Второй аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             Заметьте, адреса запросов, используемых нами при отправке на сервер, совпадают с теми, что мы использовали в прошлом уроке в утилите “Postman”.             На этом урок завершен.             На следующем уроке мы продолжим разработку клиента, создадим контроллеры и представления для редактирования пользователей, подключим работу сервисов к странице входа в систему, создадим форму регистрации пользователя.
Введення в розробку програм під iOS. Частина 6

Автор: Volodymyr Bozhek

Здравствуйте,  дорогие читатели. В этом уроке мы: Установим менеджер установки пакетов “Brew”; Научимся пользоваться базой данных “Mongo DB” и изучим библиотеку “Mongoose”; Установим и сконфигурируем базу данных “Mongo DB”; Научимся запускать демон (службу) “Mongod” и подключаться к ней; Научимся просматривать документы базы данных “Mongo DB” с помощью приложения “Robomongo”; Создадим REST сервисы для товаров и пользователей с использованием “NodeJS”; Научимся тестировать REST сервисы с помощью расширения “Chrome” браузера “Postman”; Итак, вы скорее всего задались вопросом, почему же я выбрал именно такие технологии для написания REST сервисов. Почему не выбрал то, что уже давно у всех на слуху, такие технологии как Web API + Entity Framework. Ответ прост, я выбрал ровно те технологии, которые явно подходят платформе, на которой мы ведем разработку macOS. Да, можно было установить Xamarin Community Edition, в нем создать ASP.NET проект и сделать сервисы на Mono.NET. Еще был вариант сделать сервисы на виндовс платформе, развернутой на виртуальной машине в веб сервере IIS с сетевым адаптером мост. Это для того, чтобы веб сайт и сервисы были доступны на хостовой машине macOS в браузере. Возможно, в одном из моих будущих видео курсов по разработке iOS приложений я буду использовать именно платформу Windows и Visual Studio для разработки REST сервисов. Но в этом уроке  мы будем делать сервисы на NodeJS,  и вы сами убедитесь в том, насколько это просто и как мало кода надо для этого написать :) В качестве среды разработки я буду использовать WebStorm, он довольно хорошо себя оправдал как один из продуктов JetBrains. В этом уроке мы не будем работать с приложением “Warehouse” под iOS, поскольку мы должны приступить к изучению библиотеки “Alamofire”. А для нее необходимы REST сервисы, которых у нас сейчас нет. Показывать работу на всяких httpbin, parse и других сервисах тестирования я не буду, этого полно в сети интернет, вы и сами будете видеть это в поиске нужной вам информации. “Alamofire” -  это асинхронная сетевая библиотека для работы с REST сервисами. В большинстве туториалов  не рассказывается, как сделать REST сервисы и клиент к ним на разных платформах. Мне хотелось бы нарушить этот стереотип и предоставить вам учебные примеры по созданию полнофункционального REST сервиса и полнофункционального iOS клиента в составе приложения “Warehouse” к нему. Проще говоря, дать готовое решение  для быстрого понимания, что и как делается. Это то, чего обычно не хватает новичкам и то, чего обычно нигде не выкладывают. Приходится самостоятельно разбираться и искать нужную информацию. Откройте “WebStorm”. Выберите в меню “File -> New Project”. Выделите тип проекта “Node.js Express App”. В поле “Location”  укажите название проекта “Warehouse” и путь, по которому вы будете сохранять данный проект. В поле “Template”  выберите “EJS”. Нажмите кнопку “Create”. Вы увидите диалоговое окно с предложением, откуда взять библиотеку “NodeJS”. Оставьте пункт по умолчанию “Download from the Internet” и нажмите кнопку “Configure”. Будет загружен из сети интернет “NodeJS”, “Express” и другие библиотеки. Панель навигации будет выглядеть у вас так: Рассмотрим, что создал нам данный шаблон. В папке “bin”  хранится файл “www”, в нем находится настройка сервера “NodeJS”, с этого файла происходит запуск нашего сервера. В папке “node_modules”  находятся библиотеки, которые были автоматически загружены  шаблоном проекта NodeJS. В папке “public”  находятся ресурсы, используемые нашим сервером (картинки, CSS стили, скрипты). В папке “routes”  находятся скрипты, которые должны содержать маршрутизацию относительно текущего URL,  введенного пользователем. Например,  файл с именем “users.js”  вызывется, когда пользователь введет в браузере URL: “http://localhost:{port}/users”. Файл “index.js”  вызывется, когда пользователь введет URL: “http://localhost:{port}”. Если сравнивать с паттерном MVC (Model View Controller), то в папке “routes”  находятся “контроллеры”. В папке “views”  находятся представления. Расширение “*.ejs”  говорит о том, что содержимое данного файла будет транслироваться через библиотеку “Express JS”. Данная библиотека создана в помощь, чтобы можно было проще создавать маршрутизацию в NodeJS  и проще создавать пользовательский интерфейс. Внутри этих файлов “*.ejs”  содержится обычная “HTML” разметка. Файл маршрутизации и файл представления должны называться одинаково, если они будут связаны. В текущем проекте связаны только “index.js” и “index.ejs”. Маршрут “users.js”  не имеет явного представления, для него преставление  генерируется средствами “NodeJS”. Наша задача создать только REST сервисы, поэтому работу с “EJS”  мы не будем рассматривать в данном уроке. Рассмотрим содержимое файла “www” в папке “bin”. На 7 строке  через библиотеку “Require JS”  мы подключаем содержимое модуля “app.js”  в текущий исполняемый модуль “www” и запускаем это содержимое на исполнение. В файле “app.js”  содержатся настройки для работы нашего сервера. На 8 строке  мы задаем режим отладки для нашего сервера. На 9 строке  мы подключаем модуль “http”  для работы с HTTP запросами. На 15 строке  задается порт “3000”, на этом порту сервер будет принимать входящие запросы к нему. На 16 строке  устанавливает этот порт для свойства “port” сервера. На 22 строке  создается сервер на основе настроек экземпляра “app”. На 28 строке  сервер запускается и начинает прослушивание на порту “3000”. На 29 строке  привязывается функция “onError”, вызываемый при возникновении ошибок. На 30 строке  привязывается функция “onListening”, вызываемая  при начале прослушивания запросов сервером. Остальной код в данном модуле не представляет для нас интереса. Чтобы запустить приложение, выполните в меню “Run -> Run bin/www”, сервер запустится. Затем откройте браузер и введите URL: “http://localhost:3000”, вы увидите приветствие. Чтобы остановить сервер, выполните в меню “Run -> Stop”. Теперь необходимо установить базу данных “Mongo DB”. Подробнее об установке  вы можете прочитать тут. Проще всего устанавливать базу данных  через менеджер пакетов “Brew”. Давайте установим его. Перейдите в браузере по адресу, на сайте будет предложено скопировать строку “/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)” в терминал. Откройте терминал и скопируйте в него строку установки “Brew”. После установки  вы увидите следующее: Теперь необходимо установить базу данных “Mongo DB”. Установка базы данных через “Brew”  проще тем, что “Brew”  сам заполняет нужные переменные среды окружения и вам не надо этого делать вручную  в случае с установкой базы данных вручную. Перейдите в “WebStorm”, откройте внутри него терминал. И введите команду “brew install mongodb && mongod”, данная команда скачает и установит “Mongo DB”, а также выполнит настройки по умолчанию для демона “mongod”. Выполните в терминале команду “sudo mkdir -p /data/db”. Введите свой пароль и нажмите кнопку “Enter”. Данная команда создаст папку “data” и в ней папку “db”, эта папка используется по умолчанию для добавления базы данных “Mongo DB”. Команда “sudo”  используется, чтобы выполнить остальную часть команды от имени администратора.       Теперь необходимо установить в наш проект библиотеку “Mongoose”  для осуществления возможности работы с базой данных их кода приложения сервера . Выполните в терминале команду “npm install mongoose”. Документацию по этой библиотеке можно почитать тут:  Данную команду мы выполняли через  встроенный в NodeJS  менеджер пакетов, так как устанавливаем данную библиотеку исключительно для нашего проекта, а не глобально. Откройте терминал, введите команду “mongod”. Данная команда запускает сервер базы данных “Mongo DB”, который принимает входящие подключения на порту “27017”. На картинке выше  вы можете в этом убедиться “waiting for connections on port 27017”. Демон запустили, теперь перейдите в “WebStorm”, в терминале внутри введите команду “mongod –version”, вы увидите версию базы данных “Mongo DB”, которая была установлена на ваш компьютер. Теперь необходим клиент с GUI  для работы с базой данных. Откройте браузер, перейдите по адресу, нажмите кнопку “Download”, скачайте и установите приложение. После установки приложения  откройте его. Нажмите на иконку: Откроется приложение “Robomongo”. Нажмите кнопку “Connect to local or remote Mongo DB instance”. Откроется диалоговое окно подключения к базе данных. Нажмите кнопку “Create”. Откройте диалоговое окно настройки подключения к базе данных. Заполните настройки, как показано на картинке выше и только на вкладке “Connection”, нажмите кнопку “Save”. После этого в окне “Mongo DB connections”  у вас появится добавленное подключение. Выделите его и нажмите кнопку “Connect”. Нажмите правой кнопкой в панели навигации по “Warehouse” и в контекстном меню выберите пункт “Create Database”. В поле “Database Name”  введите “warehouse”, нажмите кнопку “Create”. На картинке выше  видно, что база данных “warehouse”  была успешно добавлена. Супер :) Теперь надо добавить в проект модель и схему базы данных, они создаются с помощью библиотеки “Mongoose”. Перейдите в “WebStorm”. В панели навигации, нажмите правой кнопкой по “Warehouse”, в контекстном меню выберите “New -> Directory”. Создайте папку с именем “models”. Создайте в папке “models”  модули “Product.js” и “User.js”. А в папке “routes” создайте модули “auth.js”, “products.js”. Откройте модуль “Product.js”, заполните его, как показано на картинке ниже: На 1 строке  мы подключаем библиотеку “mongoose” и создаем ее экземпляр с именем “mongoose”. На 2 строке  мы правым операндом создаем схему таблицы товаров и присваиваем ее левому операнду с именем “ProductSchema”. Таблица товаров содержит поля: скрытое поле “_id”, типа “String”, в этом поле содержится уникальный идентификатор строки. скрытое поле “__v”, типа “Number”, в этом поле содержится версия API  по умолчанию 0. Версия API  нужна для того, чтобы можно было менять реализацию сервисов и иметь несколько версий  для обратной совместимости. поле “productImage”, типа “String”, тут будет храниться название картинок из нашего проекта под iOS, названия от “tool001” до “tool012”. поле “productName”, типа “String”, тут будет храниться название товара. поле “productDescription”, типа “String”, тут будет храниться описание товара. На 7 строке  мы правым операндом создаем экземпляр модели с именем “Product” и указываем создать экземпляр на основе схемы, описанной в экземпляре “ProductSchema”.      Библиотека “mongoose”  создает данный экземпляр и присваивает его левому операнду. Свойство “exports”  содержит экземпляры объектов, которые  будут доступны из модуля  при подключении его через библиотеку “Require JS”. Откройте модуль “User.js”, заполните его, как показано на картинке ниже: На 1 строке  мы подключаем библиотеку “mongoose” и создаем ее экземпляр с именем “mongoose”. На 2 строке  мы правым операндом создаем схему таблицы товаров и присваиваем ее левому операнду с именем “UserSchema”. Таблица пользователей содержит поля: скрытое поле “_id”, типа “String”, в этом поле содержится уникальный идентификатор строки. скрытое поле “__v”, типа “Number”, в этом поле содержится версия API  по умолчанию 0. Версия API  нужна для того, чтобы можно было менять реализацию сервисов и иметь несколько версий для обратной совместимости. поле “userName”, типа “String”, тут будет храниться логин пользователя. поле “userPwd”, типа “String”, тут будет храниться пароль пользователя. поле “userDesc”, типа “String”, тут будет храниться описание пользователя. На 7 строке  мы правым операндом создаем экземпляр модели с именем “User” и указываем создать экземпляр на основе схемы, описанной в экземпляре “UserSchema”. Затем присваем созданный экземпляр свойству “exports” данного модуля. Откройте модуль “auth.js”, заполните его в соответствии с содержимым ниже: На 1 строке  подключаем модуль “express” и создаем его экземпляр с именем “express”. На 2 строке  создаем экземпляр маршрутизатора запросов с именем “router”. На 3 строке  подключаем модуль “User.js” и создаем его экземпляр с именем “User”. На 4 строке  строим маршрут типа “http://localhost:3000/auth/”. На экземпляре “router”  вызываем функцию “post”. Вызов данной функции говорит, что созданный с помощью него маршрут будет доступен только через HTTP глагол POST.       Первым аргументом задаем маршрут, вторым аргументом задаем функцию обработчик, который будет вызван при переходе по данному маршруту с учетом заданного HTTP глагола. Т.е. когда пользователь отправит POST запрос по адресу. Функция обработчик принимает три аргумента. Первый аргумент “request”  содержит экземпляр запроса по данному маршруту.     Второй аргумент “response”  содержит экземпляр ответа по запросу по данному маршруту. В этот экземпляр можно добавлять любой ответ, который вы хотите, чтобы пользователь получил. Третий аргумент “next”  содержит текущий итератор в стеке запросов по маршрутизации. Данный экземпляр необходим, чтобы была возможность перейти на следующую итерацию в стеке маршрутизации. Сам по себе сервер NodeJS  работает асинхронно и никогда никого и ничего не ждет, это нужно учитывать при работе с ним. На 5 строке мы выводим сообщение на консоль. На 6 строке мы на экземпляре “User” вызываем функцию “find”, в которую единственным аргументом передаем специальную функцию обработчик, в которой будет содержаться результат данной операции. Функция “find”  получает все записи из таблицы “User” и присваивает их в JSON формате  второму аргументу функции обработчика с именем “data”. Для  примера, в “data” может содержаться такой JSON: “[{_id:'789787sdfsd78sdfsd7', __v:0, userName: “Test1”,...},{_id:'4444447sdfsd78sdfsd7', __v:0, userName: “Test2”,...}]”. Где квадратные скобки - это массив, а в фигурных скобках - это экземпляр JSON. В аргументе “err”  содержится экземпляр ошибки, если произошла ошибка при выполнении данной функции “find”. На 7 строке мы создаем экземпляр объекта с именем “res”, данный объект содержит свойство “Error”, которое инициализировано значением по умолчанию “Authorization Error”. Как вы уже поняли, это текст ошибки, который вернется на клиент в случае ошибки авторизации клиента. На 8 строке мы проверяем, если при получении данных через функцию “find” произошла ошибка и аргумент “err” не содержит значение “undefined”, мы обращаемся к экземпляру “next” и говорим серверу выполнить следующую итерацию маршрутизации и вернуть ошибку на клиент. На клиент средствами NodeJS  будет возвращена “Stack Trace” ошибки. На 9 строке, если ошибки не было, мы выполняем цикл foreach по массиву JSON объектов “data”. В функцию “forEach”  передается специальная функция, которая содержит три аргумента. Первый аргумент “item”  содержит текущий итерируемый экземпляр JSON. Второй аргумент “i” содержит позицию итератора и имеет тип Number. Третий аргумент “data”  содержит исходный массив объектов JSON, по которым проводится итерация. На 10 строке  мы задаем условие поиска пользователя по его имени и паролю в списке всех пользователей полученной функцией “find”. В условии сравниваются имя и пароль, которые пришли в POST запросе на сервер, с именем и паролем, полученным от функции “find”. На 12 строке, если пользователь был найден, мы присваиваем экземляру “res”  JSON объект найденного пользователя. На 15 строке  мы обращаемся к экземпляру ответа “response”, вызываем на нем функцию “json”, которая принимает данные и возвращает ответ на клиент в JSON формате. В аргумент данного метода мы передаем или экземпляр найденного пользователя, или экземпляр ошибки, что пользователь не найден и авторизация не удалась. На 18 строке  мы экспортируем из модуля экземпляр маршрутизации “router”. Откройте модуль “index.js”. Заполните его в соответствии с содержимым ниже: На 1 строке  подключаем модуль “express” и создаем его экземпляр с именем “express”. На 2 строке  создаем экземпляр маршрутизатора запросов с именем “router”. На 3 строке  строим маршрут типа “http://localhost:3000/index/”, доступный через HTTP глагол GET. На 4 строке, на экземпляр ответа вызываем функцию “render”. Данная функция возьмет представление, указанное в первом аргументе функции и вернет его на сторону клиента. Вторым аргументом задается словарь, в который можно добавить свойства со значениями, к которым можно будет обращаться со стороны представления. На 6 строке  мы экспортируем из модуля экземпляр маршрутизации “router”. Откройте модуль “index.ejs”. Заполните его в соответствии с содержимым ниже: Библиотека Express JS позволяет нам обращаться к свойствам, которые мы указали во втором аргументе метода “render”  путем добавления следующей конструкции “<%= Название_Свойства %>”. На этапе отрисовки страницы данные конструкции будут заменены на значение указанного свойства. Откройте модуль “products.js”, заполните его в соответствии с содержимым ниже: На 1 строке  подключаем модуль “express” и создаем его экземпляр с именем “express”. На 2 строке  создаем экземпляр маршрутизатора запросов с именем “router”. На 3 строке  подключаем модуль “Product.js” и создаем его экземпляр с именем “Product”. На 4 строке  строим маршрут типа “http://localhost:3000/products/”. Данный маршрут будет доступен по HTTP глаголу GET. На 5 строке  мы вызываем на экземпляре “Product”  функцию “find”. Внутри функции мы проверяем, были ли ошибки, и возвращаем в ответе JSON массив со списком всех товаров. На 10 строке  строим маршрут типа “http://localhost:3000/products/”. Данный маршрут будет доступен по HTTP глаголу POST. На 11 строке  мы вызываем на экземпляре “Product”  функцию “create”. Внутри функции мы проверяем, были ли ошибки, и возвращаем в ответе JSON объект успешно созданного товара. На 16 строке  строим маршрут типа “http://localhost:3000/products/{_id}” (например, “http://localhost:3000/products/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу GET. На 17 строке  мы вызываем на экземпляре “Product”  функцию “findById”. Из названия функции ясно, что функция выполняет поиск по идентификатору товара. Для поиска используется скрытое поле “_id” в модели “Product”, объявленной в модуле “Product.js”. Внутри функции мы проверяем, были ли ошибки, и возвращаем в ответе JSON объект успешно найденного товара. На 23 строке  строим маршрут типа “http://localhost:3000/products/{_id}” (например, “http://localhost:3000/products/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу PUT. На 24 строке  мы вызываем на экземпляре “Product”  функцию “findByIdAndUpdate”. Из названия функции ясно, что выполняется поиск товара по его идентификатору “_id”, затем, в случае если найден, обновляются все его поля, кроме скрытых полей. В ответе возвращается JSON объект успешно обновленного товара. На 30 строке  строим маршрут типа “http://localhost:3000/products/{_id}” (например, “http://localhost:3000/products/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу DELETE. На 31 строке  мы вызываем на экземпляре “Product”  функцию “findByIdAndRemove”. Из названия функции ясно, что выполняется поиск товара по его идентификатору “_id”, затем, в случае если найден, товар удаляется. В ответе возвращается удаленный JSON объект. Откройте модуль “users.js”, заполните его в соответствии с содержимым ниже: В модуле “users.js”  я не вижу смысла расписывать подробно, что происходит, поскольку тут все то же самое, что и происходило в модуле “products.js”, рассмотренном выше. Разница только в том, что мы используем другую модель данных “User”. Опишу только доступные маршруты. На 4 строке  строим маршрут типа “http://localhost:3000/users/”. Данный маршрут будет доступен по HTTP глаголу GET. На 10 строке  строим маршрут типа “http://localhost:3000/users/”. Данный маршрут будет доступен по HTTP глаголу POST. На 16 строке  строим маршрут типа “http://localhost:3000/users/{_id}” (например, “http://localhost:3000/users/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу GET. На 23 строке  строим маршрут типа “http://localhost:3000/users/{_id}” (например, “http://localhost:3000/users/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу PUT. На 30 строке  строим маршрут типа “http://localhost:3000/users/{_id}” (например, “http://localhost:3000/users/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу DELETE. Откройте модуль “app.js”, обновите его в соответствии с содержимым ниже: Конкретно в модуль “app.js”  были внесены следующие изменения. С 8 по 11 строку  были подключены модули “index.js”, “users.js”, “products.js”, “auth.js”. На 13 строке  был подключен модуль “mongoose”. На 14 строке  мы задаем свойство “Promise” в значение “global.Promise”, чтобы начать обмениваться сообщениями с базой данной “Mongo DB”. На 15 строке  мы подключаемся к базе данных “Mongo DB” c названием “warehouse”, мы ее ранее создавали в приложении “Robomongo”. Источник данных “mongodb://localhost/warehouse” можно разбить так: “mongodb://” протокол взаимодействия (аналоги “http://”, “tcp://”, “file://”). “localhost” - это адрес машины, на которой хостится база данных. “warehouse” - имя базы данных. На 16 строке  метод “then”  вызывается в случае успешного подключения к базе данных. На 17 строке  метод “catch”  вызывается в случае сбоя подключения к базе данных. С 33 по 36 строку мы задаем доступные глобальные маршруты, которые будет обрабатывать сервер. Надеюсь, все помнят разницу между сервером и сервисами, это не одно и то же. Сервер - это просто приложение, которое внутри себя способно содержать различные ресурсы. Сервер может запускать внутри себя различные потоки для обработки данных. Сервис - это функционал, который как раз запускается в отдельном потоке внутри сервера. Сам по себе сервис без наличия приложения ,в котором его можно разместить, работать не будет. В случае с WebAPI сервером является Microsoft IIS или отдельно стоящее исполняемое приложение с функцией self  хостинга. Мы закончили написание сервера. Запустите демон “mongod” в терминале. Запустите сервер. Вы увидите обновленное приветствие. Я бы рекомендовал вам прослушать курс “Angular JS”, автор Дмитрий Охрименко. Это один из лучших курсов, которые я когда-либо слушал. После данного курса вы без особых проблем сможете сделать клиентскую часть для сервера на “Angular JS”. Теперь давайте научимся тестировать наши REST сервисы. Тестировать сервисы можно и через Fiddler, но на macOS  у него есть проблемы с отрисовкой и работой. Поэтому откройте браузер и перейдите по адресу. “Postman” - это аналог “Fiddler”, доступный в качестве расширения для браузера “Google Chrome”. После установки расширения “Postman” откройте его. Выполним первый запрос к сервису “users” по HTTP глаголу GET. Заполните поле адрес, выберите глагол “GET”, нажмите кнопку “Send”. Результат придет - пусто. Обратите внимание, что в терминале “WebStorm” тоже пишется логирование вызовов при обращения к сервисам. Откройте приложение “Robomongo”, подключитесь к базе данных. Разверните папку “Collections” базы данных “warehouse”, выполните щелчок правой кнопкой мыши по таблице “users”, в контекстном меню выберите “View Documents”. В “Mongo DB” нет колонок, там есть всего одна колонка, в которой хранится JSON объект. Строки называются документами. Причем, в каждую строку одной таблицы можно класть абсолютно разный по структуре JSON объект. База данных позволяет такую операцию. Видим, что данных нет, это не удивительно, ведь мы еще ничего не добавляли. Давайте добавим. Перейдите в “Postman”, выберите глагол “POST”. Выберите “Content-Type” - “x-www-form-urlencoded”. Внимание, через другие типы “form-data”  “raw” работать не будет. Заданные свойства “userName”, “userPwd”, “userDesc” будут отправлены в запросе как JSON объект “{ userName: 'admin', userPwd: 'abc123!', userDesc: 'Administrator' }”. Нажмите кнопку “Send”. Придет ответ: В ответе мы видим JSON объект модели “User”, созданной на сервере. Перейдите в приложение “Robomongo”, выполните пункт контекстного меню “View Documents” на таблице “users”. Отлично, у нас появился первый пользователь :) Перейдите в “Postman”, выберите HTTP глагол “GET”. Нажмите кнопку “Send”. Вы увидите такой результат. Обратите внимание на квадратные скобки, в ответе пришел массив JSON объектов, а не один объект. Теперь давайте изменим пароль пользователю “admin”, на “12345678”. Нам понадобится идентификатор пользователя, выделите и скопируйте значение свойства “_id”. Выберите HTTP глагол “PUT”. Обновите данные так, как показано ниже. Обратите внимание на добавление идентификатора пользователя после основного маршрута “../users/”. Нажмите кнопку “Send”. В ответе получим старый JSON объект до внесения изменений. Теперь для того, чтобы посмотреть измененный объект, воспользуемся сервисом , который возвращает пользователя по его идентификатору “_id”. Выберите HTTP глагол “GET”, обновите адрес, добавьте в конец адреса идентификатор пользователя. Нажмите кнопку “Send”. Получим такой результат. Видим, что пароль пользователя был успешно обновлен и мы получили в ответе один JSON объект, а не массив пользователей. Теперь попробуем удалить пользователя. Адрес оставьте тот же. Выберите HTTP глагол “DELETE”  и нажмите кнопку “Send”. Видим, что в ответе содержится JSON объект удаленного пользователя.             Теперь проверим, доступен ли пользователь после удаления. Адрес оставьте тот же. Выберите HTTP глагол “GET”, нажмите кнопку “Send”. Видим, что пользователь действительно был удален. Также можете проверить это в приложении “Robomongo”.             Теперь приступим к тестированию сервиса “products”.             Получим список товаров. Товаров нет. Добавим первый товар. Обновим описание товара. Получим обновленный товар по его идентификатору. Удалим товар. На этом мы завершаем урок. К данной статье прикреплен архив с готовым сервером на NodeJS , в помощь  учащимся :) На следующем уроке мы: выполним рефакторинг проекта “Warehouse” под “iOS”; научимся пользоваться CollectionViewController; научимся пользоваться библиотекой “Alamofire”, в конце урока будут доступны исходники полноценного рабочего приложения; научимся сохранять объекты в настройки телефона и извлекать их; реализуем функциональность для работы со всеми сервисами, созданными в данном уроке; добавим представление регистрации; добавим представление редактирования пользователей. Материалы к статье тут.
Введення в розробку програм під iOS. Частина 4

Автор: Volodymyr Bozhek

Здравствуйте, дорогие читатели. В этом уроке мы научимся: скрывать клавиатуру мобильного устройства для полей “Text Field”, “Text View”; добавлять  различные типы клавиатур цифровая, e-mail, URL; добавлять панель инструментов на клавиатуру мобильного устройства; добавим панель поиска в список товаров; добавим предварительный просмотр на ячейки списка товаров с использованием аппаратной технологии 3D Touch. Откройте проект Warehouse. В панели навигатора откройте модуль “ViewController.swift”, нам необходимо унаследоваться от протокола “UITextFieldDelegate”: Теперь реализуем этот протокол: Разберем код построчно. На 43 строке мы реализовали метод “textFieldShouldReturn” протокола “UITextFieldDelegate”. Метод вызывается, когда вы на клавиатуре мобильного устройства нажимаете кнопку “Return” (Done, Next). Метод принимает один аргумент, который содержит экземпляр текстового поля, для которого была вызвана клавиатура. Возвращаемое значение данного метода говорит о том, скрывать клавиатуру или нет. Мы возвращаем значение “true”, означающее, что мы хотим скрывать клавиатуру. С 44 по 51 строку идет поиск активного в данный момент текстового поля, куда пользователь установил курсор. Данный код мы добавили, чтобы сделать следующую функциональность. Пользователь ставит курсор в поле “Имя пользователя”, вводит имя пользователя. Затем на клавиатуре нажимает кнопку “Next”, курсор автоматически попадает на поле пароль. После того, как пользователь ввел пароль, он нажимает кнопку “Done” и клавиатура пропадает. Чтобы поставить курсор в текстовое поле, необходимо на экземпляре этого текстового поля вызвать метод “becomeFirstResponder”. Чтобы убрать курсор с текстового поля, необходимо на экземпляре текстового поля вызвать метод “resignFirstResponder”. На 55 строке мы переопредили метод “touchesBegan”. Данный метод объявлен в классе “UIResponder”, класс “UIViewController” наследуется от этого класса. Метод “touchesBegan” вызывается, когда вы нажимаете в любую свободную область экрана, находящуюся вне редактируемого текстового поля. На 56 строке мы обращаемся к внутреннему свойству “view”, которое содержит экземпляр представления, подвязанного к контроллеру. И на нем вызываем метод “endEditing” со значение аргумента “true”. Данный метод посылает представлению событие о том, что необходимо завершить редактирование элементов управления, которые на нем расположены. Но это еще не все. Теперь необходимо сообщить текстовым полям, что мы реализовали для них соответствующий протокол “UITextFieldDelegate” и хотим проинициализировать у них свойство “delegate”. Обновите код в методе “viewDidLoad”: На 30 и 31 строке мы проинициализировали свойство “delegate” для текстовых полей “Имя пользователя” и “Пароль пользователя”. Теперь эти поля знают, откуда им надо вызывать реализацию метода “textFieldShouldReturn”. Откройте модуль “Main.storyboard”, найдите и выделите представление “View Controller”. На этом представлении выделите текстовое поле “Имя пользователя”. В панели свойств откройте вкладку “Show the Attributes inspector”. Найдите свойство “Return key” и установите в него значение “Next”. Затем выделите текстовое поле “Пароль пользователя” и установите для его свойства “Return key” значение “Done”. Запустите приложение. Если клавиатура у вас не открылась в симуляторе, выполните в меню симулятора “Hardware -> Keyboard -> Toggle Software Keyboard”. Теперь давайте разберем, какие типы клавиатур имеются и в каком свойстве они задаются. Потренируемся пока на текстовом поле “Имя пользователя”. Выделите это текстовое поле в дизайнере и в панели свойств на вкладе “Show the Attributes inspector” найдите свойство “Keyboard Type”, в этом свойстве задается тип вызываемой клавиатуры для текстового поля. Часто используемые виды клавиатур: Default – клавиатура по умолчанию, содержит в себе все раскладки. Numbers and punctuation – клавиатура содержит цифры и знаки пунктуации. URL – клавиатура содержит все необходимые символы для быстрого ввода URL адреса. Number Pad – клавиатура содержит только цифры, без знаков разделения на дробные составляющие. Phone Pad – телефонная клавиатура. E-mail Address – клавиатура содержит необходимые символы для быстрого ввода e-mail адреса. Decimal Pad – клавиатура содержит цифры и знаки разделения на дробные составляющие. Теперь добавим функциональность закрытия клавиатуры для представления редактирования продукта. Откройте модуль “ProductViewController.swift”. Добавьте наследование от двух протоколов “UITextFieldDelegate”, “UITextViewDelegate”, для класса “ProductViewController”: Реализуем эти протоколы: Методы на 58 строке и 69 мы рассматривали уже ранее, не буду повторяться. На 64 строке мы реализовали метод “textViewDidEndEditing”, данный метод вызывается, когда редактирование многострочного текстового поля было завершено. Обновим код в методе “viewDidLoad”: Запустите приложение. Закрытие клавиатуры будет работать только для текстового поля “Название”, для описания работать не будет. Дело в том что, чтобы скрыть клавиатуру у поля “Text View”, надо добавить дополнительную панель инструментов над клавиатурой и на нее добавить кнопку, которая будет закрывать эту клавиатуру. Разберем, как это сделать. Обновите код: Разберем код построчно. На 37 строке мы вызываем метод “addToolbar”, который добавим ниже. Данный метод принимает аргумент типа “UITextView”, в него мы передаем экземпляр текстового поля, к клавиатуре которого будет привязана панель инструментов с кнопкой закрыть. На 40 строке мы объявили метод “addToolbar”. На 41 строке мы создали экземпляр панели инструментов. Чтобы добавить эту панель на клавиатуру, надо проинициализировать свойство “inputAccessoryView” созданной панелью инструментов. С 42 по 44 строку мы задаем визуальные стили панели инструментов. На 45 строке мы создаем экземпляр кнопки пробел. На 46 строке мы создаем экземпляр кнопки “Х Закрыть” и привязываем к нему метод обработчик “donePressed” на нажатие по кнопке. На 47 строке мы добавляем кнопки на панель инструментов. На 48 строке говорим, что панель инструментов будет принимать действия пользователя. На 49 строке мы вызываем метод “sizeToFit”, который растянет панель инструментов по ширине экрана. На 51 строке инициализируем свойство “delegate” текстового поля. На 52 строке добавляем панель инструментов на клавиатуру. Запустите приложение. Теперь, при нажатии на кнопку “Х Закрыть” на панели инструментов клавиатуры, клавиатура закрывается. Данный код еще удобно использовать для цифровых клавиатур. Добавим поле поиска товаров в представление списка товаров. Откройте модуль “SuppliesViewController.swift”. Добавьте наследование от протоколов “UISearchResultsUpdating” и “UISearchBarDelegate”. Обновите код: Разберем код. На 27 строке мы добавили свойство “filteredSupplies”, в котором будут содержаться найденные товары через поле поиска. На 28 строке мы добавили свойство, которое указывает, активно ли сейчас поле поиска или нет. На 29 строке мы объявили пока еще не инициализированный экземпляр контроллера для поля поиска типа “UISearchController”. Отлично, теперь обновим методы табличного представления “numberOfRowsInSection” и “cellForRowAt indexPath”: Разберем код. На 74 строке мы проверяем, является ли поле поиска в данный момент активным (т.е. мы в данный момент поставили в него курсор). Тогда на 75 строке мы возвращаем количество товаров из отфильтрованной коллекции. Иначе на 78 строке мы возвращаем исходный список товаров. На 84 строке мы изменили получение модели товара. Теперь мы проверяем: если поле поиска в данный момент активно, то возвращаем товар из отфильтрованной коллекции, иначе возвращаем товар из исходной коллекции. Ошибки с индексом за пределами коллекции у нас не будет, так как выше мы обновили реализацию метода “numberOfRowsInSection”. Теперь необходимо добавить метод, который будет инициализировать контроллер для поля поиска: Разберем код этого метода. На 131 строке мы инициализируем свойство “searchController”, которое объявляли ранее. На 132 строке мы указываем ссылку на представление, где находятся данные, которые мы будет фильтровать. На 133 строке мы указываем, доступен ли нам основной контент в ходе поиска данных. На 134 строке мы задаем текст, который будет отображаться в поле поиска. На 135 строке мы инициализируем свойство “delegate” поля поиска, тем самым говоря, откуда ему брать реализацию связанных с ним протоколов. Мы указали, что реализация находится в контроллере “SuppliesViewController”. На 136 строке мы вызываем метод “sizeToFit” на поле поиска, чтобы растянуть панель поиска по ширине экрана. На 137 строке левым операндом мы обращаемся к свойству “tableHeaderView” экземпляра таблицы и правым операндом инициализируем его нашим полем поиска. Тем самым мы добавляем в заголовок нашей таблицы поле поиска. Наше приложение на русском языке. А когда поле поиска становится активным, мы видим справа от него кнопку с текстом на английском языке “Cancel”, давайте сразу это исправим, чтобы потом не возращаться к этому: Разберем построчно код. На 141 строке мы обращаемся к свойству “searchController”, от него вызываем свойство “searchBar” - это наше поле поиска. И на экземпляре поле поиска вызываем свойство “showsCancelButton”, говорящее о том, отображать кнопку “Cancel” справа от поля поиска или не отображать. Мы говорим, что будем отображать. На 142 строке мы объявляем переменную типа “UIButton” с именем “cancelButton”. На 143 строке мы получаем представление, в котором будем производить ниже поиск кнопки с текстом “Cancel” по типу разумеется :) На 144 строке мы проходимся по иерархии вложенных представлений в поле поиска. На 145 строке мы проверяем: если представление имеет тип “UINavigationButton”, то это то представление, которое нам надо, это и есть наша кнопка с текстом “Cancel”. На 146 строке мы инициализируем переменную “cancelButton” экземпляром найденной кнопки. На 147 строке мы задаем текст кнопки “Отмена”. Реализуем протокол “UISearchResultsUpdating”: Разберем код. На 154 строке мы вызываем метод “searchDisplayControllerWillBeganSearch”, который ранее рассматривали уже, и передаем в аргумент “controller” экземпляр контроллера поиска, который у нас есть в аргументе метода “updateSearchResults”. На 155 строке мы проверяем, ввел ли пользователь какой -либо  текст в поле поиска, если ввел, тогда сохраняем этот текст в переменную “searchString”, если не ввел, тогда выходим из метода “updateSearchResults”. На 159 строке мы выполняем фильтрацию данных из основного источника данных по тексту, который ввел пользователь в поле поиска. Для этого у коллекции “supplies” вызываем метод “Filter”, в который передаем лямбда метод с одним аргументом типа “ProductModel”, и возвращаемым значением типа Bool (найден или не найден товар). На 160 строке мы создаем переменную с именем “product” типа “ProductModel” и инициализируем ее экземпляром аргумента “productItem”. Заметьте, лямбда метод будет вызываться столько раз, сколько элементов товара содержит коллекция “supplies”. На 161 строке мы объявляем переменную, в которой будет содержаться результат сравнения текста поиска с названием или описанием нашего товара. На 162 строке мы выполняем сравнение текста текущего товара в лямбда методе с текстом поля поиска . Оба текста путем вызова метода “lowercased” переведены в нижний регистр и теперь регистро независимы. Если по названию товара совпадений не было найдено, пытаемся сравнить по описанию товара. Результат сравнения записываем в переменную результата “res”. На 164 строке возвращаем результат метода “filter”. На 166 строке запускаем переинициализацию таблицы для отображения отфильтрованных данных и скрытия данных, не попавших под фильтр. Реализуем протокол “UISearchBarDelegate”: Разберем код. На 170 строке мы реализовали метод “searchBarTextDidBeginEditing”, который вызывается, когда пользователь начинает вводить текст в поле поиска. В реализации данного метода мы задаем свойство “shouldShowSearchResults” в значение “true”, тем самым давая понять контроллеру поиска, что поле поиска сейчас активно. Затем мы переинициализируем таблицу. На 175 строке мы реализовали метод “searchBarCancelButtonClicked”, который вызывается, когда пользователь нажал кнопку “Cancel” или в нашем случае “Отмена”, справа от поля поиска. В реализации мы говорим, что поле поиска теперь больше не активно и переинициализируем таблицу. На 180 строке мы реализовали метод “searchBarSearchButtonClicked”, который вызывается, когда на клавиатуре, отобразившейся к полю поиска, мы нажали кнопку “Search” (“Поиск”). В реализации метода мы проверяем: если свойство “shouldShowSearchResults” еще не активно, активируем его и переинициализируем таблицу. Затем скрываем клавиатуру для поля поиска на 185 строке. Обновите метод “viewDidLoad”: На 64 строке мы добавили заголовок представления “Продукты”. На 66 строке мы вызвали метод, который выполняет инициализацию контроллера поиска и добавляет его в наше представление. Запустите приложение. Нажмите на поле “Поиск товара”, введите туда, например, букву “Ш”. Все работает :) Теперь в этом уроке нам еще осталось добавить предварительный просмотр товара при выполнении на нем функции 3D Touch. Данная функция поддерживается только на устройствах iPhone 6s / 6s Plus / 7 / 7 Plus. В этих телефонах установлен специальный дисплей, который реагирует на силу нажатия на него. Даже если у вас нет подобного девайса это не проблема, так как данную функциональность можно проверить в симуляторе. Реализуем протокол “UIViewControllerPreviewingDelegate”. Обновите код “SuppliesViewController”: Теперь обновите метод “viewDidLoad”: На 67 строке мы обращаемся к свойству контроллера “traitCollection”, в котором содержится свойство “forceTouchCapability”, и проверяем: если данное устройство поддерживает данную функцию (3D Touch), тогда вызываем метод “registerForPreviewing”, в который первым аргументом передаем ссылку на контроллер, для которого надо применить данную функциональность, и экземпляр таблицы, на содержимом которой эта функциональность и будет выполняться. Добавим реализацию протокола “UIViewControllerPreviewingDelegate”: Разберем код. На 192 строке мы реализовали метод “previewingContext”, который возвращает экземпляр представления , которое надо отобразить в предварительном просмотре. Метод принимает два аргумента. Первый аргумент “previewContext” типа “UIViewControllerPreviewing” принимает экземпляр контроллера предварительного просмотра. Второй аргумент “location” типа “CGPoint” содержит позицию места, где пользователь выполнил усиленное нажатие по ячейке таблицы. На 193 строке мы получаем экземпляр “indexPath” путем вызова на таблице метода “indexPathForRow”, аргументом которого мы передаем аргумент “location”. Данный экземпляр нам необходим, поскольку у него имеется свойство “row” и по значению этого свойства мы сможем получить нужную модель товара. На 194 строке мы производим поиск контроллера ”ProductViewController” по его “Storyboard ID”. Это представление редактирования товара. На 195 строке мы передаем идентификатор товара в свойство “id” найденного выше контроллера. На 196 строке мы передаем в свойство “sourceRect” полную область ячейки, по которой было выполнено “3D Touch”. Благодаря этому данная область будет выделена, когда вы выполните нажатие “3D Touch” в таблице. На 197 строке возвращаем найденный контроллер “ProductViewController”. На 199 строке возвращается пустое значение, если пользователь выполнил нажатие “3D Touch” вне области таблицы. На 202 строке мы реализовали метод “previewingContext” перегруженный, который ничего не возвращает и отображает представление того контроллера, который мы вернули в методе выше. Запустите приложение. На этом завершим урок. На следующем уроке мы рассмотрим создание иконки нашего приложения и разберемся, как пользоваться “Assets”.
Введення в розробку програм під iOS. Частина 3

Автор: Volodymyr Bozhek

Здравствуйте, дорогие читатели. В этом уроке мы: создадим форму редактирования товара; рассмотрим понятие делегата и удобство его использования; добавим действия редактирования / удаления товара в списке товаров. Откройте проект Warehouse. В первую очередь, давайте изменим цвет View входа в систему с “Light Green” на “White”, я явно перестарался с выбором цвета в самом начале :). В будущих уроках, когда разберем, как пользоваться Assets, мы заменим фон на красивую картинку, подходящую по смыслу к этому приложению, а пока сделаем нейтральный цвет, не бросающийся в глаза. Для этого в панели навигатора откройте модуль “Main.storyboard”, выделите “View Controller”, затем в панели свойств откройте вкладку “Show the Attributes inspector”. Найдите свойство “Background” и установите значение “White”. Теперь найдите в библиотеке компонентов компонент “Table View Controller” и перетащите его в свободную область дизайнера рядом с “Supplies View Controller”, созданным в прошлом уроке. Выделите только что добавленный компонент. Найдите кнопку в дизайнере “Show Document Outline”, нажмите на нее, откроется панель документов. На этой панели разверните компонент “Table View Controller” и выделите компонент “Table View”. Затем в панели свойств откройте вкладку “Show the Attributes inspector”. Найдите свойство “Content”. По умолчанию у этого свойства установлено значение “Dynamic Prototypes”, установите значение “Static Cells”, в дизайнере вы увидите, что интерфейс контроллера изменился и отобразилось 3 строки. Разберем значения этого свойства. Значение “Dynamic Prototypes” используется, если вам необходимо иметь динамический набор данных, который будет постоянно изменяться. Установив это значение, обязательно необходимо переопределить методы протокола “UITableViewDataSource” для корректной работы контроллера. Значение “Static Cells” используется, если вам необходимо иметь статические данные, которые не будут меняться. Установив это значение, вам не нужно переопределять методы протоколы “UITableViewDataSource”. В данном примере мы устанавливаем это значение с целью удобства размещения элементов управления на нашей View. У нас будут ячейки с таким содержимым: ячейка под картинку, идентификатор товара, название товара, описание товара. Сейчас у нас 3 ячейки, надо чтобы было 4. В первой ячейке будет компонент “Image View”, в остальных 2 ячейках будут элементы управления “Label” и “Text Field”, в последней 4-й ячейке будут элементы управления “Label” и “Text View”. На панели документов для “Table View Controller”  выделите компонент “Table View Section”. В панели свойств откройте вкладку “Show the Attributes inspector”. Найдите свойство “Rows”. Сейчас у этого свойство установлено значение “3”. Измените значение на “4”, у вас появится 4 ячейка. В панели документов раскройте компонент “Table View Sections”, вы увидите список ячеек типа “Table View Cell”. Выделите первую ячейку. В панели свойств откройте вкладку “Show the Size inspector”. На этой вкладке можно управлять размерами компонента. Найдите свойство “Custom” и отметьте его галочкой. Затем найдите свойство “Row Height”. Установите значение этого свойства “144”, высота ячейки изменится. Перетащите на эту ячейку из библиотеки компонентов  компонент “Image View” и растяните его на всю область ячейки. Выделите компонент “Image View” и нажмите кнопку “Pin”. В открывшемся диалоговом окне снимите галочку с поля “Constrain to margin” и отметьте позиционирование относительно всех стенок ячейки. Нажмите кнопку “Add 4 Constraints”. В панели документов выделите вторую ячейку. Перетащите из библиотеки компонентов компонент “Label” на эту ячейку. Поместите его с левого края ячейки и растяните по высоте ячейки. В панели свойств откройте вкладку “Show the Size inspector”, установите свойство “Width” в значение “120”. Откройте вкладку “Show the Attributes inspector”, установите безымянное свойство с текстом “Label” на текст “Идентификатор”. Найдите свойство “Font”, нажмите на кнопку со значком “T”, откроется диалоговое окно задания свойств шрифта. Установите свойство “Style” в значение “Bold”, свойство “Size” в значение “12”. Нажмите кнопку “Done”. Представление должно выглядеть так: Из библиотеки компонентов перетащите компонент “Text Field” и расположите рядом с компонентом “Label”. Растяните компонент “Text Field” на оставшуюся ширину ячейки. Выделите элемент управления “Label”, нажмите кнопку “Pin”. Снимите галочку с поля “Constrain to margin”, отметьте позиционирование относительно верхней и левой стенок ячейки. Отметьте галочкой поле “Height”. Нажмите кнопку “Add 3 Constraints”. Выделите элемент управления “Text Field”, нажмите кнопку “Pin”. В диалоговом окне снимите галочку с поля “Constrain to margin”, установите позиционирование относительно левой, верхней, правой стенок ячейки, отметьте галочкой поле “Height”. Нажмите кнопку “Add 4 Constraints”. В панели документов выделите 3 ячейку. Повторите для нее все операции, которые были применены ко второй ячейке. Измените текст элемента управления “Label” на “Название”. Должно получиться вот так: В панели документов выделите последнюю ячейку, на вкладе “Show the Size inspectors”  установите в поле “Row Height” значение “144”, предварительно отметив поле “Custom” галочкой. Перенесите на ячейку два компонента “Label” и “Text View”. В          Установите ограничения для элемента управления “Label”, как мы устанавливали для предыдущих элементов управления “Label”, а для элемента управления “Text View” такие же ограничения, как для “Text Field”. Для элемента управления “Label” установите текст “Описание”, для элемента управления “Text View” уберите текст по умолчанию. Должно получиться вот так: В панели навигатора, добавьте новый модуль “Swift File” с именем “ProductViewController.swift”. Измените код, как показано ниже: Перейдите снова в дизайнер , выделите “Table View Controller”, с которым проводили изменения, в панели свойств, откройте вкладку “Show the Identity inspector”. Установите свойство “Class” значением “ProductViewController”, свойство “Storyboard ID” значением “ProductViewController”. В панели инструментов нажмите кнопку “Show the Assistant editor”. Для элемента управления “Image View” создайте переменную с именем “imageView”.       Во второй ячейке, для элемента управления “Text Field”, создайте переменную с именем “idTxt”. В третьей ячейке, для элемента управления “Text Field”, создайте переменную с именем “nameTxt”. В четвертой ячейке, для элемента управления “Text View”, создайте переменную с именем “descriptionTxt”. Должно получиться вот так: В панели навигатора, добавьте новый модуль “Swift File” с именем “ProductModel.swift”. Добавьте в него следующий код: Разберем добавленный код построчно. На 9 строке мы подключили пространство имен “Foundation” для класса “NSObject”. На 11 строке мы объявили класс с именем “ProductModel”, который наследуется от класса “NSObject” для того, чтобы считаться типом данных. На 12 строке мы объявили переменную с именем “productId” типа “Int” и инициализировали ее значение по умолчанию “-1”. На 13 строке мы объявили поле с именем “productImage” типа “String” и инициализировали ее значение по умолчанию “”. На 14 строке мы объявили поле с именем “productName” типа “String” и инициализировали ее значение по умолчанию “”. На 15 строке мы объявили поле с именем “productDescription” типа “String” и инициализировали ее значение по умолчанию “”. На 17 строке мы объявили параметризированный инициализатор класса “ProductModel” (это тоже самое, что и конструктор в языке программирования C#). Инициализатор - это метод, который вызывается при создании нового экземпляра класса. Данный инициализатор принимает аргументы с такими же именами, как имена полей в классе. Для того, чтобы инициализировать внутренние поля класса значениями полей аргументов. Мы левым операндом обращаемся к самому себе, через ключевое слово “self” и обращаемся к нужному нам полю, а правым операндом задаем значение аргумента. Мы плавно подходим к созданию делегата. В панели навигатора добавьте новый модуль “Swift File” с именем “ProductProtocol.swift”. Добавьте в него следующий код: Разберем добавленный код. На 11 строке мы объявили протокол с именем “ProductProtocol”. Протокол - это тоже самое, что и интерфейс в языке программирования C#. Экземпляр протокола через инициализатор создать нельзя, поскольку это только прототип, который не содержит реализации и используется в качестве описания и задания соглашений, которые разработчик должен реализовать сам в будущем. На 12 строке мы объявили метод прототип с именем “changeProduct”,  который принимает аргумент типа “ProductModel” с именем “model”. Откройте модуль “ProductViewController.swift”, добавьте поле с именем “delegate” типа “ProductProtocol?” и поле с именем “id” типа “Int” со значением по умолчанию “-1”. Должно получиться вот так: Добавьте после метода “viewDidLoad”, новые методы с именами “initNavigationButtons”, “backToParent”, “saveData”, которые ничего не принимают и ничего не возвращают. Код: Разберем построчно добавленный код. На 21 строке мы вызываем метод “initNavigationButtons”, когда View находится в процессе загрузки, чтобы инициализировать кнопки навигации. На 24 строке мы объявили метод с именем “initNavigationButtons”. На 25 строке мы переопределяем левую кнопку навигации, задаем ей текст “Отмена” и обработчик события на нажатие, метод “backToParent”. На 26 строке мы создаем кнопку с именем “saveButton” и текстом “Сохранить”, добавляем для нее обработчик событие на нажатие, метод “saveData”. На 27 строке мы обращаемся к экземпляру панели навигации контроллера “navigationItem” и вызываем от него метод “setRightBarButton”, который принимает два аргумента. Первый аргумент - это экземпляр добавляемой на панель навигации кнопки, второй аргумент - это стиль добавления кнопки, добавить с анимацией или без. Сам метод, как видно из названия, добавляет кнопку справа на панель навигации. Это вариант, как добавить правую кнопку из кода. На 30 строке мы объявили метод с именем “saveData”. На 31 строке мы обращаемся к нашему делегату с именем “delegate” и проверяем, если он инициализирован, тогда вызываем у него метод “changeData” и передаем туда модель данных, заполненную данными собранными с элементов управления. На 32 строке мы создаем экземпляр типа “ProductModel” с именем “model”. На 33 строке мы вызываем у делегата метод “changeData” и передаем туда экземпляр “model”. Данный метод будет реализован в классе “SuppliesViewController”. На 35 строке мы возвращаемся на предыдущую View (SuppliesViewController) в стеке иерархий вызовов View. На 38 строке мы объявляем метод с именем “backToParent”. Данный метод делает переход на предыдущую View (SuppliesViewController) в стеке иерархий вызовов View. Теперь откройте модуль “SuppliesViewController.swift”. Нам необходимо обновить его реализацию: Разберем обновленный код построчно. На 12 строке в классе SuppliesViewController мы наследуемся от протокола “ProductProtocol”. На 13 строке мы заменили тип данных для поля “supplies”. Тип данных был “[String]”, стал “[ProductModel]”. И сделали это поле статическим для того, чтобы была возможность доступа к нему из контроллера “ProductViewController”. На 29 строке мы реализовываем метод “changeProduct” протокола “ProductProtocol”. Именно этот метод будет вызываться при нажатии кнопки “Сохранить” в контроллере “ProductViewController”. На 30 строке мы проверяем идентификатор продукта, если “-1” - это означает, что это новый продукт. Иначе существующий продукт, и мы его обновляем. На 32 строке мы правым операндом генерируем идентификатор продукта путем вызова метода “makeNewProductId”. Данный метод возвращает значение типа “Int”, которое мы присваем левому операнду. Левый операнд - это поле “productId” экземпляра “ProductModel”. На 33 строке мы добавляем новый продукт в коллекцию продуктов. На 36 строке мы объявляем переменную с именем “changedIndex” типа “Int” со значением по умолчанию “-1”. В эту переменную мы запишем найденную ниже в цикле позицию продукта в коллекции “supplies”. На 37 строке мы в цикле проходимся по элементам коллекции “supplies” и ищем продукт, который нужно обновить. На 38 строке описано условие поиска продукта. На 39 строке, когда продукт был найден, мы сохраняем его позицию в переменную “changedIndex”. На 40 строке выходим из цикла. На 43 строке проверяем, если продукт был найден , тогда обновляем этот продукт. С 44 по 46 строку мы обновляем свойства продукта , значениями свойств модели, переданной аргументом метода “changeProduct”. На 49 строке мы запускаем переинициализацию “DataSource” для табличного представления, чтобы данные в ячейках обновились и добавились/удались обновленные данные. На 52 строке мы объявили метод с именем “makeNewProductId”, который ничего не принимает и возвращает значение типа “Int”. На 53 строке левым операндом мы объявили переменную с именем “sortProducts” типа “[ProductModel]”. Правым операндом мы на экземпляре коллекции “supplies” вызываем метод “sorted”, который принимает лямбда метод сортировки коллекции объектов и возвращает отсортированную коллекцию. У лямбда метода есть два аргумента, в них находятся экземпляры сравниваемых объектов из коллекции “suppliers”. Данный метод возвращает значение типа “Bool”. На 54 строке мы возвращаем условие сортировки по возрастанию. Сортировка производится по свойству “productId” класса “ProductModel”. На 56 строке мы обращаемся к последнему элементу отсортированной коллекции, тем самым получаем максимальный идентификатор продукта в коллекции и увеличиваем его на единицу. Теперь надо обновить реализацию метода “cellForRowAt indexPath”: Разберем код построчно. На 69 строке мы изменили вид обращения к коллекции “supplies”. На 73 строке мы изменили для экземпляра ячейки тип стиля. Был “.plain”, стал “subtitle”. На 74 строке мы левым операндом объявили константу с именем “model” типа “ProductModel”. Правым операндом мы инициализируем константу значением из коллекции “supplies”. На 75 строке мы записываем в свойство “tag” идентификатор продукта для этой ячейки. На 76 строке в элемент управления “Label”, который находится в верхней области ячейки, мы записываем название продукта. На 77 строке в элемент управления “Label”, который находится в нижней области ячейки, мы записываем описание продукта. На 78 строке мы активируем в ячейке информационную кнопку справа. При нажатии на эту кнопку мы будем редактировать продукт. Теперь нам надо подвязаться на событие нажатия на информационную кнопку в ячейке. Для этого необходимо переопределить метод протокола “UITableViewDelegate” ,реализованного в классе “UITableViewController” с именем “accessoryButtonTappedForRowWith”. Разберем код построчно. На 83 строке мы получаем экземпляр типа “ProductModel”, привязанный к ячейке, и сохраняем его в константу с именем “model”. На 84 строке мы производим поиск контроллера с именем “ProductViewController” в модуле “Main.storyboard”. На 85 строке на найденном экземпляре контроллера обращаемся к свойству “delegate” типа “ProductProtocol” и инициализируем его ссылкой на самого себя, тем самым говоря, что в этом классе уже реализован метод этого протокола с именем “changeProduct” и вызывать метод надо именно из этого класса. На 86 строке мы на найденном экземпляре контроллера обращаемся к свойству “id” и инициализируем его идентификатором продукта, привязанного к текущей ячейке. На 87 строке мы открываем контроллер “ProductViewController”. Редактирование мы почти сделали, осталось добавить инициализацию редактируемых данных в контроллере “ProductViewController”. Давайте реализуем еще в этом контроллере удаление данных. Для этого надо переопределить два метода объявленных в протоколе “UITableViewDelegate” и реализованных в классе “UITableViewController”. Эти методы называются “canEditRowAt IndexPath” и “commit editingStyle”. Обновите свой код в соответствии с представленным ниже: Разберем код построчно. На 90 строке мы переопределили метод “canEditRowAt IndexPath”, данный метод возвращает значение , которое говорит о том, активировать удаление строк или нет. На 91 строке возвращаем значение активировать. На 94 строке мы переопределили метод “commit editingStyle”, данный метод вызывается, когда мы нажали кнопку удалить на ячейке. На 95 строке мы удаляем продукт из коллекции “supplies”. На 96 строке обновляем содержимое таблицы. Теперь добавим кнопку добавления нового товара. Для этого обновим код метода “initNavigationButtons”.   Разберем код построчно. На 100 строке мы создали экземпляр кнопки с текстом “Новый продукт” и обработчиком события на нажатие - метод “newProduct”. На 101 строке мы добавили выше созданную кнопку в панель навигации контроллера в область справа. На 104 строке мы объявили метод “newProduct”, которые ничего не принимает и ничего не возвращает. На 105 строке мы производим поиск контроллера “ProductViewController” в модуле “Main.storyboard”. На 106 строке мы на найденном экземпляре контроллера обращаемся к свойству “delegate” и инициализируем его ссылкой на самого себя. На 107 строке мы  на найденном экземпляре контроллера обращаемся к свойству “id” и говорим, устанавливая значение “-1”, что мы создаем новый продукт. Теперь откройте модуль “Main.storyboard”, в нем на контроллере “ProductViewController” выделите элемент управления с идентификатором “idTxt”. В панели свойств на вкладке “Show the Attributes inspector” найдите свойство “User Interaction Enabled” и снимите галочку. Тем мы самым мы запретили редактирование значения этого поля. Теперь перейдите в модуль “ProductViewController.swift”. Обновите в нем код, как это показано ниже: Разберем добавленный код построчно. На 19 строке мы добавили метод с именем “initData”, который ничего не возвращает и принимает аргумент с именем “id” типа “Int”. На 20 строке мы проверяем идентификатор продукта, если он больше значения “-1”, значит продукт уже существует и содержит внутри себя данные, которые можно отобразить на текущей View. На 22 строке мы ищем продукт по идентификатору продукта, переданному аргументом в методе в коллекции продуктов “supplies”. С 23 по 25 строку мы заполняем элементы управления на пользовательском интерфейсе данными найденного продукта. На 33 строке в методе “viewDidLoad” мы добавили вызов метода “initData” и передали ему аргумент поле “id”, которое мы задаем в контроллере “SuppliesViewController”. Запустите приложение. На анимации выше видно, что мы вошли в систему, открыли товар на редактирование, откредактировали товар и изменения сохранились. Затем удалили товар. Теперь давайте проверим добавление нового товара: Проверили, добавление нового товара работает хорошо. Но все равно есть один нюанс, наше приложение на русском языке. А когда выполняли на ячейки жест справа на- лево, то показывалась кнопка удалить “Delete” на английском, давайте исправим это, чтобы показывалось русское название “Удалить”. Откройте модуль “SuppliesViewController.swift”, нам необходимо переопределить метод, объявленный в делегате “UITableViewDelegate” c именем “titleForDeleteConfirmationButtonForRowAt” и реализованный в классе “UITableViewController”. Добавьте следующий код, после метода “backToParent”: Я думаю, этот метод не стоит разбирать построчно, тут и так все понятно. Запустите приложение. На этом завершим наш урок. В следующем уроке мы разберем технологию 3D Touch и применим ее к нашему приложению для предварительного просмотра товаров. Добавим поле поиска в заголовок контроллера со списком товаров, реализуем поиск товаров. Реализуем протокол “UITextFieldDelegate”, чтобы скрывать клавиатуру при нажати кнопки “Ввод” на клавиатуре мобильного устройства. Разберем, как добавить собственную панель интрументов с кнопками.
Тайм-менеджмент для програміста, як встигнути всі?

Автор: Редакция ITVDN

Опять не выспались, потому что всю ночь кодили, учились, искали баги? Не сходили на обед, потому что дел выши крыши, плюс еще и весь небесный свод усыпан задачами? Забываете сделать обещанное, вспоминаете в последнюю секунду о встрече или запланированном мероприятии? Вы круглые сутки пашете, аки конь, но без результатов? Остановитесь, выдохните, вам надо заняться планированием и освоить азы тайм-менеджмента. Не секрет, что в IT почва для планирования не шибко благодатная. Сколько бы ни придумали систем, все равно всегда есть место форс-мажору, заваленным проектам, испорченным нервам. В большинстве случаев так происходит не потому, что системы плохие, а потому что каждый конкретный человек, принимающий участие, например, в разработке, должен организовываться самостоятельно и уметь распределять дела, а у него не очень то и получается. В этой статье мы поговорим с вами, как начать с себя, чтобы и у вас все успевалось, и у тех, с кем вы взаимодействуете, тоже. Как говорил кто-то очень умный и просвещенный, работать нужно не 12 часов в сутки, а головой. Ладно, мы знаем, это фраза Стива Джобса. =) Итак, как же успевать все? Планирование Да, это банально, скучно и уныло. Но это работает. Более того, планирование смело можно считать одним из основных столпов грамотного распределения времени. Чтобы планирование было успешным, вам нужно любое место, куда писать: - милый блокнот и мягкий карандаш, - любимый девайс с приложением, - "вездедоступный" онлайн календарь, - почтовый клиент… В общем, подойдет абсолютно любое человеческое изобретение, где есть даты и куда можно записать что-то. Выберите время суток, когда вы можете выделить 10 минут и спокойно поразмыслить о делах. Кому-то удобнее этим заниматься утром, перед работой, занятиями и т. д. Кто-то спокойнее отдается планированию вечером перед сном. Поставьте себе на это время напоминание. Сначала вам будет тяжело, обременительно, но с каждый днем привычка планировать будет укореняться, а разгребать завалы дел вы будете быстрее и успешнее. Сложное и простое Часто бывает, что одна задача очень большая и необъятная. И она такая страшная, что порой даже не знаешь, как к ней подойти. В таком случае выручает разбивание огромного на что-то маленькое. То есть, одной большой задачи - на подзадачи. Согласитесь, перенести огромный стеллаж с книгами за один раз — это непосильно и нереально. Но если вытащить книги, разложить их на стопки, а шкаф разобрать, то справиться с этим будет намного проще и быстрее. Составляйте ваш список дел на день так, чтобы в нем было несколько простых, но нужных дел, например, разобрать почту, позвонить маме, заказать книгу и - более сложных. Выполнение простых задач обычно стимулирует и вы видите, как список сокращается, входите во вкус и завершаете все! Мультизадачность Она не работает и ее не существует. Это миф. И Юлий Цезарь со своими умениями не показатель. Наш мозг не умеет делать 2 дела одновременно и параллельно, ну не поддерживает он многопоточность. Даже когда вам кажется, что вы успешно сели на два стула - вы себя обманываете. Вы просто переключаетесь с одной задачи на другую. При этом с каждым переключением вспомнить о том, что вы делали по этой задаче раньше, становится все труднее и труднее и, как следствие, вы быстрее устаете. У вас болит голова, вы больше тратите времени, чтобы завершить начатое. И, в общем-то, либо ничего не успеваете, либо посредственно справляетесь с заданиями. Всегда делайте задачи одна за другой. Поверьте, последовательность — это идеальный помощник в решении сложных дел. Одно дело сделали — вычеркнули, наградили себя вкусным кофе, побежали делать следующее. Приоритеты Итак, мы договорились с вами, что будем планировать. Записали, все что нужно сделать, разбили сложные задачи на подзадачи, а как же сделать так, чтобы не перескакивать с одного дела на другое? В этом нам помогут приоритеты. Как бы это странно ни звучало, но не бывает задач одинаково важных в один и тот же момент времени. Давайте рассмотрим на жизненном примере. Каждый водитель, подъезжающий к перекрестку, где есть светофор, помимо него также видит перед собой знак, который сообщает ему о том, что он на главной дороге, например, и может свободно ехать. Но вот вам ситуация: светофор горит красным, а знак говорит — поезжай. Стоять и ехать одновременно  невозможно. Как решается эта ситуация? Правильно, приоритетами. У светофора он выше, значит, когда горит красный, какой бы знак ни висел, машина — стоит. Если же не работает светофор, то в силу вступает знак. И все. Так же вы можете поступать со своими делами, решая, какая задача из списка более важная и нужная, а какая может подождать и подняться вверх только после выполнения более важной, а какую можно и вовсе не делать — никто не умрет, заводы и пароходы не остановятся. Если же вам трудно сразу научиться расставлять приоритеты, почитайте о матрице Эйзенхауэра. Все ваши задачи можно разделить на 4 категории: Срочные и важные, Важные, но не срочные, Срочные, но неважные, Несрочные и неважные. С таким подходом выполнять намеченное станет еще проще. Время У каждого из нас есть рутинные дела. И обычно мы приблизительно знаем, сколько тратим на них времени. Значит, мы можем четко прописывать, что на чтение почты и ответы на письма мы отводим 30 минут, например. Более того, ограничение по времени можно превратить в игру. Каждый раз пытаться побить рекорд предыдущего дня. Например, сегодня вы работали с почтой 30 минут, а завтра попробуйте справиться за 28. Так становится немного веселее. Существуют дела без времени, например чтение профессиональной литературы. Тут проще, отведите себе какое-то время, например, час, поставьте таймер и садитесь читать. Время вышло — задача выполнена. Следите за тем, как вы проводите свое время. Бывает, нам кажется, что мы так много работали, но ничего не сделали, хотя на самом деле, львиную долю рабочего дня мы просидели в социальных сетях. В общем-то, четкое планирование должно вас оградить от зыбучих песков интернета, но если вы уже и решили себя порадовать очередным спором в интернете, то также ограничьте это по времени. Не делайте этого Если вы понимаете, что то, что вам предстоит сделать, будет приносить вам страдания — не делайте это. Просто остановитесь и откажитесь, передайте эту задачу другому, забудьте о ней. Поверьте, всегда есть люди, которые сделают это за вас и никто не пострадает. Зато вы спокойно будете заниматься другими важными делами, а не страдать над подходами к выполнению намеченного. Если вся ваша работа причиняет вам боль, значит пора менять либо работу, либо профессию. Серьезно. В общем, старайтесь делать меньше того, что вам не нравится. Отдыхайте От работы дохнут кони, ну а я - бессмертный пони? Это про вас? Значит, выдохните. Знаете, если вы сегодня уйдете вовремя, небесная твердь не свалится, планеты не сойдут с орбит. А если вы завтра и вовсе возьмете отгул, то тоже ничего страшного не произойдет. Найдите для себя занятие, которое поможет вам расслабиться. И, да, запланируйте на него время. Это может быть все, что угодно: спорт, чтение, вышивание, прогулки, медитации и прочее, прочее. И спите, обязательно давайте мозгу время  перезагрузиться и восстановиться, тогда вам все будет по плечу! А теперь небольшой свод коротких правил, которые дополнят и уточнят все вышесказанное: 1. Планируйте и проставляйте приоритеты. 2. Ставьте на все задачи временные рамки, желательно, чтобы эти рамки не выходили за пределы рабочего времени (если мы говорим о рабочих делах). 3. Перерывы и отдых тоже планируйте. 4. В списке дел должны быть разные задачи: легкие, средние и посложнее. 5. Фиксируйте намеченное и вычеркиваете сделанное. 6. Мотивируйте себя музыкой, перерывом или чем-то еще приятным. 7. Разбивайте большие задачи на маленькие. 8. Суперконцентрация. Если нужно, а никак не получается — отключите все раздражители, уйдите в другую комнату, в общем, сделайте все, чтобы вы остались со своим заданием наедине. 9. Не делайте то, что вам не нравится. 10. Любите себя и берегите!
Як я побудував проект на Django, Django REST Framework, Angular 1.1.x та Webpack

Автор: Редакция ITVDN

Моя идея состояла в том, чтобы построить простой репликабельный проект на Angular с бэкэндом на Django. Я искал и не смог найти нужных решений, пришлось во всем разбираться самому. В итоге я разобрался и решил сам написать гайд для всех, кого может заинтересовать данная проблема.  Данная статья поможет вам построить простое приложение Angular с бэкэндом на Django, организованного с помощью Webpack. Проблема Я хочу настроить проект на Angular 1.1.x и скормить ему данные с сервера Django. Мне бы хотелось использовать Django REST Framework (DRF), чтобы пострить RESTful API. Я также хочу сбандлить JS ассеты. Сейчас я собираюсь запустить сайт на одном сервере. Предварительные требования Python 2.x Django 1.9.x npm 2.15.8+ Webpack 1.13.x (sudo npm i -g webpack) ESLint 2.13.1+ (sudo npm i -g eslint) NodeJS 4.4.7+ Содержание Скаффолдинг проекта. Создайте свои начальные директории. Скаффолдинг проекта на Django. Настрока переменных среды, нужных для запуска сервера Django. Установка Django REST Framework и настройка Django с использованием переменных среды. Создание API. Запуск Django сервера с использованием dev settings. Инициализация npm-пакета и установка front-end JS зависимостей. Создание Angular entry-point и загрузка начальных зависимостей. Настройка Webpack'а. Дайте команду Django загрузить приложение. Создайте шаблон базы приложения Angular. Напишите компонент home. Напишите Angular роуты, ведущие к вашему компоненту home и странице 404. Добавьте директивы ангуляр-маршрутизатора к шаблону входной точки приложения. Проверьте ваше REST API в приложении Angular.  Шпаргалка. Итак, начнем! 0. Настройте среду для Python. mkvirtualenv mysite 1. Скаффолдинг проекта на Django. Создайте начальные директории. Мы хотим сфокусироваться на модулярности в ходе разработки. Следовательно, существует множество директорий в конечном итоге использования. Мы хотим, чтобы наше дерево изначально выглядело так: mysite ├── backend │ ├── docs │ ├── requirements └── frontend ├── app │ ├── components │ └── shared ├── assets │ ├── css │ ├── img │ ├── js │ └── libs ├── config ├── dist └── js  Сделайте следующее: mkdir mysite && cd mysite mkdir -p backend/docs/ backend/requirements/ \ frontend/app/shared/ \ frontend/app/components/ \ frontend/config \ frontend/assets/img/ frontend/assets/css/ \ frontend/assets/js/ frontend/assets/libs/ \ frontend/dist/js/  *Примечание: Структура этого проекта была навеяна опытом с несколькими другими проектами. Я считаю эту организацию идеальной, но вам не обязательно ей следовать. Но, пока вы читаете этот гайд, вы должны придерживаться этой структуры. 2. Скаффолдинг проекта на Django. В директории backend/ создайте Django проект: python django-admin.py startproject mysite  Также создайте requirements.txt: pip freeze > requirements/requirements.txt В директории (вашего проекта) backend/mysite/ произведите скаффолдинг директории, той, где будет жить ваше API: mkdir -p applications/api/v1/ touch applications/__init__.py applications/api/__init__.py \ applications/api/v1/__init__.py applications/api/v1/routes.py \ applications/api/v1/serializers.py applications/api/v1/viewsets.py Теперь создайте структуру директории настроек: mkdir -p configlord/settings/ touch configlord/settings/__init__.py \ configlord/settings/base.py configlord/settings/dev.py configlord/settings/prod.py \ configlord/dev.env configlord/prod.en 3. Настройте переменные окружения, которые нужны для запуска сервера Django. На этом этапе я предпочитаю пользоваться django-environ для работы с переменными окружения. Существует множество способов сделать это, но пакет django-environ чрезвычайно упрощает этот процесс, поэтому я использую его во всех своих проектах. Установите django-environ: pip install django-environ  В mysite/dev.env добавьте следующее: DATABASE_URL=sqlite:///mysite.db DEBUG=True FRONTEND_ROOT=path/to/mysite/frontend/ SECRET_KEY=_some_secret_key Мы собираемся использовать эти переменные среды в наших настройках. Выгода от использования наших переменных окружения в отдельных файлах состоит в основном в том, что такая настройка позволяет облегчить переключение между средами. В нашем случае файл the dev.env является списком переменных, которые мы бы использовали в локальной среде разработки. *Примечание: SECRET_KEY можно взять из settings.py, который был сгенерирован django-admin.py startproject. 4. Установите Django REST Framework и настройте Django, используя переменные среды. Установка DRF: pip install djangorestframework Наполните settings/base.py  следующим: Укажите, где искать переменные окружения. import environ project_root = environ.Path(__file__) - 3 env = environ.Env(DEBUG=(bool, False),) CURRENT_ENV = 'dev' # 'dev' is the default environment # read the .env file associated with the settings that're loaded env.read_env('./mysite/{}.env'.format(CURRENT_ENV)) Установите базу данных. В данном случае мы собираемся использовать встроенные в django-environ настройки SQLite. DATABASES = { 'default': env.db() } Установите SECRET_KEY ,а также debug. SECRET_KEY = env('SECRET_KEY') DEBUG = env('DEBUG') Добавьте DRF в пул приложений, которые Django должен использовать. # Application definition INSTALLED_APPS = [ ... # Django Packages 'rest_framework', ] Ссылки будут «жить» в специальном URL модуле, созданном с помощью базы проекта. ROOT_URLCONF = 'mysite.urls' Укажите Django, где искать все шаблоны и другие статические ассеты. STATIC_URL = '/static/' STATICFILES_FINDERS = [ 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ] STATICFILES_DIRS = [ env('FRONTEND_ROOT') ] TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [env('FRONTEND_ROOT')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] В соответствии с настройкой TEMPLATES Django должен будет искать шаблоны внутри frontend/ directory. Это то, где Angular приложение будет жить. Мы используем только Django, чтобы обслужить шаблон, внутри которого Angular приложение будет загружаться, которое будет выполнено через entry-point директиву. Если вы не знаете, о чем я, продолжайте чтение...  Наполните settings/dev.py: from mysite.settings.base import * CURRENT_ENV = 'dev' Здесь мы указываем, что этот файл настроек унаследывает настройки из base.py и переопределяет строку CURRENT_ENV, найденную в base.py. Мы говорим: «Используй это значение вместо значения, найденного в наследуемом модуле». 5. Создайте API. Нам нужно нечто, с помощью чего мы сможем протестировать службы Angular, поэтому давайте создадим небольшое API. Этот шаг можно пропустить, но я не советовал бы делать этого. Нам важно знание того, что настройки приложения Angular работают исключительно с точки зрения его потенциала, чтобы облегчить HTTP запросы. Сгенерируйте приложение. manage.py startapp games Создайте модель в games/models.py. class Game(models.model): title = models.CharField(max_length=255) description = models.CharField(max_length=750) Создайте DRF сериализатор для модели игры в  applications/api/v1/serializers.py. from rest_framework.serializers import ModelSerializer from applications.games.models import Game class GameSerializer(ModelSerializer): class Meta: model = Game Создайте  DRF viewset для модели в приложениях applications/api/v1/viewsets.py. from rest_framework import viewsets from applications.games.models import Game from applications.api.v1.serializers import GameSerializer class GameViewSet(viewsets.ModelViewSet): queryset = Game.objects.all() serializer_class = GameSerializer В applications/api/v1/routes.py зарегистрируйте роуты, используя DRF's router registration features. from rest_framework import routers from applications.api.v1.viewsets import GameViewSet api_router = routers.SimpleRouter() api_router.register('games', GameViewSet) Обозначьте ссылки для зарегистрированного DRF роута внутри mysite/urls.py: from django.contrib import admin from django.conf.urls import include, url from applications.api.v1.routes import api_router urlpatterns = [ url(r'^admin/', admin.site.urls), # API:V1 url(r'^api/v1/', include(api_router.urls)), ] 6. Запустите сервер Django, используя dev settings. manage.py runserver --DJANGO_SETTINGS_MODULE=mysite.settings.dev Впуская DJANGO_SETTINGS_MODULE в runserver, мы «говорим» - работать используя специфические параметры. Если все работает, у вас появится возможность открыть localhost:8000/api/v1/games и увидеть ответ от DRF. Если все работает – самое время заняться построением приложения Angular. Если нет – направьте автору проблему. Если вы застряли на этом этапе – оставьте комментарий автору под оригиналом публикации. 7. Инициализируйте npm-пакет и установите front-end JS зависимости. Приложение Angular не будет работать так, как мы хотим, если правильные зависимости не будут установленны. Самое время установить базовые пакеты, которые понадобятся. Инициализируйте npm-пакет. Прямо из  frontend/ запустите npm init --yes By passing the --yes flag into init, you're telling NPM to generate a package.json using NPM defaults. Otherwise, if you don't pass that in, you'll have to answer questions... Boring. Установите dev dependencies.  npm install --save-dev eslint eslint-loader Установите общие зависимости. npm install --save eslint eslint-loader angular angular-resource angular-route json-loader mustache-loader lodash Файл package.json file во frontend/ должен выглядеть приблизительно следующим образом: { "name": "my-app", "version": "0.0.1", "description": "This is my first angular app.", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "eslint": "^3.1.1", "eslint-loader": "^1.4.1" }, "dependencies": { "angular": "^1.5.8", "angular-resource": "^1.5.8", "angular-route": "^1.5.8", "eslint": "^3.1.1", "eslint-loader": "^1.4.1", "json-loader": "^0.5.4", "lodash": "^4.13.1", "mustache-loader": "^0.3.1" } } Здесь то, что мы только что установили: eslint – отличный линтер, благодаря которому код JavaScript будет в порядке (последователен). eslint-loader – для запуска eslint через Webpack. Чуть позже я объясню концепцию «загрузчиков». angular -  MVC фреймворк. Если  вы не знали об этом, стоит подумать о том, чтобы закрыть эту страничку прямо сейчас. angular-resource -  (Angular) HTTP библиотека выбора. Это абстракция $http. json-loader - загрузчик (снова, используемый Webpack) для распаковки JSON из .json файлов с помощью require() во время работы нашего приложения. mustache-loader – загрузчик, который мы будем использовать, чтобы парсить наши mustache шаблоны. Mustache шаблоны – это веселье. Я могу спокойно предположить, что вы не знаете, как все эти пакеты заиграют вместе.  Не переживайте, братишки. 8. Создайте entry-point в Angular, объявите начальные зависимости, объявите первоначальные глобальные переменные. В frontend/app/app.js добавьте следующее: /* Libs */ require("angular/angular"); require("angular-route/angular-route"); require("angular-resource/angular-resource"); /* Globals */ _ = require("lodash"); _urlPrefixes = { API: "api/v1/", TEMPLATES: "static/app/" }; /* Components */ /* App Dependencies */ angular.module("myApp", [ "ngResource", "ngRoute", ]); /* Config Vars */ // @TODO in Step 13. /* App Config */ angular.module("myApp").config(routesConfig); app.js это то, где Webpack будет искать модули, чтобы бандлить их вместе. Лично я ценю такую организацию и методику вызовов, но такой порядок не обязателен. Существует 6 секций: Libs – главные библиотеки, используемые на протяжении работы Angular приложения; Globals – зарезервированные глобальные переменные, которые мы можем использовать во время работы приложения; Components (Компоненты) – особенные модули проекта; App Dependencies (Зависимости приложения) – объявление входной точки приложения и его зависимостей; Config Vars – переменные, где хранятся настройки, такие как route config; App Config - вводит configs (настройки) в приложение, используя сохраненные из предыдущей секции. Для того, чтобы globals работали, вам следует указать ESLint на то, какие из переменных - глобальные. В config/eslint.json добавляем следующее: { "env": { "node": true }, "extends": "eslint:recommended", "rules": { "indent": [ "error", 2 ], "linebreak-style": [ "error", "unix" ], "quotes": [ "error", "double" ], "semi": [ "error", "always" ], "no-console": 0 }, "globals": { "_": true, "_urlPrefixes": true, "angular": true, "inject": true, "window": true }, "colors": true } Ниже несколько переменных, о которых мы предупредили ESLint: _ представить lodash. _urlPrefixes – объект, который мы будем использовать в приложении для гиперссылок. Я расскажу об этом позже. angular, чтобы представить AngularJS object driving our entire application. inject, который будет использоваться для ввода зависимостей Angular. window, которая просто представляет объекты окон в JavaScript, является представителем  DOM. 9. Настройка Webpack. Теперь, когда мы выложили большинство наших зависимостей приложения, мы можем построить config file для Webpack. Webpack будет консолидировать все зависимости, а также модули для приложений, которые мы создаем в один файл. В bundle. В frontend/webpack.config.js добавляем следующее. module.exports = { entry: "./app/app.js", output: { path: "./dist/js/", filename: "bundle.js", sourceMapFilename: "bundle.js.map", }, watch: true, // eslint config eslint: { configFile: './config/eslint.json' }, module: { preLoaders: [{ test: /\.js$/, exclude: /node_modules/, loader: "eslint-loader" }], loaders: [ { test: /\.css$/, loader: "style!css" }, { test: /\.html$/, loader: "mustache-loader" }, { test: /\.json$/, loader: "json-loader" }] }, resolve: { extensions: ['', '.js'] } }; Для того, чтобы Webpack бандлил все наши статические зависимости, нам нужно указать ему, где их брать, какие зависимости обрабатывать и как управлять ими до банлинга. Давайте посмотрим на то, что указывает Webpack с помощью webpack.config.js: Entry - это путь к тому, что Webpack'у нужно для старта бандлинга. Это можеть быть полный путь или путь, относительный тому, где webpack.config.js располагается. В данном случае мы говорим о последнем варианте. output -  это объект, содержащий в себе path, который является директорией, в которую связанные зависимости будут помещаться; filename - это название бандла; и, в данном случае, мы решили использовать sourceMapFilename, чтобы обозначить, что наша() source map будет вызван(а). watch указывает Webpack следить за изменениями в файле, пока он выполняется. Если это не настроено как true, Webpack прогонит процесс бандлинга единожды и остановится. eslint содержит в себе специфические ESLint настройки, используемые eslint-loader. module указывает Webpack'у, что делать с модулями, с которыми он работает. module.preLoaders «говорит», что делать перед бандлингом. В данном случае мы хотим запустить модули (исключив модули установленные npm) через eslint. module.loaders - это то, где указана последовательность загрузчика. В нашем случае мы просто настраиваем test и loader,  где test указывает Webpack’у, какие модули запускать в загрузчике (по соответствию с паттерном regex), и loader говорит Webpack’y, какой загрузчик использовать в модулях, которые соответствуют regex паттерну в test. Каждый загрузчик указан в строке и разделен восклицательным знаком. Ex:  loader!another_loader!yet_another_loader module.preLoaders указывает, какие preLoaders'у запускать модули. Используемые настройки такие же точно, какие мы использовали в module.loaders. Но, Грег, какая  разница между preLoaders и loaders? Я рад, что ты спросил, мой дорогой друг!! A loader указывает Webpack'у, как бандлить требуемые файлы. Loader смотрит на модуль и говорт: «Эй, так как вы упаковываете это в один файл как строку – это то, как оно должно быть преобразованно для bundle'а». A preLoader обрабатывает код перед loaders, например, чтобы слинтить JavaScript модули. A postLoader является плагином Webpack'а, который обрабатывает код после бандинга. Мы не специфицировали ни один  postLoader ради простоты. 10. Укажите Django загрузить приложение. Прямо сейчас все, что нужно сделать – указать Webpack’у что создавать, как создавать и что должно быть создано. (На данном этапе я бы очень удивился, если вы попробуете запустить его и он заработает без ошибок. Если так и есть, я чертов мужик.) Так как Django использует свой собственный URL процессор в нашем приложении, мы можем быть рады тому, как любезно Django управляет всем тем, что введено в строку браузера пользователя. Как бы то ни было, мы бандлим одностраничное приложение, используя абсолютно другой фреймворк, и хотим, чтобы у приложения был полный контроль над тем, что пользователь вводит. Все, что нам нужно  – обслуживать одну страничку, в которой работает SPA. Следовательно... В backend/mysite/mysite/urls.py добавляем в список urlpatterns следующее: # Web App Entry url(r'^$', TemplateView.as_view(template_name="app/index.html"), name='index'), Это значит, что когда пользователь открывает mysite.com/, env('FRONTEND_ROOT') + app/index.html будет находить STATICFILES_FINDERS  в порядке рендера HTML шаблона. 11. Создайте шаблон базы приложения Angular. frontend/app/components/app/index.html шаблон должен выглядеть как обычный шаблон Django. В frontend/app/index.html добавляем следующее: {% load staticfiles %} <html ng-app="myApp"> <head> <title>My Sitetitle> <script src="{% static 'dist/js/bundle.js' %}">script> head> <body> body> html> В таком случае вам удастся запустить Webpack. Если вы запустите Django сервер и откроете localhost:8000,вы увидите пустую страничку. Если нет – дайте знать автору. 12. Напишите home component. Давайте напишем наш первый компонент. Он отобразит текст на страничке, пока пользователь открывает localhost:8000. Создайте директорию для компонента и базовые файлы. В frontend/app/components/: mkdir home && touch home/home-controller.js home/home.js home/home.html В frontend/app/components/home/home.html добавляем следующее: <div ng-controller="HomeController as ctrl"> <div> <h1>Home!h1> div> div> Теперь добавим следующее в  frontend/app/components/home/home-controller.js: function HomeController() { var that = this; that.foo = "Foo!"; console.log(that); // should print out the controller object } angular.module("Home") .controller("HomeController", [ HomeController ]) Определение модуля Angular должно быть объявлено в home.js: angular.module("Home", []); require("./home-controller"); Теперь мы можем сослаться на "Home" в области зависимости определения модуля. Давайте сделаем это! В app/app.js добавьте следующее: /* Components */ require("./components/home/home"); /* App Dependencies */ angular.module("myApp", [ "Home", // this is our component "ngResource", "ngRoute" ]); 13. Пропишите пути Angular'а, ведущие к home component и страничке 404. Нам нужно настроить первый путь. Когда пользователь попадает на localhost:8000, Angular должен взять контроль над загрузкой отрендеренного шаблона. Чтобы сделать это, нам потребуется использовать angular-router. В frontend/app/routes.js пишем следующее: function routesConfig($routeProvider) { $routeProvider .when("/", { templateUrl: _urlPrefixes.TEMPLATES + "components/home/home.html", label: "Home" }) .otherwise({ templateUrl: _urlPrefixes.TEMPLATES + "404.html" }); } routesConfig.$inject = ["$routeProvider"]; module.exports = routesConfig; Если мы не добавим _urlPrefixes.TEMPLATES, angular-router предположит, что components/home/home.html является действительной ссылкой, которую узнает сервер. Так как STATIC_URL в настройках предполагает неправильную работу localhost:8000/components/home/home.html. Также, если вы еще не заметили, вы увидите otherwise({...})  в коде роутов. Это то, как будут реализованы страницы 404. В frontend/app/404.html добавляем следующее: <h1>NOT FOUNDh1> И в завершении  добавляем frontend/app/app.js: /* Config Vars */ var routesConfig = require("./routes"); 14. Добавьте директивы angular-router к шаблону точки входа приложения. А теперь нам нужно указать Angular, где будет происходить переключение отображаемого, когда пользователь пользуется навигацией. Чтобы сделать это, мы используем всю силу angular-router. В тэг    в frontend/app/index.html добавляем: <base href="/"> Теперь в тэг  добавляем: <div ng-view>div> Ваш index.html теперь должен выглядеть так: {% load staticfiles %} <html ng-app="myApp"> <head> <title>My Sitetitle> <script src="{% static 'dist/js/bundle.js' %}" >script> <base href="/"> head> <body> <div> <div ng-view>div> div> body> html> Запустите Webpack. Откройте localhost:8000. Вы должны увидеть, что произошло в home/home.html. (Если ничего, отправьте эти данные автору J ). 15. Проверьте REST API в приложении Angular. Если все сделано, у вас появится возможность написать angular службы для Django API. Давайте создадим небольшой компонент, чтобы увидеть, можем ли мы это сделать. Этот компонент должен перечислять игры. Я предполагаю, что вы уже заполнили базы данных, следовательно запрос HTTP к localhost:8000/api/v1/games вернет список игр. Создайте скаффолд компонент в frontend/app/components/: mkdir -p game/list/ && touch game/list/game-list-controller.js game/list/game-list-controller_test.js game/game-service.js game/game.js game/game.html Этот компонент будет перечислять игры. Этот компонент должен перечислять игры. Я предполагаю, что вы уже заполнили базы данных, следовательно запрос HTTP к localhost:8000/api/v1/games вернет список игр. В game/game-service.js: function GameService($resource) { /** * @name GameService * * @description * A service providing game data. */ var that = this; /** * A resource for retrieving game data. */ that.GameResource = $resource(_urlPrefixes.API + "games/:game_id/"); /** * A convenience method for retrieving Game objects. * Retrieval is done via a GET request to the ../games/ endpoint. * @param {object} params - the query string object used for a GET request to ../games/ endpoint * @returns {object} $promise - a promise containing game-related data */ that.getGames = function(params) { return that.GameResource.query(params).$promise; }; } angular.module("Game") .service("GameService", ["$resource", GameService]); Обратите внимание на ссылку $resource, которую мы используем для того, чтобы настроить механизмы HTTP в нашей службе. В game/list/game-list-controller.js: function GameListController(GameService) { var that = this; /* Stored game objects. */ that.games = []; /** * Initialize the game list controller. */ that.init = function() { return GameService.getGames().then(function(games) { that.games = games; }); }; } angular.module("Game") .controller("GameListController", [ "GameService", GameListController ]); В game/game.html: <div ng-controller="GameListController as ctrl" ng-init="ctrl.init()"> <div> <h1>Gamesh1> <ul> <li ng-repeat="game in ctrl.games">{{ game.title }}li> ul> div> div> В game/game.js: angular.module("Game", []); require("./list/game-list-controller"); require("./game-service"); Затем обратимся к компоненту в app.js: /* Components */ require("./components/game/game"); /* App Dependencies */ angular.module("myApp", [ "Home", "Game", "ngResource", "ngRoute" ]); В конце концов, мы собираемся настроить роуты для списка игр, поэтому в frontend/app/routes.js добавьте следующее в объект $routeProvider: .when("/game", { templateUrl: _urlPrefixes.TEMPLATES + "components/game/list/game-list.html", label: "Games" }) Запустите Webpack снова. Все должно верно скомпилироваться. Если нет – дайте знать автору. Откройте localhost:8000/#/games. Вы увидите список игр. Сделано! Это все. Сомнения/Мысли Но есть некоторые сомнения: Глобальные переменные могут конкретно подставить вас, если вы не знаете, как с ними работать. Их локальное поведение не гарантирует того же на продакшене. Насколько я помню, их можно заставить работать, если правильно описан метод. Ваше приложение на Angular тесно связанно с Django. Поэтому ваше приложение не будет просто слиянием back- и фронтенда. Если ваш Django-RIP давно устарел, значит поменялись и маршруты, следовательно сконфигурируете ваш бэкенд согласно тому, как должны вести себя статические файлы. Так же вам будет необходимо заменить index.html с точкой входа Angular. Маленькие проекты не дадут вам особо попотеть, а вот большие явно заставят понервничать. Совет: единственное место, где должны сопрягаться приложение на Angular и Django сервер - это одна точка входа. Деплоймент должен быть выполнен так же, как любой обычный деплоймент приложения. Это все. Если у вас есть какие-либо вопросы и вы испытываете трудности, пожалуйста, оставьте их в комментариях в исходной статье! Чит! Автор пообещал выложить на гитхабе репозиторий со всем кодом. Оригинальная статья на английском языке. 
Паралакс для 2D гри без нервів та милиць

Автор: Дар'я Коновалова

Недавно в моей жизни начинающего разработчика игр появилась задача - сделать фон в игре, но не просто уныленький статичный бэкграунд, а параллакс. Да, эта чудо-красота применима не только в разработке сайтов, но и при создании игр. Попытка вдохновиться в гугле практически ничем не закончилась. Пришлось справляться с задачей собственными силами. У меня получилось. Хочу поделиться с вами опытом. Справедливо замечу, что параллакс — это не достояние веба. Еще в дремучие времена существования 8-битных игр параллакс успешно применялся для создания иллюзии объема в двухмерной игре. Коротко говоря, параллакс — это наслоение изображений, каждый слой движется со своей скоростью. Ближайший к игроку имеет самую высокую скорость, соответственно дальний (последний) — самую низкую. Ну что, вроде, минимально в теории разобрались и даже нашли, откуда ноги растут, значит, мы готовы перейти непосредственно к практике и сотворить это чудо своими руками. Что вам потребуется: базовые знания Unity3D 5 (на уровне создания скриптов, добавления компонентов); понимание С#; 3 или больше картинок в формате .png; внимательность и желание. Ладно, последнее не очень обязательно =) По ссылке вы можете скачать необходимые изображения, а также уже готовый проект. Подготовка Запускаем Unity3D, создаем новый проект, называем его, например, Parallax2D. Закидываем в папку Assets наши бэкграунды. Рекомендую сложить их в отдельную папку. В моем случае они лежат в Assets – StarSky. Каждое изображение называем удобно и понятно. Я назвала их по порядку размещения (Background – задний фон, MiddleBackground – средний, TopBackground – верхний слой). Для того, чтобы картинка перемещалась гладко, нам необходимо настроить ее в Inspector. Обратите внимание, этот этап очень важен, иначе все размажет, как звезды за иллюминатором Энтерпрайза на 3-й космической скорости. В поле Texture Type выбираем тип Texture, во Wrap Mode отмечаем Repeat. И радостно тыкаем Apply. Без этого действия изменения не сохранятся, а потом можно долго недоумевать, почему же оно не работает. Совершаем эти телодвижения и для 2-х остальных текстур. Подготовив картинки, переходим к этапу размещения их на сцене. Часто в этих ваших интерентах можно встретить совет - размещать их с помощью GameObject – Plane. Вы, конечно, можете потрудиться и заставить 3D объект нормально функционировать в 2D игре. Но, на самом деле, это все будет уныло, как последний эпизод «Звездных войн», а работать это чудовище будет чуть более быстро, чем аж никак. Поэтому я рекомендую долго не мучиться и использовать элемент UI – Canvas. Canvas меняет размер фона автоматически, подстраивает его под размеры экрана гаджета, на котором запускают игру. Это избавляет нас от потребности писать скрипт, который будет отвечать за отслеживание размеров экрана и изменения размера фона. В Hierarchy выбираем UI – Canvas. Собственно, если работать с Юнькой для вас не впервой, то вы явно знаете еще много других способов, как добавить в Hierarchy объект. Так что делайте это любым удобным способом. Создав Canvas, добавляем чайлдами («внутрь» канвы) три Panel для 3-х наших фонов. После добавления наша Hierarchy выглядит так: Переименовываем Canvas и Panel, чтобы у всех были свои пароли и явки. А теперь засучиваем рукава и беремся препарировать — подготавливать каждый компонент в Inspector. Начнем с ParallaxBackground. Изменяем Layer на Ignore Raycast, чтобы наш фон не реагировал на касания пальцами. Unity обязательно спросит, применить ли эти изменения ко всем «детям» — соглашаемся.  Далее переходим к компоненту Canvas. Находим Render Mode, выбираем Screen Space – Camera. В Render Camera добавляем нашу текущую дефолтную камеру (Main Camera(Camera)). Plane Distance ставим пока 110, но этот показатель проще отстроить во время теста. По факту — это расстояние от камеры до нашей канвы. То есть, изменяя его, мы будем получать разную глубину изображения. Остальное не трогаем и переходим к Back. В Rect Transform привязываем позицию к левому краю. Теперь наш фон будет всегда отстраиваться по одному краю, и мы избежим проблем с правильной позицией на разных устройствах. Удаляем компонент Image (Script), вместо него добавляем Raw Image (Script) (напомню, Add Component – UI – Raw Image (Scriot)). В Texture добавляем картинку нашего самого последнего слоя. Те же операции проделываем и для остальных слоев. Можно сделать немного проще, наколдовать изменения в первой Panel, дублировать (Ctrl + D), поставить в каждую свою текстуру, переименовать. Тут уже зависит от того, как вам будет удобнее. Запускаем сцену — любуемся. Три картинки прекрасно легли друг на друга. Немного черной магии Теперь весь смак. Мы с вами напишем скрипт, который заставит наши картинки двигаться. Создаем новый скрипт, напомню, пишем на C#, назовем его BackgroundHelper. Он у нас будет один, поэтому нет смысла делать отдельную папку, кидаем его прямо в основную Assets. Открываем созданный скрипт и понеслась тяжкая работа на 5 строчек: using UnityEngine; using UnityEngine.UI; // обязательно добавляем библиотеку пользовательского интерфейса, без нее кино не будет using System.Collections; public class BackgroundHelper : MonoBehaviour {     public float speed = 0; //эта публичная переменная отобразится в инспекторе, там же мы ее можем и менять. Это очень удобно, чтобы настроить скорость разным слоям картинки     float pos = 0; //переменная для позиции картинки     private RawImage image; //создаем объект нашей картинки               void Start () {         image = GetComponent<RawImage>();//в старте получаем ссылку на картинку         }                    void Update () {         //в апдейте прописываем как, с какой скоростью и куда мы будем двигать нашу картинку           pos += speed;             if (pos > 1.0F)                 pos -= 1.0F;             image.uvRect = new Rect(pos, 0, 1, 1);        }     } Сохраняем скрипт и добавляем его, как компонент к каждому слою. Скорости у меня такие: Back 0.0001 Middle 0.00015 Top 0.00024 Наслаждаемся успешной работой Если все было сделано правильно, мы получим умопомрачительный эффект, от которого просто невозможно оторвать глаз. У вас остались вопросы или возникли трудности? Пишите в комментариях.
Валідація AngularJS

Автор: Редакция ITVDN

Введение Валидация достаточно часто вызывает затруднения с работой в веб-приложениях. Во многих случаях фреймворки должны быть использованы для валидации значений формы. Кроме того, эти фреймворки часто не работают во всех браузерах. AngularJS приходит с проверкой, построенной так, что теперь гораздо легче создать валидацию, которая работает во всех браузерах. На практике Использование angular-message Чтобы использовать angular-message, нужно внести в ваш проект модуль:  angular.module("realestateApp", ["ngMessages"]); Теперь ng-message будет доступен для использования. Формы Для инициализации процесса валидации, Вы должны начать с формы контейнера: <form name="tenantForm"> Теперь внутри тега можно добавить управление и логику на их проверку. В сценариях валидации, как правило, есть несколько основных атрибутов. Те, которые стоит использовать: Required, Minimum, Maximum, Pattern, Email, Number, и URL. Required Этот атрибут заставляет form быть недействительным, если обязательное поле не вводится. <input type="text" required /> Minimum Length Этот атрибут указывает минимум символов для ввода до того, как значение будет принято. <input type="text" ng-minlength=5 /> Maximum Length Этот атрибут указывает максимальную длину или проверка будет неверна. <input type="text" ng-maxlength=20 /> Pattern Matching Эта функция позволяет согласовать совпадения при использовании Regex. <input type="text" ng-pattern="[a-zA-Z]" /> Email Matching Angular обеспечивает пользовательские функции по электронной почте. <input type="email" name="email" ng-model="user.email" /> Number Этот требует ввода в цифровом формате перед проверкой. <input type="number" name="age" ng-model="user.age" /> URL Этот требует ввода в ссылочном формате перед проверкой. <input type="url" name="homepage" ng-model="user.url" /> Сообщение об ошибке Ранее использовался error-container, но теперь можно использовать директиву ng-message: <div ng-messages="tenantForm.Email.$error" ng-messages-include="messages.html" class="errors"></div> Также нужен файл messages.html для хранения сообщений об ошибках: <div class="messages"> <div ng-message="required">Required</div> <div ng-message="minlength">Too short</div> <div ng-message="maxlength">Too long</div> <div ng-message="email">Invalid email address</div> <div ng-message="compareTo">Must match the previous entry</div> <div ng-message="number">Must be a number</div> <div ng-message="url">Must be in URL format</div> </div> Сообщение об отмене ошибки Иногда Вы должны иметь пользовательские сообщения об ошибках, не охватываемых messages.html. Вы можете сделать это, добавив тег span в диапазон для любого сообщения об ошибке. То, что должно отобразиться: <div ng-messages="tenantForm.FirstName.$error" ng-messages-include="messages.html" class="errors"> <span class="messages" ng-message="minlength">Must be more than 3 characters</span> <span class="messages" ng-message="maxlength">Must be more than 20 characters</span> </div> Собираем всё вместе Теперь, объединив messages.html с index.html. <form name="tenantForm" novalidate style="width: 500px">     <div class="row">         <div ng-repeat="tenant in tenant">             <div class="form-group">                 <label>First Name: label>                 <input type="text"                        placeholder="First Name"                        name="FirstName"                        ng-model="tenant.FirstName"                        ng-minlength=3                        ng-maxlength=20 required />                 <div ng-messages="tenantForm.FirstName.$error"                      ng-messages-include="messages.html" class="errors">                     <span class="messages"                     ng-message="minlength">Must be more than 3 charactersspan>                     <span class="messages"                     ng-message="maxlength">Must be more than 20 charactersspan>                 div>             div>             <div class="form-group">                 <label>Home Phone: label>                 <input type="number"                        placeholder="Phone Number"                        name="HomePhone"                        ng-model="tenant.HomePhone"                        ng-minlength=7                        ng-maxlength=10 required />                 <div ng-messages="tenantForm.HomePhone.$error"                      ng-messages-include="messages.html" class="errors">                     <span class="messages"                     ng-message="minlength">Must be more than 7 digitsspan>                     <span class="messages"                     ng-message="maxlength">Must be less than 11 digitsspan>                  div>             div>             <div class="form-group">                 <label>Email: label>                 <input type="email"                        placeholder="Email"                        name="Email"                        ng-model="tenant.Email"                        required />                 <div ng-messages="tenantForm.Email.$error"                      ng-messages-include="messages.html" class="errors">div>             div>             <div class="form-group">                 <label>Webpage: label>                 <input type="url"                        placeholder="Webpage"                        name="Webpage"                        ng-model="tenant.Webpage"                        required />                 <div ng-messages="tenantForm.Webpage.$error"                      ng-messages-include="messages.html" class="errors">                 div>             div>         div>     div>               form> Этот подход кажется чище, чем использование директивы ng-show. Кроме того, это уменьшит дублирование кода путем центрального места хранения ваших сообщений об ошибках, но в то же время используется для пользовательских сообщений. Источник: http://www.codeproject.com/Articles/992545/AngularJS-Validation
Як створити веб-сайт за допомогою AJAX

Автор: Редакция ITVDN

Создания простого чата Для начала создадим простой чат с помощью HTML&CSS и PHP&MySQL Проектирование Базы Данных Переходим к phpMyAdmin. Создаем новую базу данных, называем “chatdb”. Создаем новую таблицу, называя “Posts”, включая в себя 4 столбца: “id” тип колонки INT, автоматическое увеличение на 1 (задаем A_I в ячейку с флажком) основной ключ (index); “nick” тип колонки VARCHAR и длина 100; “post_text” тип колонки TEXT; “post_dt” тип колонки DATETIME по умолчанию является CURRENT_TIMESTAMP. Создание веб-сайта. Во-первых, создадим главную страницу. Список комментариев и отправка формы, используя кнопку. Всё будет находится в файле “index.php” .  Давайте создадим этот файл и напишем что-то вроде этого:  <html> <head> head> <body> php $mysqli = new mysqli("127.0.0.1", "root", "", "chatdb"); /* "127.0.0.1" is MySQL host name. In local servers (XAMPP, Ampps, etc.) it is 127.0.0.1. If you using a dedicated hosting, see it in admin panel. */ /* "root" and "" is login and password for DB's user. In local servers usually default DB user is "root" with empty password. */ /* "chatdb" is DB's name. */ /* Warning: in XAMPP you should manually run MySQL server (from xampp-control.exe) to get it work. */ $result = $mysqli->query("SELECT * FROM posts;"); ?> <style> /* All CSS is very simplified. I provide it for example and no more.  * In Chrome it works tolerably, but in IE and Firefox it works very poorly.  */ .content {   display: table;   width: 50%;   min-width: 400px;   height: 80%;   /* Center horizontally and vertically */   position: absolute;   left: 0; right: 0;   top: 0; bottom: 0;   margin: auto;   /* Design */   border: 1px solid;   background-color: silver;   padding: 5px; } /* For mobile devices */ @media (max-width: 400px) {   .content {     width: 100%;     min-width: 0;     padding: 0px;   } } style> <div class="content"> <div id="comments" style="overflow-y: scroll; height: 100%;"> php if ($result) {   while ($post = $result->fetch_object()){     $nick = $post->nick;     $post_dt = $post->post_dt;     $post_text = $post->post_text;         echo "<b>$nickb> ($post_dt):<br>";     echo "$post_text<br>";     echo "<br>";   }   $result->close(); } ?> div> php $mysqli->close(); ?> <form action="post.php" method="post" style="height: 0; display: table-row;">   Nick:<br>   <input type="text" name="nick" style="width: 100%;">input><br>   <br>   Text:<br>   <textarea name="text" style="width: 100%;">textarea><br>   <br>   <input type="submit">input> form> div> <script type="text/javascript">     var divComments = document.getElementById('comments');     divComments.scrollTop = divComments.scrollHeight; script> body> html> 2. Дальше давайте создадим “post.php” файл и напишем такое:  php $nick = $_POST['nick']; $post_text = $_POST['text']; $mysqli = new mysqli("127.0.0.1", "root", "", "chatdb"); $nick = $mysqli->real_escape_string($nick); $nick = htmlspecialchars($nick); $post_text = $mysqli->real_escape_string($post_text); $post_text = htmlspecialchars($post_text); $mysqli->query("INSERT INTO posts (nick, post_text) VALUES ('$nick', '$post_text');"); $mysqli->close(); /* Redirect To Main Page */ header('Location: ' . $_SERVER['HTTP_REFERER']); ?> 3. Откроем ваш чат в любом продвинутом браузере. Вся работа:  4. Напишите ник и текст, а потом нажмите на кнопку отправки.  Когда отправили форму, сразу же происходит перенаправление формы главной страницы в “post.php”. “Post.php” моментально наполняет данными таблицу и перенаправляет на главную страницу. Также “post.php” содержит начальный XSS и SQL защищенный вход. Для упрощения не отправляем клиентам время с JS на “post_dt” на внесения данных, а указываем значение по умолчанию – CURRENT_TIMESTAMP, который предоставляет нынешнюю дату и время на сервер. Когда количество комментариев больше, чем экран может вместить, то комментарии   переполнены в div, используется вертикальный скроллбар. Во время загрузки страницы JS автоматически опускает скроллбар вниз к недавнему комментарию. Что тут не так? Проблема номер 1. Новый непрочитанный комментарий от пользователя не загружается в базу данных автоматически, без ручной перезагрузки страницы. Это очень, очень серьезная проблема для любого чата. Как это исправить? Очевидно, один из путей сделать это - использовать HTTP-запрос к фоновой работе автоматически (с помощью JS) и асинхронно, то есть это один из способов обеспечить выполнение AJAX в любой form. Проблема номер 2. Также неправильно то, что кнопка отправки перезагружает страницу (перенаправляет на вторую страницу и следующим шагом возвращает назад). При перезагрузке страницы сбрасывает “Nick” в  поле (также, как любые другие изменения, те, что сделали с пользователем) Как это исправить? Конечно, можно обеспечить выполнение обхода для сохранения изменений и перезагрузки на перезагружаемой странице. Но не эффективнее ли устранить причину, чем последствие? Можно только убрать перезагружаемую страницу и эта проблема будет решена автоматически. Проблема номер 2.1. Видите ли что-то необычное тут? Это слишком быстро? Дело с чатом очень простое – загружаем только два маленьких текстовых параметра. Что делать, если нужно улучшить чат с помощью добавления присоединения, а конкретно - изображений и видео? Видео может иметь объем в размере нескольких мегабайт, что тогда в этом случае? Давайте попробуем.  Давайте немного изменим “post.php”, после $mysql->query() добавив это:  for ($i = 0; $i < 1000000000; $i++) { } Старайтесь размещать какие-либо комментарии. Что мы видим? Нет, UI не остановилось (заморозилось), но браузер ждет до того, как закончится подключение:   Да, если “post.php” вызывает какую-то необработанную ошибку, то чат исчезает, и пользователь видит пустое окно с сообщением о непонятной ошибке. Чтобы вернуться к чату, пользователю стоит нажать кнопку “Back” в браузере. И это проблема номер 2.2. Давайте уберем петлю с “post.php” и исправим эти проблемы. Реализация легкого AJAX в простой чат Автоматическое обновление комментариев Эта веб-страница без AJAX? Это веб-страница, которая полностью перезагружается. Эта веб-страница c AJAX? Это веб-страница, что перезагружается частично. Где же взять части этих страниц? Стоит разделить нашу страницу на части, в этом случае сервер генерирует страницу частично. Страница будет состоять из двух частей – блок комментариев и другой контент страницы. Комментарии будут загружаться и перезагружаться отдельно от другой страницы. Давайте сделаем первую часть, то есть блок комментариев. Для начала создадим пустой файл, назовем “getcomment.php”. Дальше переходим в “index.php” для того, чтобы вырезать комментарии и далее отделить его. 1. Вырезаем инициализированный блок MySQL с “index.php"  php $mysqli = new mysqli("127.0.0.1", "root", "", "chatdb"); /* "127.0.0.1" is MySQL host name. In local servers (XAMPP, Ampps, etc.) it is 127.0.0.1. If you using a dedicated hosting, see it in admin panel. */ /* "root" and "" is login and password for DB's user. In local servers usually default DB user is "root" with empty password. */ /* "chatdb" is DB's name. */ /* Warning: in XAMPP you should manually run MySQL server (from xampp-control.exe) to get it work. */ $result = $mysqli->query("SELECT * FROM posts;"); ?> вставляем в “getcomments.php”. 2. Следующее, вырежем контент div c комментариями с “index.php”: php if ($result) {   while ($post = $result->fetch_object()){     $nick = $post->nick;     $post_dt = $post->post_dt;     $post_text = $post->post_text;         echo "<b>$nickb> ($post_dt):<br>";     echo "$post_text<br>";     echo "<br>";   }   $result->close(); } ?> И вставим (добавим) в “getcomments.php” после инициализации MySQL. 3. Дальше, вырежем недалекий блок MySQL с “index.php”: php $mysqli->close(); ?> Вставим его в конец файла “getcomments.php”. 4. Наконец-то, убираем JS-скрипт, который опускает скролл в самый низ. <script type="text/javascript"> var comments = document.getElementById('comments'); comments.scrollTop = comments.scrollHeight; script> Не переживай, это только временно. 5. Сделано. Теперь имеем что-то наподобие этого:  <html> <head> head> <body> <style> /* All CSS is very simplified. I provide it for example and no more.  * In Chrome it works tolerably, but in IE and Firefox it works very poorly.  */ .content {   display: table;   width: 50%;   min-width: 400px;   height: 80%;   /* Center horizontally and vertically */   position: absolute;   left: 0; right: 0;   top: 0; bottom: 0;   margin: auto;   /* Design */   border: 1px solid;   background-color: silver;   padding: 5px; } /* For mobile devices */ @media (max-width: 400px) {   .content {     width: 100%;     min-width: 0;     padding: 0px;   } } style> <div class="content"> <div id="comments" style="overflow-y: scroll; height: 100%;"> div> <form action="post.php" method="post" style="height: 0; display: table-row;">   Nick:<br>   <input type="text" name="nick" style="width: 100%;">input><br>   <br>   Text:<br>   <textarea name="text" style="width: 100%;">textarea><br>   <br>   <input value="Submit" type="submit">input> form> div> body> html> getcomments.php:  php $mysqli = new mysqli("127.0.0.1", "root", "", "chatdb"); /* "127.0.0.1" is MySQL host name. In local servers (XAMPP, Ampps, etc.) it is 127.0.0.1. If you using a dedicated hosting, see it in admin panel. */ /* "root" and "" is login and password for DB's user. In local servers usually default DB user is "root" with empty password. */ /* "chatdb" is DB's name. */ /* Warning: in XAMPP you should manually run MySQL server (from xampp-control.exe) to get it work. */ $result = $mysqli->query("SELECT * FROM posts;"); ?> Вероятно, теперь наша страница разделена. Давайте проверим её. Переходим http (точнее ССЫЛКА для локального сервера). Дальше, переходим на главную страницу, http:///index.php Это удивительно! Всё удачно получилось, разделив страницу на части за пару минут! Не останавливаемся на этом. Теперь “glue” эти части с помощью AJAX. Как это сделать? 1. Для начала стоит создать пустой JS-скрипт на главной странице: <script type="text/javascript"> alert('Test'); script> Добавив это после тега div, перед закрывающимся тегом . 2. Сделаем HTTP GET запрос от JS к “getcommet.php” Для этого используем XMLHtttpRequest (XHR) класс: <script type="text/javascript"> var xhr = new XMLHttpRequest(); xhr.open('GET', '/getcomments.php', false); xhr.send(null); if (xhr.status == 200) {   alert(xhr.responseText); } script> Это работает, но не читает старые версии IE те, что не поддерживают такую инициализацию. Для получения более кросс-браузерного пути переходим в и добавляем это: <script type="text/javascript"> function getXmlHttp(){   var xmlhttp;   try {     xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");   } catch (e) {     try {       xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");     } catch (E) {       xmlhttp = false;     }   }   if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {     xmlhttp = new XMLHttpRequest();   }   return xmlhttp; }; script> И главный скрипт (в конце) <script type="text/javascript"> var xhr = getXmlHttp(); xhr.open('GET', '/getcomments.php', false); xhr.send(null); if (xhr.status == 200) {   alert(xhr.responseText); } script> Как видно, теперь JS получает контент со страницы “getcomments.php” и показывает это в предупреждении.   2.1. Вопрос: «Это на самом деле AJAX (Asynchronous Javascript And Xml)?» Это AJAX, потому что запрос сервера отформатирован в HTML (который основан на XML). Но действительно ли это AJAX, это асинхронно? Проверим. Добавим эти уже знакомые строки в любое место между в “getcomments.php”: for ($i = 0; $i < 1000000000; $i++) { } Что теперь видно на загружаемой странице? Вначале страница зависает, его замораживает UI (становится не реагирующим на нажатие левой/правой кнопки мыши): Дальше Chrome показывает навязчивое всплывающие окно, сообщающее об удалении страницы: Это не AJAX! Это JAX! Как его сделать асинхронным? К счастью, ХHR также поддерживает асинхронный режим: <script type="text/javascript"> var xhr = getXmlHttp(); xhr.open('GET', '/getcomments.php', true); /* true for asynchronous */ xhr.onreadystatechange = function() {   if (xhr.readyState == 4) {     if(xhr.status == 200) {       alert(xhr.responseText);     }   } }; xhr.send(null); script> В этом случае браузер не ждет ответа с главного потока пользовательского интерфейса, он запускает в другом (асинхронно) и вызываемым событием “onreadystatechange” в главном контексте UI.  Теперь всё в порядке, страница полностью доступна, пока запрос запущен, и после ответа получит предупреждение. for ($i = 0; $i < 1000000000; $i++) { } И продолжаем работу. 3. Добавляем этот контент в div  вместо предупреждения. Заменить это: alert(xhr.responseText);  C этим: var divComments = document.getElementById('comments'); divComments.innerHTML = xhr.responseText; Возвращаем назад, клиент видит “glued” страницу с блоком комментариев. 4. Дальше следует установить интервал для автоматической проверки новых комментариев время от времени… и также восстановить удаленный сценарий автоматической прокрутки.  <html> <head> <script type="text/javascript">     function getXmlHttp() {         var xmlhttp;         try {             xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");         } catch (e) {             try {                 xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");             } catch (E) {                 xmlhttp = false;             }         }         if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {             xmlhttp = new XMLHttpRequest();         }         return xmlhttp;     }; script> head> <body> <style> /* All CSS is very simplified. I provide it for example and no more.  * In Chrome it works tolerably, but in IE and Firefox it works very poorly.  */ .content {   display: table;   width: 50%;   min-width: 400px;   height: 80%;   /* Center horizontally and vertically */   position: absolute;   left: 0; right: 0;   top: 0; bottom: 0;   margin: auto;   /* Design */   border: 1px solid;   background-color: silver;   padding: 5px; } /* For mobile devices */ @media (max-width: 400px) {   .content {     width: 100%;     min-width: 0;     padding: 0px;   } } style> <div class="content"> <div id="comments" style="overflow-y: scroll; height: 100%;"> div> <form action="post.php" method="post" style="height: 0; display: table-row;">   Nick:<br>   <input type="text" name="nick" style="width: 100%;">input><br>   <br>   Text:<br>   <textarea name="text" style="width: 100%;">textarea><br>   <br>   <input value="Submit" type="submit">input> form> div> <script type="text/javascript">     var divComments = document.getElementById('comments');     function loadComments() {         var xhr = getXmlHttp();         xhr.open('GET', '/getcomments.php', true);         xhr.onreadystatechange = function () {             if (xhr.readyState == 4) {                 if (xhr.status == 200) {                     if (xhr.responseText !== divComments.innerHTML) {                         divComments.innerHTML = xhr.responseText;                         divComments.scrollTop = divComments.scrollHeight;                     }                 }             }         };         xhr.send(null);     };     loadComments();     setInterval(loadComments, 1000) script> body> html> Теперь проблема исправить это. Новые комментарии с другого пользовательского чата (другие вкладки браузеров, окон и экземпляров) получают каждые 1000 миллисекунды (1 секунду) автоматически. Но отправка комментариев уже вызывает перезагрузку нашей страницы. Отправка комментариев без перезагрузки Как написано выше, XHR помогает отправлять HTTP GET-запросы без перезагрузки страницы и GUI заморозки (асинхронно). Теперь отправляем HTTP-запрос асинхронно, но на этот раз POST запрос, а не GET. И, естественно, XHR позволяет это. Используем метод send(). Для GET указываем null. Для POST устанавливаем запрос “body”.Также нужно добавить “Content-Type:application/x-www-from-urlencoded” в header  для того, чтобы разрешить серверу знать, какой формат использовать для отправки данных. Заметка: если не знаете, что отправлять, то можно захватить регулярный запрос с помощью “Fiddler” или же любой другой HTTP-перехватчик и только просимулировать запрос. HTTP-перехватчик — это незаменимый инструмент для работы с HTTP/HTTPS. Это позволяет увидеть все headers и bodies по всем HTTP(S)-запросам, что отправляются в систему. Лучше использовать “Fiddler”, это бесплатное, современное и очень простое приложение, что может поддерживать HTTP/HTTPS и оба Win x86/x64. 1. Для начала создадим пустой JS скрипт в HTML. Разместить до самого тега, потому что этот скрипт будет использован для отправки формы комментария (форма будет вызывать этот скрипт при отправке).2. В этом скрипте, реализуем функцию, что будет отправлять ник и комментарии в “post.php”  <script type="text/javascript"> function postComment(nick, text) {   var xhr = getXmlHttp();   xhr.open('POST', '/post.php', true);   xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');   xhr.onreadystatechange = function() {     if (xhr.readyState == 4) {       if (xhr.status == 200) {         /* it isn't required to add comment to DOM manually, it will done automatically on next refresh via AJAX */       }     }   };   xhr.send('nick=' + nick + '&text=' + text); /* joining the data in format simulates form */ }; script> 3. Следующие, перезагружая страницу при отправке:  ... onsubmit="return false;"> Также добавим вызов “postComment’s”: action="post.php" method="post" style="height: 0; display: table-row;" onsubmit="postComment(this.nick.value, this.text.value); return false;"> 4. Наконец, необязательно, но можно убрать “action” и “method” с формы:  <form style="height: 0; display: table-row;" onsubmit="postComment(this.nick.value, this.text.value); return false;"> 5. Результат: <html> <head> <script type="text/javascript">     function getXmlHttp() {         var xmlhttp;         try {             xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");         } catch (e) {             try {                 xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");             } catch (E) {                 xmlhttp = false;             }         }         if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {             xmlhttp = new XMLHttpRequest();         }         return xmlhttp;     }; script> head> <body> <style> /* All CSS is very simplified. I provide it for example and no more.  * In Chrome it works tolerably, but in IE and Firefox it works very poorly.  */ .content {   display: table;   width: 50%;   min-width: 400px;   height: 80%;   /* Center horizontally and vertically */   position: absolute;   left: 0; right: 0;   top: 0; bottom: 0;   margin: auto;   /* Design */   border: 1px solid;   background-color: silver;   padding: 5px; } /* For mobile devices */ @media (max-width: 400px) {   .content {     width: 100%;     min-width: 0;     padding: 0px;   } } style> <div class="content"> <div id="comments" style="overflow-y: scroll; height: 100%;"> div> <script type="text/javascript">     function postComment(nick, text) {         var xhr = getXmlHttp();         xhr.open('POST', '/post.php', true);         xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');         xhr.onreadystatechange = function () {             if (xhr.readyState == 4) {                 if (xhr.status == 200) {                     /* it isn't required to add comment to DOM manually, it will done automatically on next refresh via AJAX */                 }             }         };         xhr.send('nick=' + nick + '&text=' + text);     }; script> <form style="height: 0; display: table-row;" onsubmit="postComment(this.nick.value, this.text.value); return false;">   Nick:<br>   <input type="text" name="nick" style="width: 100%;">input><br>   <br>   Text:<br>   <textarea name="text" style="width: 100%;">textarea><br>   <br>   <input value="Submit" type="submit">input> form> div> <script type="text/javascript">     var divComments = document.getElementById('comments');     function loadComments() {         var xhr = getXmlHttp();         xhr.open('GET', '/getcomments.php', true);         xhr.onreadystatechange = function () {             if (xhr.readyState == 4) {                 if (xhr.status == 200) {                     if (xhr.responseText !== divComments.innerHTML) {                         divComments.innerHTML = xhr.responseText;                         divComments.scrollTop = divComments.scrollHeight;                     }                 }             }         };         xhr.send(null);     };     loadComments();     setInterval(loadComments, 1000) script> body> html>  Теперь простая реализация AJAX подошла к концу. Это ещё не конец, это только начало. Есть ещё достаточно всего для изучения и улучшения.
Notification success