- PVSM.RU - https://www.pvsm.ru -
Цель урока: Определить правила работы с html, js и css файлами. Bootstrap и дополнительный css. Структура js-файлов. Использование jQuery, основные моменты, изучение селекторов, событий и др. addClass, removeClass, attr, data, динамическое создание dom-объекта, ajax.
Наконец мы приступаем к более детальному изучению клиентской части, которая уже в меньшей степени связана с asp.net mvc, но всё равно важна для веб-разработки.
Twitter Bootstrap – это css-фреймворк. Т.е. набор инструментов для создания блоков, кнопок, меток, форм и навигации. Наше приложение мы будем основывать на этом фрейморке.
Подробнее тут:
http://twitter.github.com/bootstrap/ [1]
Установим bootstrap:
Install-Package Twitter.Bootstrap
Удалим Jquery.UI:
Uninstall-Package Jquery.UI.Combined
Добавим в BundleConfig bootstap и уберем оттуда jquery.UI (App_Start/BundleConfig.cs):
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-1.*"));
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
"~/Scripts/modernizr-*"));
bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
"~/Scripts/bootstrap*"));
bundles.Add(new StyleBundle("~/Content/css")
.Include("~/Content/site.css") /* не перепутайте порядок */
.Include("~/Content/bootstrap*"));
}
}
Важно помнить про порядок приоритета задания стилей:
h1, div, p
.item { }, :hover { }
[type=”checkbox”] {}
#main { }
<div style=”width : 203px”></div>
Основная работа в задании стилей идет с помощью тегов, классов (псевдоклассов), и атрибутов. Использовать !important
не рекомендуется, равно как и задавать стили в атрибуте style и использовать атрибут ID.
Для задания стилей будем использовать css-файл Site.css. Так как в bootstrap уже заданы стили для базовых форм, удалим этот блок и оставим файл (/Content/Site.css):
/* Styles for validation helpers
-----------------------------------------------------------*/
.field-validation-error {
color: #f00;
}
.field-validation-valid {
display: none;
}
.input-validation-error {
border: 1px solid #f00;
background-color: #fee;
}
.validation-summary-errors {
font-weight: bold;
color: #f00;
}
.validation-summary-valid {
display: none;
}
Это css стили, которые используются для вывода ошибок в методах Html.ValidationMessage()
, Html.ValidationSummary()
.
Теперь давайте определим правила, по которым мы будем создавать свой стиль:
#Main
. Это связано с тем, что хотя браузеры и обрабатывают все элементы содержащий данный атрибут, но в правилах DOM атрибут ID должен быть уникальный во всей странице. id=”SomeButton”
..list, .item, .button, .text, .user, .image, .container, .wrapper
на верхнем уровне..main-container-left-part-wrapper-list.
.error {color : red }, .left { float : left }.
.list
{
}
.list .item
{
}
<li>используйте селекторы для задания глубины применения стиля
.list > .item
{
}
.item.odd
{
}
для
<div class="item odd"></div>
/* Главные определения стилей для сайта
- основной шрифт
- междустрочное расстояние
- размер шрифта
- основные цвета ссылок и текста
- h1, h2, h3 ...
- a
- p
*/
/* Вспомогательные классы:
- .error
- .field-validation-error и др.
- .show-me { border : 1px solid red; }
- .hidden {display : none; }
- .relative { position : relative; }
*/
/* Основные вспомогательные окна и стили
- .popup-window
- .popup-window .close
- .popup-window .wrapper
- .popup-window .header
- .error-message
- .info-message
*/
/* Основные стили сайта
- header
- .logo
- .user-login
- nav.main-menu
- .main-content
- footer
*/
/*Главная страница */
/* Страница входа */
/* Страница ошибки */
/* Остальные страницы… */
Подключим стили и js файлы к основному layout файлу (/Areas/Default/Views/Shared/_Layout.cshtml):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
@Styles.Render("~/Content/css")
@RenderSection("styles", required: false)
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<ul class="nav nav-pills pull-right">
@Html.Action("UserLogin", "Home")
</ul>
</div>
</div>
</div>
@RenderBody()
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body>
</html>
Что здесь происходит:
В начале файла объявляется, что он будет включен в Layout:
@{
ViewBag.Title = "LessonProject";
Layout = "~/Areas/Default/Views/Shared/_Layout.cshtml";
}
Указанный layout выведет данные с помощью @RenderBody()
. Запускаем:
Видно, что body заехал под панель навигации. В нашем файле переопределяем body (/Content/Site.css):
body
{
padding-top : 40px !important;
}
Гораздо лучше.
Так как мы использовали классы для формы bootstap ранее, то регистрация у нас выглядит теперь так:
Поправим выбор даты рождения, добавим классы в Html [2].DropDownList() (/Areas/Default/Views/User/Register.cshtml):
…
<div class="controls">
@Html.DropDownList("BirthdateDay", Model.BirthdateDaySelectList, new { @class = "select-day" })
@Html.DropDownList("BirthdateMonth", Model.BirthdateMonthSelectList, new { @class = "select-month" })
@Html.DropDownList("BirthdateYear", Model.BirthdateYearSelectList, new { @class = "select-year" })
</div>
Так как вероятно, мы еще где-то будем использовать эту конструкцию по выбору даты (хотя не факт), то это более общее, чем частное (которое относится именно к регистрации) (/Content/Site.css):
.select-day
{
width : 50px;
}
.select-month
{
width : 90px;
}
.select-year
{
width : 70px;
}
Проверяем:
Уиии!
Переходим к описанию js файлов. Мы используем jquery как основной фреймворк по работе с клиентской частью кода. Один наш пользовательский js файл (назовем его /Scripts/common.js) будет вызываться всегда. В него будут добавлены те функции, которые будут присутствовать на любой странице. Остальные js-файлы будут вызываться опционально.
Чтобы не путаться создадим 2 папки «admin» и «default» в /Scripts.
Все файлы будут иметь уникальные имена, которые будут записаны в SmallCase формате, и будут относиться к определенной странице (в основном). Например: user-register.js – файл, который будет включен в страницу User/Register.cshtml:
@section scripts {
@Scripts.Render("/Scripts/default/user-register.js")
}
Эта секция выведется в том месте, где описана в _Layout.cshtml (/Areas/Default/Views/Shared/_Layout.cshtml):
…
@Scripts.Render("~/bundles/bootstrap")
@Scripts.Render("~/bundles/common")
@RenderSection("scripts", required: false)
</body>
В /App_Start/BundleConfig.cs тем временем добавим описание:
bundles.Add(new ScriptBundle("~/bundles/common").Include(
"~/Scripts/common.js"));
Все пользовательские js классы, за исключением плагинов, будут иметь следующую структуру:
function FunctionName() {
_this = this;
this.init = function () {
/* установка обработки нажатий или иных манипуляций */
$("button").click(function () {
var id = $(this).attr("id");
_this.saySomething(id);
});
}
/* другие публичные методы*/
this.saySomething = function (id) {
alert("Пыщь-пыщь! : " + id);
}
/* другие приватные методы */
function saySomething (id) {
alert("Пыщь-пыщь! Но тссс!: " + id);
}
}
var functionName = null;
$().ready(function () {
functionName = new FunctionName();
functionName.init();
});
Рассмотрим подробнее:
function FunctionName
имеет имя в стиле UpperCamelCase по имени файла, в котором находится (Common
и UserRegister
в файлах common.js и user-register.js соответственно) _this = this;
Сохранение ссылки на данную функцию, чтобы была возможность использовать внутри delegate-функцийthis.init()
– внешняя (public) функция, где будет происходить инициализация обработки.var functionName = null
– создание глобальной переменной. Возможно использование из других файлов.$().ready()
– вызывается после того, как сформирована DOM-структура. JQuery функция. functionName = new FunctionName();
— создаем объект класса.functionName.init();
— инициализируем его.Так как ресурсные файлы, со временем вырастают, и с другой стороны идет развитие мобильного интернета для мобильных устройств, в которых количество передаваемых данных играет не последнюю роль, минификация получения страницы сводится к следующим вещам:
Уменьшение количества-ва запросов к изображениям, особенно маленьким. Это делается двумя способами:
?
И его использование в html:
<div class="label label-new sprite"></div>
И css:
.sprite
{
background: url("/Media/images/sprite.png");
overflow: hidden;
text-indent: -9999px;
}
.box .label
{
position: absolute;
width: 29px;
right: -29px;
top: 35px;
}
.box .label-new
{
background-position: 0 -15px;
height: 119px;
}
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAWCAYAAAABxvaqAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDhENzk1MDI1QkREMTFFMkE2QUNDMTBGODg2MjVENjAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDhENzk1MDM1QkREMTFFMkE2QUNDMTBGODg2MjVENjAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEOEQ3OTUwMDVCREQxMUUyQTZBQ0MxMEY4ODYyNUQ2MCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEOEQ3OTUwMTVCREQxMUUyQTZBQ0MxMEY4ODYyNUQ2MCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PlBqi0kAAAAwSURBVHjaYmBgYGBnghFccIIbRPCiEvwggg8LixfOBbN44AaACU4gZvgJIv4DBBgARTIDD2TeBRAAAAAASUVORK5CYII=);
Конечно, при загрузке контентных иконок или изображений данный способ не применим.
Для css и js применяется минификация файла, т.е. убираются пробелы и разделители и используются более краткие локальные переменные. Файл или изначально подготавливается минифицированным как для jquery библиотеки или минифицируется на сервере.
Для включения минификации необходимо в Web.config файле изменить директиву compilation:
<compilation debug="false" targetFramework="4.5" />
Или напрямую включить в /App_Start/BundleConfig.cs:
BundleTable.EnableOptimizations = true;
Проверим:
До оптимизации 527 КБ
После оптимизации 251 КБ
Т.е. больше, чем в 2 раза. На самом деле эта величина может быть как меньше, так и больше, в зависимости от того, какие ресурсы мы грузим. Если есть кеширование, то страница при дальнейшей работе загружает только незначительную часть новых файлов.
Установка jQuery
Изначально jquery уже установлен, но если фреймворк обновился, а это делается часто, то очевидно, что необходимо обновить его:
Install-Package JQuery
Далее, мы до этого убрали JQueryUI (http://jqueryui.com/ [4]), так как собираемся функции datepicker, modal использовать из того, что предлагает bootstrap. Но в JQueryUI есть необходимый нам функционал взаимодействия, т.е. Draggable, Droppable, Resizable, Selectable и Sortable. Установим их выборочно:
bundles.Add(new ScriptBundle("~/bundles/jqueryui")
.Include("~/Scripts/jquery-ui-1.*"));
bundles.Add(new StyleBundle("~/Content/css/jqueryui")
.Include("~/Content/jquery-ui-1*"));
Для удобства отладки в Firefox есть расширение Firebug, а в Chrome есть встроенный механизм Developer Tool. Я рассмотрю пример с Developer Tool. Вызывается по нажатию клавиш –Shift-Ctrl-I
.
Давайте изучим его:
Осторожно используйте эту команду в IE, так как когда консоль не открыта, то выдает ошибку.
JQuery – это инструмент, который помогает нам разрабатывать клиентский код под разные браузеры. К тому же является простой и логичной библиотекой.
В основе всего лежит селектор. Селектор позволяет выбрать множество элементов, находящихся в DOM (document object model) и произвести над ними действия, такие как: назначить обработчик события, изменить местопложение, изменить атрибуты, удалить выбранные элементы, добавить в выбранные элементы текст или html, создать объект.
Основное правило пишется как:
$([“правило селектора”][, область выбора])
Если область выбора не задана, то ищется по всему документу: $(document). Это корневой узел всего DOM.
Если область выбора задана, то ищется только в границе этого узла.
Правило селектора задается по принципам назначения css свойств:
$(“div”)
– выбор всех div-элементов$(“.class”)
– выбор всех элементов с имеющимся классом class$(“.class .class1”)
– выбор всех элементов с имеющимся классом class1 содержащихся в классе class$(“.class.class1”)
– выбор всех элементов с имеющимся классом class1 и class $(“#Id1”)
– выбор элемента (одного) с id =Id1$(“[type=’password’]”)
– выбор элемента с атрибутом type=’password’$(“div”, $(“#MainPopup”))
– выбор всех элементов div содержащихся в элементе с id=MainPopup$(“input[type=’checkbox’]:checked”)
– выбор элементов ввода типа checkbox, которые отмечены галочкой.Для проверки найден или нет элемент, можно использовать свойство length:
if($("#Id1").length == 0)
{
alert("Элемент не найден")
}
Для продвижения вверх по дереву элементов от выбранного можно использовать функции .closest(), .parent() или .parents():
parent()
– возвращает непосредственного родителя (и только его)parents(selector)
– возвращает множество всех родителей (если не указан селектор вплоть до body и html)closest(selector)
– возвращает ближайшей элемент, который соотвествует селектору, причем сам элемент может быть ним же.
Для обработки событий мы назначаем события на элементы селектора. Например:
$(".button").click(function () {
alert("Button clicked");
});
Какие есть события:
Надо помнить, что события не распространяются на вновь созданные элементы, хотя и подпадающие под выбор селектора, который выполнялся ранее. Но если мы, по второму разу проинициализируем, то на элементах, где уже была назначена обработка события, это событие будет выполняться дважды.
Для задания постоянной глобальной обработки нужно использовать следующую конструкцию:
$(document).on("click", ".button", function () {
alert("Button clicked");
});
Для работы с атрибутами элемента в основном используются функции:
Для работы с элементами рассмотрим следующие функции:
Рассмотрим основную и главную функцию (в 99% случаев я обходился только ею).
$.ajax({
type: "GET",
url: "/ajaxUrl",
data: { id: id },
beforeSend: function () {
/* что-то сделать перед */
},
success: function (data) {
/* обработать результат */
},
error: function () {
/* обработать ошибку */
}
});
Есть и другие параметры, но к ним прибегать стоит в некоторых случаях.
Рассмотрим подробнее параметры:
data: $("form").serialize()
При этом надо помнить, что при передаче множества одинаковых значений чекбоксов нужно устанавливать параметр
traditional : true
beforeSend. Событие, генерирующеся перед непосредственно отправкой формы.
success. Событие, которое обозначает, что всё хорошо и в data содержится результат выполнения
error. Событие, которое возникает, если ответ от сервера был отличный от 200 OK.
Куча теории, пора бы и к практике переходить. Создадим вторую форму входа, которая будет способствовать быстрому входу на сайте. При клике на «Вход» мы переходим не на страницу Входа, вместо нее выскакивает попапокошко с предложением ввести логин прямо сейчас. При ошибочном вводе, форма выдает предупреждение. Обычную форму по адресу /Login оставляем, она нам понадобится.
Попап формы могут использоваться часто, так что будем считать это стандартной процедурой – вызвать Popup по адресу такому-то. Так как попап – всегда один, то создадим для него контейнер в _Layout.cshtml (/Areas/Default/Views/Shared/_Layout.cshtml):
<div id="PopupWrapper"></div>
Добавим функциональность в common.js (/Scripts/common.js):
this.showPopup = function (url, callback)
{
$.ajax({
type: "GET",
url: url,
success: function (data)
{
$(".modal-backdrop").remove();
var popupWrapper = $("#PopupWrapper");
popupWrapper.empty();
popupWrapper.html(data);
var popup = $(".modal", popupWrapper);
$(".modal", popupWrapper).modal();
callback(popup);
}
});
}
, где .modal() – это функция из bootstrap.js.
Так как Вход у нас на каждой странице, то следущую функциональность добавляем тоже в common.js:
this.init = function () {
$("#LoginPopup").click(function () {
_this.showPopup("/Login/Ajax", function (modal)
{
});
});
}
Добавим в контроллере обработчик (/Areas/Default/Controller/LoginController.cs):
[HttpGet]
public ActionResult Ajax()
{
return View(new LoginView());
}
[HttpPost]
public ActionResult Ajax(LoginView loginView)
{
if (ModelState.IsValid)
{
var user = Auth.Login(loginView.Email, loginView.Password, loginView.IsPersistent);
if (user != null)
{
return RedirectToAction("Index", "Home");
}
ModelState["Password"].Errors.Add("Пароли не совпадают");
}
return View(loginView);
}
Он полностью аналогичен Index, только будет вызываться другой View – «Ajax», создадим его (/Areas/Default/Views/Login/Ajax.cshtml):
@model LessonProject.Models.ViewModels.LoginView
<div class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Login</h3>
</div>
<div class="modal-body">
@using (Html.BeginForm("Ajax", "Login", FormMethod.Post, new { @class = "form-horizontal", id = "LoginForm" }))
{
<fieldset>
<legend>Вход</legend>
<div class="control-group">
<label class="control-label" for="Email">
Email</label>
<div class="controls">
@Html.TextBox("Email", Model.Email, new { @class = "input-xlarge" })
<p class="help-block">Введите Email</p>
@Html.ValidationMessage("Email")
</div>
</div>
<div class="control-group">
<label class="control-label" for="Password">
Пароль</label>
<div class="controls">
@Html.Password("Password", Model.Password, new { @class = "input-xlarge" })
@Html.ValidationMessage("Password")
</div>
</div>
</fieldset>
}
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<button class="btn btn-primary" id="LoginButton">Login</button>
</div>
</div>
Обратите внимание на id формы LoginForm и id кнопки LoginButton
Изменим вызов в UserLogin.cshtml (/Areas/Default/Views/Home/UserLogin.cshtml):
<li><span class="btn btn-link" id="LoginPopup">Вход</span></li>
В common.js добавим обработку LoginButton, при вызове установим обработчик события на $(“#LoginButton”).click(…) (/Scripts/common.js):
this.init = function () {
$("#LoginPopup").click(function () {
_this.showPopup("/Login/Ajax", initLoginPopup);
});
}
…
function initLoginPopup(modal) {
$("#LoginButton").click(function () {
$.ajax({
type: "POST",
url: "/Login/Ajax",
data : $("#LoginForm").serialize(),
success: function (data) {
showModalData(data);
initLoginPopup(modal);
}
});
});
}
function showModalData(data, callback) {
$(".modal-backdrop").remove();
var popupWrapper = $("#PopupWrapper");
popupWrapper.empty();
popupWrapper.html(data);
var popup = $(".modal", popupWrapper);
$(".modal", popupWrapper).modal();
if (callback != undefined) {
callback(popup);
}
}
Обратите внимание на рекурсивный вызов initLoginPopup
. И тут заключается дилемма. Так как при удачном входе нам не надо чтобы в PopupWrapper грузилась новая страница (или страница с ошибкой), а только чтобы страница обновилась.
Для этого сделаем хитрость. В /Areas/Default/Views/Shared/ добавим _Ok.cshtml, суть которого — перезагружать страницу:
<script>
window.location.reload();
</script>
При удачном входе мы загружаем этот View. При добавлении в дерево DOM в строке
popupWrapper.html(data);
скрипт запустится и перезагрузит страницу, не дожидаясь остальных вызовов. Изменим контроллер (/Areas/Default/Controllers/LoginController.cs):
var user = Auth.Login(loginView.Email, loginView.Password, loginView.IsPersistent);
if (user != null)
{
return View("_Ok");
}
Проверяем, работает!
Мы рассмотрели основные принципы верстки и клиентской части, но это лишь малая толика того, что вообще можно знать о верстке, стилях и программировании в клиентской части.
Мы научились пользоваться отладчиком в Chrome и создавать ajax запрос. Подробнее рассмотрите этот вопрос в дальнейшем.
Полезные ссылки:
http://jquery.com [6]
http://habrahabr.ru/post/161895/ [7]
http://habrahabr.ru/post/154687/ [8]
http://twitter.github.com/bootstrap/ [1]
http://habrahabr.ru/post/160177/ [9]
Все исходники находятся по адресу https://bitbucket.org/chernikov/lessons [10]
Автор: chernikov
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/31661
Ссылки в тексте:
[1] http://twitter.github.com/bootstrap/: http://twitter.github.com/bootstrap/
[2] Html: http://habrahabr.ru/users/html/
[3] http://webcodertools.com/imagetobase64converter: http://webcodertools.com/imagetobase64converter
[4] http://jqueryui.com/: http://jqueryui.com/
[5] jqueryui.com/download/: http://jqueryui.com/download/
[6] http://jquery.com: http://jquery.com
[7] http://habrahabr.ru/post/161895/: http://habrahabr.ru/post/161895/
[8] http://habrahabr.ru/post/154687/: http://habrahabr.ru/post/154687/
[9] http://habrahabr.ru/post/160177/: http://habrahabr.ru/post/160177/
[10] https://bitbucket.org/chernikov/lessons: https://bitbucket.org/chernikov/lessons
[11] Источник: http://habrahabr.ru/post/176053/
Нажмите здесь для печати.