refactored form handling

This commit is contained in:
Peter Schojer 2008-09-11 09:09:11 +00:00
parent b9fa3377a0
commit dd3f43c37d
35 changed files with 357 additions and 79 deletions

View File

@ -43,10 +43,11 @@
#include "Poco/WebWidgets/ExtJS/ExtJS.h"
#include "Poco/WebWidgets/Renderer.h"
#include "Poco/WebWidgets/Form.h"
#include "Poco/WebWidgets/JSDelegate.h"
namespace Poco {
namespace WebWidgets {
class Form;
namespace ExtJS {
@ -68,6 +69,10 @@ public:
static std::string formVariableName(const Form* pForm);
/// Creates the variable name for the form
static Poco::WebWidgets::JSDelegate createReloadFunction(const std::string& fctName, const Form* pForm);
/// Creates a function with the given fctName (can be empty) that reloads the form
/// E.g.: add the returned JSDelegate to pReload->buttonClicked
};

View File

@ -38,7 +38,9 @@
#include "Poco/WebWidgets/Form.h"
#include "Poco/WebWidgets/Button.h"
#include "Poco/WebWidgets/WebApplication.h"
#include "Poco/WebWidgets/RequestHandler.h"
#include "Poco/NumberFormatter.h"
#include <sstream>
namespace Poco {
@ -83,6 +85,7 @@ void FormRenderer::renderHead(const Renderable* pRenderable, const RenderContext
}
ostr << "]})";
theApp.endForm(*pForm);
WebApplication::instance().registerAjaxProcessor(Poco::NumberFormatter::format(pForm->id()), const_cast<Form*>(pForm));
}
@ -98,4 +101,18 @@ std::string FormRenderer::formVariableName(const Form* pForm)
}
Poco::WebWidgets::JSDelegate FormRenderer::createReloadFunction(const std::string& fctName, const Form* pForm)
{
std::ostringstream out;
out << "function ";
out << fctName << "(){" << std::endl;
out << "var theForm = Ext.getCmp('" << pForm->id() << "');" << std::endl;
out << "var uri = '" << pForm->getURI().toString() << "/;" << RequestHandler::KEY_EVID << "=" << Form::EV_RELOAD << "&";
out << RequestHandler::KEY_ID << "=" << pForm->id() << "';" << std::endl;
out << "theForm.load({url:uri,method:'GET'});" << std::endl; // success, failure handlers
out << "}" << std::endl;
return jsDelegate(out.str());
}
} } } // namespace Poco::WebWidgets::ExtJS

View File

@ -58,11 +58,8 @@ class WebWidgets_API Button: public Control
{
public:
typedef Poco::AutoPtr<Button> Ptr;
typedef Event<Button> ButtonEvent;
JavaScriptEvent<AjaxParameters> ajaxButtonClicked;
JavaScriptEvent<ButtonEvent> buttonClicked;
JavaScriptEvent<AjaxParameters> buttonClicked;
Button(const std::string& name);
/// Creates a Button with the given name.
@ -73,11 +70,8 @@ public:
Button();
/// Creates an anonymous Button.
void fireAjaxButtonClicked(void* pSender, AjaxParameters& params);
void fireButtonClicked(void* pSender, AjaxParameters& params);
/// Fires the ajaxButtonClicked event.
void fireButtonClicked(void* pSender);
/// Fires the buttonClicked event.
protected:
Button(const std::string& name, const std::type_info& type);

View File

@ -57,9 +57,7 @@ public:
static const std::string EV_BUTTONCLICKED;
AjaxDelegate ajaxButtonClicked;
Delegate buttonClicked;
AjaxDelegate buttonClicked;
ButtonCell(View* pOwner);
/// Creates a ButtonCell.
@ -68,6 +66,8 @@ public:
void handleForm(const std::string& field, const std::string& value);
void handleAjaxRequest(const Poco::Net::NameValueCollection& args, Poco::Net::HTTPServerResponse& response);
bool serializeJSON(std::ostream& out, const std::string& name);
protected:
~ButtonCell();
@ -79,6 +79,12 @@ protected:
};
inline bool ButtonCell::serializeJSON(std::ostream& out, const std::string& name)
{
return false;
}
} } // namespace Poco::WebWidgets

View File

