(blog ‘lucindo)

um dia eu aprendo a programar

Arquivo da categoria ‘web’

Javascript (Ajax) e HTML em Lisp

Para testar algumas coisas refiz hoje o código do post Common Lisp e Ajax. Desta vez estou usando o patch para o HT-AJAX com suporte e jQuery. Além disso, para gerar código JavaScript uso Parenscript. Assim todo HTML e JS é produzido por s-exps:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defparameter *dependencies*
    ‘(:asdf :hunchentoot :ht-ajax :cl-who :parenscript))
  (map nil ‘require *dependencies*))

(defpackage :ajax-test
  (:use :common-lisp :hunchentoot :ht-ajax :cl-who :parenscript))

(in-package :ajax-test)

(defparameter *local-dir* “/Users/lucindo/Documents/Lisp/web/3rdparty/”)
(defparameter *ajax-handler-url* “/ajax”)
(defparameter *ajax-processor*
  (make-ajax-processor :type :jquery
                       :server-uri *ajax-handler-url*
                       :js-file-uris “/static/jquery.js”))

(defun testfunc (command)
  (prin1-to-string (handler-case (eval (read-from-string command nil))
                                 (error (c) (format nil “~a” c)))))

(defun js ()
  (ps
   (defun command_clicked ()
     (let ((command (document.get-element-by-id “command”)))
       (with-slots (value) command
                   (ajax_testfunc_set_element “result” value))))))

(defun main-page ()
  (with-html-output-to-string
   (*standard-output* nil :prologue t)
   (:html
    (:head
     (:script :type “text/javascript” :src “/js”)
     (:title “AJAX test”)
     (fmt “~a” (generate-prologue *ajax-processor*)))
    (:body
     (:h1 “ajax test”)
     (:table :width “50%”
             (:tr
              (:td :colspan “2″
                   (:span :id “result”
                          (:i “no results yet”))))
             (:tr
              (:td :width “70%”
                   (:input :type “text”
                           :size “70″
                           :name “command”
                           :id “command”))
              (:td (:input :type “button”
                           :value “eval”
                           :onclick (ps-inline (command_clicked))))))))))

(eval-when (:load-toplevel :execute)
  (export-func *ajax-processor* ‘testfunc :method :post)
  (setf *dispatch-table*
        (list ‘dispatch-easy-handlers
              (create-folder-dispatcher-and-handler “/static/”
                                                    *local-dir* “text/plain”)
              (create-prefix-dispatcher *ajax-handler-url*
                                        (get-handler *ajax-processor*))
              (create-prefix-dispatcher “/js” ‘js)
              (create-prefix-dispatcher “/” ‘main-page))))

(defparameter *webserver* nil)

(defun start-web (&optional (port 4242))
  (setf *webserver* (start-server :port port)))

(defun stop-web ()
  (stop-server *webserver*))

Download: ajax2.lisp

1 comentário »

HT-AJAX e jQuery

HT-AJAX (documentação aqui) é uma extenção do Hunchentoot que permite exportar suas funções lisp de modo que elas podem ser acessadas via JavaScript, usando AJAX. Um pequeno exemplo de uso dessa biblioteca está nesse post.

HT-AJAX suporta vários AJAX processors, como Prototype e Dojo, mas não jQuery. Então fiz um pequeno patch adicionando suporte a jQuery.

Download: ht-ajax_0.0.7-jquery.patch

Aplicando o patch (SBCL):

