/*
* Copyright (C) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.api.explorer.client.embedded;
import com.google.api.explorer.client.Resources;
import com.google.api.explorer.client.Resources.Css;
import com.google.api.explorer.client.base.ApiMethod;
import com.google.api.explorer.client.base.ApiService;
import com.google.api.explorer.client.base.Schema;
import com.google.api.explorer.client.parameter.schema.SchemaForm;
import com.google.api.explorer.client.widgets.DescendantFocusPanel;
import com.google.api.explorer.client.widgets.FocusInHandler;
import com.google.api.explorer.client.widgets.FocusOutHandler;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.json.client.JSONException;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.Widget;
/**
* Form which shows a request body in an editor that can be used in both structured and freeform
* mode, with special styling to collapse the editor when it doesn't have focus.
*
*/
public class RequestBodyForm extends Composite {
private static Css globalStyle = Resources.INSTANCE.style();
@VisibleForTesting
enum BodyEditor {
SCHEMA,
FREEFORM,
}
interface RequestBodyFormUiBinder extends UiBinder<Widget, RequestBodyForm> {
}
interface RequestBodyFormStyle extends CssResource {
String hiddenControls();
}
private BodyEditor selectedEditor;
@UiField public DescendantFocusPanel requestPanel;
@UiField(provided = true) SchemaForm schemaForm;
@UiField TextArea requestBody;
@UiField Label editorSwitchError;
@UiField RequestBodyFormStyle style;
@UiField PushButton switchEditorMenu;
@UiField PopupPanel menuPopup;
@UiField PushButton showStructured;
@UiField PushButton showFreeform;
@UiHandler("switchEditorMenu")
void discloseSwitchEditorMenu(ClickEvent event) {
menuPopup.setPopupPositionAndShow(new PositionCallback() {
@Override
public void setPosition(int offsetWidth, int offsetHeight) {
int left =
switchEditorMenu.getAbsoluteLeft() + switchEditorMenu.getOffsetWidth() - offsetWidth;
int top = switchEditorMenu.getAbsoluteTop() + switchEditorMenu.getOffsetHeight();
menuPopup.setPopupPosition(left, top);
}
});
}
@UiHandler("showStructured")
void selectStructuredEditor(ClickEvent event) {
showEditor(BodyEditor.SCHEMA, /* Focus the editor after switching. */ true);
menuPopup.hide();
}
@UiHandler("showFreeform")
void selectFreeformEditor(ClickEvent event) {
showEditor(BodyEditor.FREEFORM, /* Focus the editor after switching. */ true);
menuPopup.hide();
}
/**
* Create an empty request body form.
*/
public RequestBodyForm() {
schemaForm = new SchemaForm();
initWidget(((RequestBodyFormUiBinder) GWT.create(RequestBodyFormUiBinder.class))
.createAndBindUi(this));
requestPanel.addFocusInHandler(new FocusInHandler() {
@Override
public void onFocusIn(Event event) {
// Fade in the style for the controls.
requestPanel.removeStyleName(style.hiddenControls());
}
});
requestPanel.addFocusOutHandler(new FocusOutHandler() {
@Override
public void onFocusOut(Event event) {
// Fade out the style for the controls
requestPanel.addStyleName(style.hiddenControls());
}
});
setupEditorDataBinding();
// Remove the popup from the flow.
menuPopup.show();
menuPopup.hide();
}
/**
* Bind the free form and schema based editors together so that when the user switches the data is
* moved between editors.
*/
private void setupEditorDataBinding() {
// Add a handler to clear body error message when the user intends to edit
// the text.
requestBody.addFocusHandler(new FocusHandler() {
@Override
public void onFocus(FocusEvent event) {
editorSwitchError.setVisible(false);
}
});
requestBody.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange(ValueChangeEvent<String> event) {
resizeTextArea();
}
});
requestBody.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
resizeTextArea();
}
});
requestBody.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
resizeTextArea();
}
});
}
/**
* Returns the text associated with the contents of the editor.
*/
public String getRequestBodyText() {
return selectedEditor == BodyEditor.SCHEMA ? schemaForm.getStringValue() : requestBody
.getText();
}
/**
* Sets the text of the currently selected editor to the value provided.
*
* @param value Json or free-form text that should be used to populate the form.
*/
public void setRequestBodyText(String value) {
if (selectedEditor == BodyEditor.SCHEMA) {
try {
schemaForm.setJSONValue(JSONParser.parseStrict(value));
} catch (Exception e) {
showEditor(BodyEditor.FREEFORM, /* Do not focus on this content fill. */ false);
}
}
// This may have been the original selection, or it may have been switched to by a json parsing
// error or an error when assigning the json to the schema editor.
if (selectedEditor == BodyEditor.FREEFORM) {
requestBody.setText(value);
}
}
/**
* Initialize the body editors based on the schema provided, starting with an
* empty request.
*
* @param service Service which is used to find schemas.
* @param method Method for which this form is being displayed.
* @param requestSchema Schema for which to enable the editor of {@code null}
* if none.
* @param initialText Initial text which should populate the editor.
*/
public void setContent(
ApiService service, ApiMethod method, Schema requestSchema, String initialText) {
selectedEditor = null;
BodyEditor editorToShow;
if (requestSchema != null) {
editorToShow = BodyEditor.SCHEMA;
schemaForm.setSchema(service, method, requestSchema);
} else {
editorToShow = BodyEditor.FREEFORM;
}
showEditor(editorToShow, /* Do not focus on the content event. */ false);
String textToShow = Strings.emptyToNull(initialText) == null ? "{}" : initialText;
setRequestBodyText(textToShow);
}
private void resizeTextArea() {
int rows = requestBody.getVisibleLines();
while (rows > 1) {
requestBody.setVisibleLines(--rows);
}
while (requestBody.getElement().getScrollHeight()
> requestBody.getElement().getClientHeight()) {
requestBody.setVisibleLines(requestBody.getVisibleLines() + 1);
}
}
/**
* When the user switches tabs, the data is persistent.
*/
@VisibleForTesting
void showEditor(BodyEditor editorType, boolean isFocused) {
String text = getRequestBodyText();
switch (editorType) {
case SCHEMA:
// About to switch to guided view
if (selectedEditor != BodyEditor.SCHEMA) {
try {
// If the user cleared the text, use an empty object
if (text.isEmpty()) {
text = "{}";
}
schemaForm.setJSONValue(JSONParser.parseStrict(text));
schemaForm.setVisible(true);
requestBody.setVisible(false);
selectedEditor = BodyEditor.SCHEMA;
showStructured.addStyleName(globalStyle.checked());
showFreeform.removeStyleName(globalStyle.checked());
requestPanel.setFocus(isFocused);
} catch (JSONException e) {
// If there was an error parsing the JSON abort the switch and show the cause message to
// the user.
editorSwitchError.setVisible(true);
editorSwitchError.setText(e.getMessage());
}
}
break;
case FREEFORM:
// About to switch to free form view
schemaForm.setVisible(false);
requestBody.setVisible(true);
requestBody.setText(text);
resizeTextArea();
requestBody.setFocus(isFocused);
selectedEditor = BodyEditor.FREEFORM;
showStructured.removeStyleName(globalStyle.checked());
showFreeform.addStyleName(globalStyle.checked());
break;
}
}
}