@ -102,6 +102,8 @@ public:
/// the Formatter. If no Formatter has been set, sets the value
/// to the given string.
bool hasValue() const;
void setString(const std::string& value);
/// Sets the value for this Cell to the given string.
@ -281,6 +283,12 @@ inline Cell::EditMode Cell::getEditMode() const
}
inline bool Cell::hasValue() const
{
return !getValue().empty();
}
} } // namespace Poco::WebWidgets

View File

@ -124,6 +124,8 @@ public:
void handleAjaxRequest(const Poco::Net::NameValueCollection& args, Poco::Net::HTTPServerResponse& response);
/// Handles a complete AJAX request submitted by the client.
bool serializeJSON(std::ostream& out, const std::string& name);
protected:
~ComboBoxCell();
/// Destroys the ComboBoxCell.

View File

@ -73,6 +73,8 @@ public:
const Poco::DateTime& getDate() const;
/// returns the date if set, otherwise an exception, use getValue().empty() to check if it is valid
bool serializeJSON(std::ostream& out, const std::string& name);
private:
std::string _format;
};

View File

@ -41,20 +41,34 @@
#include "Poco/WebWidgets/ContainerView.h"
#include "Poco/WebWidgets/RequestProcessor.h"
#include "Poco/URI.h"
#include "Poco/FIFOEvent.h"
#include <map>
namespace Poco {
namespace Net {
class HTMLForm;
}
namespace WebWidgets {
class WebWidgets_API Form: public ContainerView
class WebWidgets_API Form: public ContainerView, public RequestProcessor
/// A Form represents a HTML form.
{
public:
typedef Poco::AutoPtr<Form> Ptr;
static const std::string FORM_ID;
static const std::string FORM_ID; /// form name to
static const std::string EV_RELOAD; /// event name for reloading the form
static const std::string METHOD_GET;
static const std::string METHOD_POST;
static const std::string ENCODING_URL; /// "application/x-www-form-urlencoded"
static const std::string ENCODING_MULTIPART; /// "multipart/form-data"
Poco::FIFOEvent<Form*> beforeReload; /// triggered before a Form is reloaded so that you can update form fields with recent values
Form(const Poco::URI& uri);
/// Creates an anonymous Form.
@ -76,12 +90,26 @@ public:
const Poco::URI& getURI() const;
/// Returns the URL of the form
static const std::string METHOD_GET;
static const std::string METHOD_POST;
static const std::string ENCODING_URL; /// "application/x-www-form-urlencoded"
static const std::string ENCODING_MULTIPART; /// "multipart/form-data"
void handleForm(const std::string& field, const std::string& value);
/// Handles a form field submitted by the client.
void handleForm(const Poco::Net::HTMLForm& form);
/// Handles a complete form submit
void handleAjaxRequest(const Poco::Net::NameValueCollection& args, Poco::Net::HTTPServerResponse& response);
/// Handles a complete AJAX request submitted by the client.
bool serializeJSON(std::ostream& out, const std::string&);
void serializeJSONImpl(std::ostream& out);
RequestProcessor* getFormProcessor(const std::string& name);
/// Returns the requestprocessor or null
void registerFormProcessor(const std::string& name, RequestProcessor* pProc);
/// Registers a RequestProcessor for a request.
protected:
Form(const std::string& name, const std::type_info& type, const Poco::URI& uri);
@ -94,9 +122,11 @@ protected:
/// Destroys the Form.
private:
std::string _method;
std::string _encoding;
Poco::URI _uri;
typedef std::map<std::string, RequestProcessor* > RequestProcessorMap;
std::string _method;
std::string _encoding;
Poco::URI _uri;
RequestProcessorMap _namedChildren;
};
@ -121,6 +151,13 @@ inline const Poco::URI& Form::getURI() const
}
inline bool Form::serializeJSON(std::ostream& out, const std::string&)
{
serializeJSONImpl(out);
return true;
}
} } // namespace Poco::WebWidgets

View File

@ -116,6 +116,8 @@ public:
// Cell
void handleForm(const std::string& field, const std::string& value);
bool serializeJSON(std::ostream& out, const std::string& name);
protected:
~ListBoxCell();

View File

