Enunciado: Dado un formulario Ajax con los campos Asunto y Contenido cuyo resultado es el envío de su contenido en un email, añadir un campo de tipo archivo para poder adjuntar documentos al mismo.
Bueno, pues lo primero es cambiar el formulario. Primero, reemplazamos la helper que lo genera de form_remote_tag a form_tag. Segundo, hacemos que su enctype sea "multipart/form-data". De esta forma permitimos adjuntar archivos a través del formulario. Y tercero, debajo del formulario creamos un iframe oculto donde enviar la petición del formulario para que parezca ajax.
<div style="overflow:hidden;width:0px;height:0px"><iframe id="hiddenIframe" name="hiddenIframe" src="" width="0" height="0"></iframe><div>
Lo siguiente es añadir un campo de tipo archivo. En rails
<%= file_field 'mail','attachment' %>
Probamos el envío para ver si nos llega al servidor lo que nos tiene que llegar y todo va bien. Así que ponemos en el evento onsubmit del formulario todos los efectos javascript que antes manejaba el helper form_remote_tag. Y todo se rompe. Y lo que leo en los logs y nada es lo mismo. Hasta que quito el efecto de deshabilitar el formulario en el evento onsubmit y todo vuelve a la normalidad. En fin, así es la vida. Sigamos.
En el controller hemos de recoger el parámetro que recoje el archivo adjunto y pasarlo tal cual al método que realiza el envío del archivo. Además, utilizaremos el plugin respond_to_parent para manejar la respuesta en el iframe oculto.
responds_to_parent do
render :update do |page|
...
end
end
La clase que realiza el envío debe crear con el archivo temporal un attachment pero como de un TempFile no se conoce su tipo (salvo si utilizamos algún plugin al uso), lo haremos de forma genérica. Siendo att la variable que contiene el archivo temporal...
filename = att.original_filename
filedata = att.read
attachment "application/octet-stream" do |a|
a.body = filedata
a.filename = filename
end
Y cuando parece que todo está listo, nos encontramos con una cosilla más. Resulta que cuando se adjuntan attachments no se ejecuta el render normal del email por lo que es necesario crear a mano un part con el contenido del correo y poner el content-type del correo a multipart/alternative. En el método de envío del correo, donde antes decía
@body[:content] = content
ahora debe decir
part :content_type => "text/html", :body => content
Y con esto y un bizcocho, todo funcionando. Espero que se entienda. ¡Salud y rocanrol!
Kick your ass to heaven
With rock'n'roll tonight
I'll make this night a special one
Make you feel alright
Shoot my heat into your body
Give you all my size
I'm gonna beat the beat tonight
It's time to break the ice
Dynamite
Mostrando entradas con la etiqueta ajax. Mostrar todas las entradas
Mostrando entradas con la etiqueta ajax. Mostrar todas las entradas
viernes, 26 de marzo de 2010
viernes, 17 de julio de 2009
Combos dependientes re-visited
Hace tiempo ya conté cómo hacer combos dependientes con observe_field pero debe ser que me hago viejo y que me gusta hacer las cosas cada vez más pedrestres. En fin, que tirando a pelo de prototype, un poco de javascript y adaptando un controlador, conseguimos lo mismo de forma muy sencilla.
Supongamos dos modelos dependientes
class Country < ActiveRecord::Base
has_many :provinces
end
class Province < ActiveRecord::Base
belongs_to :country
end
enlazados en nuestro archivo de rutas como
map.resources :countries do |country|
country.resources :provinces
end
En a vista enlazamos países y provincias en dos combos...
<%= select :person, :country_id, countries_for_select.map{|c| [c.name, c.id]}, {}, {:onchange => "change_provinces('person_province_id', this.value);", :class => 'w150'} %>
<%= select :person, :province_id, provinces_for_select.map{|c| [c.name, c.id]}, {}, {:class => 'w150'} %>
donde countries_for_select y provinces_for_select nos dan los registro a pintar inicialmente en cada uno de ellos.
Sólo falta la función javascript change_provinces que haga la llamada Ajax y el repintado del combo cuyo ide recibe como primer parámetro...
function change_provinces(combo_id, country_id){
var url = '/countries/' + country_id + '/provinces';
var combo = $(combo_id);
for (var i = combo.options.length-1; i >= 0; i--){
combo.options[i] = null;
}
new Ajax.Request(url, {
method: 'get',
onSuccess: function(transport) {
prov_array = eval(transport.responseText);
for (var k = 0; k < prov_array.length; k++){
combo.options[k] = new Option(prov_array[k][1], prov_array[k][0]);
}
combo.options[0].selected = true;
},
onFailure:function(transport) {
}
});
}
... y finalmente adaptar provinces_controller
def index
@provinces = ...
if request.xhr?
render :text => @provinces.map{|province| [province.id, province.name]}.inspect
return
end
end
Y a correr. Ale, salud y rocanrol!
We shall go on to the end, we shall fight in France,
we shall fight on the seas and oceans,
we shall fight with growing confidence and growing strength in the air,
we shall defend our Island, whatever the cost may be,
we shall fight on the beaches,
we shall fight on the landing grounds,
we shall fight in the fields and in the streets,
we shall fight in the hills, we shall never surrender!"
Supongamos dos modelos dependientes
class Country < ActiveRecord::Base
has_many :provinces
end
class Province < ActiveRecord::Base
belongs_to :country
end
enlazados en nuestro archivo de rutas como
map.resources :countries do |country|
country.resources :provinces
end
En a vista enlazamos países y provincias en dos combos...
<%= select :person, :country_id, countries_for_select.map{|c| [c.name, c.id]}, {}, {:onchange => "change_provinces('person_province_id', this.value);", :class => 'w150'} %>
<%= select :person, :province_id, provinces_for_select.map{|c| [c.name, c.id]}, {}, {:class => 'w150'} %>
donde countries_for_select y provinces_for_select nos dan los registro a pintar inicialmente en cada uno de ellos.
Sólo falta la función javascript change_provinces que haga la llamada Ajax y el repintado del combo cuyo ide recibe como primer parámetro...
function change_provinces(combo_id, country_id){
var url = '/countries/' + country_id + '/provinces';
var combo = $(combo_id);
for (var i = combo.options.length-1; i >= 0; i--){
combo.options[i] = null;
}
new Ajax.Request(url, {
method: 'get',
onSuccess: function(transport) {
prov_array = eval(transport.responseText);
for (var k = 0; k < prov_array.length; k++){
combo.options[k] = new Option(prov_array[k][1], prov_array[k][0]);
}
combo.options[0].selected = true;
},
onFailure:function(transport) {
}
});
}
... y finalmente adaptar provinces_controller
def index
@provinces = ...
if request.xhr?
render :text => @provinces.map{|province| [province.id, province.name]}.inspect
return
end
end
Y a correr. Ale, salud y rocanrol!
We shall go on to the end, we shall fight in France,
we shall fight on the seas and oceans,
we shall fight with growing confidence and growing strength in the air,
we shall defend our Island, whatever the cost may be,
we shall fight on the beaches,
we shall fight on the landing grounds,
we shall fight in the fields and in the streets,
we shall fight in the hills, we shall never surrender!"
miércoles, 14 de enero de 2009
Comenzamos 2009 con pair programming
Feliz 2009.
Últimamente no pasan cosas interesantes entre Rails y yo. No sé si, como cantaba Medina Azahara 'se perdió el amor' o la cosa va más por 'la vida sigue igual' de Julio Iglesias.
Sin embargo, hoy, por primera vez y después de mucho escuchar cómo otros hablaban de ello, he perdido la virginidad en el pair programming. Y lo he hecho con una persona tan escéptica o más que yo acerca de sus bondades (hola David).
Teníamos que meter un combo de idiomas en 9 formularios de actualización de datos y que en el evento onchange del combo se recargase el valor de cierto campo con su traducción al idioma seleccionado en dicha combo(este párrafo es para ahogarse).
Hemos decidido hacer juntos un partial con el combo y el observe_field que maneje la recarga del valor del campo a traducir y un método de controlador que haga la parte servidor de búsqueda de traducciones. Después, por separado, cada uno ha metido el partial en sus formularios de edición y ha modificado los métodos que éstos invocaban. Previamente, habíamos acordado la forma de implementar los cambios en estos métodos.
El resultado, desde mi punto de vista, ha sido muy positivo:
Salud y rocanrol
Well I'm upper upper class high society
God's gift to ballroom notoriety
And I always fill my ballroom
The event is never small
The social pages say I've got
The biggest balls of all
Últimamente no pasan cosas interesantes entre Rails y yo. No sé si, como cantaba Medina Azahara 'se perdió el amor' o la cosa va más por 'la vida sigue igual' de Julio Iglesias.
Sin embargo, hoy, por primera vez y después de mucho escuchar cómo otros hablaban de ello, he perdido la virginidad en el pair programming. Y lo he hecho con una persona tan escéptica o más que yo acerca de sus bondades (hola David).
Teníamos que meter un combo de idiomas en 9 formularios de actualización de datos y que en el evento onchange del combo se recargase el valor de cierto campo con su traducción al idioma seleccionado en dicha combo(este párrafo es para ahogarse).
Hemos decidido hacer juntos un partial con el combo y el observe_field que maneje la recarga del valor del campo a traducir y un método de controlador que haga la parte servidor de búsqueda de traducciones. Después, por separado, cada uno ha metido el partial en sus formularios de edición y ha modificado los métodos que éstos invocaban. Previamente, habíamos acordado la forma de implementar los cambios en estos métodos.
El resultado, desde mi punto de vista, ha sido muy positivo:
- hemos desarrollado el trabajo común en menos tiempo que si lo hubiera hecho uno sólo
- el código resultante de ese trabajo sólo ha necesitado un pequeño retoque a posteriori
- además, David y yo tenemos fortalezas y defectos bastante complementarios, con lo que los dos hemos aportado
- el trabajo por separado nos ha llevado muy poco tiempo porque hemos dejado la parte común muy fina
Salud y rocanrol
Well I'm upper upper class high society
God's gift to ballroom notoriety
And I always fill my ballroom
The event is never small
The social pages say I've got
The biggest balls of all
miércoles, 7 de mayo de 2008
La caché (I) - revisited
Comenta mi amigo Xavi a mi anterior post que las peticiones Ajax, como cualquier otra petición http realizada desde un navegador pueden ser tanto POST como GET. Y no le falta razón.
Tirando del hilo llegamos a prototype.js donde el objeto básico para las peticiones Ajax tiene un parámetro de configuración donde indicarle con qué método se hace la petición (por defecto, POST):
Ajax.Base = Class.create({
initialize: function(options) {
this.options = {
method: 'post',
asynchronous: true,
contentType: 'application/x-www-form-urlencoded',
encoding: 'UTF-8',
parameters: '',
evalJSON: true,
evalJS: true
}; ...
El parámetro termina llegándole al objeto Ajax.Request que es el en cargado de hacer las peticiones en función del método, el navegador, etc.
Esta vez he sido yo el que ha caído en el assume.
Malditos roedores!!!
Tirando del hilo llegamos a prototype.js donde el objeto básico para las peticiones Ajax tiene un parámetro de configuración donde indicarle con qué método se hace la petición (por defecto, POST):
Ajax.Base = Class.create({
initialize: function(options) {
this.options = {
method: 'post',
asynchronous: true,
contentType: 'application/x-www-form-urlencoded',
encoding: 'UTF-8',
parameters: '',
evalJSON: true,
evalJS: true
}; ...
El parámetro termina llegándole al objeto Ajax.Request que es el en cargado de hacer las peticiones en función del método, el navegador, etc.
Esta vez he sido yo el que ha caído en el assume.
Malditos roedores!!!
martes, 6 de mayo de 2008
La caché (I)
Llevo algunos días viendo cosillas básicas de la caché de Rails. En concreto, he estado intentando cachear el resultado de una acción solicitada por Ajax cuyo resultado es renderizado por un archivo rjs. Y no ha habido manera. Hasta que recordando los tiempos en los que el 90% de mi tiempo lo dedicaba a depurar código Java sobre la plataforma Banksphere (todos tenemos un pasado oscuro) me metí un poquito en el código y llegué a caching.rb.
private
def caching_allowed
request.get? && response.headers['Status'].to_i == 200
end
Así que, por defecto, si una petición no se hace por GET no se cachea. Y las peticiones Ajax son POST de tó la vida.
Confiando un poquito más en el programador podemos cambiar la condición de cacheo por
(request.get? || request.xhr?) && response.headers['Status'].to_i == 200
de forma que las peticiones Ajax, aún no siendo GET puedan cachearse.
Otro funcionamiento que no me convence es que el archivo cacheado se genera siempre y con extensión HTML. Entiendo que, una vez enganchado el resultado del cacheo (esto se podrá decir en castellano de algún modo mejor, no?) el hecho de que se genere siempre el archivo será irrelevante, pero se puede escribir una condición extra al método cache_page para que si el archivo existe no lo escriba:
return if File.exists?(page_cache_path(path))
Respecto a que el archivo cacheado tenga extensión HTML creo recordar que se puede solucionar en la configuración de Apache, indicándole que el contenido de ciertos directorios se sirva como javascript, pero esto tengo que verlo.
Salud y rocanrol!
private
def caching_allowed
request.get? && response.headers['Status'].to_i == 200
end
Así que, por defecto, si una petición no se hace por GET no se cachea. Y las peticiones Ajax son POST de tó la vida.
Confiando un poquito más en el programador podemos cambiar la condición de cacheo por
(request.get? || request.xhr?) && response.headers['Status'].to_i == 200
de forma que las peticiones Ajax, aún no siendo GET puedan cachearse.
Otro funcionamiento que no me convence es que el archivo cacheado se genera siempre y con extensión HTML. Entiendo que, una vez enganchado el resultado del cacheo (esto se podrá decir en castellano de algún modo mejor, no?) el hecho de que se genere siempre el archivo será irrelevante, pero se puede escribir una condición extra al método cache_page para que si el archivo existe no lo escriba:
return if File.exists?(page_cache_path(path))
Respecto a que el archivo cacheado tenga extensión HTML creo recordar que se puede solucionar en la configuración de Apache, indicándole que el contenido de ciertos directorios se sirva como javascript, pero esto tengo que verlo.
Salud y rocanrol!
Suscribirse a:
Entradas (Atom)