Showing posts with label AJAX. Show all posts
Showing posts with label AJAX. Show all posts

Thursday, January 13, 2011

springmvc 中ajax提交发生乱码问题

这个问题目前只是在springmvc项目中碰到,在IE和Chrome中,当用jQuery.post()方法发起ajax请求时,Controller中收到的中文内容变成乱码了,而在firefox中用jQuery.post()进行ajax请求时是正常的。
在Controller中调用如下代码(项目为UTF-8编码):


// name 是post收到的字符串变量名
System.out.println(new String(name.getBytes(HTTP.ISO_8859_1), HTTP.UTF_8));

可以看到乱码恢复正常,说明IE和Chrome提交的ajax请求中的内容被编码成 ISO-8859-1 编码了。
而实际上,对于IE而已,所有的ajax请求都是以UTF-8方式发起的,通过以下方式设置ajax请求的编码为GBK实际是没有用的,服务器收到仍然是UTF-8编码的请求体:

xmlhttp.setRequestHeader( "Content-Type", "text/html;charset=GBK" );
// or
xmlhttp.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded; charset=GBK");

因此可以肯定是springmvc在收到POST请求后,将请求体的数据用 ISO-8859-1 编码来处理了,最后传到Controller中时变成了乱码,但是为何firefox中提交的却仍然是正确的呢?
在firebug中观察firefox中的ajax请求头,可以看到firebug中的提示:

Content-Type application/x-www-form-urlencoded; charset=UTF-8

而在IE中用http analysis工具看到的却是:

Content-Type application/x-www-form-urlencoded

google Chrome中与IE一样,ajax请求头中没有指明编码,所以Chrome和IE一样将发生乱码了。
查看jquery-1.4.4.js源码可以看到,jQuery中原来的 contentType设置为"application/x-www-form-urlencoded",IE和Chrome中的请求头显示是正确的,没有问题,反而是firefox将contentType中设置了编码。
从这个请求头分析来看,应该是springmvc没有得到请求的编码,而将其内容设置为ISO-8859-1了,因此发生了乱码的情况。

解决方法还是参考firefox的请求头,在contentType中指定编码,明确告诉服务器端,当前请求体的编码方式为UTF-8。

xmlhttp.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");

如果使用jQuery发起ajax请求,javascript的代码改为如下方式发起ajax请求,而不是以$.post()和$.get()等快捷方法:

$.ajax({
url: url,
type: "POST",
dataType: "html",
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
data: data,
complete:function(data) {
}
});

Tuesday, December 23, 2008

IE6/IE7上ajax请求的一个bug

UTF-8编码的页面,在IE6/IE7中用ajax方式和普通直接访问方式请求此url,结果是不一样的:
http://localhost/test.php?q=测试中文

ajax: http://localhost/test.php?q=测试中文
服务器端收到的请求,其中的中文参数是以ISO-8859-1编码的(浏览器的默认编码方式),所以服务器端收到的参数其实是乱码的,所以ajax请求URL中的参数一定要经过encode,除了ie之外,其他浏览器测试下来都会自动url encode其中的参数。

非ajax: http://localhost/test.php?q=%E6%B5%8B%E8%AF%95%E4%B8%AD%E6%96%87
服务器端收到正常的UTF-8编码后的参数。

Monday, October 27, 2008

Ajax in Tapestry5

在Tapestry5中使用一些简单的ajax还是比较方便的,ajax返回结果可以是JSONObject/JSONArray/Component/Block/String/ResponseStream等,可设置zone组件中的show/update这二个自定义的javascript方法实现复杂效果。

返回的Block和Component是以JSON对象形式返回,其中key必须为"content",value即为要渲染的内容,这是用Zone组件更新页面的最基本用法。也可以将ResponseStream以"application/json"形式返回客户端,见下面例子。

Component是指org.apache.tapestry5.runtime.Component对象,可以通过ComponentSource.getPage()或者ComponentSource.getComponent()方法获取。
具体使用方法见以下例子:

import java.util.Date;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.StreamResponse;
import org.apache.tapestry5.annotations.BeginRender;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.BeanDisplay;
import org.apache.tapestry5.internal.services.ClientBehaviorSupport;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.runtime.Component;
import org.apache.tapestry5.services.ComponentSource;
import org.apache.tapestry5.util.TextStreamResponse;