@ -59,6 +59,8 @@ public:
virtual ~NumberFieldCell();
/// Destroys the NumberFieldCell.
bool serializeJSON(std::ostream& out, const std::string& name);
};

View File

@ -97,6 +97,9 @@ public:
void handleForm(const std::string& field, const std::string& value);
/// Dummy implementation
bool serializeJSON(std::ostream& out, const std::string& name);
/// Dummy implementation
void addDynamicFunction(const std::string& jsCode);
/// Adds a JavaScript function to the page. Static functions should be written to a JS file
/// and included via the ResourceManager, only dynamic fucntions (ie. functions that are generated
@ -189,6 +192,12 @@ inline void Page::appendPostRenderCode(const std::string& js)
}
inline bool Page::serializeJSON(std::ostream&, const std::string&)
{
return false;
}
} } // namespace Poco::WebWidgets

View File

@ -58,6 +58,8 @@ public:
virtual ~PasswordFieldCell();
/// Destroys the PasswordFieldCell.
bool serializeJSON(std::ostream& out, const std::string& name);
};

View File

@ -41,6 +41,7 @@
#include "Poco/WebWidgets/WebWidgets.h"
#include <ostream>
namespace Poco {
@ -65,6 +66,8 @@ public:
virtual void handleAjaxRequest(const Poco::Net::NameValueCollection& args, Poco::Net::HTTPServerResponse& response) = 0;
/// Handles a complete AJAX request submitted by the client.
virtual bool serializeJSON(std::ostream& out, const std::string& name) = 0;
/// Serializes a form field to the client.
protected:
RequestProcessor();
/// Creates the RequestProcessor.

View File

@ -41,6 +41,7 @@
#include "Poco/WebWidgets/Button.h"
#include "Poco/FIFOEvent.h"
namespace Poco {
@ -55,6 +56,8 @@ class WebWidgets_API SubmitButton: public Button
{
public:
typedef Poco::AutoPtr<SubmitButton> Ptr;
FIFOEvent<SubmitButton*> afterSubmit; /// thrown whenever all form values are assigned and the submit is done
SubmitButton();
/// Creates an anonymous SubmitButton.

View File

@ -208,6 +208,8 @@ public:
bool autoEdit() const;
// Returns if autoEdit is on/off
bool serializeJSON(std::ostream& out, const std::string& name);
protected:
Table(const std::string& name, const std::type_info& type, const TableColumns& tc, TableModel::Ptr pModel);
/// Creates a Table and assigns it the given name.

View File

@ -79,6 +79,8 @@ public:
// Cell
void handleForm(const std::string& field, const std::string& value);
bool serializeJSON(std::ostream& out, const std::string& name);
protected:
~TextEditCell();

View File

@ -75,6 +75,8 @@ public:
// Cell
void handleForm(const std::string& field, const std::string& value);
bool serializeJSON(std::ostream& out, const std::string& name);
protected:
~TextFieldCell();

View File

@ -73,6 +73,8 @@ public:
const Poco::DateTime& getTime() const;
/// returns the time if set, otherwise an exception, use getValue().empty() to check if it is valid
bool serializeJSON(std::ostream& out, const std::string& name);
private:
std::string _format;
TimeField::Format _fmt;

View File

@ -73,6 +73,9 @@ public:
// Cell
void handleForm(const std::string& field, const std::string& value);
bool serializeJSON(std::ostream& out, const std::string& name);
protected:
~ToggleButtonCell();

View File

@ -140,7 +140,7 @@ private:
typedef std::map<std::string, RequestProcessor* > RequestProcessorMap;
typedef std::map<Renderable::ID, SubmitButtonCell*> SubmitButtons;
typedef std::map<Renderable::ID, RequestProcessorMap> FormMap;
typedef std::map<Renderable::ID, Form*> FormMap;
typedef std::stack<Renderable::ID> OpenForms;
ResourceManager::Ptr _pResource;

View File

@ -101,8 +101,7 @@ void Button::init(Cell::Ptr ptrCell)
{
ButtonCell::Ptr pCell = ptrCell.cast<ButtonCell>();
poco_check_ptr (pCell);
pCell->buttonClicked = delegate(*this, &Button::fireButtonClicked);
pCell->ajaxButtonClicked = ajaxDelegate(*this, &Button::fireAjaxButtonClicked);
pCell->buttonClicked = ajaxDelegate(*this, &Button::fireButtonClicked);
setCell(pCell);
}
@ -114,16 +113,9 @@ void Button::init()
}
void Button::fireAjaxButtonClicked(void* pSender, AjaxParameters& params)
void Button::fireButtonClicked(void* pSender, AjaxParameters& params)
{
ajaxButtonClicked(pSender, params);
}
void Button::fireButtonClicked(void* pSender)
{
ButtonEvent clickedEvent(this);
buttonClicked(this, clickedEvent);
buttonClicked(pSender, params);
}

View File

@ -76,10 +76,9 @@ void ButtonCell::handleAjaxRequest(const Poco::Net::NameValueCollection& args, P
if (ev == EV_BUTTONCLICKED)
{
bool handled(false);
ajaxButtonClicked(this, args, response, handled);
buttonClicked(this, args, response, handled);
if (!handled)
response.send();
buttonClicked(this);
}
else
response.send();

View File

@ -36,6 +36,7 @@
#include "Poco/WebWidgets/ComboBoxCell.h"
#include "Poco/WebWidgets/RequestHandler.h"
#include "Poco/DateTime.h"
namespace Poco {
@ -133,4 +134,20 @@ void ComboBoxCell::setSelected(const Any& elem)
selected(this, aPair);
}
bool ComboBoxCell::serializeJSON(std::ostream& out, const std::string& name)
{
out << name;
if (hasSelected())
{
const Poco::Any& sel = getSelected();
if (sel.type() == typeid(std::string) || sel.type() == typeid(Poco::DateTime))
out << ":'" << getFormatter()->format(sel) << "'";
else
out << ":" << getFormatter()->format(sel);
}
return true;
}
} } // namespace Poco::WebWidgets

View File

@ -63,4 +63,15 @@ void DateFieldCell::setFormat(const std::string& dateFormat)
}
bool DateFieldCell::serializeJSON(std::ostream& out, const std::string& name)
{
out << name;
if (this->hasValue())
{
out << ":'" << getFormatter()->format(getValue()) << "'";
}
return true;
}
} } // namespace Poco::WebWidgets

