Показаны сообщения с ярлыком javascript. Показать все сообщения
Показаны сообщения с ярлыком javascript. Показать все сообщения

среда, 9 февраля 2011 г.

swank-js - удивительное рядом

Поскольку мне приходится писать и отлаживать много JavaScript кода, то я уже давно мечтал о возможность изменять исходный код при работе в Emacs и сразу же отправлять изменения в браузер. Так же очень здорово было бы иметь JavaScript-консоль в Emacs, которая бы реально взаимодействовал с открытой веб-страницей. Или очень часто нужно немного подправить CSS и заставить браузер применить эти изменения без перезагрузки страницы. Звучит несколько фантастически, но сейчас это совершенно реально благодаря проекту swank-js.

Мне, правда, пришлось внести небольшое изменение в оригинальный код, что бы это заработало для меня. Суть отличий в том, что в веб-страницу надо дополнительно включать такой JavaScript-код:
 SwankJS.setup("localhost", {port: 8009});
Мои изменения здесь.

вторник, 8 февраля 2011 г.

RESTAS и JavaScript

Популярность JavaScript в качестве серверного языка стремительно увеличивается, чему способствует в том числе и свойства самого языка - JavaScript это удивительно гибкий и пластичный язык, он даже мягче, чем лисп. Кроме того, в современных веб-приложениях часто логика перемешивается между клиентом и сервером и очень удобно использовать и там и там один и тот же код. cl-closure-template решает многие проблемы разделения логики, но не все. Поэтому, у меня зародилась мысль дать возможность создавать модули для RESTAS на JavaScript. Тем более, что существует CL-JavaScript - достаточно качественная реализация JavaScript на Common Lisp.

restas-javascript - проект, который должен дать возможность смешивать код на Common Lisp и JavaScript при разработке web-приложений на базе RESTAS. Кое-что уже работает. В частности, я уже смог переписать на JavaScript примеры из статьи Hello World - смотрите код в файле demo.js.

Структура этого кода полность аналогична соответствующему кода на Common Lisp, только использует идиомы JavaScript. Доступ к объектам request и reply (которые в CL оформлены в виде специальных переменных) в обработчиках маршрутом осуществляется через this.request и this.reply. Интерфейсы request и reply повторяет функции из документации к Hunchentoot, но именуются в стиле CamelCase, плюс некоторые методы оформлены в виде "активных свойств".

