SimpleChat made easy » History » Revision 7
Revision 6 (Ana Evans, 07/26/2013 08:21 PM) → Revision 7/12 (Ana Evans, 08/16/2013 05:27 PM)
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 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.
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)
{
boost::mutex::scoped_lock lock(CometMutex);
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)
{
boost::mutex::scoped_lock lock(CometMutex);
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)
{
boost::mutex::scoped_lock lock(CometMutex);
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;
}
static boost::mutex CometMutex;
};
}
}
#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()
{
boost::mutex::scoped_lock lock(CometMutex);
if ( widgets.find(this) != widgets.end() ) // Erases the current session from the widget map
widgets.erase(this);
}
CometApplication::WidgetMapType CometApplication::widgets;
CometApplication::boost::mutex CometMutex;
}
}
</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 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 <boost/thread/mutex.hpp>
#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.
http://depuyhiprecallnewscenter.webs.com/
http://depuyhipdevicerecall.blogspot.com/
http://hipfractures.spruz.com/
http://zithromaxheartlawsuit.webs.com/
http://biomet-hip-replacement-lawyer.webnode.com/
http://biometlawyer.zohosites.com/
http://biomethiplawyer.blog.com/