View File

@ -35,6 +35,9 @@
#include "Poco/WebWidgets/Form.h"
#include "Poco/WebWidgets/RequestHandler.h"
#include "Poco/NumberParser.h"
#include "Poco/Net/HTMLForm.h"
namespace Poco {
@ -42,6 +45,7 @@ namespace WebWidgets {
const std::string Form::FORM_ID("__form__");
const std::string Form::EV_RELOAD("reload");
const std::string Form::METHOD_GET("GET");
const std::string Form::METHOD_POST("POST");
const std::string Form::ENCODING_URL("application/x-www-form-urlencoded");
@ -52,7 +56,8 @@ Form::Form(const Poco::URI& uri):
ContainerView(typeid(Form)),
_method(METHOD_POST),
_encoding(ENCODING_MULTIPART),
_uri(uri)
_uri(uri),
_namedChildren()
{
}
@ -61,7 +66,8 @@ Form::Form(const std::string& name, const Poco::URI& uri):
ContainerView(name, typeid(Form)),
_method(METHOD_POST),
_encoding(ENCODING_MULTIPART),
_uri(uri)
_uri(uri),
_namedChildren()
{
}
@ -70,7 +76,8 @@ Form::Form(const std::string& name, const std::type_info& type, const Poco::URI&
ContainerView(name, type),
_method(METHOD_POST),
_encoding(ENCODING_MULTIPART),
_uri(uri)
_uri(uri),
_namedChildren()
{
}
@ -79,7 +86,8 @@ Form::Form(const std::type_info& type, const Poco::URI& uri):
ContainerView(type),
_method(METHOD_POST),
_encoding(ENCODING_MULTIPART),
_uri(uri)
_uri(uri),
_namedChildren()
{
}
@ -101,4 +109,92 @@ void Form::setEncoding(const std::string& encoding)
}
void Form::handleForm(const std::string& field, const std::string& value)
{
}
void Form::handleForm(const Poco::Net::HTMLForm& form)
{
Renderable::ID formID = Poco::NumberParser::parse(form.get(Form::FORM_ID));
poco_assert (formID == id());
Poco::Net::NameValueCollection::ConstIterator it = form.begin();
RequestProcessorMap processors = _namedChildren; // copy so that a second submit works too!
for (;it != form.end(); ++it)
{
const std::string& key = it->first;
RequestProcessorMap::iterator itR = processors.find(key);
if (itR != processors.end())
{
itR->second->handleForm(key, it->second);
processors.erase(itR);
}
}
//those that are not included are either deselected or empty
RequestProcessorMap::iterator itR = processors.begin();
std::string empty;
for (; itR != processors.end(); ++itR)
{
itR->second->handleForm(itR->first, empty);
}
}
void Form::handleAjaxRequest(const Poco::Net::NameValueCollection& args, Poco::Net::HTTPServerResponse& response)
{
//a form only supports the refresh event
const std::string& ev = args[RequestHandler::KEY_EVID];
if (ev == EV_RELOAD)
{
Form* pThis = this;
beforeReload.notify(this, pThis);
/// send the JS presentation of the page
response.setContentType("text/javascript");
response.setChunkedTransferEncoding(true);
serializeJSONImpl(response.send());
}
response.send();
}
void Form::serializeJSONImpl(std::ostream& out)
{
// FIXME: ExtJs specific
out << "{";
out << "success: true,";
out << "data: { ";
// serialize children
RequestProcessorMap::iterator it = _namedChildren.begin();
bool writeComma = false;
for (; it != _namedChildren.end(); ++it)
{
if (writeComma)
out << ",";
writeComma = it->second->serializeJSON(out, it->first);
}
out << "}";
out << "}";
}
void Form::registerFormProcessor(const std::string& fieldName, RequestProcessor* pProc)
{
// per default we register everything that has a name as form processor
std::pair<RequestProcessorMap::iterator, bool> res = _namedChildren.insert(std::make_pair(fieldName, pProc));
if (!res.second)
res.first->second = pProc;
}
RequestProcessor* Form::getFormProcessor(const std::string& fieldName)
{
RequestProcessorMap::iterator it = _namedChildren.find(fieldName);
if (it == _namedChildren.end())
return 0;
return it->second;
}
} } // namespace Poco::WebWidgets