Загрузить данный файл можно следующим образом:
(restas.javascript:execute #P"/path/to/demo.js")
после чего уже можно будет идти в браузер смотреть результат.

С помощью
(restas.javascript:repl)
можно запустить примитивную JavaScript-консоль и поиграться с маршрутами. Что бы несколько упростить это я добавил простейшую реализацию console.log.

Кстати, дизайн интерфейса для JavaScript мне нравится в некоторых аспектах больше, чем для CL.

среда, 2 февраля 2011 г.

Ещё раз про cl-uglify-js

Я уже писал про cl-uglify-js, но тут случилось примечательное событие, которое на мой взгляд стоит отметить отдельно. cl-uglify-js и UglifyJS суть библиотеки-близнецы, от одного автора, развивающиеся синхронно и делающие совершенно одно и то же, просто написанные на разных языках - одна на Common Lisp, а другая на JavaScript (для Node.js). Так вот, теперь система сборки jquery использует именно UglifyJS (вместо Google Closure Compiler!), подтверждение чему можно найти здесь или здесь (раздел про BUILD SYSTEM).

В общем, сейчас можно смело утверждать, что cl-uglify-js (наряду с UglifyJS) претендует на роль одного из лучших (или даже лучшего) решений в своей области.

IE 9 - я удивлён

Я, конечно, слышал, что IE 9 это чудо техники и поддерживает все современные стандарты почти полностью и т.п. Слышал, но не верил. Но вот стало любопытно, попросил админа установить на его машине сей продукт и решил попробовать запустить на нём свой мерчендайзинг. Тут должен заметить, что я никогда не планировал использовать данное приложение под IE. Мои пользователи используют Firefox, разработку я веду в основном с помощью Chromium, время от времени тестирую под Opera. Приложение основано на XHTML + SVG, использует достаточно нетривиальную обработку XML (DOMParser и т.п.). Под этими тремя (Firefox, Chrome, Opera) браузерами приложение заработало не сразу, был целый ряд различий в поведении, так что пришлось искать код, который одинаково работает во всех этих браузерах.

И вот, просто ради любопытства запускаю под IE 9 - и, о чудо, оно просто работает. Вообще без какой-либо адаптации. У меня, честно говоря, культурный шок.

среда, 10 ноября 2010 г.

cl-uglify-js

Совершенно случайно (автор форкнул мой форк cl-pdf, а я пошёл посмотреть кто такой) обнаружил cl-uglify-js - библиотеку (Common Lisp) для "сжатия" кода на JavaScript. Я вообще давно уже мечтал о подобной либе для CL, ибо тянуть в проект на CL что-нибудь типа Google Closure Compiler совсем не хочется. Теперь же всё получается просто. Я протестировал эту библиотеку на своём основном проекте, в частности, на коде который генерирует cl-closure-template и обнаружил, что проект всё ещё работает (никаких изменений не выявленно), а код превратился в жуткое нечитабельное сжатое месиво - именно то, что нужно.

Вообще, ситуация с разработкой высоко-интерактивных веб-приложений на Common Lisp начинает казаться мне всё более и более симпатичной.

Кстати, cl-uglify-js является портом UglifyJS - аналогичной библиотеки для Node.js того же автора, которая на github имеет более 300 подписчиков, что внушает серьёзный оптимизм. Забавно, что в UglifyJS для разбора JavaScript используется порт parse-js - вот такая вот тесная интеграция JavaScript и Common Lisp.

среда, 11 августа 2010 г.

Запретить drag-and-drop изображений в Firefox

В Firefox есть очень противная вещь - если нажать на мышкой на изображение, то инициируется совершенно бессмысленный drag-and-drop. Мне это просто не нравится, но тут ещё и мешает моему приложению, ибо у меня своя реализация drag-and-drop и она должна срабатывать на div, который в том числе содерижт img, но если пользователь щёлкает на img, то начинается совершенно левый перенос картинки. В webkit есть css-свойство -webkit-user-drag, которое можно выставить в none, но для Firefox его аналога нет. Немного потыкал гугл, но нужного мне рецепта не нашёл. Пришлось найти самому, впрочем, он тривиален:
$(document).bind("dragstart", function (evt) { evt.preventDefault(); } );
Всё, никакого левого drag-and-drop.

суббота, 6 февраля 2010 г.

AJAX с cl-closure-template. Часть 1.

В результате работы над текущим проектом графического редактора у меня зародилась идея, которая может быть применима к широкому кругу AJAX-приложений. Поскольку эта идея демонстрирует преимущества использования cl-closure-template, то решил рассказать об этом здесь. Ну и надеюсь, что будет целая серия постов, поэтому это "Часть 1". И да, в данный момент я не обладаю полным решением, поэтому оно будет изменяться от поста к посту.

Мне нравится как сделан github, поэтому я решил показать, как можно реализовать элементы управления в его стиле. Самый простой элемент (он встречается там достаточно часто) можно найти на странице проекта (если у вас есть проекты на github, то вы им безусловно пользовались), например строка с описанием проекта. По ней можно кликнуть мышкой, в результате чего появится форма, в которой можно редактировать это описание. Несколько обобщив концепцию данного элемента можно получить следующую схему:
  • Элемент модели данных (хранящийся на сервере) описывается с помощью фрагмента разметки
  • Пользователь может активировать процесс редактирования этого элемента с помощью клика мышкой или иным образом
  • В результате изначальная разметка скрывается и вместо неё появляется форма для ввода данных
  • Пользователь может либо сохранить внесённые изменения, либо отказаться от них, в результате чего форма для ввода данных скрывается и снова появляется изначальная разметка, но модифицированная на основе введённых данных
Реализовать подобное поведение можно полностью на уровне JavaScript (как это видимо и сделано на github), но в этом случае JavaScript код должен иметь достаточно точные знания о разметке. Во-первых, это приводит к появлению сильных связей между серверным кодом, генерирующим страницу, и JavaScript-кодом. Во-вторых, написание JavaScript кода даже в достаточно простых случаях может оказаться довольно нудным занятием. Я хочу, что бы JavaScript код, решающий подобную проблему был абсолютно минимальным и обладал бы некоторой степенью общности, что позволило бы использовать его для различных элементов, работающих по подобной схеме.

Используемое мной решение заключается в том, что бы не заниматься "тонким редактированием" разметки, а производить полное её замещение на основе шаблонов, реализованных с помощью cl-closure-template: изменилась модель - создали новую разметку и подставили её вместо старой.

Для начала потребуются следующие шаблоны (которые в основном повторяют соответствующую разметку, используемую в github на момент написания данного поста):
{namespace example.githubway.view}

// Представление текстового элемента
{template editable-text}
<div class="editable-text" json="{$json}">
<p>
{$value}
<em class="edit-text">click to edit</em>
</p>
</div>
{/template}

// Форма для редактирование элемента
{template edit-text}
<form method="post" action="{$saveLink}" json="{$json}">
<input type="text" value="{$value}" name="value"></input>

<div class="form-actions">
<input type="submit" class="minibutton save" value="Save"></input>
<span class="fakelink cancel">cancel</span>
</div>
</form>
{/template}
Эти шаблоны принимают следующие аргументы:
  • value - значение элемента
  • saveLink - URL, который может использоваться для обновления значения элемента с помощью POST-запроса
  • json - а вот это любопытно, это предыдущие аргументы в формате JSON. Зачем это надо? Напомню, что cl-closure-template позволяет создавать шаблоны, доступные как на сервеной, так и на клиентской стороне. Я хочу, что бы JavaScript-код получил полную информацию об элементе модели, которую он сможет в последующем использовать для передачи в эти же шаблоны для генерации новой разметки. Для этого я сохраняю эти данные в атрибуте json корневого элемента генерируемой разметки, что существенно упрощает последующую обработку.
Теперь, JavaScript код, я выделил базовый класс, который может быть использован для различных подобных элементов:
function EObject (node) {
// node - узел DOM-дерева, представляющего элемент данных
if (node) {
this.node = node;
}
}

// Возвращает описание элемента модели в виде
// пригодном для генерации разметки с помощью шаблонов
EObject.prototype.modelData = function () {
var data = $.evalJSON(this.node.attr("json"))
data.json = $.toJSON(data);
return data;
};

// Метод для генерации основной разметки
EObject.prototype.toHTML = function (data) {
throw "Method toHTML not implemented";
};

// Метод для генерации формы редактирования
EObject.prototype.editForm = function (data) {
throw "Method editForm not implemented";
};

// Вспомогательный метод, позволяющий заменить разметку элемента
// таким образом, что бы в объекте осталась ссылка на актуальный
// элемент DOM-дерева
EObject.prototype.replaceHTML = function (html) {
this.node.after(html);
this.node = this.node.next();
this.node.prev().remove();
};

// Заменяет основное представление на форму редактирования
EObject.prototype.startEdit = function () {
this.replaceHTML(this.editForm(this.modelData()));

var obj = this;

$('.cancel:first', this.node).click(function (evt) { obj.endEdit(); });

this.node.ajaxForm({
dataType: 'json',
success: function (data) { obj.endEdit(data)},
error: function () { alert("Не удалось сохранить данные"); obj.endEdit() }
});
};

// Заменяет форму редактирования на основной вариант разметки
EObject.prototype.endEdit = function (data) {
this.replaceHTML(this.toHTML(data || this.modelData()));

// Похоже на хак, но мне кажется нормальным решением.
// Фактически, просто ре-инициализация объекта, которая,
// например, приведёт к активизации нужных обработчиков событий
this.constructor(this.node);
};
Данный код использует библиотеке jquery и плагины jquery.form и jquery.json. Теперь, создать код для редактирования текстового элемента очень просто:
function EditableText (node) {
if (node) {
// вызываем конструктор базового класса
EObject.prototype.constructor.call(this, node);

// Инициируем вызов процедуру редактирования по клику мышкой
var obj = this;
this.node.click(function (evt) { obj.startEdit(); });
}
}

// Инициализируем прототип
EditableText.prototype = new EObject;

// Необходимо, что бы иметь возможность создать новый класс, наследующий от EditableText
EditableText.prototype.constructor = EditableText;

// Генерируем разметку с помощью ранее описанного шаблона editable-text
EditableText.prototype.toHTML = example.githubway.view.editableText;

// Генерируем форму редактирования с помощью ранее описанного шаблона edit-text
EditableText.prototype.editForm = example.githubway.view.editText;
И наконец инициализация при загрузке документа:
$(document).ready(function () {
// Тоже довольно любопытно. Просто создаём новые объекты "в никуда".
// Но благодаря привязке этих объектов к DOM-дереву через обработчики
// событий они останутся "жить" и будут выполнять свою работу
$(".editable-text").each( function (i, node) { new EditableText($(node)); })
});
Теперь серверная часть. Для демонстрации я использую очень простое приложение с одной страницей, на которой показывается имя и email некоего человека. Пользователь приложения может отредактировать их в стиле github. Код данного приложения зависит от:Сначала определим очень простую модель
(defparameter *name* "Ivan Petrov")

(defparameter *email* "Ivan.Petrov@example.com")


(defun with-json (&rest args)
(list* :json (json:encode-json-plist-to-string args)
args))

(defun name-to-json ()
(with-json :value *name*
:save-link (genurl 'save-name)))

(defun email-to-json ()
(with-json :value *email*
:save-link (genurl 'save-email)))
Здесь функции name-to-json и email-to-json генерируют plist, подходящий для использования с шаблонами, описанными в начале.

Описываем маршрут для основной страницы:
(define-route main ("")
(example.githubway.view:page (list :name (name-to-json)
:email (email-to-json))))
Ради экономии места здесь я не буду приводить текст шаблона example.githubway.view:page, укажу лишь, что в нём есть следующие строчки:
{call editable-text data="$name" /}
{call editable-text data="$email" /}
Теперь определяем обработчики для обновления данных об имени и email, ради упрощения какая-либо обработка ошибок опущена:
(define-route save-name ("api/name" :method :post :content-type "application/json")
(setf *name*
(hunchentoot:post-parameter "value"))
(json:encode-json-plist-to-string (name-to-json)))

(define-route save-email ("api/email" :method :post :content-type "application/json")
(setf *email*
(hunchentoot:post-parameter "value"))
(json:encode-json-plist-to-string (email-to-json)))
Вот и всё. Полный код данного приложения я включил в состав closure-template и посмотреть его можно здесь.

Продолжение следует...