public class Test {

@Environmental
private ClientBehaviorSupport clientBehaviorSupport;

@Inject
@Property
private Block block;

@Inject
private ComponentSource componentSource;

@InjectComponent
private BeanDisplay beanDisplay;

@BeginRender
void begin() {
// clientBehaviorSupport.addZone("link1", "show", "update");
clientBehaviorSupport.linkZone("link1", "zone1");
}

StreamResponse onActionFromLink1() {
StreamResponse response = new TextStreamResponse("application/json", "{\"content\":\"更新zone1中原来的内容\"}");
return response;
}

JSONObject onActionFromLink2() {
return new JSONObject().put("content", "update zone2.");
}

Block onActionFromLink3() {
return block;
}

BeanDisplay onActionFromLink4() {
// return a component as ajax result.
return beanDisplay;
}

// JSONArray onActionFromLink2() {
// return new JSONArray("[{user: 'test', firstName: 'yu'}]");
// }

// Component onActionFromLink4() {
// return componentSource.getPage("Start");
// }

public Date getStr() {
return new Date();
}

}
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>ajax in tapestry5</title>
</head>
<body>
<div style="margin-left: 50px">
zone1:<t:zone t:id="zone1">ajax</t:zone>
<t:actionLink id="link1" t:id="link1">用ajax返回ResponseStream更新zone1,此链接通过ClientBehaviorSupport关联到zone1</t:actionLink>
<br />
zone2:<t:zone t:id="zone2">ajax</t:zone>
<t:actionLink t:id="link2" t:zone="zone2">update zone2 using json Object</t:actionLink>
<br />
zone3:<t:block t:id="block">${str}</t:block>
<t:zone t:id="zone3">
<t:delegate to="block"/>
</t:zone>
<a t:type="actionlink" t:id="link3" href="#" t:zone="zone3">update zone3 using Block</a>
<br />
<t:block><t:beanDisplay t:object="this"/></t:block>
zone4:<t:zone t:id="zone4">ajax</t:zone>
<t:actionLink t:id="link4" t:zone="zone4">update zone4 using Component</t:actionLink>
</div>
</body>
</html>

Saturday, March 22, 2008

Google AJAX Language API - Basic Scripting HTTP Request


<!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">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Google AJAX Language API - Basic Translation</title>
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">

google.load("language", "1");

function initialize() {
google.language.translate("Hello world", "en", "zh-CN", function(result) {
if (!result.error) {
var container = document.getElementById("translation");
container.innerHTML = result.translation;
}
});

var text = "简体中文测试";
google.language.detect(text, function(result) {
if (!result.error) {
var language = 'unknown';
for (l in google.language.Languages) {
if (google.language.Languages[l] == result.language) {
language = l;
break;
}
}
var container = document.getElementById("detection");
container.innerHTML = text + " is: " + language + "";
}
});
}
google.setOnLoadCallback(initialize);

</script>
</head>
<body>
<div id="translation"></div>
<div id="detection"></div>
</body>
</html>

Google get response and execute response text through scripting http request. Scripting HTTP Request step:
  • First add an dynamic "script" node after head, even if document source have not head tag, javascript will still run right. If add "script" node to document.body node, javascript may be error because javascript can't get body element.
  • Then set this "script" node "src" to get google translate service, and it will return a javascript statement, and when "script" loaded, it will be executed auto.
  • At last, Delete this "script" node.
Here is an simple example:
1. javascript file - "getTextWithScript.js";

var getTextWithScript = function(callback) {
// Create a new script element and add it to the document.
var head=document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type="text/javascript";
script.charset="utf-8";
head.appendChild(script);

// Get a unique function name.
var funcname = "func" + getTextWithScript.counter++;

// Define a function with that name, using this function as a
// convenient namespace. The script generated on the server
// invokes this function.
getTextWithScript[funcname] = function() {
// Pass the text to the callback function
callback(Array.prototype.slice.call(arguments));

// Clean up the script tag and the generated function.
script.onload = null;
head.removeChild(script);
delete script;
delete getTextWithScript[funcname];
}

// Encode the URL we want to fetch and the name of the function
// as arguments to the jsquoter.php server-side script. Set the src
// property of the script tag to fetch the URL.
script.src = "jsquoter.php" + "?func=" + encodeURIComponent("getTextWithScript." + funcname);
}