View File

@ -35,6 +35,7 @@
#include "Poco/WebWidgets/ListBoxCell.h"
#include "Poco/DateTime.h"
namespace Poco {
@ -168,4 +169,19 @@ const Any& ListBoxCell::getSelected() const
}
bool ListBoxCell::serializeJSON(std::ostream& out, const std::string& name)
{
out << name;
if (hasSelected())
{
const Poco::Any& sel = getSelected();
if (sel.type() == typeid(std::string) || sel.type() == typeid(Poco::DateTime))
out << ":'" << getFormatter()->format(sel) << "'";
else
out << ":" << getFormatter()->format(sel);
}
return true;
}
} } // namespace Poco::WebWidgets

View File

@ -54,4 +54,15 @@ NumberFieldCell::~NumberFieldCell()
}
bool NumberFieldCell::serializeJSON(std::ostream& out, const std::string& name)
{
out << name;
if (this->hasValue())
{
out << ":" << getFormatter()->format(getValue());
}
return true;
}
} } // namespace Poco::WebWidgets

View File

@ -52,4 +52,15 @@ PasswordFieldCell::~PasswordFieldCell()
}
bool PasswordFieldCell::serializeJSON(std::ostream& out, const std::string& name)
{
out << name;
if (this->hasValue())
{
out << ":'" << getFormatter()->format(getValue()) << "'";
}
return true;
}
} } // namespace Poco::WebWidgets

View File

@ -181,12 +181,7 @@ void RequestHandler::handleAjaxRequest(Poco::Net::HTTPServerRequest& request, Po
response.send();
return;
}
SubmitButtonCell* pCell = dynamic_cast<SubmitButtonCell*>(pProc);
if (pCell) // hide click event from submitbuttons
{
response.send();
return;
}
try
{
pProc->handleAjaxRequest(args, response);

View File

@ -352,4 +352,10 @@ Table::LoadData::LoadData(Poco::Net::HTTPServerResponse* pR, Table* pT, int row,
}
bool Table::serializeJSON(std::ostream& out, const std::string& name)
{
return false;
}
} } // namespace Poco::WebWidgets

