Перейти к основному содержимому

10 записей с тегом "javascript"

Посмотреть все теги

JSDoc

· 3 мин. чтения

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

Общие тэги

/** 
* @deprecated Устаревшая функция
* @see nameSpace.ClassName#methodName Note, there are no { } brackets.
* @see namespace.ClassName Note, there are no { } brackets.
**//** This is a comment of how {@link nameSpace.ClassName} is similar to this object. **/

@param

/** 
* @param {paramType} paramName Basic parameter description.
* @param {String|Number} paramName Can be one of these types.
* @param {String} [paramName] Parameter is optional.
* @param {String[]} paramName Parameter is an array of strings.
* @param {function(string)} paramName Parameter is a function callback.
* @param {String} [paramName="mathew"] Parameter has a default value of "mathew" via code. (must be optional).
* @param objectName This parameter is an object with values.
* @param objectName.property1 This is a property of above object.
**/

Inline Documentation

/** Define parameter types in function arguments (useful for inline functions) **/
function(/**String**/arg1) { }

/** @type {string} msg **/ Handy for unknown return types.
var msg = foo();

Классы

/**
* @class {namespace.ClassName} Defines a reference to a class.
* @augments {namespace.ClassName} Define a derived base class.
* @extends alias for @augments
* @lends {namespace.ClassName} Declares a block to define part of a class.
* @constructor Defines a function as the creator for a class.
* @constructs Must be used with @lends to define the initialization function.
* @private Highlights invalid references to a property in PHPStorm
* @protected Highlights invalid references to a property in PHPStorm
* @override Defines a function as overriding a function of the same name in a lower class.
* @borrows namespace.ClassName#methodName Tells PHPStorm to use the documentation from another class/method for this method.
* @memberOf namespace.ClassName Declares something outside of a class block as part of that class.
**/

Функции

/**
* @this {type} Defines what object "this" referrers to.
* function(/**String**/arg1)Defines the parameters of an inline function.
* @return{type} The return type.
* @returns alias of above.
**/

Пример использования

Backbone Model and Inheritance A typical example using a Backbone model to demonstrate how to declare inheritance.

/**
* This is a model class what be will inherited.
*
* @class {gems.BaseModel}
* @extends {Backbone.Model}
**/
gems.BaseModel = Backbone.Model.extend(
/**
* @lends {gems.BaseModel}
*/
{
/**
* This is a property of the class.
*
* @type {string}
**/
message: 'Hello world',

/**
* A constructor for this class.
*
* @param {Object} attributes
* @param {Object} options
*
* @constructs
*/
initialize: function(attributes, options)
{
},

/**
* This is a virtual method on the model.
*
* @param {string} arg An argument
* @public
* @returns {boolean}
*/
foo: function(arg)
{
return false;
}
});

Теперь модель унаследовавшая BaseModel

/**
* A document model for Gems.
*
* @class {gems.DocumentModel}
* @extends {gems.BaseModel}
**/
gems.DocumentModel = gems.BaseModel.extend(
/**
* @lends {gems.DocumentModel}
**/
{
/**
* @param {string} arg An argument
* @see gems.BaseModel#foo
* @override
*
* @returns {boolean}
**/
foo: function(arg)
{
if(/**something**/)
{
return true;
}
return gems.BaseModel.prototype.foo.apply(this,[arg]);
}
});

Задержанные (Deferred) jquery объекты

· 1 мин. чтения

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

//Backbone пример
Model1.fetch({success:function(){
Model2.fetch({success:function(){
Model3.fetch({success:function(){
Model4.fetch({success:function(){
Model5.fetch();
}});
}});
}});
}});

Именно многопоточность даёт возможности ускорять backend на nodejs, масштабироваться на многоядерных процессорах, многомашинных обработках с map-reduce и тп. Для простого решения этой проблемы на frontend можно конечно написать костыль самому, но в jQuery уже есть Deferred объекты, с простым синтаксисом..

$.when(Model1.fetch(), Model2.fetch(), Model3.fetch(), Model4.fetch(), Model5.fetch()).then(
function(){alert('success!');}
);