// We use this to generate unique function callback names in case there
// is more than one request pending at a time.
getTextWithScript.counter = 0;

2. server process request file - "jsquoter.php";

<?php
// Tell the browser that we're sending a script
header("Content-Type: text/javascript");
// Get arguments from the URL
$func = $_GET["func"];
// process
echo "$func('test', 'from', 'php', 'file');";
?>

3. test file - "getTextWithScript.html":

<!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">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Scripting HTTP</title>
<script type="text/javascript" src="getTextWithScript.js"></script>
<script type="text/javascript">
function callback(){
document.getElementById('main').innerHTML = arguments[0].join(" ");
}
getTextWithScript(callback);
</script>
</head>
<body>
<div id="main"></div>
</body>
</html>

test file will get result: "test from php file"

Sunday, August 05, 2007

prototype javascript library ajax example


<div id="ajax_div">
use ajax to update it!
</div>
<br/>
<div id="ajax_failure">
if evalScript set false, sayHi is not defined!
</div>

<input type="button" name="button_ajax" value="ajax update div" id="button_ajax" />

<script type="text/javascript" charset="utf-8">
function doc(argument) {
document.write("<p>\n");
document.write(argument);
document.write("</p>\n");
}
var url = '/prototype/ajax_eval_script';
var options = {
method: 'get',
evalScripts: true,
parameters: 'id=1',
insertion: Insertion.Top
}

//new Ajax.Updater('ajax_div', url, options);
//new Ajax.Request(url, {onComplete: function (req) { alert(req.responseText); }});
var ajax = new Ajax.Request();
doc('ajax.transport type is ' + ajax.transport); //ajax.transport = Ajax.getTransport();
new Ajax.Updater({success: 'ajax_div', failure: 'ajax_failure'}, url, options);

ajax.setOptions(Object.extend(options, {onComplete: function(req) {
$('ajax_div').innerHTML = req + "<br/>\n" +
"req == ajax.transport is " + (req == ajax.transport) + "<br/>\n" +
"Server is " + req.getResponseHeader('Server') + "<br/>\n" +
"ajax request status is " + ajax.transport.status + "<br/>\n";
}
}));
$('button_ajax').onclick = function (event) { ajax.request('/prototype/ajax_eval_script'); };
</script>

/prototype/ajax_eval_script

<script language="javascript" type="text/javascript">
//function sayHi(){ // do nothing, function is not generated in runtime
//var sayHi = function(){ // not use the "var" keyword
sayHi = function(){
alert('Hi');
}
</script>

<input type="button" value="Click Me" onclick="sayHi()"/>
Note that in the previous example we did not use the var keyword to declare the variable. Doing so would have created a function object that would be local to the script block (at least in IE and FireFox). Without the var keyword the function object is scoped to the window, which is our intent.

Wednesday, July 25, 2007

usage of form_remote_tag and remote_form_for


<%= form_remote_tag :html => { :action => url_for(:controller => "some", :action => "place", :id => 1), :id => "form_id" }, :update => "ajax_update_div" %></form>

A "fall-through" target for browsers that doesn‘t do JavaScript can be specified with the :action/:method options on :html.
<% form_remote_tag({:html => { :action => url_for(:controller => "other", :action => "place", :id => 1), :id => "form_id", :method => :get }, :url => url_for(:controller => "some", :action => "place", :id => 1), :update => "ajax_update_div"}) do%>
<% end %>

<% remote_form_for :person, @person, :html => {:id => 'form_id', :name => 'form_name', :method => :get, :action => url_for(:controller => 'where', :action => 'some', :id => @person)}, :url => { :action => "test" }, :update => 'ajax_div' do |f| %>
First name: <%= f.text_field :first_name %>
Last name : <%= f.text_field :last_name %>
Biography : <%= f.text_area :biography %>
Admin? : <%= f.check_box :admin %>
<% end %>


