Jun 16

На работе столкнулись с “особенностью” работы jQuery.live(), на которую хотелось бы обратить внимание, потому как, судя по всему, отнюдь не все о ней знают (и в результате чего пишут неработающий код).

Итак, простой пример - навешивание двух событий на один и тот же элемент:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").bind("click", function(e) {
      alert("a");
      return false;
    });
    $("a").bind("click", function(e) {
      alert("b");
      return false;
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

Пример можно посмотреть здесь. В результате клика на ссылку - получаем 2 alert-а, всё хорошо, ожидаемо и предсказуемо.

Переписываем код для работы с jQuery.live(). Для тех, кто в танке - live() вешает событие не на сам элемент, а на document. В результате bubbling-а событие, которое произошло над каким-либо элементом поднимается вверх по DOM-дереву и соответственно вызывает обработчики всех элементов, которые оно встретит. Если вы и этого не знали - то вам не нужно читать мой блог, а пора идти и покупать книгу по JavaScript-у (мне, кстати, тоже давно пора, но всё никак не соберусь). Итак, в конце концов событие доходит до document-а и обработчики вызываются у него. Обработчик, который устанавливает jQuery.live() проверяет - соответствует ли event.target (а именно здесь хранится обьект DOM-дерева, с которым произошло событие) соответствующему селектору (в данном случае - это селектор “a”) и если соответствует - то выполняет обработчик.

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

Далее - зачем нужен “return false” в конце обработчика? Он предотвращает от того, чтобы вызывалось действие по умолчанию (в данном случае - переход по ссылке и событие не поднималось выше). Чаще всего JS-разработчики вообще не думают о bubbling-е и под “return false” понимают только “отмену действия по умолчанию”, ну или они вообще не знают, что именно происходит и пишут “return false”, потому что так работает.

Такое отношение jQuery частенько прощает. Но не в случае с live()-методом. Попробуем запустить следующий пример:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").live("click", function(e) {
      alert("a");
      return false;
    });
    $("a").live("click", function(e) {
      alert("b");
      return false;
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

В результате клика теперь выскакивает только один alert. Пора обратится к документации:

# To stop further handlers from executing after one bound using .live(), the handler must return false. Calling .stopPropagation() will not accomplish this.

Хаха. В данном случае jQuery интерпретирует false “несколько иначе”. :)

Для того, чтобы “пофиксить” подобный баг нужно обратится все к тому же bubbling-у и обработке событий и сделать именно то, что предполагается разработчиком - “отменить действие по умолчанию”. Это делается с помощью метода event.preventDefault() (пример):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").live("click", function(e) {
      alert("a");
      e.preventDefault();
    });
    $("a").live("click", function(e) {
      alert("b");
      e.preventDefault();
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

И самое главное (барабанная дробь!) - при использовании bind() для навешивания обработчиков preventDefault() тоже можно использовать!

Наткнулись мы на эту “фичу”, когда у нас почему-то перестали вызываться некоторые обработчики

Напоследок, замечу еще одно - элемент, на который навешено хотя бы один обработчик события через bind() с “правильно работающим return false”, никогда не будет вызывать никакие live()-события. ;)

Так что будьте бдительны и не забывайте об особенностях обработки событий в JS. Удачи.

written by fxposter \\ tags: , , ,

May 10

Недавно писал проект на Rails 2.3 + MongoDB + MongoMapper. Не могу сказать, что все было хорошо - для того, чтобы существующие плагины для рельсов заработали с MongoMapper-ом пришлось немного повозиться, но в итоге все закончилось хорошо. :)

А мой сегодняшний рассказ пойдет о некоторых особенностях MongoDB и MongoMapper-а, с которыми вы, скорее всего столкнетесь, если будете использовать эти библиотеки.

Для начала поговорим немного о MongoDB. Что это такое?

MongoDB — документо-ориентированная система управления базами данных (СУБД) с открытым исходным кодом, не требующая описания схемы таблиц. Написана на языке C++. СУБД управляет наборами JSON-подобных документов, хранимых в двоичном виде в формате BSON.

Если попробовать вкратце охарактеризовать эту базу данных, то получится что-то вроде этого: аналог реляционной СУБД без join-ов и транзакций, зато с поддержкой структур данных (массивов, хешей).

MongoMapper - это “ORM” для MongoDB, написанный на руби. “ORM” в кавычках потому что сложно себе представить ORM для нереляционной базы данных. Я бы скорее назвал это высокоуровневой оберткой над API, которое предоставляет MongoDB, с поддержкой ассоциаций между записями и много чем еще.