Про infinite scrolling

· 4 мин. чтения

Бесконечный скролл, т.е. постепенный показ и подгрузка данных как реакция на действия пользователя, закрепился в SPA-приложениях. Я работаю с angular, но в целом это может и к backbone относиться..

Технически, данные на frontend хранятся в какой-то коллекции/сервисе. То что показывается пользователю — подмножество этих данных, т.е. может быть отфильтрованным, отсортированным и изменяемым на лету. Таких подмножеств в приложении может быть несколько, в зависимости от отображения (view). 

В одном месте у вас товары одного бренда, в другом месте у вас заказанные товары, в третьем - товары конкретного пользователя.

Что значит «ещё»?

Это значит, что вопрос подгрузки новых данных - нетривиальная задача.

Сначала я подумал что лучше всего запрашивать данные по времени. У каждого ряда данных будет timestamp, как у твиттера, соответсвенно запрос будет высылаться "подгрузи 20 рядов данных, старше X", а время будет браться у последнего ряда. 

Но тут возникает несколько проблем. 

  1. Что если у данных одно и то же время? Если на backend добавить вторую сортировку по ID, и передавать ID последнего ряда вместе со временем, то это всё-равно не спасает положения, т.к. значительно усложняется вычисление OFFSET в SQL запросе.
  2. Если запрос зависит от последнего показанного ряда, то получается что запрос формируется на основе подмножества, т.е. зависит от view. Это плохо, т.к. завязывает модель, контроллер и view в единое целое. Альтернатива - дублировать фильтрацию которая происходит для view в модели.
  3. Что если данные обновились со времени последнего запроса? Надо ли их добавлять в начало списка? 
  4. Что делать если данные отсортированы не по времени, а по алфавиту? И они вдруг поменялись? 

Вобщем приходишь к тому, что для универсального решения, надо по-прежнему работать с OFFSET и ORDER BY, как то было с постраничной навигацией. При этом offset высылается в зависимости от нужды view и он равен размеру подмножества.На случай обновившихся данных, я использую socket.io, который оповещает что надо обновить. Добавившиеся данные я сразу внедряю в список, а update происходит автоматически

Когда загружать?

ngInfiniteScroll - простенькая ангулярная директива для ангуляра, которая триггерит нашу функцию подгрузки если вы достаточно проскроллили вниз (scrollTop() близок к высоте элемента). Проблемы в ней две - она привязана ко всему окну (window). Обычно же у нас может быть несколько контейнеров на странице где надо подгружать данные. Есть и более простой пример

И вторая проблема следующая из неё - если вы запросили слишком мало данных за запрос (LIMIT 5), и/или их высота динамическая и меньше чем высота контейнера, то ваш scroll никогда не появится и не запустит постоянное дополнение списка. Пришлось это поисправлять, каждый раз расчитывая высоту содержания контейнера.

Тут главное не заDDOSить сервер из-за неправильного расчёта высоты элементов и рекурсии. По-хорошему вызов подгрузки надо начинать до достижения нижнего края.

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

Когда использовать не стоит

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

Во-вторых, бесконечность дезориентирует и лишает контроля. Вы не знаете сколько вы уже проскроллили, дажа примерно глядя на размер бегунка, не можете понять сколько осталось до конца. Аналогично, вы не можете перепрыгнуть в "значительно более далёкие" версии данных.

В-третьих, вы не можете скинуть URL-ссылку на это место другому человеку и соответственно, если вы уйдёте с бесконечной ленты по ссылке и потом попробуете вернуться назад, вы попадёте на начало ленты. Т.е. такой скролл требует изменения поведения при навигации и пользователь должен открывать ссылки в новом окне.

Само собой, если у вас SPA, то могут быть проблемы с индексацией поисковиками. Гугл предлагает гибридный вариант, где данные таки будут доступны и по страницам тоже.