form_for(object_name, *args, &proc)
Creates a form and a scope around a specific model object, which is then used as a base for questioning about values for the fields. Examples:

<% form_for :person, @person, :url => { :action => "update" } do |f| %>
First name: <%= f.text_field :first_name %>
Last name : <%= f.text_field :last_name %>
Biography : <%= f.text_area :biography %>
Admin? : <%= f.check_box :admin %>
<% end %>

Worth noting is that the form_for tag is called in a ERb evaluation block, not a ERb output block. So that’s <% %>, not <%= %>. Also worth noting is that the form_for yields a form_builder object, in this example as f, which emulates the API for the stand-alone FormHelper methods, but without the object name. So instead of text_field :person, :name, you get away with f.text_field :name.
That in itself is a modest increase in comfort. The big news is that form_for allows us to more easily escape the instance variable convention, so while the stand-alone approach would require text_field :person, :name, :object => person to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with :person, person and all subsequent field calls save :person and :object => person.
Also note that form_for doesn’t create an exclusive scope. It’s still possible to use both the stand-alone FormHelper methods and methods from FormTagHelper. Example:

<% form_for :person, @person, :url => { :action => "update" } do |f| %>
First name: <%= f.text_field :first_name %>
Last name : <%= f.text_field :last_name %>
Biography : <%= text_area :person, :biography %>
Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
<% end %>

Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base. Like collection_select and datetime_select.
Html attributes for the form tag can be given as :html => {…}. Example:

<% form_for :person, @person, :html => {:id => 'person_form'} do |f| %>
...
<% end %>

You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers, then use your custom builder like so:

<% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<%= text_area :person, :biography %>
<%= check_box_tag "person[admin]", @person.company.admin? %>
<% end %>

In many cases you will want to wrap the above in another helper, such as:

def labelled_form_for(name, object, options, &proc)
form_for(name, object, options.merge(:builder => LabellingFormBuiler), &proc)
end

Monday, July 23, 2007

usage of Prototype Ajax.Responders.register



Ajax.Responders.register({
onCreate: function() {
Ajax.activeRequestCount++;
},

onComplete: function() {
Ajax.activeRequestCount--;
}
});

Ajax.Responders.register(obj) 是将obj注册到Ajax.Responders.responders中,原代码:this.responders.push(responderToAdd) 上面是注册了一个对象,其中包含了二个方法(onCreate和onComplete)。这个obj注册了之后,就成了responders数组中的一个值,在后面Ajax.Responders.dispatch里调用对象responder时会遍历到此obj,callback即为obj中的function name(onCreate和onComplete)。 dispatch里会对所有responders数组中对象执行此callback方法。



Del.icio.us :

Thursday, March 08, 2007

AJAX problem with drag and drop for Internet Explorer using Scriptaculous

I have been having this kind of hair tearing problem with Internet Explorer 6 in the last few days while trying to implement drag and drop within the AJAX framework of Ruby On Rails. The problem is extremely frustrating to track down as it works perfectly with Firefox (a sign of the time, I suppose, IE used to be the benchmark for sane rendering, but alas no more !). Hopefully by detailing it here, no one else will have to go through the same pain I did.

I should point out here that most advertised features work perfectly in both Internet Explorer and Firefox. However, it was a combination of what I did which caused problems in IE. So if you ever get this problem where Internet Explorer throws out this Javascript error "Unspecified error" each time you attempt to move a drag-able item over potential drop sites, then read on.

Ruby On Rails comes integrated with a pretty cool Javascript client library called Scriptaculous, which takes away the pain of cross browser incompatibilities (supposedly, but as I will show here, even this library has its limits when it comes to browser idiosyncrasies). Scriptaculous provides AJAX support, drag and drop, auto-complete, and many other cool features out of the box. I had no problem implementing the basic drag and drop, which worked fine in both Firefox and Internet Explorer. However, I ran into the above mentioned error where Internet Explorer will consistently throw out streams of Javascript "Unspecified error" once a drop has been made. The error itself is of no help as it gives you no clues as to what the problem is. What is it which has completely messed up the DOM in such a way that once the first drop is made, no other items can be dragged around without causing errors ? The Javascript error happens consistently on a block of code in the dragdrop.js file at line 1589, column 7, ( or prototype.js 2326, column 7) which is highlighted bold below.