Теперь, собственно, о “особенностях” MongoDB и MongoMapper-а.

Вложенные документы

Вложенные документы в понятии MongoMapper-а - это когда одни обьекты хранят внутри себя другие. Для примера, возьмем следующую модель:

class User
  include MongoMapper::Document

  key :login
  key :password
  key :salt

  many :posts
  many :addresses
end

class Post
  include MongoMapper::Document
  key :title, String
  key :body, String
  timestamps!
end

class Address
  include MongoMapper::EmbeddedDocument
  key :type, String
  key :country_id, ObjectId
  key :city_id, ObjectId
  key :city_address, String
end

Чтобы понимать, что тут происходит нужно, во-первых знать Ruby :) и во вторых - прочесть пост John Nunemaker (даже не знаю, как правильно перевести :)) о MongoMapper-е. Вот еще один, кстати, тоже интересный.

Таким образом имеем модель пользователя, которая хранить набор постов, написанных этим пользователем и набор адресов пользователя (домашний, рабочий, etc).

Особенности работы со встроенными документами легче показать на примере:

user = User.first
user.posts

Последняя комманда вернет “scope”/”relation”, а не просто массив элементов (люди, знакомые с named_scope в ActiveRecord версии < 3 поймут).
Соответственно, можно дродолжить эту комманду, например, так:

user.posts.all(:conditions => { :created_at => { "$gt" => (Date.today - 10.days) } })

В то время как

user.addresses

вернет массив и всю дополнительную фильтрацию прийдется производить с помощью Ruby.

Манипуляции со вложенными документами

У EmbeddedDocument нет понятия “id”, т.е. отличать обьекты Address один от другого можно только по их индексу в массиве user.addresses либо вводя “свои” идентификаторы. Но даже в этом случае выбирать соответствующий обьект прийдется “вручную”:

user.addresses.detect { |address| address.identifier == params[:address_identifier] }

PS. Для тех, кто в танке: detect - это аналог select { |address| … }.first.

Соответственно - вложенные обьекты легко добавлять, выбирать всю связь полностью, но с ними становится “сложно” работать, как с отдельными обьектами. В частности - неудобно обновлять из-за проблем с выборкой отдельного элемента и неудобно удалять.

С удалением вообще интересная история. Удалить обьект имея только ссылку на него - нельзя, нужна еще и ссылка на массив, в котором он хранится:

user.addresses.first.delete/destroy

не работает, т.к. таких методов у вложенных обьектов нет. Нужно делать так:

user.addresses.delete_at(address_index)

если вы знаете индекс в массиве адресов, либо

user.addresses.delete_if { |address| address.identifier == address_identifier }

если знаете идентификатор или удаляете по какому-либо другому полю.

Несложно, но проблемы на первых порах с этим бывают.

“идентификаторы” обьектов MongoDB

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

Например, вместо такой модели:

class Tag
  include MongoMapper::Document
  key :name, String
end

иметь такую

class Tag
  include MongoMapper::Document
  # id-шник явно никогда не прописывается
end

И создавать обьект так:

Tag.create(:_id => "ruby on rails")

MongoDB это позволяет делать. Более того - драйвер Ruby для MongoDB это тоже может делать. Проблема в том, что это не умеет делать MongoMapper. Совершенно. Как обойти эту проблему я на данный момент не знаю (я в итоге сделал модель первого типа и забил), есди кто знает решение - напишите, будет интересно.

И напоследок поговорим о

Has And Belongs To Many в MongoDB

… или habtm, знакомый многим по ActiveRecord.

Приведу пример из моего проекта: есть домены, есть их модераторы, связть many-to-many. Для каждого домена хочется видеть список модераторов и для каждого модератора хочется видеть список доменов, которые он модерирует.

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

Первое решение, которое мне пришло в голову - хранить массив id-шников доменов для каждого пользователя и хранить массив id-шников пользователей для каждого домена и синхронизировать их.

Последние два слова очень смущали - синхронизация вносила излишнюю сложность. Т.е. реализовать-то её можно, но очень не хотелось.

Собственно, обратился за помощью к знакомым и вышел на одного человека (ник - Necromant, сайта его я не знаю :) ), который помог решить мне эту проблему.

Итак, решение - хранить только один список id-шников (или в юзерах или в доменах) и сделать по нему индекс, соответственно, вывод как доменов по юзеру так и юзеров по домену будет достаточно быстрый.

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