Наконец, если у вас что-то случилось с ajax-запросом и нет хорошей обработки ошибок, бесконечный скролл ломает глубину просмотра. А если сервер достаточно нестабильный, то риск что пользователь никогда не сможет добраться до нужных данных увеличивается с каждым запросом. Например если вероятность ответа сервера HTTP 500 оценивается в 5%, то после 15ти последовательных запросов вероятность что лента будет сломана уже (100 - 100 × 0.9515) = 55%.

Тем не менее, если вы делаете крутой динамический сайт, который может обновляется блоками с нестатичными размерами и с нестандартной навигацией вверх/вниз и вправо/влево, постепенная подгрузка musthave.

По теме:

Недостатки AngularJS

· 3 мин. чтения

Я уже несколько месяцев работаю с AngularJS и после Backbone/jQuery впечатления неоднородные

Framework

Рамки (frames) всегда ограничивают. С Backbone и jQuery можно было писать код, который хочешь, где главное что-бы он работал. С Angular надо глубоко влазить в дебри настроек что-бы всё было по правилам. Это хорошо что приложение следует правилам. Плохо что документации слишком много. Почитайте описания ui-router или binding у директив!

Программирование декларативное

Код связывающий DOM с логикой переместился полностью в html. Селекторы в стиле jquery практически не нужны. Это серьёзно ограничивает используемость фреймворка с существующими скриптами и работающими системами. Попытка соединить два мира и использовать из ng-контроллера каких-то jquery событий будет выглядеть как хак.

Все события вешаются на самом элементе. Это в чём-то хорошо (меньше кода в js), но с другой стороны это распыляет видимость всего приложения и сильней привязывает html к логике. Например теперь проще делать показывание элементов с ng-show, но когда у вас на элементе висит ng-class, ng-src, ng-repeat, ng-click и ещё с десяток директив, в том числе самописных, то становится уже сложно не то что читать html, но и понять состояния к которым элемент может прийти или порядок вызова событий.

Проблема с bindингом моделей

Проблема обновления DOM никуда не пропала. Модели это обычные объекты в которых спрятан параметр $$hashKey, по которому и привязывается двустороннее обновление DOM. Проблема в том что своей логикой вы можете модели поломать так, что они лишатся этого ключа и тогда DOM останется жить своей жизнью, а вы не заметите этого. Такой баг отловить сложней всего.

Чаще всего потеря случается из-за того что у вас есть локальный кэш данных в памяти (модель), который асинхронно обновляется через $http, но вы используете присваивание для обновления.

Например - в контроллере вы привязали view к кешированным данным из сервиса

$scope.user = UserService.getUser();
UserService.updateCache();

Если ваш сервис будет просто перезаписывать внутреннюю переменную, то это не обновит view.

UserService = ['$http',
function($http){
var _localUserCache;
return{
updateCache:function(){
$http.get('user').then(function(response){
_localUserCache = response.user;
});
},
getUser: function(){
return _localUserCache;
}
}
}
]

Это из-за того, что в javascript данные передаются по ссылке (by reference), но если вы присваиваете новое значение, то старое значение (которое присвоено во view) в памяти то останется, но создадутся и новые данные. Поэтому правильно использовать angular.copy:

angular.copy(response.user, _localUserCache);

Второй частый источник батхёрта с bindом - удаление элементов из коллекций. Их надо делать с помощью array.slice(i), а не с присваиванием undefined или delete ключевым словом

Escaping и кавычки

Escaping в ангуляре встроен - вы не можете из фильтра просто так выплюнуть HTML. Это с точки зрения безопасности хорошо, но с другой стороны - долпонительная проблема. Надо использовать $sce.trustAsHtml(). Но я даже не на это жалуюсь..

Допустим у вас есть переводы как фильтр. Вы хотите в переводы вставить ссылку.. то как вы её передадите? 

{
'text_with_link':"<a ng-click='$state.go(\"app.dosomething\")'>click me!</a>",
'text_with_vars:"{{start_tag}}clickme!{{end_tag}}"
}

Замечаете escaping кавычек? А это внешний, чистый JSON. В общем случае escapingа кавычек внутри реального HTML - беда. Т.е. я себе представляю это так.. но на самом деле оно работать не будет:

<div ng-bind-html="'text_with_link' | translate: '{start_tag:\"<a ng-click=\'$state.go(\\"app.dosomething\\")\'>\"}'"></div>

Наконец Angular никак не развивает язык программирования ради языка. Вам не нужны классы, прототипы, иерархии, наследования, публичные-приватные методы. Подключи зависимости, внедри scope и нужные сервисы и пиши контроллер. Это своего рода деградация ради эффективности.

Из плюсов - мне понравились директивы. То что ты можешь определить визуально изолированный элемент как кирпичик даёт удобство для компоновки всяких wysiwyg, комбобоксов, календариков и тп. Понравилась магия с автообновлением view-моделей, с переводом на лету и с dependency-injection компонентов просто по имени сервиса.

Backbone.js

· 4 мин. чтения

Backbone это javascript-библиотека для тяжёлых frontend javascript приложений, как например gmail или twitter. В таких приложениях вся логика интерфейса ложится на браузер, что впрочем даёт очень значительное преимущество в скорости интерфейса. А как организовать код, для таких достаточно больших проектов? Который не просто набор глобальных переменных и jquery—DOM связей. Для меня перенос бэкэнда вперёд был довольно непривычен.

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

Основные классы

В качестве ядра используются наследуемые "классы"

  • Router и History - принимают url и говорят какой view надо запустить
  • View - привязывается к dom-элементам и в зависимости от их вложённости отвечает за их данные. Именно View вызываются из Route что-бы открыть прошлое состояние. И именно View реагирует на пользователя через подписку на события ( поле events)
  • Collection - массив моделей (не обязательно одинаковых), привязывается к View и может оповещать его об изменениях
  • Model - основные классы сущностей, имеют url для получения и изменения данных по RESTful http с backend

View, будучи конечным результатом и наиболее нагруженным классом, напрямую зависит от js-шаблонизатора из underscore библиотеки. Шаблонизатор ничем практически не отличается - только тем что все шаблоны подгружаются сразу в script-тэгах и переменные выделяются <%=foobar%> тэгами с мини-логикой. Начало выглядит примерно так..

MyApp.EditableSpanView = Backbone.View.extend({
template: _.template($('#span_wrapper').html()),

initialize: function(){
this.render();
},

render:function(){
$(this.el).html(this.template(this.model.toJSON()));
}
});

Это объявление View с 1-1 зависимостью с DOM элементом #span_wrapper откуда мы возьмём html конструкцию и модель this.model с данными, которая ещё не задана. Документация backbone не блещет деталями, о том присвоить < model или collection если он не общий. Как-то так:

realSpanView = new MyApp.EditableSpanView({model: ItemModel, id:'output' });

Это реализация View. Тут выполнится унаследованные конструктор initialize и рисование в render. Результат будет в элементе с id output — если он уже на странице имеется то в нем, если нет то появится.

События

Backbone имеет своё представление о том как должны распространятся события. Я когда-то писал о сложности этого в DOM. Тут же всё проще. События распространяются двумя путями

  1. От DOM-элемента через View.events в соответствующий написанный вами View-метод

  2. От модели или коллекции через подписанные на неё объекты выше (см. диаграмму выше), в т.ч. View

Насчёт первого пункта можно добавить что имеет смысл делать вложенные View-объекты (как группы DOM-деревьев). Например MyBodyView для глобально позиционируемых слоёв, MyListView с коллекцией и MyItemView для конкретного элемента.

По второму пункту - есть магический метод this.bind, который связывает два типа объектов, обычно в initialize. Например MyListView.initialize имеет смысл связать view с коллекцией, что-бы изменение коллекции вызывало изменение view. 

this.collection.bind('add', this.add, this); 
this.collection.bind('remove', this.remove, this);

Кажется таким очевидным и простым, но этого нет нигде по умолчанию, вы сами должны определить что должно кого оповещать. И хуже всего то, что порой не понимаешь механизма работы платформы. Модели не должны ничего знать о своём отображении, управление идёт наверху - view должен привязать себя к уничтожению и всякому изменению модели

