001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.sample.json.client;
017:
018: import com.google.gwt.http.client.Request;
019: import com.google.gwt.http.client.RequestBuilder;
020: import com.google.gwt.http.client.RequestCallback;
021: import com.google.gwt.http.client.RequestException;
022: import com.google.gwt.http.client.Response;
023: import com.google.gwt.json.client.JSONArray;
024: import com.google.gwt.json.client.JSONException;
025: import com.google.gwt.json.client.JSONObject;
026: import com.google.gwt.json.client.JSONParser;
027: import com.google.gwt.json.client.JSONString;
028: import com.google.gwt.json.client.JSONValue;
029: import com.google.gwt.user.client.Window;
030: import com.google.gwt.user.client.ui.Button;
031: import com.google.gwt.user.client.ui.ClickListener;
032: import com.google.gwt.user.client.ui.RootPanel;
033: import com.google.gwt.user.client.ui.Tree;
034: import com.google.gwt.user.client.ui.TreeItem;
035: import com.google.gwt.user.client.ui.Widget;
036:
037: import java.util.Set;
038:
039: /**
040: * Class that acts as a client to a JSON service. Currently, this client just
041: * requests a text which contains a JSON encoding of a search result set from
042: * yahoo. We use a text file to demonstrate how the pieces work without tripping
043: * on cross-site scripting issues.
044: *
045: * If you would like to make this a more dynamic example, you can associate a
046: * servlet with this example and simply have it hit the yahoo service and return
047: * the results.
048: */
049: public class JSON {
050: /**
051: * Class for handling the response text associated with a request for a JSON
052: * object.
053: *
054: */
055: private class JSONResponseTextHandler implements RequestCallback {
056: public void onError(Request request, Throwable exception) {
057: displayRequestError(exception.toString());
058: resetSearchButtonCaption();
059: }
060:
061: public void onResponseReceived(Request request,
062: Response response) {
063: String responseText = response.getText();
064: try {
065: JSONValue jsonValue = JSONParser.parse(responseText);
066: displayJSONObject(jsonValue);
067: } catch (JSONException e) {
068: displayParseError(responseText);
069: }
070: resetSearchButtonCaption();
071: }
072: }
073:
074: /*
075: * Class for handling the fetch button's click event.
076: */
077: private class SearchButtonClickListener implements ClickListener {
078: public void onClick(Widget sender) {
079: jsonTree.setVisible(false);
080: doFetchURL();
081: }
082: }
083:
084: /*
085: * Default URL to use to fetch JSON objects. Note that the contents of this
086: * JSON result were as a result of requesting the following URL:
087: *
088: * http://api.search.yahoo.com/ImageSearchService/V1/imageSearch?appid=YahooDemo&query=potato&results=2&output=json
089: *
090: */
091: private static final String DEFAULT_SEARCH_URL = "search-results.js";
092:
093: /*
094: * Text displayed on the fetch button when we are in a default state.
095: */
096: private static final String SEARCH_BUTTON_DEFAULT_TEXT = "Search";
097:
098: /*
099: * Text displayed on the fetch button when we are waiting for a JSON reply.
100: */
101: private static final String SEARCH_BUTTON_WAITING_TEXT = "Waiting for JSON Response...";
102:
103: /*
104: * RequestBuilder used to issue HTTP GET requests.
105: */
106: private final RequestBuilder requestBuilder = new RequestBuilder(
107: RequestBuilder.GET, DEFAULT_SEARCH_URL);
108:
109: private Tree jsonTree = new Tree();
110:
111: private Button searchButton = new Button();
112:
113: /**
114: * Entry point for this simple application. Currently, we build the
115: * application's form and wait for events.
116: */
117: public void onModuleLoad() {
118: initializeMainForm();
119: }
120:
121: /*
122: * Add the object presented by the JSONValue as a children to the requested
123: * TreeItem.
124: */
125: private void addChildren(TreeItem treeItem, JSONValue jsonValue) {
126: JSONArray jsonArray;
127: JSONObject jsonObject;
128: JSONString jsonString;
129:
130: if ((jsonArray = jsonValue.isArray()) != null) {
131: for (int i = 0; i < jsonArray.size(); ++i) {
132: TreeItem child = treeItem.addItem(getChildText("["
133: + Integer.toString(i) + "]"));
134: addChildren(child, jsonArray.get(i));
135: }
136: } else if ((jsonObject = jsonValue.isObject()) != null) {
137: Set<String> keys = jsonObject.keySet();
138: for (String key : keys) {
139: TreeItem child = treeItem.addItem(getChildText(key));
140: addChildren(child, jsonObject.get(key));
141: }
142: } else if ((jsonString = jsonValue.isString()) != null) {
143: // Use stringValue instead of toString() because we don't want escaping
144: treeItem.addItem(jsonString.stringValue());
145: } else {
146: // JSONBoolean, JSONNumber, and JSONNull work well with toString().
147: treeItem.addItem(getChildText(jsonValue.toString()));
148: }
149: }
150:
151: private void displayError(String errorType, String errorMessage) {
152: jsonTree.removeItems();
153: jsonTree.setVisible(true);
154: TreeItem treeItem = jsonTree.addItem(errorType);
155: treeItem.addItem(errorMessage);
156: treeItem.setStyleName("JSON-JSONResponseObject");
157: treeItem.setState(true);
158: }
159:
160: /*
161: * Update the treeview of a JSON object.
162: */
163: private void displayJSONObject(JSONValue jsonValue) {
164: jsonTree.removeItems();
165: jsonTree.setVisible(true);
166: TreeItem treeItem = jsonTree.addItem("JSON Response");
167: addChildren(treeItem, jsonValue);
168: treeItem.setStyleName("JSON-JSONResponseObject");
169: treeItem.setState(true);
170: }
171:
172: private void displayParseError(String responseText) {
173: displayError("Failed to parse JSON response", responseText);
174: }
175:
176: private void displayRequestError(String message) {
177: displayError("Request failed.", message);
178: }
179:
180: private void displaySendError(String message) {
181: displayError("Failed to send the request.", message);
182: }
183:
184: /*
185: * Fetch the requested URL.
186: */
187: private void doFetchURL() {
188: searchButton.setText(SEARCH_BUTTON_WAITING_TEXT);
189: try {
190: requestBuilder.sendRequest(null,
191: new JSONResponseTextHandler());
192: } catch (RequestException ex) {
193: displaySendError(ex.toString());
194: resetSearchButtonCaption();
195: }
196: }
197:
198: /*
199: * Causes the text of child elements to wrap.
200: */
201: private String getChildText(String text) {
202: return "<span style='white-space:normal'>" + text + "</span>";
203: }
204:
205: /**
206: * Initialize the main form's layout and content.
207: */
208: private void initializeMainForm() {
209: searchButton.setStyleName("JSON-SearchButton");
210: searchButton.setText(SEARCH_BUTTON_DEFAULT_TEXT);
211: searchButton.addClickListener(new SearchButtonClickListener());
212:
213: // Avoids showing an "empty" cell
214: jsonTree.setVisible(false);
215:
216: // Find out where the host page wants the button.
217: //
218: RootPanel searchButtonSlot = RootPanel.get("search");
219: if (searchButtonSlot == null) {
220: Window
221: .alert("Please define a container element whose id is 'search'");
222: return;
223: }
224:
225: // Find out where the host page wants the tree view.
226: //
227: RootPanel treeViewSlot = RootPanel.get("tree");
228: if (treeViewSlot == null) {
229: Window
230: .alert("Please define a container element whose id is 'tree'");
231: return;
232: }
233:
234: // Add both widgets.
235: //
236: searchButtonSlot.add(searchButton);
237: treeViewSlot.add(jsonTree);
238: }
239:
240: private void resetSearchButtonCaption() {
241: searchButton.setText(SEARCH_BUTTON_DEFAULT_TEXT);
242: }
243: }
|