var Enumerable = {
each: function(iterator) {
var index = 0;
try {
this._each(function(value) {
try {
iterator(value, index++);
} catch (e) {
if (e != $continue) throw e;
}
});
} catch (e) {
if (e != $break) throw e;
}
},


I tried out the Scriptaculous demo shopping cart on Internet Explorer, which works perfectly. This tells me Internet Explorer is not the problem, or indeed the basic boiler plate drag and drop code (just a process of elimination). Well, back to my code. It must be something in my code that causes this. The basic code must work as I am able to drop the draggable item once. So, it must be something to do with the sequence of events, rather than the drag and drop technology itself. I viewed the html before and after the drag and drop action, looks ok. Then I viewed the list of Droppables objects using the JScript debugger, this looked OK too.

Google was of no help. Not sure if it is because the subject is relatively new (Ruby on Rails, AJAX, Scriptaculous etc.). Whatever I could find led me to try out these desperate measures:

* download the latest version of Scriptaculous instead of using the one packaged with Rails
* deploy an application called Browser Object Helper daemon, which allows me to look at all the third party extensions loaded into IE on start up, as these BOH's have been known to cause problems with IE crashing with "Unspecified error"
* re-engineer my pages so that the different fragments get generated together instead of via cascaded Javascript hooks on the :onComplete event

None of the above solutions helped !! Where is Sherlock when you need him ?

Then, on the third day (what is it about the third day ? I always seem to have more luck on the third day !), something dawned on me. I quickly checked out the list of drop sites again in the JScript debugger, and sure enough, the error always happened on the second drop site in the list. A quick drill down gave me an insight into my problem, in that the second drop site has the same name as the first drop site. How is this possible ? pages are generated on the fly, and all the drop sites are guaranteed to have unique id's within the page. Looking through the code again told me the problem, which is to do with Rails's partial rendering mechanism. Rails has a very good built in AJAX engine, which allows you to refresh fragments of the page without regenerating the whole page (this is the technology used extensively at Google Maps). It is this partial rendering which is the root of all problems. Normally, when a page is reloaded, the whole list of drop sites, which is built up from the initial rendering, is started from scratch. In fact, this should work perfectly too, normally, if you do not re-generate the drop sites within the page as part of partial rendering. However, if these drop sites are re-generated (as mine is), then this is when IE behaves differently to Firefox.

Firefox looks like it did the right thing (or maybe not, depending on your point of view), and still honours calls to the DOM element associated with the defunct drop site, specifically the cumulativeOffset() call in dragdrop.js, which traverses up the DOM in order to calculate the overlap between the dragged item and the drop site. It could be because Firefox looks up the DOM element by name and resolves it to the newly created element automatically (cool or what ?).

However, Internet Explorer goes completely crazy on this (or maybe it should, again depending on your point of view) and throws out these JScript errors about the dead drop site still sitting happily in the cached list of Droppables. This list is created by the drag and drop technology, and because we are not reloading the page, is never cleared out properly between AJAX calls.

The solution, once known looks so simple, is to place this Javascript call into the page fragment which is re-built on each AJAX call (but only if you intend to re-generate the drop sites):

<script type="text/javascript">
if (document.all) { Droppables.drops = [] }
</script>

Go, go, gadgets, go !!

The footnote is, however, even though Firefox is more forgiving in this instance (I mean it does the right thing in terms of intended design), in the long run, if your partial rendering is more intensive, it is probably a good idea to clear out the drop sites regardless of the browser to make it more memory efficient and also quicker to iterate through the list of possible drop sites each time an item is dragged around on the screen.

Also, the moral of the story is, Internet Explorer absolutely detests having different DOM elements with the same identifier. Firefox seems to do something internally to cope with this rather impressively. As a client side developer, I know which one I'd rather use. Alas, the majority out there do not write web pages for a living. Also, if technical superiority counts, we'd all be using Betamax tapes, right ?

This article source: http://public.transcraft.co.uk/articles/development/ie_ajax_dragdrop.html