Столкнулся с проблемами

Получение данных на бэкэнде из модели приходят вовсе не в POST, а экзотически

$params = (array)json_decode(file_get_contents('php://input'));

Например вам необходимо постоянно поддерживать свежесть коллекции. Коллекция загружается по url параметру из fetch и дальше вызывается reset где обычно коллекция наполняется опять моделями. 

Так вот для того что-бы сделать преобработку данных без перезаписи собственно reset метода, я вешаю bind на reset, вот только аргумент результатов - не json-список и не список переконвертированных моделей у которых распечатать свойства можно только через .get(), а непонятный формат в котором впрочем есть models параметр по которому можной пройтись.

И такого непривычного поведения достаточно что-бы выбить из колеи. То же ключевое слово this - при bind теряется и на самом деле используется связанный объект (коллекция), для этого есть третий параметр или же bindAll из underscore.

Вывод

Библиотека интересная, ограничивающая программистов привыкших к расхлябанному jquery-подходу подвешивания событий на DOM и принуждающая frontend писать едва ли не так же качественно как и backend. Поэтому использовать её стоит в ограниченных условиях, когда надо написать больше JS-кода чем обычно. Альтернативы Backbone — Spine.jsSproutcoreCappuccinoEJS. Из "почитать по теме" советую подкаст sitepoint 145bbtutorialsхабр, а для особо одарённых — про тестирование с Jasmine.

Редакторы кода с помощью javascript

· 2 мин. чтения

Очень часто в web-проектах надо использовать визуальный редактор кода (richtext code editor), похожий на существующие IDE, с номерами строк и подсветкой кода. Наиболее часто он используется в редактировании исходного кода статьи или шаблонов в админке. В этой статье я перечислю существующие скрипты по аналогии со списком WYSIWYG-редакторов.

Практически все существующие визуальные редакторы создают iframe и генерируют внутри страницу в соответсвии с данными javascript-объекта, занимающимся всеми задачами по генерации кода и обработке клавиш.

Основанные на iframe:

  • EditArea — наиболее популярный редактор благодаря табуляции, gzip, совместимость пространства имён с другими библиотеками

  • CodePress — парсит SQL, Perl, C#, XSL, ASP, VBscript. Отдельные движки для разных браузеров (gecko=firefox, ie, opera). Создаётся iframe со внутренним CodePress'объектом с обращением через contentWindow. Сразу внутри скрипта идёт поиск textarea-элементов по классу, так что с динамическими ajax-редакторами прийдётся изменять скрипт. Кроме того нумерация (до 1500) строк сделана при помощи одной картинки

  • CodeMirror — парсит JS, HTML, CSS

Напомню что iframe не соответсвует XHTML спецификации, а с использоватьпредложенный тэг object с не сильно получится, из-за ограничения надоступ внутренних dom-элементов (поправьте если я неправ).

Основанные на div'ах

  • Simple CodeArea 0.1b - очень простой но помоему самый логичный подход использовать подсветку синтаксиса благодаря полупрозрачной textarea и div-элементу с форматированным текстом под ней. Единственная проблема - запаздывание при скролле, но в будующем я думаю это перспективный продукт

  • MDK-editor — относительно навороченный редактор, есть контекстное меню. В минусы можно отнести некрасивый скин, небольшую тормознутость, неизвестную лицензию на использование, перезапись window.onload, размер кода свыше 100 кб. Достаточно много классов и обычных функций, просто так не разобраться, тем более без документации.

  • Helene — вместо iframe используется фоновый div и динамически позиционируемая textarea поверх. Поскольку изменяется только один ряд, то невозможно выделить нескольких строк сразу

  • 9ne — похож на консоль, но нет возможности выделения всей строки Shift+End. Эмулирует каретку мигающим div'ом.

Code highlighting

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

Что-бы сделать подсветку кода цветом, можно воспользоваться отечественноым highlight.js , который правда у меня странно реагировал, вообще убирая код, кроме того он заточен и на pre тэг, любит когда указывается класс, согласно используемому языку.