View File

@ -73,4 +73,15 @@ void TextEditCell::handleForm(const std::string& field, const std::string& value
}
bool TextEditCell::serializeJSON(std::ostream& out, const std::string& name)
{
out << name;
if (this->hasValue())
{
out << ":'" << getFormatter()->format(getValue()) << "'";
}
return true;
}
} } // namespace Poco::WebWidgets

View File

@ -85,4 +85,15 @@ void TextFieldCell::handleForm(const std::string& field, const std::string& valu
}
bool TextFieldCell::serializeJSON(std::ostream& out, const std::string& name)
{
out << name;
if (this->hasValue())
{
out << ":'" << getFormatter()->format(getValue()) << "'";
}
return true;
}
} } // namespace Poco::WebWidgets

View File

@ -77,4 +77,15 @@ void TimeFieldCell::setFormat(TimeField::Format fmt)
}
bool TimeFieldCell::serializeJSON(std::ostream& out, const std::string& name)
{
out << name;
if (this->hasValue())
{
out << ":'" << getFormatter()->format(getValue()) << "'";
}
return true;
}
} } // namespace Poco::WebWidgets

View File

@ -85,4 +85,16 @@ void ToggleButtonCell::setChecked(bool checked)
}
}
bool ToggleButtonCell::serializeJSON(std::ostream& out, const std::string& name)
{
//FIXME: this is extjs specific
out << name << ":";
if (isChecked())
out << "'on'";
else
out << "'off'";
return true;
}
} } // namespace Poco::WebWidgets

View File

@ -113,20 +113,19 @@ void WebApplication::beginForm(const Form& form)
if (!_forms.empty())
throw WebWidgetsException("nested forms not allowed");
_forms.push(form.id());
_formMap.insert(std::make_pair(form.id(), RequestProcessorMap()));
_formMap.insert(std::make_pair(form.id(), const_cast<Form*>(&form)));
}
void WebApplication::registerFormProcessor(const std::string& fieldName, RequestProcessor* pProc)
{
// per default we register everyting that has a name as form processor
// per default we register everything that has a name as form processor
if (!_forms.empty())
{
FormMap::iterator itForm = _formMap.find(_forms.top());
poco_assert (itForm != _formMap.end());
std::pair<RequestProcessorMap::iterator, bool> res = itForm->second.insert(std::make_pair(fieldName, pProc));
if (!res.second)
res.first->second = pProc;
itForm->second->registerFormProcessor(fieldName, pProc);
}
}
@ -137,10 +136,7 @@ RequestProcessor* WebApplication::getFormProcessor(Renderable::ID formId, const
if (itForm == _formMap.end())
return 0;
RequestProcessorMap::iterator it = itForm->second.find(fieldName);
if (it == itForm->second.end())
return 0;
return it->second;
return itForm->second->getFormProcessor(fieldName);
}
@ -184,27 +180,8 @@ void WebApplication::handleForm(const Poco::Net::HTMLForm& form)
FormMap::iterator itForm = _formMap.find(formID);
if (itForm == _formMap.end())
throw Poco::NotFoundException("unknown form id");
Poco::Net::NameValueCollection::ConstIterator it = form.begin();
RequestProcessorMap& processors = itForm->second;
for (;it != form.end(); ++it)
{
const std::string& key = it->first;
RequestProcessorMap::iterator itR = processors.find(key);
if (itR != processors.end())
{
itR->second->handleForm(key, it->second);
processors.erase(itR);
}
}
//those that are not included are either deselected or empty
RequestProcessorMap::iterator itR = processors.begin();
std::string empty;
for (; itR != processors.end(); ++itR)
{
itR->second->handleForm(itR->first, empty);
}
processors.clear();
itForm->second->handleForm(form);
}
@ -219,8 +196,7 @@ void WebApplication::notifySubmitButton(Renderable::ID id)
SubmitButton* pSubmit = dynamic_cast<SubmitButton*>(pOwner);
if (pSubmit)
{
Button::ButtonEvent clickedEvent(pSubmit);
pSubmit->buttonClicked.notify(pSubmit, clickedEvent);
pSubmit->afterSubmit.notify(pSubmit, pSubmit);
}
}