class Domain
  include MongoMapper::Document
  # ...
  key :moderator_ids, Array, :index => true
  def moderators
    User.find moderator_ids
  end
end

class User
  include MongoMapper::Document
  # ...
  def moderator_of?(domain)
    domain.moderator_ids.include? id.to_s
  end

  def moderated_domains
    Domain.all :moderator_ids => id.to_s
  end
end

Еще хочу рассказать о том, как подружить MongoMapper и Clearance, но пост вроде и так не маленький получился, так что ждите еще один пост о MongoMapper-е в ближайшие дни.

PS. Совсем писать разучился… :(

PPS. Если вам понравился этот пост, проголосуйте за него на Habrahabr-е. :)

written by fxposter \\ tags: , , ,

May 02

Настало время возродить этот блог. За более чем полугодие на нем не появилось ни одной статьи и я наконец-то намерен прекратить этот “застой”. Да, по сравнению с временами, которые были раньше у меня стало существенно меньше времени, чтобы писать что-то большое и серьезное - работа, университет (если быть точнее - то написание диплома) и еще один “секретный сайд проект” о котором, возможно, я когда-нибудь всем расскажу.

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

Ну, во-первых, я теперь не пишу на PHP. Вообще не пишу. И не очень сильно слежу за последними веяниями моды, связанными с этим языком. Да, я все равно иногда появляюсь в группе симфонистов (кому нужен доступ туда - пишите, там новичков любят и на многие нубские вопросы отвечают :) ). Да, я иногда посматриваю на фреймворки, которые сейчас являются популярными в сфере PHP - всякие Yii, ZF, etc. Посматриваю исключительно для того, чтобы быть в курсе происходящего. Учить их и писать на них в общем-то не очень хочется.

На что я променял PHP? Естественно на Ruby. Я не восхищаюсь этим языком, не превозношу его над остальными, просто на работе я пишу на нем (кстати, я работаю над стартапом для бизнесов - WaysGo, запуск будет, я надеюсь, очень скоро) и ни на чем другом желания писать особо не возникает - с точки зрения веб-разработки Ruby меня полностью устраивает. Этот язык удобен и достаточно экспрессивен. А Ruby On Rails - отличное средство для тех, кто хочет писать веб-приложения. Ну, в общем, вы меня поняли. ;)

Кстати говоря, я немного соврал насчет того, что я пишу на Ruby на работе. С начала этой недели я пишу iPhone-приложение на Objective-C и, в связи с этим, шустренько осваиваю Mac, Xcode, сам Objective-C и всякую прочую фигню, которая нужна для разработки под iPhone. Определенного мнения по поводу мака я пока сказать не могу, а вот Objective-C с первого подхода отличается похожестью на руби с точки зрения вызовов методов у объектов и открытых классов, но в то же время отличается несколько некрасивым синтаксисом (квадратные скобки, рррррр). С другой стороны, к синтаксису Ruby у меня тоже изначально было плохое отношение. Это отношение, кстати, не изменилось - я по прежнему считаю, что Ruby - непонятный язык для новичка, и если код на, например, Python можно просто читать, то для того, чтобы почитать код на Ruby придется сначала посмотреть туториалы, или что-то в этом роде, потому что синтаксис кроме выразительности отличается еще и неочевидностью. После пары дней работы с Ruby проблемы отпадают сами собой, так что этот аспект меня уже не очень беспокоит. Собственно, я надеюсь, что рано или поздно я перестану замечать синтаксис Objective-C и буду просто писать код, который делает что мне нужно.

Вот, собственно, и все на сегодня. Вроде ничего не забыл. До скорого! :)

written by fxposter \\ tags: , , , , ,

Oct 22

Вдогонку к первой части.

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

Решение №2

Все, что написано далее подходит к операционной системе Windows 7, есть ли что-то подобное в предыдущих версиях этой ОС я сказать затрудняюсь.

Итак, заходим в то же самое меню, которое упоминалось в прошлом посте: “Control Panel” -> “Clock, Language, and Region” -> “Change keyboards or other input methods”. После чего переходим на вкладку “Administrative” и нажимаем на кнопку “Copy settings..”:

region_and_language

После чего видим следующее окошко:

region_and_language_2

Желтым выделены те настройки, которые сейчас выбраны в вашей учетной записи. Розовым я выделил те настройки, которые используются в окне загрузки (т.е. те настройки, которые мы хотим поменять). У меня эти настройки уже изменены на правильные, а тем, кто только хочет их изменить достаточно поставить галочку в выделенной красным цветом части окна и нажать “OK”.