Любители обработки кода до его показа - могут попробовать GeSHi

Читайте также

jQuery для продолжающих (с плагинами)

· 4 мин. чтения

jQuery - библиотека о которой в последнее время говорит практически каждый web-разработчик, верстальщик и дизайнер. Написанная с учётом CSS, она упрощает доступ к одному или нескольким DOM-элементам. Если вы ещё используете prototype, то можно использовать режим совместимости (правда не факт что у вас будут работать плагины). Стандартно доступ происходит благодаря функции $ или JQuery. Элементу можно добавить (.addClass) или отнять (.removeClass) CSS-класс. Если это input-элемент, то запись и чтение происходит в аттрибуты элемента (.attr) и напрямую к значению (.val) . Внутренние элементы можно задать как через (.html). Ajax очень просто вызывается функцией $.post (url, val, callback)

Кроме минимализма, ускоренности и CSS-селекторов библиотека мало чем по функциональности отличается от prototype, mootools. Она не расширяет родные JS-объекты, как это делает protype и существует в своём пространстве переменных, поэтому не конфликтует с другими библиотеками

Примеры

  1. Допустим надо сделать массовое удаление из списка (как в почте), для этого генерируется огромный список checkbox'ов, а что-бы их все их массово выделить можно создать глобальный checkbox<input type="checkbox" name="group[{$item.id}]" class="group_checkbox" onchange="$('.group_checkbox').attr('checked',this.checked);">
  2. Часто встаёт вопрос оповещения пользователей об удачном добавлении, ошибке или просто информации. Для этого можно использовать три CSS-класса, при этом элементы можно прятать по прошествии какого-то времени. Для этого используется конструкция, запускающаяся сразу после создания DOM-дерева (а не всего документа)$(document).ready(function() { setTimeout(function() {$('.error').fadeOut('slow');}, 20000);});
  3. Работа с select'ами очень проста (даже с multiple=true), например если надо двумя кнопками перекидывать элементы двух списков, то поможет такой код:$().ready(function() { $('#add').click(function() { return !$('#select1 option:selected').remove().appendTo('#select2'); }); $('#remove').click(function() { return !$('#select2 option:selected').remove().appendTo('#select1'); }); $('#save').click(function(){ $('#select1 option').attr('selected',true); $('#select2 option').attr('selected',true);}); //при сохранении формы выделяем все эелементы });
  4. Выделить в select'е нужный элемент можно без цикла:
    $("#mySelect option[value='3']").attr('selected', 'selected');
  5. Проверить число отмеченных checkbox'ов очень просто:if (!$('.check_one:checked').length)
  6. Каждому элементу можно подключать (bind-ить) обработчики событий, даже несколько за раз…$('#someinput').bind('keyup change keydown', function() { alert ("one of three events is called!");});
  7. Изменить направление формы в зависимости от select'а можно явно (но лучше выносить все обработчики в отдельный файл)
    <form method="post" action="{$data.filter_link}" onsubmit="this.action=this.action+'/'+$('#filter_category option:selected').val();">

Классы в javascript

Несмотря на отличные библиотеки я не спешу бросать prototype.js и переходить на jQuery, потому что плохой код можно писать в любом языке и с любой библиотекой. Поэтому немного ликбеза. В Javascript можно делать объекты - очень удобно инициализировать, но они уникальны и их как массив использовать нельзя:

var CommentControl={ // объект читающий все комментарии
strURL:'/comments/read/', //обычный параметр
Init:function(){ //декларация функции
}}

Классы в javascript инициализируются как обычные функции, внутри которых используется ключевое слово this

