Результати пошуку за запитом: mvc 5*
Введення в розробку програм під 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. Любите себя и берегите!
Паралакс для 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 подошла к концу. Это ещё не конец, это только начало. Есть ещё достаточно всего для изучения и улучшения.
Індексація стовпців у Redis
Автор: Редакція ITVDN
Введение
В отличие от memcache, Redis может быть использован в качестве постоянного хранилища, а не только как временный кэш. Так случилось, что Redis — это невероятно быстрая база данных, дающая поразительно лучшую производительность Вашему приложению в случае правильной настройки. В качестве предостережения хотелось бы добавить, что при работе с Redis в качестве основного хранилища таится много рисков, и эти риски значительно увеличиваются в случае некорректной настройки. Настоятельно рекомендуем предварительно провести тщательное исследование Redis перед тем, как заменять Вашу текущую базу данных в пользу Redis.
Несмотря на преимущество Redis в виде невероятной скорости работы, дающейся Вашему приложению, есть факт, который стоит обязательно учесть – Redis – это фундаментальное хранилище ключей/значений, и он не поддерживает индексы. И Вы можете столкнутся с непредвиденным «челленджем» при попытке проиндексировать Ваши значения. Однако, Вы можете обойти эти ограничения с помощью удивительно полезных предоставляемых Redis типов данных. В этой статье буду рассмотрены способы использования каждых наборов и отсортированных наборов для каждого индекса, и как сортировать записи по датам, а также извлекать строки в пределах диапазона дат.
О комплексных типах Redis
Redis поддерживает несколько комплексных типов — списки, «сеты», отсортированные «сеты» и хэши. В текущей статье не будут упомянуты типы, за исключением «сетов» и отсортированных «сетов». Собственно, «сеты» — это коллекция уникальных неупорядоченных значений. Между тем, отсортированные сеты немного отличаются от обычных сетов. Они позволяют хранить значения, к примеру, с баллами и, таким образом, все члены отсортированного набора можно будет запросить с помощью баллов или диапазона баллов. Это может быть очень удобным при хранении, например, даты. Происходит конвертация каждого одиночного элемента (даты) и хранится в его «тике». Таким образом получается упорядоченный набор значений. Затем можно использовать команду ZRANGEBYSCORE REDIS для получения значений, которые подходят под определённый диапазон.
Redis, как хранилище ключевых значений, может также хранить элемент с любым типом данных в базе данных до тех пор, пока этот элемент будет иметь уникальный ключ, определённый для него, вне зависимости от того, какого типа этот элемент – строковый или числовой.
Использование в коде
Код не так страшен, как может показаться с первого взгляда. Например, целый объект сохраняется с указателем ко всем индексам. Подход таков, что объект может быть сохранён, как скалярная строка с id в различных индексах.
В коде есть класс «Person», позволяющий сохранять объекты класса «Person» с параметрами, например, имя, пол, страна проживания и дата рождения. Класс написан достаточно просто, на мелкие детали не придётся обращать внимание. Также в коде есть статический класс «RedisAdaptor», который содержит в себе вспомогательные функции для сохранения класса «Person» и вызова его объектов.
Основная программа
В основной программе имеется цикл, который прогоняется ежедневно на протяжении указанного года – 1971 и создаёт новый объект класса «Person» с указанием даты рождения. В случае с полом мы оставляем один единственный объект с указанием женского или мужского пола. Для страны (учитывая, что определены только 3 страны) каждый объект класса «Person» проживает в Индии, США и Англии. В качестве имени используется строка, генерируемая случайным образом.
static void Main(string[] args)
{
const int YEAR = 1971;
// We create one Person object for every single day in the given year.
for (int month = 1; month <= 12; ++month)
{
for (int day = 1; day <= 31; ++day)
{
try
{
// Get any random name:
string name = Util.GetAnyName();
// And a DoB:
DateTime dob = new DateTime(YEAR, month, day);
// As for the gender, let's alternate:
Gender gender = Gender.FEMALE;
if (day % 2 == 0)
{
gender = Gender.MALE;
}
// And the country, let's round-robin between all three:
Country country = Country.INDIA;
if (day % 3 == 1)
{
country = Country.USA;
}
else if (day % 3 == 2)
{
country = Country.GB;
}
// Create a new Person object:
Person person = new Person(name, gender, country, dob);
//Console.WriteLine ("Created new Person object: {0}", person);
// We call the function that will store a new person in Redis:
RedisAdaptor.StorePersonObject(person);
}
catch (Exception)
{
// If the control reaches here, it means the date was illegal.
// So we just shrug your shoulders and move on to the next date.
continue;
}
}
}
// At this point, we have 365 Person objects as a sorted set in our Redis database.
// Next, let's take a date range and retrieve Person objects from within that range.
DateTime fromDate = DateTime.Parse("5-May-" + YEAR);
DateTime toDate = DateTime.Parse("7-May-" + YEAR);
List persons = RedisAdaptor.RetrievePersonObjects(fromDate, toDate);
Console.WriteLine("Retrieved values in specified date range:");
foreach (Person person in persons)
{
Console.WriteLine(person);
}
// Next, let's select some folks who are female AND from the USA.
// This calls for a set intersection operation.
List personsSelection = RedisAdaptor.RetrieveSelection(Gender.FEMALE, Country.USA);
Console.WriteLine("Retrieved values in selection:");
foreach (Person person in personsSelection)
{
Console.WriteLine(person);
}
}
В классе «Redis Adaptor» есть одиночная функция, используемая для хранения и индексации значений, прошедших через него, и функция для запрашивания значений по диапазонам дат, полу и стране. Также в программе имеется несколько статических полей и констант для данных.
Учтите, что индексация уже произошла в процессе сохранения. В этом участке мы проиндексировали пол, страну и дату рождения.
static class RedisAdaptor
{
const string REDIS_HOST = "127.0.0.1";
private static ConnectionMultiplexer _redis;
// Date of birth key:
const string REDIS_DOB_INDEX = "REDIS_DOB_INDEX";
// Gender keys:
const string REDIS_MALE_INDEX = "REDIS_MALE_INDEX";
const string REDIS_FEMALE_INDEX = "REDIS_FEMALE_INDEX";
// Country keys:
const string REDIS_C_IN_INDEX = "REDIS_C_IN_INDEX";
const string REDIS_C_USA_INDEX = "REDIS_C_USA_INDEX";
const string REDIS_C_GB_INDEX = "REDIS_C_GB_INDEX";
static RedisAdaptor()
{
// First, init the connection:
_redis = ConnectionMultiplexer.Connect(REDIS_HOST);
}
public static void StorePersonObject(Person person)
{
// We first JSONize the object so that it's easier to save:
string personJson = JsonConvert.SerializeObject(person);
//Console.WriteLine ("JSONized new Person object: {0}", personJson);
// And save it to Redis.
// First, get the database object:
IDatabase db = _redis.GetDatabase();
// Bear in mind that Redis is fundamentally a key-value store that does not provide
// indexes out of the box.
// We therefore work our way around this by creating and managing our own indexes.
// The first index that we have is for gender.
// We have two sets for this in Redis: one for males and the other for females.
if (person.Gender == Gender.MALE)
{
db.SetAdd(REDIS_MALE_INDEX, personJson);
}
else {
db.SetAdd(REDIS_FEMALE_INDEX, personJson);
}
// Next, we index by country.
if (person.Country == Country.INDIA)
{
db.SetAdd(REDIS_C_IN_INDEX, personJson);
}
else if (person.Country == Country.USA)
{
db.SetAdd(REDIS_C_USA_INDEX, personJson);
}
else if (person.Country == Country.GB)
{
db.SetAdd(REDIS_C_GB_INDEX, personJson);
}
// Next, we need to create an index to be able to retrieve values that are in a particular
// date range.
// Since we need to index by date, we use the sorted set structure in Redis. Sorted sets
// require a score (a real) to save a record. Therefore, in our case, we will use the
// DoB's `ticks' value as the score.
double dateTicks = (double)person.DoB.Ticks;
db.SortedSetAdd(REDIS_DOB_INDEX, personJson, dateTicks);
}
public static List RetrievePersonObjects(DateTime fromDate, DateTime toDate)
{
// First. let's convert the dates to tick values:
double fromTicks = fromDate.Ticks;
double toTicks = toDate.Ticks;
// And retrieve values from the sorted set.
// First, get the database object:
IDatabase db = _redis.GetDatabase();
RedisValue[] vals = db.SortedSetRangeByScore(REDIS_DOB_INDEX, fromTicks, toTicks);
List opList = new List();
foreach (RedisValue val in vals)
{
string personJson = val.ToString();
Person person = JsonConvert.DeserializeObject(personJson);
opList.Add(person);
}
return opList;
}
public static List RetrievePersonObjects(Gender gender)
{
// First, get the database object:
IDatabase db = _redis.GetDatabase();
string keyToUse = gender == Gender.MALE ? REDIS_MALE_INDEX : REDIS_FEMALE_INDEX;
RedisValue[] vals = db.SetMembers(keyToUse);
List opList = new List();
foreach (RedisValue val in vals)
{
string personJson = val.ToString();
Person person = JsonConvert.DeserializeObject(personJson);
opList.Add(person);
}
return opList;
}
public static List RetrievePersonObjects(Country country)
{
// First, get the database object:
IDatabase db = _redis.GetDatabase();
string keyToUse = REDIS_C_IN_INDEX;
if (country == Country.USA)
{
keyToUse = REDIS_C_USA_INDEX;
}
else if (country == Country.GB)
{
keyToUse = REDIS_C_GB_INDEX;
}
RedisValue[] vals = db.SetMembers(keyToUse);
List opList = new List();
foreach (RedisValue val in vals)
{
string personJson = val.ToString();
Person person = JsonConvert.DeserializeObject(personJson);
opList.Add(person);
}
return opList;
}
public static List RetrieveSelection(Gender gender, Country country)
{
// First, get the database object:
IDatabase db = _redis.GetDatabase();
string keyToUseGender = gender == Gender.MALE ? REDIS_MALE_INDEX : REDIS_FEMALE_INDEX;
string keyToUseCountry = REDIS_C_IN_INDEX;
if (country == Country.USA)
{
keyToUseCountry = REDIS_C_USA_INDEX;
}
else if (country == Country.GB)
{
keyToUseCountry = REDIS_C_GB_INDEX;
}
RedisKey[] keys = new RedisKey[] { keyToUseGender, keyToUseCountry };
RedisValue[] vals = db.SetCombine(SetOperation.Intersect, keys);
List opList = new List();
foreach (RedisValue val in vals)
{
string personJson = val.ToString();
Person person = JsonConvert.DeserializeObject(personJson);
opList.Add(person);
}
return opList;
}
}
Каждый ключ константы определяется в статическом классе, как, например, REDIS_DOB_INDEX, REDIS_MALE_INDEX, REDIS_FEMALE_INDEX и остальные, всё это – ключи к индивидуальным сетам в хранилище Redis.
Однажды, когда мы сохранили значения и создали индексы для сетов в Redis, мы сможем запрашивать их, используя различные версии перегруженных функций RetrievePersonObjects с параметрами – диапазоном дат, полом и страной.
Запрос по полу достаточно прост: основываясь на определении пола, мы «погружаемся» в один из двух гендерных сетов и получаем запрошенное значение. Точно та же процедура используется в отношении индекса стран.
Чтобы извлечь значения диапазона дат, используется метод SortedSetRangeByScore в объекте базы данных. Он принимает три аргумента: первый – имя сортированного сета, минимальные и максимальные значения.
Ещё одна интересная фича в работе с сетами Redis – великолепные семантические сеты. С их помощью можно определять два и более сетов, в результате чего БД Redis делает объединение, пересечение или определяет разность сетов. В последней секции кода посмотрите на функцию снизу – «RetrieveSelection», которая декларирует два параметра – пол и страна. Эта функция соответственно возвращает два параметра – пол и страну.
Источник: http://www.codeproject.com/Articles/1072137/Indexing-Columns-in-Redis
Використання форм у HTML
Автор: Редакція ITVDN
Введение
Формы используются для сбора информации, внесенной пользователем. Введенные данные взаимодейстуют с веб-приложениями, например, или когда нужно отправлять информацию в Интернет.
Формы сами по себе не очень полезные. Вместе с языком программирования их используют для обработки информации, введенной пользователем. Эти разнообразные скрипты нуждаются в других языках, отличающихся от HTML и CSS.
Теги form, input, textarea, select и option – базовые теги для форм в HTML.
Form
Тег form формирует такой себе «бланк». Если используется пользовательская форма для отправки данных, то нужно описать атрибут action для указания, куда контент будет отправлен.
Атрибут method указывает форме, как данные будут отправляться на сервер, также имеет дефолтное значение get, а также post, что фактически незаметно передает информацию о форме.
Get применяется для более коротких участков неконфиденциальной информации с сайта. Например, поиск будет отображаться в адресе страницы результатов поиска. Значение post - для более продолжительных, более защищенных материалов, таких как контактные формы, например.
Вот элемент формы будет выглядеть примерно так:
<form action="processingscript.php" method="post">
form>
Input
Тег Input - чуть ли не важнейшее в формах. Он может принимать огромное число значений, самые распространенные:
<input type=”text”> или просто <input> - стандартное текстовое поле. Также может иметь атрибут value, что превращает исходный текст в textbox.
<input type=”password”> - похожий на textbox, однако символы скрыты от пользователя.
<input type=”checkbox”> - кнопка с флажком, пользователь может задать режим вкл/выкл. Также может иметь атрибут checked ( <input type=”checkbox” checked> ), делает флажок «включенным».
<input type=”radio”> - похожий на checkbox, пользователь может выбрать только одну радиокнопку из группы. Также может иметь атрибут checked.
<input type=”submit”> - кнопка, что отправляет форму. Пользователь может изменять исходный текст формы через атрибут value, например
<input type="submit" value="Ooo. Look. Text on a button. Wow">
Обратите внимание на то, что тег input как и img, и br не имеет закрывающегося тега.
Textarea
Textarea – по сути, большое многострочное текстовое поле. Через атрибуты rows и cols задается число строк и столбцов соответственно, хотя можно управлять размером поля через CSS.
<textarea rows="5" cols="20">A big load of texttextarea>
Select
Тег Select в паре с option создает выпадающий список.
<select>
<option>Option 1option>
<option>Option 2option>
<option value="third option">Option 3option>
select>
Выбранное значение отправляется при подтверждении формы. Этим значением будет текст, заключенный в тег option, но будет отослано значение атрибута value, если он явно задан. Так, из примера выше, если выбран первый пункт, «Option 1» будет отправлено, если же третий -
Тег option может иметь атрибут selected, аналогично как checked для checkbox и радиокнопок. Например, <option selected>Rodentoption> будет изначально выбран вариант “Rodent”.
Names
Все вышеописанные теги будут красиво размещаться на странице, но, если подключить скрипт для обработки формы – все они будут проигнорированы. Так случится потому, что поля формы должны иметь уникальные имена. Так что нужно добавить атрибут name во все поля:
<input type="text" name="talkingsponge">
Пример формы:
<form action="contactus.php" method="post">
<p>Name:p>
<p>
<input type="text" name="name" value="Your name">p>
<p>Comments: p>
<p>
<textarea name="comments" rows="5" cols="20">Your commentstextarea>p>
<p>Are you:p>
<p>
<input type="radio" name="areyou" value="male">
Malep>
<p>
<input type="radio" name="areyou" value="female">
Femalep>
<p>
<input type="radio" name="areyou" value="hermaphrodite">
An hermaphroditep>
<p>
<input type="radio" name="areyou" value="asexual">
Asexualp>
<p>
<input type="submit">p>
form>
Источник: http://www.htmldog.com/guides/html/beginner/forms/
Використання LINQ на об'єктах у мові C#
Автор: Редакція ITVDN
Введение
Применение LINQ к объектам подразумевает, что можно использовать LINQ для запроса объекта из коллекции. Возможно использование LINQ для получения доступа к структурам данных, хранящихся в оперативной памяти (в структурах данных in-memory). Возможно запросить любой тип объекта, который реализует интерфейс IEnumerable или IEnumerable, относящийся к общему типу. Списки, массивы, словари – некоторые коллекции объектов, запрашиваемые с помощью LINQ.
В этой статье будет показано, как выполняется запрос различных объектов с использованием операторов LINQ и избегается необходимость использования метода зацикливания для фильтрации данных.
Не используя LINQ, необходимо проходить через значения снова и снова, а затем находить необходимые детали. Однако, с помощью LINQ можно запросить непосредственно сами коллекции данных и отфильтровать искомые значения, не используя зацикливание. LINQ предоставляет мощные возможности по фильтрации, группировке и упорядочиванию, не требующие больших объемов исходного кода. Например, если необходимо выяснить типы, хранящиеся в сборке, затем отфильтровать необходимые данные, можно использовать LINQ для запроса деталей сборки, используя классы System.Reflection.
Пространство имен System.Reflection содержит типы, извлекающие информацию о сборках, модулях, членах, параметрах и других объектах как о коллекциях управляемого кода, исследуя их метаданные. Кроме того, файлы в папке представляют собой набор объектов и эти объекты можно запросить, используя LINQ. Далее будут представлены некоторые примеры запросов.
Массив целых чисел
Следующий пример демонстрирует целочислительный массив, содержащий некоторый набор целых чисел. Можно применить запросы LINQ в массиве для извлечения требуемых значений.
int[] integers = { 1, 6, 2, 27, 10, 33, 12, 8, 14, 5 };
IEnumerable twoDigits =
from numbers in integers
where numbers >= 10
select numbers;
Console.WriteLine("Integers > 10:");
foreach (var number in twoDigits)
{
Console.WriteLine(number);
}
Переменная integers содержит массив целых чисел с разными значениями. Переменная twoDigits, имеющая тип IEnumerable, проводит запрос. Для получения результата необходимо выполнение запроса.
Исполнение запроса произойдет, когда переменная запроса будет итерироваться в цикле вызовом GetEnumerator() для перечисления результата. Любая переменная типа IEnumerable может быть перечислена с использованием цикла foreach. Типы, поддерживающие IEnumerable или производный интерфейс, например, IQueryable, называют запрососпособными типами. Присутствуют также некоторые нетипичные коллекции данных, например, ArrayList, которые также могут быть запрошены с помощью LINQ. Для этого необходимо явно объявить тип ранжированной переменной для конкретного типа объекта в коллекции, как в примерах ниже.
Переменная twoDigits проведет запрос для извлечения значений, которые не меньше 10. Таким образом одно за другим извлекаются числа из массива. Цикл будет выполнять запрос, а затем будет выводить в консоль значения, полученные из массива. Как можно заметить, выше продемонстрирован достаточно простой способ получения необходимых данных из коллекции.
Если нужны только первые четыре значения коллекции, можно использовать метод запроса Take() на необходимой коллекции. Ниже написано, как можно извлечь первые четыре элемента коллекции и вывести их в консоль, используя цикл.
IEnumerable firstFourNumbers = integers.Take(4);
Console.WriteLine("First 4 numbers:");
foreach (var num in firstFourNumbers)
{
Console.WriteLine(num);
}
Противоположность метода Take() – оператор Skip(), который используется для пропуска определенного количества первых элементов и получения остальных. В примере ниже будет пропущено первые 4 элемента.
IEnumerable skipFirstFourNumbers = integers.Skip(4);
Console.WriteLine("Skip first 4 numbers:");
foreach (var num in skipFirstFourNumbers)
{
Console.WriteLine(num);
}
В примерах выше было продемонстрированно, как извлечь/пропустить определенное количество начальных элементов списка. Если необходимо извлечь/пропустить заранее неизвестное число элементов, используются методы TakeWhile() и SkipWhile(), которые работают, пока не будет найдено совпадение.
В коде ниже будет изображено, каким образом получить все номера из коллекции, которые стоят до значения 50. TakeWhile() использует выражение для включения элементов коллекции, пока условие истинно, и игнорирует другие элементы списка. Выражение представляет собой условие, проверяющее элементы коллекции на совпадение.
int[] integers = { 1, 9, 5, 3, 7, 2, 11, 23, 50, 41, 6, 8 };
IEnmerable takeWhileNumber = integers.TakeWhile(num =>
num.CompareTo(50) != 0);
Console.WriteLine("Take while number equals 50");
foreach (int num in takeWhileNumber)
{
Console.WriteLine(num.ToString());
}
Подобным образом работает и метод SkipWhile(), только он пропускает значения, а не извлекает их. Самая высокая эффективность использования данных методов наблюдается при их использовании на упорядоченных списках, т.к. их выполнение прекращается при первом невыполнении условия поиска.
IEnumerable skipWhileNumber = integers.SkipWhile(num =>
num.CompareTo(50) != 0);
Console.WriteLine("Skip while number equals 50");
foreach (int num in skipWhileNumber)
{
Console.WriteLine(num.ToString());
}
Коллекции объектов
В этом разделе будет показано, каким образом можно запросить произвольную коллекцию объектов. Будет использован объект Icecream, построена коллекция, после чего ее можно будет запросить. Класс Icecream в следующем коде содержит различные свойства (имя, ингредиенты, вес, холестерин и т.д.)
public class Icecream
{
public string Name { get; set; }
public string Ingredients { get; set; }
public string TotalFat { get; set; }
public string Cholesterol { get; set; }
public string TotalCarbohydrates { get; set; }
public string Protein { get; set; }
public double Price { get; set; }
}
Далее строится список Icecreams, используя ранее определенный класс.
List icecreamsList = new List
{
new Icecream {Name="Chocolate Fudge Icecream", Ingredients="cream,
milk, mono and diglycerides...", Cholesterol="50mg",
Protein="4g", TotalCarbohydrates="35g", TotalFat="20g",
Price=10.5
},
new Icecream {Name="Vanilla Icecream", Ingredients="vanilla extract,
guar gum, cream...", Cholesterol="65mg", Protein="4g",
TotalCarbohydrates="26g", TotalFat="16g", Price=9.80 },
new Icecream {Name="Banana Split Icecream", Ingredients="Banana, guar
gum, cream...", Cholesterol="58mg", Protein="6g",
TotalCarbohydrates="24g", TotalFat="13g", Price=7.5 }
};
Имеется коллекция icecreamsList, состоящая из трех объектов со значениями типа Icecream. Пусть теперь необходимо извлечь всё мороженное, стоящее меньше 10. Можно использовать зацикливание, при котором необходимо смотреть на цену каждого элемента списка друг за другом, затем извлечь объекты, которые имеют меньшие значения поля Price. Использование LINQ позволяет избежать итерирования всех объектов и их свойств для поиска необходимых, т.е. облегчает поиск. Далее будет представлен запрос, выбирающий мороженое с низкими ценами из коллекции. Для работы запрос использует оператор where. Внешне запрос напоминает запрос из реляционной БД. Запрос выполняется, когда переменная типа IEnumerable перечислена в цикле.
List Icecreams = CreateIcecreamsList();
IEnumerable IcecreamsWithLessPrice =
from ice in Icecreams
where ice.Price < 10
select ice;
Console.WriteLine("Ice Creams with price less than 10:");
foreach (Icecream ice in IcecreamsWithLessPrice)
{
Console.WriteLine("{0} is {1}", ice.Name, ice.Price);
}
Также можно использовать ArrayList для хранения объектов, как было использовано List. Запрос LINQ, в таком случае, можно использовать для получения конкретных объектов из коллекции в зависимости от потребности. Например, нижеследующий код для добавления тех же самых объектов Icecreams в ArrayList, как это делалось в предыдущем примере.
ArrayList arrListIcecreams = new ArrayList();
arrListIcecreams.Add( new Icecream {Name="Chocolate Fudge Icecream",
Ingredients="cream, milk, mono and diglycerides...",
Cholesterol="50mg", Protein="4g", TotalCarbohydrates="35g",
TotalFat="20g", Price=10.5 });
arrListIcecreams.Add( new Icecream {Name="Vanilla Icecream",
Ingredients="vanilla extract, guar gum, cream...",
Cholesterol="65mg", Protein="4g", TotalCarbohydrates="26g",
TotalFat="16g", Price=9.80 });
arrListIcecreams.Add( new Icecream {Name="Banana Split Icecream",
Ingredients="Banana, guar gum, cream...", Cholesterol="58mg",
Protein="6g", TotalCarbohydrates="24g", TotalFat="13g", Price=7.5
});
Следующий запрос выбирает недорогое мороженое из списка.
var queryIcecreanList = from Icecream icecream in arrListIcecreams
where icecream.Price < 10
select icecream;
Как будет показано ниже, можно использовать цикл для отображения цены объектов, извлеченных вышеуказанным запросом.
foreach (Icecream ice in queryIcecreanList)
Console.WriteLine("Icecream Price : " + ice.Price);
Чтение из строк
Как известно, строка – набор символов. Т.е. можно запросить непосредственно строковое значение. Для примера можно рассмотреть случай, когда необходимо посчитать количество заглавных букв в строке aString:
string aString = "Satheesh Kumar";
Далее строится запрос на чтение строки и нахождение количества заглавных букв. Тип запроса – IEnumerable.
IEnumerable query =
from ch in aString
where Char.IsUpper(ch)
select ch;
Запрос использует метод Char.IsUpper в условии where для нахождения букв в верхнем регистре из строки. Следующий код отображает количество символов, написанных в верхнем регистре в данной строке.
Console.WriteLine("Count = {0}", count);
Чтение из текстового файла
Файл можно назвать коллекцией независимо от хранящихся в нем данных. Будет создан текстовый файл, содержащий некоторое количество строк. Для получения значений из текстового файла можно использовать запросы LINQ. В примере будет использован текстовый файл, содержащий названия разнообразных сортов мороженого. Для чтения каждой строки текстового файла можно использовать объект StreamReader. Для хранений значений, считанных из текстового файла, создается объект List. После записи в список значений, полученных из текстового файла, можно достаточно просто запросить список, используя LINQ, как было показано выше. В примере ниже рассмотрен код, считывающий строки из текстового файла и загружающий их в список строк.
List IcecreamNames = new List();
using( StreamReader sReader = new StreamReader(@"C:Icecreams.txt"))
{
string str;
str = sReader.ReadLine();
while (str != null)
{
IcecreamNames.Add(str);
}
}
В следующем коде считывается список строк и возвращаются названия мороженого, отсортированные по убыванию.
IEnumerable icecreamQuery =
from name in IcecreamNames
orderby name descending
select name;
Для проверки выполнения запроса можно вывести названия мороженого на дисплей, например, так
foreach (string nam in icecreamQuery)
{
Console.WriteLine(nam);
}
Следующий код выводит названия и проверяет результат работы запроса.
foreach (string nam in icecreamQuery)
{
Console.WriteLine(nam);
}
Как и коллекции, использованные в примерах выше, библиотека классов .NET может быть использована для чтения метаданных сборки .NET и создавать типы, члены типов, параметры, и другие свойства коллекции. Эти коллекции поддерживают интерфейс IEnumerable, который помогает запрашивать с использованием LINQ.
LINQ имеет много стандартных операторов запросов, которые можно использовать для запроса разных объектов, поддерживающих IEnumerable. На этих объектах можно использовать все стандартные операторы запросов, перечисленные ниже.
Тип оператора запросов
Операторы запроса
ограничение
Where, OfType
проекция
Select, SelectMany
присоединение
Join, GroupJoin
Конкатенация
Concat
Сортировка
OrderBy, OrderByDescending, ThenBy, ThenByDescending, Reverse
установка
Distinct, Except, Intersect, Union
группировка
GroupBy
Преобразование
AsEnumerable, Cast, OfType, ToArray, ToDictionary, ToList, ToLookup
Сравнение
SequenceEqual
Выбор элемента
DefaultIfEmpty, ElementAt, ElementAtOrDefault, First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault
образование
Empty, Range, Repeat
Количественное определение
All, Any, Contains
Агрегирование
Aggregate, Average, Count, LongCount, Max, Min, Sum
Разметка
Skip, SkipWhile, Take, Takewhile
Итог
В статья были рассмотрены некоторые примеры выполнения запросов с использованием операторов LINQ. LINQ можно использовать на любом объекте, поддерживающем интерфейс IEnumerable. Использование LINQ позволяет избежать использования циклических методов для получения необходимых данных из коллекции. LINQ предоставляет мощные методы для фильтрации, группировки и упорядочения данных. Использование LINQ позволит уменьшить объем исходного кода, тем самым ускорив время разработки.
Источник: http://www.codedigest.com/Articles/CSHARP/218_LINQ_to_Objects_in_C_.aspx
Замикання C#
Автор: Редакція ITVDN
Введение
Замыкание, как правило, используется функциональными языками программирования, где они связывают функцию с определенным типом параметров, это позволяет дать доступ к переменным, находящимся за пределами границы функции. С использованием делегатов замыкание доступно в С#.
Что такое Замыкание?
Чаще всего, лексика замыкания используется в функциональных языках программирования. Замыкание – это специальный тип функции, с помощью которого она ссылается на свободные переменные. Это позволяет замкнутым функциям использовать переменные из внешнего окружения, несмотря на то что они не входят в границы. Когда функция создана, внешние переменные, которыми мы пользуемся, «захватываются», иными словами, они связаны с замкнутой функцией, так что они становятся доступными. Часто это обозначает то, что делаются копии значений переменных, когда инициализируется замыкание.
Использование замыкания в С#
В С# замыкание может быть создано с помощью анонимного метода или лямбда-выражения, все зависит от версии .NET framework, на которой вы разрабатываете. Когда вы создаете функцию, переменные, что используются в ней и находятся за областью видимости, скопированы и хранятся в коде с замыканием. Они могут использоваться везде, где вы вызовете оператор delegate. Это дает огромную гибкость при использовании делегатов, но также создает возможность неожиданных багов. К этому мы вернемся позже. А пока, давайте рассмотрим простой пример замыкания.
В коде, который ниже, мы создаем переменную «nonLocal» типа integer. Во второй строчке создаем экземпляр делегата «Action», что выводит в сообщение значение переменной типа integer. В конце мы запускаем функцию-делегат, чтобы увидеть сообщения.
int nonLocal = 1;
Action closure = delegate
{
Console.WriteLine("{0} + 1 = {1}", nonLocal, nonLocal + 1);
};
closure(); // 1 + 1 = 2
Мы можем сделать то же самое с лямбда-выражением. В следующем коде мы используем «lambda» для вывода информации, при этом лямбда-выражение имеет одинаковую силу.
int nonLocal = 1;
Action closure = () =>
{
Console.WriteLine("{0} + 1 = {1}", nonLocal, nonLocal + 1);
};
closure(); // 1 + 1 = 2
Замыкания и переменные за пределами
С помощью анонимных методов или лямбда-выражения примеры выше,при этом получаем те результаты, что вы могли ожидать, так как захват переменных замыканием не очевиден сразу же. Мы можем сделать его более явным, изменяя пределы делегатов.
Рассмотрим следующий код. Здесь замыкание находится в классе «program» с переменной «action». В главном методе вызываем метод «SetUpClosure» для инициализации замыкания перед его использованием. Метод «SetUpClosure» очень важен. Вы можете увидеть, что переменная типа integer создана и инициализирована, и только тогда используется замыкание. В конце метода «SetUpClosure» эта переменная типа integer выходит за пределы. Однако, мы все еще вызываем делегат после этого. Скомпилируется и запустится ли этот код правильно? Произошло ли исключение при получении доступа к переменной за пределами? Попробуйте выполнить код.
class Program
{
static Action _closure;
static void Main(string[] args)
{
SetUpClosure();
_closure(); // 1 + 1 = 2
}
private static void SetUpClosure()
{
int nonLocal = 1;
_closure = () =>
{
Console.WriteLine("{0} + 1 = {1}", nonLocal, nonLocal + 1);
};
}
}
Вы могли заметить, что мы получили одинаковый результат как и в оригинальном примере. Это и есть замыкание в действии. Переменная «nonLocal» была охвачена или «замкнута» кодом delegate, в результате чего она остается в нормальных пределах. По сути, переменная будет доступна, пока никаких дальнейших ссылок на делегат не останется.
Несмотря на то, что мы увидели замыкание в действии, они не поддерживаются С# и .NET framework. То, что действительно происходит - это работа на заднем фоне компилятора. Когда вы создаете собственные проекты, компилятор генерирует новые, скрытые классы, инкапсулируют нелокальную переменную и описанный код в анонимный метод или лямбда-выражение. Код, описанный в методе, и нелокальная переменная представлены в виде полей. Этот новый метод класса вызовется, когда делегат выполняется.
Автоматически сгенерированный класс для нашего простого замыкания - аналогичный приведенному ниже:
[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
public int nonLocal;
public void b__0()
{
Console.WriteLine("{0} + 1 = {1}", this.nonLocal, this.nonLocal + 1);
}
}
Замыкание захватывает переменную, а не его значение
В некоторых языках программирования определяют значение переменной, которая используется в замыкании. В С# захватываются сами переменные. Это важное отличие, так как мы можем изменять значение переменной за пределами функции. Для иллюстрации рассмотрим следующий код. Здесь мы создаем замыкание, которое выводит наше начальное математическое значение переменной. При создании делегатов значение переменной типа integer равно 1. Но после того замыкания, как мы объявили замыкание, и перед тем, как его вызвали, значение переменной поменялось на 10.
int nonLocal = 1;
Action closure = delegate
{
Console.WriteLine("{0} + 1 = {1}", nonLocal, nonLocal + 1);
};
nonLocal = 10;
closure();
Так как нелокальная переменная имела значение 1 перед созданием замыкания, вы могли бы ожидать, что результатом вывода будет «1+1=2». На самом деле, на других языках программирования так бы и было. Однако, так как мы изменили значение переменной до вызова функции замыкания, это значение влияет на выполнение функции замыкание. В действительности, вы увидите на дисплее:
10 + 1 = 11
Изменения в нелокальную переменную внутри функции замыкания также передаются в другом направлении. В следующем коде внутри делегата изменяем значение переменной перед тем, как объявленный код выведет ее. Изменения видны во внешней части кода несмотря на то, что происходят они внутри замыкания.
int nonLocal = 1;
Action closure = delegate
{
nonLocal++;
};
closure();
Console.WriteLine(nonLocal); // 2
Переменная, которую мы изменяем, может привести нас к неожиданным багам в нашем коде. Мы можем продемонстрировать эту проблему в другом примере. На этот раз мы используем замыкание в простом алгоритме: многопоточное или параллельное программирование. Код ниже показывает цикл for, который имеет 5 новых потоков. Каждая пауза короткая, перед выводом значения переменной внутри цикла. Если значение переменной в цикле были захвачены, мы увидим цифры от 1 до 5 показаны в консоли, хотя, возможно, не в правильном порядке. Однако, так как эта переменная находится внутри замыкания и цикл закончится до того, как переменные будут выведены в сообщение, в конечном итоге мы увидим значение 6 для каждого потока.
for(int i = 1; i <= 5; i++)
{
new Thread(delegate()
{
Thread.Sleep(100);
Console.Write(i);
}).Start();
}
// Outputs "66666"
К счастью, такая проблема легко устраняется, когда вы понимаете, что переменные, а не их значения захватываются. Все, что нам нужно сделать, это создать новую переменную для каждого прохождения(итерации) цикла. Это объявление можно записать в теле цикла и давать значение в управляющую переменную. При нормальных обстоятельствах временная переменная будет находится за переделами, когда цикл закончится, но замыкание будет связывать и поддерживать ее.
В коде ниже вы можете увидеть 5 примеров «значений», переменные, созданные и им назначенные 5 различных значений, каждая из них привязана к разному потоку.
for(int i = 1; i <= 5; i++)
{
int value = i;
new Thread(delegate()
{
Thread.Sleep(100);
Console.Write(value);
}).Start();
}
// Outputs "12345"
Обратите внимание: вывод может меняться в зависимости от порядка, в котором потоки выполняются.
Источник: http://www.blackwasp.co.uk/CSharpClosures.aspx
SVG animation
Автор: Дмитро Івченко
Обзор
SVG графика может быть анимирована с использованием анимационных тегов. Они были описаны в спецификации Animation SMIL.
Рассмотрим эти теги:
позволяет анимировать свойства в течение времени.
это удобное сокращение, которое полезно для присвоения значений анимационных нечисловых атрибутов и свойств, таких как свойства opacity.
который двигает вдоль траектории движения path.
которая модифицирует значение цвета отдельных атрибутов или свойств с течением времени.
В дополнение к элементам, определенных в SMIL, SVG включает расширения, совместимые с SMIL анимацией спецификации.
Эти расширения включают в себя атрибуты, которые расширяют функционал элемента.
Расширения SVG включают в себя:
- дает возможность анимировать один из SVG атрибутов в течение промежутка времени, например, в качестве атрибута преобразования нового центра фигуры или преобразование фигуры и использование поворота вокруг одной из осей (Х, Y, Z).
path(attr) - позволяет анимировать вдоль определенного пути.
- используется в сочетании с animateMotion элемента для ссылки на траекторию движения, которая должна быть использована в качестве пути для движения. Элемент mpath входит внутрь animateMotion элемента перед закрывающим тегом.
keypoints (attr) - задается в качестве атрибута для animateMotion, обеспечивая точный контроль скорости траектории движения анимации.
rotate(attr) - используется в качестве атрибута для animateMotion для того, чтобы контролировать поворот относительно оси поворота.
SVG анимации могут быть похожи на CSS анимации. Ключевые кадры создаются, объекты движутся. Но они могут сделать нечто, что CSS анимации не делает.
Применение SVG Анимации
SVG элементы можно стилизовать и анимировать и с помощью CSS. В принципе, любое преобразование или анимации перехода, которые могут быть применены к HTML элементу, также могут быть применены к SVG. Но существуют некоторые SVG свойства, которые не могут быть сделаны через CSS. Векторная версия путь, например, поставляется с набором данных path, который определяет траекторию этому пути. Эти данные могут быть изменены и анимированных через SMIL, но не CSS. Это потому, что SVG элементы описаны набором атрибутов, известных как SVG атрибуты представления.
Если вы предпочитаете использовать JavaScript, я рекомендую использовать snap.svg, который описан как "в JQuery в SVG".
Вот коллекция примеров.
http://snapsvg.io/demos/
Если вы предпочитаете декларативный подход анимации, вы можете применять элементы SVG анимации, о которых я расскажу.
Еще одно преимущество SMIL над JS анимацией в том, что JS анимации не работают, когда SVG встроен в качестве IMG или используется в качестве фона изображения в CSS. SMIL анимации работают в обоих случаях. Это большое преимущество, на мой взгляд.
Поддержка браузеров
Поддержка браузеров для SMIL анимации довольно приличная. Они воспроизводятся во всех браузерах, кроме IE. Подробный обзор поддержки браузеров вы можете посмотреть в таблице совместимости на caniuseit.
Если вам нужно обеспечить альтернативный вариант для SMIL анимации, вы можете проверить поддержки браузера на лету, используя Modernizr. Если SMIL не поддерживается, вы можете предоставить какой-то запасной вариант (анимации JavaScript, например).
Анимация атрибутов элемента из одного значения к другому в течение произвольного времени с указанием конечного состояния: from, by, to, dur и fill.
Давайте рассмотрим с перемещением круга из одного положения в новое. Это можно сделать, изменив значение его атрибута сх (который определяет х - положение его центра).
Мы собираемся использовать элемент, чтобы сделать это. Атрибуты, которым устанавливают числовые значения и цвета, как правило, анимированные с помощью . Для получения списка атрибутов, которые могут быть анимированными, обратитесь к этой таблице:
http://www.w3.org/TR/SVG2/animate.html#AnimationAttributesAndProperties
Чтобы изменить значение на другое в течение времени используются from, by, to, dur и fill. В дополнение к этому, вы также хотите указать, когда анимация должна начинаться с атрибутом начала.
В приведенном примере, я определил круг, а затем вызываю анимацию на этом круге. Центр окружности перемещается из исходного положения - 500 единиц, до 1750 единиц вдоль оси х.
Начальное значение установлено на кнопку мыши. Это означает, что круг будет двигаться, когда она нажата. Вы можете установить это значение к значению времени, а также. Например, начинают = "0s" начнет анимацию, как только страница загружена. Вы можете задержать анимацию, установив положительное значение времени. Например, начать = "6s" запустит анимацию через шесть секунды после нагрузки.
Атрибут Dur похож на анимации-импульса в CSS.
from - to атрибуты похожи на from to ключевых кадров в keyframe блока анимации в CSS:
Повторяющиеся анимации с Repeat-Count
Когда вы хотите воспроизвести анимацию несколько раз, вы можете сделать это с использованием атрибута RepeatCount. Можно указать, сколько раз вы хотите повторить или использовать ключевое слово, чтобы он без конца повторять. Так что, если мы должны были повторить анимацию вида круга в течение двух раз, код будет выглядеть так:
Управление значениями ключевых кадров анимации: keyTimes и values. В CSS, мы можем задать значения, которые мы хотим, чтобы взять в определенные рамки во время анимации.
0%, 40 % , 80 % и 100% являются кадрами анимации.
Анимация вдоль определенных путей:
Хорошие примеры таких анимаций можно посмотреть здесь
http://codepen.io/mileselam/pen/kprKm
http://codepen.io/rossfenrick/pen/gpzJzz
http://codepen.io/tmrDevelops/pen/yyveKv
Так же более подробный пример есть на хабре
http://habrahabr.ru/post/207908/
Функция прохода анимации
Еще один важный элемент — это функция по которой будет проходить анимация. Среди всем известных функций анимации мы знаем ease, ease-in, ease-out, linear. Но если Вы хотите создать свою функцию прохождения анимации, то вам сюда
http://cubic-bezier.com/
И напоследок лучший пример, от которого просто невозможно оторвать глаз
http://codepen.io/thiennhat/pen/BNByzJ?editors=001
Пробуйте и у вас все получится!