Enjoy!

written by fxposter \\ tags: , ,

Oct 22

До 25-го октября (т.е. еще 3 дня) можно купить замечательную игрушку под названием World Of Goo всего лишь за один цент. Спешите, времени осталось не так уж много.

PS. Я купил за доллар. Отдал бы больше, но на карточке закончились деньги. :( А игра того, несомненно, стоит.

written by fxposter \\ tags:

Oct 22

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

Проблема

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

Решение

Поставить английский языком ввода по умолчанию довольно легко - входим в “Control Panel” -> “Clock, Language, and Region” -> “Change keyboards or other input methods” (названия взяты из Windows 7, у вас они могут быть другими), кликаем на кнопку “Change keyboards…” и выставляем английский как “Default input language”.

Радуемся.

Радуемся.

Радуемся.

Перезагружаемся.

Пытаемся ввести пароль (у меня пароли всегда англоязычные).

Перестаем радоваться… Т.к. в окне загрузки язык ввода по умолчанию остался русским.

Чтобы разобраться с этим неудобством - идем в regedit. Находим ветку “HKEY_USERS\.DEFAULT\Keyboard Layout\Preload” и видим нечто подобное:

Regedit

На самом деле все просто. “00000419″ - это идентификатор русского языка, а “00000409″ - английского. Собственно, дальше все просто - меняем местами значения (т.е. ключу “1″ ставим значение “00000409″, а ключу “2″ - “00000419″), перегружаемся и убеждаемся, что все теперь работает замечательно.

Profit!

Updated: найдено более простое решение.

written by fxposter \\ tags: ,

Sep 16

Видео от создателя Ruby On Rails и одного из первых сотрудников 37 Signals о том, что нужно делать, чтобы зарабатывать деньги в интернете (”The secret of making money online”). Видео я смотрел давно уже, но без слайдов. После того, как мне наш проджект менеджер прислал ссылку на это же видео, решил найти вариант со слайдами. Смотрим.

PS. DHH рассказывает, казалось бы, об очевидных вещах. Однако отнюдь не всем эти вещи приходят в голову без “толчка” со стороны.

written by fxposter \\ tags: ,

Sep 13

Iterators must go by Andrei Alexandrescu. Всем C++-никам посвящается!

PS. Всех коллег поздравляю с Днем программиста.

written by fxposter \\ tags:

Sep 05

Что-то к пятому курсу меня все-таки потянуло на изучение алгоритмов… Нет, я не стал читать книги по алгоритмам, пока не стал. Но всякие статьи по алгоритмам и структурам данных стали мне интересны - если я увижу где-то подобную статью, то я скорее всего её прочту, хотя раньше все было наоборот - увидев заголовок типа “Нахождение минимального остовного дерева в графе” я бы 100% прошел мимо.

Так вот, к чему я это - после первой недели учебы я однозначно могу сказать, что идти в магистратуру к нам на специальность стоит, по крайней мере из-за одного предмета -  “Эвристические алгоритмы комбинаторной оптимизации”. Судя по первой лекции - здесь нам будут рассказывать очень интересные и универсальные алгоритмы нахождения приближенного к оптимальному решения всяческих задач. В частности, NP-трудных и NP-полных задач - например, “задача коммивояжёра“, “Bin Packing” или “задача о рюкзаке“. Не знаю, как вас, а меня, например, уже давно интересовала задача оптимальной “упаковки” многоугольников в прямоугольную фигуру. И она решается! Причем добиться достаточно оптимального решения можно гораздо быстрее чем за экспоненциальное время! Правда мы будем рассматривать очень упрощенный вариант, но сам алгоритм решения остается тот же. В общем, магистратурой нашей специальности я очень даже  доволен. :)

И напоследок - учитесь, знания вам ой как пригодятся в будущем.

written by fxposter \\ tags:

Sep 04

Да, кстати, всех студентов и школьников поздравляю с началом нового учебного года (поздновато, но такой уж я забывчивый). Не знаю, как вы, а я как-то даже ждал возможности вернуться в родные стены университета для того, чтобы провести здесь еще один год, на этот раз последний, теперь уже для того, чтобы получить диплом магистра по специальности “Программное обеспечение автоматизированных систем” (свой диплом бакалавра я уже “получил” - нам их даже не выдали :( ). Судя по расписанию, семестр обещает быть не очень трудным, но очень интересным. Чего и вам желаю!

written by fxposter