function CommentBox(ID){
this.strURL='/comments/reply/'+ID; //параметр куда отсылать ответ
this.ID=ID;
this.reply=function(){
alert("AJAX request goes here");
}
this.update=CommentControl.Read(ID); //ссылка на }

Расширения и библиотеки

Расширения (plugins) в основном написаны обычными разработчиками. В качестве более функциональных наработок с интерфейсом создаётся и jQuery UI, как аналог scriptaculous для prototype, куда входят перетаскиваемые и сортируемые элементы и виджеты для интерфейса - аккордеон, табы, слайдеры и тп. Ниже привожу свой список полезных плагинов + советую уделить внимание презентации об разработке с учётом отключённого javascript. Значительно более детальный список есть на jquerylist

Изображения

  • ThickBox - галерея, аналог lightbox
  • FancyBox - тоже галерея, мне меньше нравится
  • Galleria - галерея
  • Multimedia portfolio - горизонтальный слайдер с видео, картинками и звуком
  • imgAreaSelect - выделение области для вырезания
  • Анимированный блок изображений, подходит при портфолио и панорамах
  • Lightbox с более плавной анимацией чем в prototype + занавеска как положено.
Таблицы
  • Flexigrid - табличные данные
  • InGrid - ajax'ная таблица, но не на json, а на открытом html'е. Впрочем компактном и без хаков.
Формы
  • ajaxForm - создаём как обычно статичную форму, а скрипт делает из неё ajax'овую
  • Autocomplete - на самом деле подгружает html. Небольшие заминки были с настройкой параметра extraParams
  • DatePicker - показывает генерирует календарик под указынными полями
  • FaceBook like - автоподсказки
  • jGrow - размер textarea в зависимости от размера текста
  • DamnSmallRTE - мелкий WYSIWYG
  • Multiselect как обычный select + выезжающие checkbox-ы
  • NiceForms - обрамляет input-ы div-элементами с закруглёнными углами, стилизует radio и checkbox'ы. Я правда предпочитаю более независимую версию nice forms
  • JQuery select - конвертирует все select-элементы в UL, которые можно более гибко стилизовать
  • MaskedInput - маска заполнения input-форм
  • Закачка файлов с созданием iframe-элементов (напоминаю что в XHTML) они не валидные
  • Манипуляция checkboxами и radio с превращением в iphone-стиль
  • jWYSIWYG - простейший редактор
Layout
  • RoundedCorners - закруглённые углы при помощи генерации элемента canvas
  • CodaSlider - слайды в дополнение закладкам (tabs)
  • idTabs - закладки для экономии пространства, но надо стилизовать
  • tooltips - расширенные всплывающие подсказки

Смотрите также:

Порядок выполнения событий в DOM

· 2 мин. чтения

Столкнулся с проблемой в своём календарике - есть два элемента, один из которых позиционируется абсолютно на весь экран, полупрозрачная затемняющая занавеска а второй - форма. Вы наверняка видели такие решения при показе картинок в lightbox или аутидентификации на habrahabr'е..

<div id='form_bg' onclick="$('form_bg').hide();$('form').hide();"> <div id='form'></div> </div>

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


Две с половиной модели

Как оказывается, существует две модели передачи событий в объектно-ориентированной иерархии

  • Пузырьковый метод (bubbling), когда событие возникает внутри и затем передаётся родительским элементам наружу. MS Internet Explorer, Opera, Firefox
  • Захват события (capturing), событие обрабатывается сначала у родителей, а потом проникает глубже. Opera, Firefox

Консорциум W3C благоразумно решили что разработчикам может быть удобно в любую сторону направлять события (event propagation), поэтому по стандарту две модели объединены - событие сначала захватывается, а потом возвращается как пузырёк.

Таким образом разработчик может привязать вызываемый метод к фазе события:

$('form_bg').addEventListener('click',hideBackground,true); // true - говорит о фазе capturing $('form').addEventListener('click',function(){},false); // false - говорит о фазе bubbling
Получается что при клике на form_bg происходит сразу hideBackground, и form на фазе capturing не вызывается, затем возвращаясь, в фазе bubbling вызывается анонимная функция. 

Традиции по умолчанию

Модель W3C приятна, но по умолчанию по всей видимости из-за IE, обычная регистрация события подразумевает bubbling фазу, т.е. изнутри наружу. А это в свою очередь значит, что если я явно укажу на родительский элемент:

$('form_bg').onclick = hideBackground; // или как выше - в <div id='form_bg' onclick=..

То любое событие без остановки вызывает у всех родительских элементов обработку onclick-события. Есть у них оно или нет, вплоть до корня - document.

Заплыв без пузырьков

В кросс-браузерном варианте для остановки распространения обработки события к родительским элементам, надо поменять параметр cancelBubble на true (для IE) и вызывать функцию stopPropagation (W3C модель):

function hideBackground(e){ if (!e) var e = window.event; e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); }