$ cd ~/.sbcl/site/ht-ajax_0.0.7
$ wget http://lucindo.com.br/lisp/ht-ajax_0.0.7-jquery.patch
$ cat ht-ajax_0.0.7-jquery.patch | patch -p0
$ sbcl
* (require :asdf)
* (asdf:oos 'asdf:compile-op :ht-ajax)
Sem comentários »

Hunchentoot e REST, parte 2: suporte a ETags

Bom, esse post é mais um snippet de como implementar um serviço REST usando Hunchentoot. Dessa vez adicionei suporte a ETags no código do outro post. Ficou assim o tratamendo das requisições:

(defvar *data-id* “e8d8993494ffc11:b8e”)
(defun get-data-id (target)
  “get the last id for requested data”
  *data-id*)

(defmethod handle :around (request-method)
  “ETag support for all methods”
  (let ((data-id (get-data-id (script-name)))
        (last-id (header-in :If-None-Match)))
    (setf (header-out :ETag) data-id)
    (if (and last-id (equalp last-id data-id))
        (setf (return-code) +http-not-modified+)
        (call-next-method))))

Fazendo um request normal:

GET /rest HTTP/1.1
Host: eddie

HTTP/1.1 200 OK
Content-Length: 3
Content-Type: text/html; charset=iso-8859-1
Date: Sun, 27 Apr 2008 20:07:53 GMT
Server: Hunchentoot 0.15.6
Etag: e8d8993494ffc11:b8e

GET

Agora o cliente passando o ETag:

GET /rest HTTP/1.1
Host: eddie
If-None-Match: e8d8993494ffc11:b8e

HTTP/1.1 304 Not Modified

Suportar ETags é muito fácil e se o cliente do seu serviço souber usar é muito útil para os dois.

Download: rest.lisp

3 comentários »

David Heinemeier Hansson na Startup School 2008

Como programador ele é um ótimo businessman



Sem comentários »

Hunchentoot e REST

Um snippet de teste que eu fiz para um serviço REST usando Hunchentoot.

Usando função genérica é possível fazer um “pattern matching” e a implementação fica parecida com Erlang/YAWS (a vir no BedDB).

(eval-when (:load-toplevel :compile-toplevel :execute)
  (requirehunchentoot))

(defpackage :ht-rest
  (:use :common-lisp :hunchentoot))

(in-package :ht-rest)

(defun add-dispatcher (dispatcher-fn)
  “Helper function to add dispatcher functions to dispatch table”
  (nconc *dispatch-table* (list dispatcher-fn)))

(defun create-dispatcher (url-prefix handler &key (regexp nil))
  “Creates a dispatcher and add it to dispatch table given the
url prefix and handler function. The url prefix can be a regular
expression (in this case set the :regexp keyword).”
  (let ((dispatcher-fn
         (funcall
          (if regexp ‘create-regexex-dispatcher ‘create-prefix-dispatcher)
          url-prefix handler)))
    (add-dispatcher dispatcher-fn)))

(defun handle-rest ()
  “Simply delegate to appropriate handler”
  (handle (request-method)))

(defgeneric handle (request-method)
  (:documentation “Generic REST handler”))

(defmethod handle :before (request-method)
  (log-message :info “REST in: [method ~a] [target ~a] [qs ~s]”
               (request-method) (script-name) (query-string)))

(defmethod handle :after (request-method)
  (log-message :info “REST out: [code ~a] [method ~a] [target ~a] [qs ~s]”
               (return-code) (request-method)
               (script-name) (query-string)))

(defmethod handle ((request-method (eql :get)))
  (string (request-method)))

(defmethod handle ((request-method (eql :post)))
  (string (request-method)))

(defmethod handle ((request-method (eql :put)))
  (string (request-method)))

(defmethod handle ((request-method (eql :delete)))
  (string (request-method)))

(defmethod handle (request-method)
  (setf (return-code) +http-method-not-allowed+))

(defvar *ht-server* nil)

(defun setup ()
  (create-dispatcher “/rest” ‘handle-rest))

(defun start (&optional (setup-p t))
  (prog1
      (setq  *show-lisp-errors-p* t
             *show-lisp-backtraces-p* t
             *dispatch-table* (list ‘dispatch-easy-handlers)
             *ht-server* (start-server :port 8080))
    (when setup-p (setup))))

(defun stop ()
  (stop-server *ht-server*))

(defun re-start ()
  (progn
    (stop)
    (start nil)))
1 comentário »

Benchmarks

O Ivan colocou na sua página dois benchmarks interessantes:

Linguagens testadas: C, C++, Java, Perl, Python, Ruby, Ocaml, PHP, D, Erlang e Common Lisp.

Observação: Nenhum dos testes prova nada além do que um bando de programadores não tem nada mais para fazer da vida depois rola uma discussão na hora do café.

7 comentários »

Milki: esboço de wiki em Common Lisp

Eu precisava de uma wiki para ajudar a organizar idéias de um projeto. Depois de conversar com os outros envolvidos eu fiquei de instalar uma em um servidor para começarmos a usar no dia seguinte.
Resolvi que seria uma experiência interessante escrever uma wiki em CL. Como precisávamos da wiki para o dia seguinte eu tinha que escrever tudo em uma noite, e se não desse certo eu instalaria uma wiki qualquer.
Bom, foi aí que surgiu a Milki, que estamos usando até hoje. As bibliotecas que eu usei:

Todas elas podem ser instaladas via ASDF-Install.

Para a formatação usei a sintaxe Markdown. A renderização é feita via JavaScript, com o pacote Showdown.

Vou tentar colocar algumas partes do código explicando como tudo foi implementado. No final desse post tem alguns links com o código-fonte completo.

Começamos carregando todas as dependências e definindo o pacote:

;;; MILKI - MInimal Lisp wiKI
(eval-when (:compile-toplevel :load-toplevel :execute)
  (require :hunchentoot)
  (require :cl-who)
  (require :cl-store)
  (require :s-utils)
  (require :cl-fad))

(defpackage :milki
  (:use :common-lisp :cl-who)
  (:export #:start-wiki #:stop-wiki
           #:add-user #:change-password #:remove-user))

(in-package :milki)

As funções para iniciar e terminar a execução do servidor web são wrappers para funções do Hunchentoot:

; we need a variable to hold the server instance
(defvar *wiki-server* nil “the hunchentoot server instance”)

(defun start-wiki (&key (port 8080))
  (setf *wiki-server* (hunchentoot:start-server :port port)))

(defun stop-wiki ()
  (hunchentoot:stop-server *wiki-server*))

Milki usa três diretórios separados: um para os arquivos estáticos (CSS, JavaScript, etc), um para os arquivos recebidos via upload e um outro para armazenar os dados das páginas. Nesses dois últimos é necessário ter permissão de escrita:

(defparameter *static-files-dir* “/home/lucindo/milki/static/”
  “place where we can put all JavaScript and CSS files”)
(defparameter *data-storage-dir* “/home/lucindo/milki/data/”
  “place where we’ll store all data”)
(defparameter *upload-dir* “/home/lucindo/milki/files/”
  “place to put uploaded files”)

Como esse sistema vai rodar num ambiente multi-threaded usei um lock global que será aplicado ao acesso de arquivos e estruturas.

;; this will run on a multithreaded environment and for
;; some operations we will use a global lock
(defvar *lock* (hunchentoot-mp:make-lock “milki-lock”))

(defmacro locked (&body body)
  `(hunchentoot-mp:with-lock (*lock*) ,@body))

Agora temos a implementação do controle usuários. Apenas a autenticação é acessada pelo Hunchentoot, ou seja, a criação, remoção e mudança de senha não são feitas online, e sim avaliando as expressões correspondentes na imagem lisp (por isso essas funções são exportadas no pacote).

;; we need users
;; users are stored in a assoc-list: (”username” password)
;; the password is a md5 of password string
;; here we use the md5 package (required by hunchentoot)
;; the users list is stored in a file using cl-store
(defun users-file ()
  (concatenate ’string *data-storage-dir* “.users”))

; will be nil at first run
(defvar *user-alist* (ignore-errors (cl-store:restore (users-file))))

(defun sync-users-file ()
  (locked
    (cl-store:store *user-alist* (users-file))))

(defun add-user (user pass)
  (push (cons user (md5:md5sum-sequence pass)) *user-alist*)
  (sync-users-file))

(defun remove-user (user)
  (setf *user-alist*
        (remove-if #’(lambda (up-pair)
                       (string= user (car up-pair))) *user-alist*))
  (sync-users-file))

(defun user-pass-match-p (user pass)
  (and user pass
       (equalp (cdr (assoc user *user-alist* :test #’string=))
               (md5:md5sum-sequence pass))))

(defun change-password (user oldpass newpass)
  (when (and (user-pass-match-p user oldpass) newpass)
    (setf (cdr (assoc user *user-alist* :test #’string=))
          (md5:md5sum-sequence newpass))
    (sync-users-file)))

Essa wiki é privada. Só é possível acessá-la tendo um usuário. Assim todas as páginas precisam de controle de acesso, e para isso usamos macros:

; require a digest autorization
(defmacro with-authorization (&body body)
  (let ((user (gensym))
        (pass (gensym)))
    `(multiple-value-bind (,user ,pass)
         (hunchentoot:authorization)
       (if (user-pass-match-p ,user ,pass)
           ,@body
           (hunchentoot:require-authorization “[milki login]”)))))

; all wiki pages requires authorization (this is a private wiki!)
(defmacro with-wiki-page-body (&body body)
  `(with-authorization
     (with-html-output-to-string (*standard-output* nil :prologue t)
       (:html
        (:head
         (:title “milki - a very simple wiki”)
         (:script :type “text/javascript” :src “/static/showdown.js”)
         (:script :type “text/javascript” :src “/static/milki.js”)
         (:link :rel “stylesheet” :href “/static/milki.css” :type “text/css”))
        (:body ,@body)))))

Agora, cada página é armazenada como uma lista de instancias do objeto wiki-post. Sempre que uma página é alterada criamos uma instância desse objeto e adicionamos à lista. Assim mandemos o histórico. Cada item da lista é uma versão da página, sendo que o primeiro deles é a versão mais atual.

;; we’ll store wiki posts in files
(defun wiki-post-file-name (post-name)
  (concatenate ’string *data-storage-dir* “wiki-” (hunchentoot:url-encode post-name)))

(defun sync-wiki-post (post-name wiki-post-list)
  (locked
    (cl-store:store wiki-post-list (wiki-post-file-name post-name))))

; may return nil
(defun get-wiki-post (post-name)
  (locked
    (ignore-errors (cl-store:restore (wiki-post-file-name post-name)))))

;; a wiki post is very simple
(defclass wiki-post ()
  ((contents :initarg :contents
             :accessor post-contents)
   (timestamp :initform (get-universal-time)
              :accessor post-timestamp)
   (user :initarg :user
         :accessor post-user)))

(defun add-wiki-post (post-name contents user)
  (let ((new-post (make-instance ‘wiki-post :contents contents :user user))
        (post-list (get-wiki-post post-name)))
    (if post-list
        (push new-post post-list)
        (setf post-list (list new-post)))
    (sync-wiki-post post-name post-list)))

A seguir definimos as funções principais da wiki: a que imprime uma página, a de edição de páginas e o handler principal. Como a renderização é feita via JavaScript não existe preview da edição, pois o mesmo é feito online, conforme o usuário edita:

(defun show-wiki-post (post-name post-version post)
  (let ((edit-link (conc post-name “?edit=true&version=” (princ-to-string post-version))))
    (with-wiki-page-body
      (:div :align “right”
            (:font :size “-1″
                   (:a :href (str edit-link) “edit”)))
      (:div :id “wiki-post” :class “wiki-post”
            :ondblclick (conc “document.location=”" edit-link “”;”)
            (str (post-contents post)))
      (:hr :class “footer”)
      (:span :id “footer” :class “footer”
             (:font :size “-1″
                    (:table :border “0″
                            :width “98%”
                            :align “center”
                            :cellpadding “0″
                            :cellspacing “0″
                            (:tr
                             (:td :align “left”
                                  “useful links: “
                                  (:a :href “/” “start page”)
                                  ” - “
                                  (:a :href “/index” “wiki index”))
                             (:td :align “right”
                                  (str (conc “last updated by “
                                             (post-user post)
                                             ” on “
                                             (s-utils:format-universal-time
                                              (post-timestamp post))))))))))))

(defun print-wiki-post (post-name post-version)
  (let* ((wiki-post-list (get-wiki-post post-name))
         (the-post (nth post-version wiki-post-list)))
    (cond ((not wiki-post-list)
           (with-wiki-page-body (:center (:h2 “Hey! I’m an empty page!”)
                                         (:a :href (conc post-name “?edit=true”)
                                             (:h3 “edit me”)))))
          ((not the-post) (show-wiki-post post-name post-version (car wiki-post-list)))
          (t (show-wiki-post post-name post-version the-post)))))

(defun edit-wiki-post (post-name post-version)
  (let* ((wiki-post-list (get-wiki-post post-name))
         (the-post (nth post-version wiki-post-list)))
    (with-wiki-page-body
      (:center
       (:h2 (str (conc “editing: “ post-name)))
       (:div :align “right”
             (:font :size “-1″
                    (:a :href “/upload” “click here to upload files”)
                    (:br)
                    “use markdown syntax: cheatsheet “
                    (:a :href “javascript: open_markdown_cheatsheet()” “here”)))
       (:form :method :post :action (str post-name)
              (:table :with “98%” :border “0″ :cellpadding “10″ :cellspacing “2″
                      (:tr
                       (:td :width “48%” :valign “top”
                            (:textarea :id “wiki-input”
                                       :name “contents” :cols 60 :rows 20 :style “width: 100%;”
                                       (when the-post (str (post-contents the-post)))))
                       (:td :width “48%” :valign “top” :style “border-left: solid 1px #736F6E”
                            (:div :id “wiki-preview”)))
                      (:tr :rowspan “2″
                           (:td
                                (:input :type :submit :value “save”)
                                (str ” or “)
                                (:a :href (princ-to-string post-name) “cancel”))))))
       (when  wiki-post-list
         (htm
          (:p “version history:”)
          (:ul
           (loop for i from 0
              for wiki-post in wiki-post-list
              do (htm (:li (fmt “version ~a by ~a: “ i (post-user wiki-post))
                           (:a :href (conc post-name “?version=” (princ-to-string i))
                               (str (conc (s-utils:format-duration
                                           (- (get-universal-time)
                                              (post-timestamp wiki-post)))
                                          ” ago”))))))))))))

;; the main function handler
(defun milki ()
  (hunchentoot:no-cache)
  (let ((post-name (hunchentoot:script-name))
        (post-version (hunchentoot:get-parameter “version”))
        (edit-p (hunchentoot:get-parameter “edit”))
        (contents (hunchentoot:post-parameter “contents”)))
    (if post-version
        (setq post-version (parse-integer post-version))
        (setq post-version 0))
    (when contents
      (add-wiki-post post-name contents (hunchentoot:authorization)))
    (if edit-p
        (edit-wiki-post post-name post-version)
        (print-wiki-post post-name post-version))))

Além de poder criar e editar páginas, deveríamos poder fazer upload de arquivos. O seguinte handler adiciona essa funcionalidade:

; file-upload handler
(defun milki-upload ()
  (let ((sent-file (hunchentoot:post-parameter “file”))
        (remove-file (hunchentoot:get-parameter “remove”)))
    (when remove-file
      (ignore-errors (delete-file
                      (cl-fad:pathname-as-file
                       (concatenate ’string *upload-dir* remove-file))))
      (hunchentoot:redirect (hunchentoot:script-name)))
    (when (and sent-file (listp sent-file))
      (let ((path (car sent-file))
            (file-name (cadr sent-file)))
        ;; strip directory info sent by Windows browsers
        (when (search “Windows” (hunchentoot:user-agent) :test #’char-equal)
          (setq file-name (cl-ppcre:regex-replace “.*\\” file-name “”)))
        (let ((new-path (concatenate ’string *upload-dir* file-name)))
          (rename-file path (ensure-directories-exist new-path))))))
  (hunchentoot:no-cache)
  (let ((file-list (cl-fad:list-directory *upload-dir*)))
    (with-wiki-page-body
      (:center
       (:h2 “file uploader”)
       (:form :method :post :enctype “multipart/form-data”
              (:input :type :file :name “file”)
              (:input :type :submit :value “upload”))
       (when file-list
         (htm
          (:table :border “1″ :width “95%”
                  (dolist (file file-list)
                    (let ((file-link-name
                           (hunchentoot:url-encode
                            (cl-ppcre:regex-replace “.*/” (princ-to-string file) “”))))
                      (htm (:tr
                            (:td (:a :href
                                     (conc “/static/files/” file-link-name)
                                     (str file-link-name)))
                            (:td (str (conc “/static/files/” file-link-name)))
                            (:td (:a :href
                                     (conc (hunchentoot:script-name)
                                           “?remove=”
                                           file-link-name) “remove”)))))))))

Uma outra funcionalidade necessária é o índice, que lista todas as páginas já criadas (mesmo as que não conseguimos acessar pela estrutura de links):

;; milki page index
(defun generate-wiki-index ()
  (let ((wiki-pages-list ())
        (wiki-files (cl-fad:list-directory *data-storage-dir*)))
    (dolist (file-path wiki-files)
      (let ((file-name (cl-ppcre:regex-replace “.*/” (princ-to-string file-path) “”)))
        (when (string= (subseq file-name 0 5) “wiki-”)
          (push (hunchentoot:url-decode (subseq file-name 5)) wiki-pages-list))))
    (sort wiki-pages-list #’string<)))

; index handler
(defun milki-index ()
  (let ((wiki-pages (generate-wiki-index)))
    (with-wiki-page-body
      (:center (:h2 “wiki index”)
               (:h4 (fmt “~a pages so far…” (length wiki-pages))))
      (:ul
       (dolist (page wiki-pages)
         (htm (:li (:a :href page (str page)))))))))

Por fim, precisamos associar as funcões e diretórios às urls:

;; finally we setup hunchentoot environment
(eval-when (:execute :load-toplevel)
  (setf hunchentoot:*show-lisp-errors-p* t
        hunchentoot:*show-lisp-backtraces-p* t
        hunchentoot:*dispatch-table*
        (list ‘hunchentoot:dispatch-easy-handlers
              (hunchentoot:create-folder-dispatcher-and-handler
               “/static/files/” *upload-dir*)
              (hunchentoot:create-folder-dispatcher-and-handler
               “/static/” *static-files-dir*)
              (hunchentoot:create-prefix-dispatcher “/upload” ‘milki-upload)
              (hunchentoot:create-prefix-dispatcher “/index” ‘milki-index)
              (hunchentoot:create-prefix-dispatcher “/” ‘milki))))

E é isso. Uma wiki bem simples, feita para poucos usuários. Estou usando há quase um mês e até agora parece que funciona. Uma coisa que eu não vi em outras wikis (mas também não procurei muito) é a funcionalidade de preview online, que ajuda muito na hora de editar. Quem quiser o código completo é só seguir os links abaixo.

Download: milki.lisp
Arquivos que ficam no diretório de arquivos estáticos:

Sem comentários »

Server lisp no unix

Uma maneira fácil de rodar o seu server feito em lisp (talvez uma aplicação web usando Hunchentoot) é com o Detachtty.

No Ubuntu ou Debian basta apenas: apt-get install detachtty

Vamos supor que sua aplicação pode ser carregada com o arquivo web-app.lisp e iniciada com (web-app:start). No linux, com SBCL pode fazer o seguinte script:

#!/bin/bash

detachtty \
    --dribble-file /var/log/web-app-dribble \
    --log-file /var/log/detachtty.log \
    --pid-file /var/run/web-app.pid \
    /var/run/web-app.socket \
    /usr/bin/sbcl --eval "(compile-file \"web-app.lisp\")" \
                  --eval "(load \"web-app\")" \
                  --eval "(web-app:start)" \

Pronto, rodando o script o processo é executado como filho de init, e você pode deslogar sem problemas.

Uma coisa interessante é que com isso podemos fazer hot deploy, pois podemos conectar no processo lisp com:

$ attachtty /var/run/web-app.socket

E então, avaliar qualquer expressão, como por exemplo:

(compile-file "web-app.lisp")
(load "web-app")

Depois desconectar pressionando: CTRL+\

Assim atualizamos a aplicação com downtime 0.

Mais informações sobe o detachtty na CLiki.

3 comentários »

Ajax simples

Warning: Isso deve ser muito manjado, eu que nunca tinha visto. Estou escrevendo só para não esquecer

Imagine o seguinte caso de uma aplicação web: o usuário executa uma ação e o servidor apenas precisa ser notificado, todos os dados necessários para uma eventual alteração da tela já estão presentes. É o caso por exemplo de marcar um item (star-it), votar em um sistema de ratings, adicionar uma tag a um item, e por aí vai.

Podemos fazer da seguinte maneira: um link que o usuário vai clicar para executar uma ação:

<a id=’a_theid’ onclick=“return ajax(this)” href=“/action?user=x&a=y”>

Nesse link, no campo id colocamos uma codificação simples de um eventual objeto a ser alterado, nesse caso usamos a_theid, onde deve um existir um objeto na página com o id theid que poderá ser alterado. A URL que fará a notificação da execução no server esta no href. Dessa forma podemos implementar a função JavaScript ajax assim:

function ajax(node) {
    var v = node.id.split(/_/); // a_theid
    var item = v[1]; // theid

    // Executa a alteração de tela
    document.getElementById(item).innerHTML = …

    // notifica o servidor
    var action = new Image();
    action.src = node.href;

    return false;
}

O browser carrega imagens de forma assíncrona, então essa função não bloca, a alteração na tela é feita na hora e o server é notificado. A vantagem é que isso funciona em qualquer browser, mesmo alguns bem velhos.

Bom, a desvantagem é que você não sabe se o request falhou. Mas isso pode acontecer em poucos casos como:

  • O servidor está fora do ar: assim o usuário não conseguirá continuar navegando no sistema por muito tempo sem perceber isso, e o número de usuários que acessou o sistema antes dele cair e tem como executar umas ações que vão falhar (sem ele saber) é reduzido. E nessa caso o que você ia fazer? Cuspir um alert na cara do usuário com “Ooops, We did it again!“?
  • Existe um problema de rede entre a máquina do usuário e o servidor: isso recai no problema acima, tem o mesmo efeito.
  • O servidor está se comportando de maneira inesperada, gerando erros: aí amigo, o AJAX é o menor dos seus problemas.

Bom, eu vi isso no código do YCombinator News (Hacker News). Dá uma olhada lá para ver isso funcionando. Já o Digg e o Reddit usam Prototype. Alias, o Reddit poderia usar isso, pois eles não passam uma função de callback no caso de erro do Ajax.Request. Já o Digg passa uma função com um alert “ERROR: …”

3 comentários »

Common Lisp e AJAX

E não é que funciona! :D Usei:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (require :hunchentoot)
  (require :cl-who)
  (require :ht-ajax))

(defpackage :ajax-test
  (:use :cl :hunchentoot :cl-who)
  (:export #:start-web #:stop-web))

(in-package :ajax-test)

;; local directory with lokris.js
(defparameter */static-local-dir* “/Users/lucindo/Documents/Lisp/tmp/”)
;; can be any url
(defparameter *ajax-handler-url* “/ajax”)
;; using lokris (very small)
(defparameter *ajax-processor* (ht-ajax:make-ajax-processor
                                :type :lokris
                                :server-uri *ajax-handler-url*
                                :js-file-uris “/static/lokris.js”))

;; hunchentoot handlers setup
(eval-when (:compile-toplevel :load-toplevel :execute)
  (setf *dispatch-table*
        (list ‘dispatch-easy-handlers
              (create-folder-dispatcher-and-handler “/static/”
                                                    */static-local-dir*
                                                    “text/plain”)
              (create-prefix-dispatcher *ajax-handler-url*
                                        (ht-ajax:get-handler
                                         *ajax-processor*))
              (create-prefix-dispatcher “/js” ‘java-script)
              (create-prefix-dispatcher “/” ‘main-page))))

(defparameter *web-server* nil)

(defun start-web (&optional (port 4242))
  (setf *web-server* (start-server :port port)))

(defun stop-web ()
  (stop-server *web-server*))

;; eval and print a lisp expression
(defun testfunc (command)
  (prin1-to-string (eval (read-from-string command nil))))
(ht-ajax:export-func *ajax-processor* ‘testfunc :method :post)

(defun java-script ()
  “function command_clicked(txt) {
    var command = document.getElementById(’command’).value;
    ajax_testfunc_set_element(’result’, command); }”)

(defun main-page ()
  (with-html-output-to-string (*standard-output* nil :prologue t)
    (:html
     (:head
      (:script :type “text/javascript” :src “/js”)
      (:title “AJAX test”))
      (fmt “~a” (ht-ajax:generate-prologue *ajax-processor*))
     (:body
      (:h1 “AJAX test”)
      (:table :width “50%”
              (:tr
               (:td :colspan “2″
                    (:span :id “result”
                           (:i “no results yet”))))
              (:tr
               (:td :width “70%”
                    (:input :type “text”
                            :size “70″
                            :name “command”
                            :id “command”))
               (:td
                (:input :type “button”
                        :value “Eval”
                        :onclick “javascript:command_clicked();”))))))))

Source: ajax-test.lisp (nele mudei para usar Prototype. O legal é o que o HT-AJAX suporta vários processadores ajax: Lokris, Prototype, Dojo, Yahoo! [YUI]).

Se você também não gosta de HTML junto com código pode usar HTML-TEMPLATE. Se você não gosta mesmo de HTML pode usar o Weblocks em que o design fica em CSS e o resto é codigo Lisp (o framework gera todo o HTML e JavaScript para AJAX).

Quase tudo tendo como base Ediware.

2 comentários »