SimpleChat made easy » History » Revision 3
Revision 2 (Omer Katz, 03/23/2010 01:23 AM) → Revision 3/12 (Omer Katz, 03/23/2010 04:39 AM)
h1. SimpleChat made easy - Part 1 Currently the situation with Wt's Comet is that you have to track the sessions you want to push data to yourself. I have written some classes that help dealing with this issue and simplifies building the simple chat example. We start with coding writing a new application type that is derived from Wt application, a Wt application that uses my comet example should use this application class instead of the normal WApplication class. application: h2. CometApplication.hpp <pre> #ifndef CometApplication_HPP #define CometApplication_HPP // <STL> #include <list> #include <string> #include <map> // </STL> // <Boost> #include <boost/unordered_map.hpp> // </Boost> // <Wt> #include <Wt/WApplication> #include <Wt/WWidget> // </Wt> namespace UI { namespace Widgets { // Forward declerations class CometWidget; template <class> class CometWidgetFactory; } namespace Application { class CometApplication : public Wt::WApplication { public: CometApplication(const Wt::WEnvironment &env); // Ctor, passes enviorment variables to WApplication ~CometApplication(); // Dtor friend class Widgets::CometWidget; // Befriending with CometWidget so it can access the widget map template <class> friend class Widgets::CometWidgetFactory; // Same here private: typedef std::pair<int, Wt::WWidget *> WidgetEntryType; typedef boost::unordered_multimap<CometApplication *, WidgetEntryType > WidgetMapType; typedef boost::unordered_multimap<CometApplication *, WidgetEntryType >::iterator WidgetMapIteratorType; typedef std::pair<WidgetMapIteratorType, WidgetMapIteratorType> WidgetRangeType; static WidgetMapType widgets; // Pushes a new widget to the current session with group id and the widget itself inline void registerWidget(unsigned long int id, Wt::WWidget *w) { widgets.insert(WidgetMapType::value_type(this, WidgetEntryType(id, w))); } // Searches for the widget of the group id for the current session and erases it inline void unregisterWidget(unsigned long int id) { WidgetRangeType range = widgets.equal_range(this); for ( WidgetMapIteratorType iter = range.first; iter != range.second; ++iter ) { if ( iter->second.first == id ) { widgets.erase(iter); break; } } } // Searches for a widget in the current session, casts it to the right widget type and returns it template <class T> inline T *getWidget(unsigned long int id) { WidgetRangeType range = widgets.equal_range(this); for ( WidgetMapIteratorType iter = range.first; iter != range.second; ++iter ) if ( iter->second.first == id ) return dynamic_cast<T *>(iter->second.second); return NULL; } }; } } #endif </pre> h2. CometApplication.cpp <pre> #include "CometApplication.hpp" namespace UI { namespace Application { CometApplication::CometApplication(const Wt::WEnvironment &env) : WApplication(env) { enableUpdates(); // Enables ajax-push for the current session } CometApplication::~CometApplication() { if ( widgets.find(this) != widgets.end() ) // Erases the current session from the widget map widgets.erase(this); } CometApplication::WidgetMapType CometApplication::widgets; } } </pre> h3. Code Review I have created an unordered multimap that might look a little strange to you guys but I'm going to elaborate about it in a minute. # The multimap key is the pointer to the current session which is represented by CometApplication, because as you guys already know, a session is an application instance. That way I can access the current session's widgets using *this* or iterate through all sessions. # The value of the multimap is a pair of a group id and a widget. Different widgets in different sessions with the same group id can be accessed this way. As you can see the CometApplication instance only refers to *this*, which means only to the current session. I have befriended all other classes because registerWidget, unregisterWidget and getWidget should only be accessed by those classes. This doesn't indicate that my code has a design problem in my opinion. Sometimes you actually have to use class friendship. It also makes sense that the end user will not control anything related to registering/unregistering widgets as you will see. After that we create a factory which will instanciate a widget for each session. Here the magic is introduced into the system. h2. CometWidgetFactory.hpp <pre> #ifndef CometWidgetFactory_HPP #define CometWidgetFactory_HPP #include <Wt/WApplication> #include <Wt/WWidget> #include "CometApplication.hpp" namespace UI { namespace Widgets { // CometWidgetFactory creates a widget of type WidgetType, WidgetType is derived from CometWidget template <class WidgetType> class CometWidgetFactory { private: static unsigned long int idCounter; // Counts how many ids there are unsigned long int id; // Group id public: CometWidgetFactory() : id(idCounter) { idCounter++; // New factory instance has been created. } ~CometWidgetFactory() { idCounter--; // New factory instance has been destroyed } void create() { WidgetType *w = new WidgetType; // Creates a new widget w->id = id; // Assigns the group id // Registers the widget to the current session dynamic_cast<UI::Application::CometApplication *>(wApp)->registerWidget(id, w); } void destroy() { // Unregisters the widget from the current session dynamic_cast<UI::Application::CometApplication *>(wApp)->unregisterWidget(id); } // Those operator overloadings make the factory "point" to the current widget we're refering to in the current session WidgetType *operator->() { return dynamic_cast<UI::Application::CometApplication *>(wApp)->getWidget<WidgetType>(id); } operator Wt::WWidget *() { return dynamic_cast<UI::Application::CometApplication *>(wApp)->getWidget<WidgetType>(id); } }; template <class T> unsigned long int CometWidgetFactory<T>::idCounter = 0; } } #endif </pre> h3. Code Review This class looks very simple but it has a trick. If you declare it as static on your target widget it will create a group id for a certain type of widgets for all sessions. For example: <pre> class someWidget : public CometWidget<Wt::LineEdit> { //... }; class MyPage : Wt::WContainer { private: static CometWidgetFactory<someWidget> myWidget; public: MyPage() { myWidget.create(); // Creates a widget on the same group id } ~MyPage() { myWidget.destroy(); } }; </pre> For each session a someWidget will be created and you can access the widget on the current session. h2. CometWidget.hpp <pre> #ifndef CometWidget_HPP #define CometWidget_HPP #include <string> #include <boost/thread/mutex.hpp> #include <Wt/WContainerWidget> #include "CometApplication.hpp" namespace UI { namespace Widgets { template <class WidgetType> class CometWidget : public WidgetType { protected: virtual void updateOthers() { boost::mutex::scoped_lock lock(CometMutex); for ( Application::CometApplication::WidgetMapIteratorType iter = Application::CometApplication::widgets.begin(); iter != Application::CometApplication::widgets.end(); ++iter ) { Application::CometApplication::WidgetRangeType range = Application::CometApplication::widgets.equal_range(iter->first); for ( Application::CometApplication::WidgetMapIteratorType iter2 = range.first; iter2 != range.second; ++iter2 ) { if ( iter2->second.first == id ) { Wt::WApplication::UpdateLock uiLock = iter->first->getUpdateLock(); dynamic_cast<CometWidget *>(iter2->second.second)->updateWidget(); iter->first->triggerUpdate(); } } } } virtual void updateWidget() = 0; // TBD: Convert this to a signal/slot instead of a virtual function template <class> friend class CometWidgetFactory; private: unsigned long int id; // Group ID boost::mutex CometMutex; }; } } #endif </pre> h3. Code Review updateOthers() updates each widget in the widget group and it should be called whenever a change was made in the widget. updateWidget should be a slot that will be connected to signals that update the widget itself. In the next part of the article I will demonstrate how to re-write the SimpleChat example very simply.