Теперь мне интересно как устроена модель обработки событий в других языках и платформах - Java, NET, Flash..

Написано по мотивам статьи "Event order".

Excel через javascript

· 2 мин. чтения

Microsoft Excel - незаменимый spreadsheet-редактор, попробуем сделать нечто похожее уже в web-середе.

Выбираем Simple spreadsheet, который уже умеет достаточно много, немного его исправим и создадим возможность сохранять все данные в БД. GPL лицензия также радует.

Прежде всего - изменить размеры редактируемого окна, которое запускается из spreadsheet.php. Для этого изменяем styles.css и при необходимости - spreadsheet.js, где делаются собственно таблицы.

Дальше - сделаем сохранение данных. Для этого надо весь код поместить в форму, добавить submit, к которому приписать onclick='save();'

Simple spreadsheet хранит все данные в своём "javascript" формате который фактически просто переменные инициализации, поэтому сохранять данные мы будем не только их, но и данные в csv формате. Для этого есть функция saveCSV, которую достаточно немного изменить и добавить спрятанный textarea c id='csv'.

getObj("csv").value = out;

Данные сохраняются в таблицу БД. Что-бы их обратно показать в таблице для изменений, достаточно в spreadsheet.php сохранённые данные передать в $init_data

Для чтения CSV надо выдавать заголовок типа

header("Pragma: public"); header('Content-Type: text/csv'); //header('Content-Type: application/vnd.ms-excel'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header("Content-Description: downloaded from iunu.net as an example"); header('Content-Disposition: attachment; filename="'.MD5(time()).'.csv"');

Код достаточно понятен, например что-бы при нажатии Tab фокус переходил на соседнюю клетку, я добавил в функцию keypress практически в самый конец

if (keyCode==9) { saveCell(); ret=false; editCell(currRow,currCol+1,keyCode); }

Если csv не подходящий вариант, и хочется создания xls, можно воспользоваться портированным с perl, writeexcel 'ем, а для импорта xls есть spreadsheet_excel_reader однако проблемы с utf8 всё ещё не имеются..

Контекстное меню на javascript

· 2 мин. чтения

Контекстное меню всплывает при нажатии правой кнопкой мыши по некоторому объекту, отсюда и его название. К сожалению многие (windows) разработчики пытаются впихнуть в него все возможные действия над объектом, из-за чего скорость работы и удобство может значительно упасть.

Для web-разработки нет определённых стандартов в создании такого меню для своего сайта, но есть несколько таких javascript'ов:

Вызвать событие

Firefox, IE и Safari поддерживают простой атрибут у html-элементов:

<body oncontextmenu='alert(10);'>

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

Не забудьте сделать ещё event-ы убирающие меню при обычном щелчке по пустому месту, при минимизации страницы или потери фокуса.

function ContextMenu(e,ID){ 
e = e ? e : window.event;
var mouse_position = { 'x' : e.clientX, 'y' : e.clientY };
if( typeof( window.pageYOffset ) == 'number' ) {
scroll_position={'x':window.pageXOffset, 'y': window.pageYOffset};
} else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
scroll_position={'x':document.documentElement.scrollLeft, 'y': document.documentElement.scrollTop};
} else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
scroll_position={'x':document.body.scrollLeft, 'y': document.body.scrollTop};
}

$('context_menu').innerHTML=$('context_menu_prototype').innerHTML.replace(/\[ID\]/g,ID);
Menu.Select(ID);
$('context_menu').style.left = mouse_position.x + scroll_position.x + 'px';
$('context_menu').style.top = mouse_position.y + scroll_position.y + 'px';
$('context_menu').show();
return false;
}

Интересные статьи по этой теме: