Project

General

Profile

SimpleChat made easy » History » Version 3

Omer Katz, 03/23/2010 04:39 AM

1 2 Omer Katz
h1. SimpleChat made easy - Part 1
2 1 Omer Katz
3 2 Omer Katz
Currently the situation with Wt's Comet is that you have to track the sessions you want to push data to yourself.
4
I have written some classes that help dealing with this issue and simplifies building the simple chat example.
5 3 Omer Katz
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.
6 2 Omer Katz
7
h2. CometApplication.hpp
8
9
<pre>
10
#ifndef CometApplication_HPP
11
#define CometApplication_HPP
12
13
// <STL>
14
#include <list>
15
#include <string>
16
#include <map>
17
// </STL>
18
19
// <Boost>
20
#include <boost/unordered_map.hpp>
21
// </Boost>
22
23
// <Wt>
24
#include <Wt/WApplication>
25
#include <Wt/WWidget>
26
// </Wt>
27
28
namespace UI
29
{
30
	namespace Widgets
31
	{
32
                // Forward declerations
33
		class CometWidget;
34
35
		template <class>
36
		class CometWidgetFactory;
37
	}
38
39
	namespace Application
40
	{
41
		class CometApplication : public Wt::WApplication
42
		{
43
		public:
44
			CometApplication(const Wt::WEnvironment &env); // Ctor, passes enviorment variables to WApplication
45
			~CometApplication(); // Dtor
46
47
			friend class Widgets::CometWidget; // Befriending with CometWidget so it can access the widget map
48
49
			template <class>
50
			friend class Widgets::CometWidgetFactory; // Same here
51
		private:
52
			typedef std::pair<int, Wt::WWidget *> WidgetEntryType;
53
			typedef boost::unordered_multimap<CometApplication *, WidgetEntryType > WidgetMapType;
54
			typedef boost::unordered_multimap<CometApplication *, WidgetEntryType >::iterator WidgetMapIteratorType;
55
			typedef std::pair<WidgetMapIteratorType, WidgetMapIteratorType> WidgetRangeType;
56
57
			static WidgetMapType widgets;
58
59
			// Pushes a new widget to the current session with group id and the widget itself
60
			inline void registerWidget(unsigned long int id, Wt::WWidget *w)
61
			{
62
				widgets.insert(WidgetMapType::value_type(this, WidgetEntryType(id, w)));
63
			}
64
65
			// Searches for the widget of the group id for the current session and erases it
66
			inline void unregisterWidget(unsigned long int id)
67
			{
68
				WidgetRangeType range = widgets.equal_range(this);
69
70
				for ( WidgetMapIteratorType iter = range.first; iter != range.second; ++iter )
71
				{
72
					if ( iter->second.first == id )
73
					{
74
						widgets.erase(iter);
75
						break;
76
					}
77
				}
78
			}
79
80
			// Searches for a widget in the current session, casts it to the right widget type and returns it
81
			template <class T>
82
			inline T *getWidget(unsigned long int id)
83
			{
84
				WidgetRangeType range = widgets.equal_range(this);
85
86
				for ( WidgetMapIteratorType iter = range.first; iter != range.second; ++iter )
87
					if ( iter->second.first == id )
88
						return dynamic_cast<T *>(iter->second.second);
89
90
				return NULL;
91
			}
92
		};
93
	}
94
}
95
96
#endif
97
</pre>
98
99
h2. CometApplication.cpp
100
101
<pre>
102
#include "CometApplication.hpp"
103
104
namespace UI
105
{
106
	namespace Application
107
	{
108
		CometApplication::CometApplication(const Wt::WEnvironment &env)
109
		: WApplication(env)
110
		{
111
			enableUpdates(); // Enables ajax-push for the current session
112
		}
113
114
		CometApplication::~CometApplication()
115
		{
116
			if ( widgets.find(this) != widgets.end() ) // Erases the current session from the widget map
117
				widgets.erase(this);
118
		}
119
120
		CometApplication::WidgetMapType CometApplication::widgets;
121
	}
122
}
123
</pre>
124
125
h3. Code Review
126
127
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.
128
# 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.
129
# 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.
130
131 1 Omer Katz
As you can see the CometApplication instance only refers to *this*, which means only to the current session.
132 3 Omer Katz
I have befriended all other classes because registerWidget, unregisterWidget and getWidget should only be accessed by those classes.
133
This doesn't indicate that my code has a design problem in my opinion.
134
Sometimes you actually have to use class friendship.
135
It makes sense that the end user will not control anything related to registering/unregistering widgets as you will see.
136
137
138
After that we create a factory which will instanciate a widget for each session.
139
Here the magic is introduced into the system.
140
141
h2. CometWidgetFactory.hpp
142
143
<pre>
144
#ifndef CometWidgetFactory_HPP
145
#define CometWidgetFactory_HPP
146
147
#include <Wt/WApplication>
148
#include <Wt/WWidget>
149
150
#include "CometApplication.hpp"
151
152
namespace UI
153
{
154
	namespace Widgets
155
	{
156
		// CometWidgetFactory creates a widget of type WidgetType, WidgetType is derived from CometWidget
157
		template <class WidgetType>
158
		class CometWidgetFactory
159
		{
160
		private:
161
			static unsigned long int idCounter; // Counts how many ids there are
162
163
			unsigned long int id; // Group id
164
		public:
165
			CometWidgetFactory()
166
			: id(idCounter)
167
			{
168
				idCounter++; // New factory instance has been created.
169
			}
170
171
			~CometWidgetFactory()
172
			{
173
				idCounter--; // New factory instance has been destroyed
174
			}
175
176
			void create()
177
			{
178
				WidgetType *w = new WidgetType; // Creates a new widget
179
				w->id = id; // Assigns the group id
180
181
				// Registers the widget to the current session
182
				dynamic_cast<UI::Application::CometApplication *>(wApp)->registerWidget(id, w); 
183
			}
184
185
			void destroy()
186
			{
187
				// Unregisters the widget from the current session
188
				dynamic_cast<UI::Application::CometApplication *>(wApp)->unregisterWidget(id);
189
			}
190
191
			// Those operator overloadings make the factory "point" to the current widget we're refering to in the current session
192
			WidgetType *operator->()
193
			{
194
				return dynamic_cast<UI::Application::CometApplication *>(wApp)->getWidget<WidgetType>(id);
195
			}
196
197
			operator Wt::WWidget *()
198
			{
199
				return dynamic_cast<UI::Application::CometApplication *>(wApp)->getWidget<WidgetType>(id);
200
			}
201
		};
202
203
		template <class T>
204
		unsigned long int CometWidgetFactory<T>::idCounter = 0;
205
	}
206
}
207
208
#endif
209
</pre>
210
211
h3. Code Review
212
213
This class looks very simple but it has a trick.
214
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.
215
For example:
216
<pre>
217
class someWidget : public CometWidget<Wt::LineEdit>
218
{
219
//...
220
};
221
222
class MyPage : Wt::WContainer
223
{
224
private:
225
  static CometWidgetFactory<someWidget> myWidget;
226
public:
227
  MyPage()
228
  {
229
    myWidget.create(); // Creates a widget on the same group id
230
  }
231
232
  ~MyPage()
233
  {
234
    myWidget.destroy();
235
  }
236
};
237
</pre>
238
239
For each session a someWidget will be created and you can access the widget on the current session.
240
241
h2. CometWidget.hpp
242
243
<pre>
244
#ifndef CometWidget_HPP
245
#define CometWidget_HPP
246
247
#include <string>
248
249
#include <boost/thread/mutex.hpp>
250
251
252
#include <Wt/WContainerWidget>
253
254
#include "CometApplication.hpp"
255
256
namespace UI
257
{
258
    namespace Widgets
259
    {
260
        template <class WidgetType>
261
        class CometWidget : public WidgetType
262
        {
263
        protected:
264
            virtual void updateOthers()
265
            {
266
		boost::mutex::scoped_lock  lock(CometMutex);
267
268
                for ( Application::CometApplication::WidgetMapIteratorType iter = Application::CometApplication::widgets.begin(); iter != Application::CometApplication::widgets.end(); ++iter )
269
                {
270
                    Application::CometApplication::WidgetRangeType range = Application::CometApplication::widgets.equal_range(iter->first);
271
272
                    for ( Application::CometApplication::WidgetMapIteratorType iter2 = range.first; iter2 != range.second; ++iter2 )
273
                    {
274
                        if ( iter2->second.first == id )
275
                        {
276
                            Wt::WApplication::UpdateLock uiLock = iter->first->getUpdateLock();
277
                            dynamic_cast<CometWidget *>(iter2->second.second)->updateWidget();
278
                            iter->first->triggerUpdate();
279
                        }
280
                    }
281
                }
282
            }
283
284
            virtual void updateWidget() = 0; // TBD: Convert this to a signal/slot instead of a virtual function
285
286
            template <class>
287
            friend class CometWidgetFactory;
288
        private:
289
            unsigned long int id; // Group ID
290
291
            boost::mutex CometMutex;
292
        };
293
    }
294
}
295
296
#endif
297
</pre>
298
299
h3. Code Review
300
301
updateOthers() updates each widget in the widget group and it should be called whenever a change was made in the widget.
302
updateWidget should be a slot that will be connected to signals that update the widget itself.
303
304
In the next part of the article I will demonstrate how to re-write the SimpleChat example very simply.