FELIX-2331 : webconsole event plugin should be able to send/post events.
https://issues.apache.org/jira/browse/FELIX-2331

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1172536 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/event/src/main/resources/OSGI-INF/l10n/bundle.properties b/webconsole-plugins/event/src/main/resources/OSGI-INF/l10n/bundle.properties
index 9402ce8..e9a94b0 100644
--- a/webconsole-plugins/event/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/webconsole-plugins/event/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -37,4 +37,10 @@
 topic=Event Topic

 properties=Event Properties

 

-plugin.events.title=Events
\ No newline at end of file
+plugin.events.title=Events

+

+# send event

+sendEvent=Send/Post Event

+post=Post Event

+send=Send Event

+close=Close

diff --git a/webconsole-plugins/event/src/main/resources/res/events.html b/webconsole-plugins/event/src/main/resources/res/events.html
index 9fe35ac..4f7b700 100644
--- a/webconsole-plugins/event/src/main/resources/res/events.html
+++ b/webconsole-plugins/event/src/main/resources/res/events.html
@@ -1,8 +1,13 @@
-<script type="text/javascript" src="${pluginRoot}/res/ui/events.js"></script>

+<script type="text/javascript" src="${pluginRoot}/res/ui/addremove.js"></script>

+<script type="text/javascript" src="${pluginRoot}/res/ui/propeditor.js"></script>

+<script type="text/javascript" src="${pluginRoot}/res/ui/events.js"></script>

 <script type="text/javascript">

 var i18n = {

 	displayTimeline: '${displayTimeline}',

-	displayList    : '${displayList}'

+	displayList    : '${displayList}',

+	close          : '${close}',

+	send           : '${send}',

+	post           : '${post}'

 }

 </script>

 

@@ -13,6 +18,7 @@
 <div class="ui-widget-header ui-corner-top buttonGroup">

 	<button id="switch">${displayTimeline}</button>

 	<button id="clear">${clear}</button>

+	<button id="sendButton">${sendEvent}</button>

 	<button id="reload">${reload}</button>

 </div>

 

@@ -35,9 +41,27 @@
 </table>

 

 <div id="timeline" class="ui-helper-hidden">&nbsp;</div>

+

 <div id="timelineLegend" class="ui-helper-hidden">

 	<span class="event eventservice">Service Event</span>

 	<span class="event eventbundle">Bundle Event</span>

 	<span class="event eventconfig">Config Event</span>

 	<span class="event eventframework">Framework Event</span>

 </div>

+

+<div id="sendDialog" title="${sendEvent}" class="ui-helper-hidden">

+	<table>

+	<tbody>

+		<tr>

+			<th>${topic}:</th>

+			<td><input id="sendTopic"/></td>

+		</tr>

+		<tr>

+			<th>${properties}:</th>

+			<td>

+				<div id="sendProperties">&nbsp;</div>

+			</td>

+		</tr>

+	</tbody>

+	</table>

+</div>

diff --git a/webconsole-plugins/event/src/main/resources/res/ui/addremove.js b/webconsole-plugins/event/src/main/resources/res/ui/addremove.js
new file mode 100644
index 0000000..9ff4cef
--- /dev/null
+++ b/webconsole-plugins/event/src/main/resources/res/ui/addremove.js
@@ -0,0 +1,99 @@
+/*
+	Structure is:
+	<div class="my-element-container">
+		<div class="multiInput">
+			<div id="myElement" /> + -
+		</div>
+	</div>
+	
+	Options:
+	add : function(element) - called AFTER add
+	remove : function(element) - called BEFORE remove
+*/
+(function( $ ){
+
+	var methods = {
+		init : function(options) {
+			return this.each( function() {
+				// If options exist, lets merge them with our default settings
+				var settings = {
+					add    : false,
+					remove : false
+				};
+				if (options) settings = $.extend(settings, options);
+				
+				var _this = $(this);
+				var template = _init_template( _this );
+				_this.data('addremove_settings', settings);
+				_new_entry(template, _this);
+			})
+		},
+		reset : function() {
+			return this.each( function() {
+				var self = $(this);
+				self.find('div.addremove').not(':first').each( function() {
+					$(this).find('button.rem').click();
+				});
+			});
+		},
+		add : function(count) {
+			return this.each( function() {
+				var self = $(this);
+				var addfn = self.find('div.addremove:last button.add');
+				if (addfn.size()) {
+					var num = count ? count : 1;
+					for(var i=0; i<num; i++) addfn.click();
+				}
+			});
+		},
+		count : function() {
+			var self = $(this);
+			return $(this).find('div.addremove').size();
+		}
+	};
+
+	$.fn.addremove = function( method ) {
+		// Method calling logic
+		if ( methods[method] ) {
+		  return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+		} else if ( typeof method === 'object' || ! method ) {
+		  return methods.init.apply( this, arguments );
+		} else {
+		  $.error( 'Method ' +  method + ' does not exist on jQuery.addremove' );
+		} 
+	};
+	
+	var _new_entry = function(template, container) {
+		var settings = container.data('addremove_settings');
+		var _entry = template.clone()
+			.find('button.add').click( function() {
+				_new_entry(template, container);
+				return false;
+			}).end()
+			.find('button.rem').click( function() {
+				if (container.addremove('count') > 1) {
+					if (typeof settings.remove == 'function') {
+						settings.remove(_entry);
+					}
+					_entry.remove();
+				}
+				return false;
+			}).end()
+			.appendTo(container);
+		if (typeof settings.add == 'function') settings.add(_entry);
+	}
+
+	var _init_template = function(entry) {
+		return _el('div', 'addremove')
+			.append(entry.children())
+			.append(_el('button', 'add').text('+'))
+			.append(_el('button', 'rem').text('-'));
+	}
+
+	var _el = function(el, clazz) {
+		var ret = $(document.createElement(el));
+		if (clazz) ret.addClass(clazz);
+		return ret;
+	}
+
+})( jQuery );
\ No newline at end of file
diff --git a/webconsole-plugins/event/src/main/resources/res/ui/events.css b/webconsole-plugins/event/src/main/resources/res/ui/events.css
index 2c25dfb..ea62a6b 100644
--- a/webconsole-plugins/event/src/main/resources/res/ui/events.css
+++ b/webconsole-plugins/event/src/main/resources/res/ui/events.css
@@ -24,4 +24,13 @@
 
 table.propTable, table.propTable tr, table.propTable td { border: none !important }
 td.propName { padding: 0 4px 0 0; text-align: right !important; text-decoration: underline }
-td.propVal   { padding: 0 0 0 4px }
\ No newline at end of file
+td.propVal   { padding: 0 0 0 4px }
+td.time { white-space: nowrap }
+
+/* send dialog styling */
+.addremove button { width: 16px; height: 16px; line-height: 10px; font-size: 10px; margin: 2px 2px }
+.addremove_inner { display: inline }
+.propeditor_entry select {	margin-left: 4px }
+#sendTopic { width: 100% }
+#sendDialog table { margin-left: auto; margin-right: auto }
+#sendDialog table th { text-align: right; font-weight: bold; padding-right: .5em }
diff --git a/webconsole-plugins/event/src/main/resources/res/ui/events.js b/webconsole-plugins/event/src/main/resources/res/ui/events.js
index 02cd849..2cd1abd 100644
--- a/webconsole-plugins/event/src/main/resources/res/ui/events.js
+++ b/webconsole-plugins/event/src/main/resources/res/ui/events.js
@@ -62,9 +62,9 @@
     }
 
 	$(tr( null, { id: 'entry' + dataEntry.id }, [
-		td( null, null, [ text( printDate(dataEntry.received) ) ] ),
-		td( null, null, [ text( dataEntry.topic ) ] ),
-		td( null, null, [ propE ] )
+		td( 'time', null, [ text( printDate(dataEntry.received) ) ] ),
+		td( 'topic', null, [ text( dataEntry.topic ) ] ),
+		td( 'detailes', null, [ propE ] )
 	])).appendTo(eventsBody);
 }
 
@@ -96,4 +96,60 @@
 	$('#reload').click(function() {
 		$.get(pluginRoot + '/data.json', null, renderData, 'json');
 	}).click();
+
+	function sendData(action) {
+		// check topic
+		var topic = sendTopic.val();
+		var topicOk = topic.match(/^[\w-]+(\/[\w-]+)*$/g) != null;
+		if (topicOk) {
+			sendTopic.removeClass('ui-state-error');
+		} else {
+			addTopic.removeClass('ui-state-error');
+		}
+		var data = sendProperties.propeditor('serialize');
+		if (topicOk && data != false) {
+			$.post(pluginRoot,
+				data.concat([
+					{name : 'action', value : action},
+					{name : 'topic', value : topic}
+				]),
+				renderData,
+				'json'
+			);
+			sendDialog.dialog("close");
+		}
+	}
+
+	/* send dialog code */
+	var sendButtons = {};
+	sendButtons[i18n.close] = function() {
+		$(this).dialog("close");
+	}
+	sendButtons[i18n.send] = function() {
+		sendData('send');
+	}
+	sendButtons[i18n.post] = function() {
+		sendData('post');
+	}
+	var sendDialog = $('#sendDialog').dialog({
+		autoOpen: false,
+		modal   : true,
+		width   : '40%',
+		buttons : sendButtons,
+		open    : function() {
+			sendTopic.val('');
+			sendProperties.propeditor('reset');
+		}
+	});
+	var sendTopic = $('#sendTopic');
+	var sendProperties = $('#sendProperties').propeditor({
+		add: function(el) {
+			el.find('select').addClass('dynhover');
+			initStaticWidgets(el);
+		}
+	});
+	$('#sendButton').click(function() {
+		sendDialog.dialog('open');
+	});
+
 });
diff --git a/webconsole-plugins/event/src/main/resources/res/ui/propeditor.js b/webconsole-plugins/event/src/main/resources/res/ui/propeditor.js
new file mode 100644
index 0000000..51f47d7
--- /dev/null
+++ b/webconsole-plugins/event/src/main/resources/res/ui/propeditor.js
@@ -0,0 +1,193 @@
+/*
+	Structure is:
+	<div class="propeditor">
+		<div>
+			<input class="key"/> = 
+			<input class="value"/>
+			<select>
+				<option>byte</option>
+				<option>int</option>
+				<option>long</option>
+				<option>float</option>
+				<option>double</option>
+				<option>string</option>
+				<option>char</option>
+				<option>hex</option>
+				<option>sha1</option>
+				<option>base64</option>
+			</select>
+		</div>
+	</div>
+	
+	Options:
+	validator : function(keyInputField, valInputField, type)
+*/
+(function( $ ){
+	var TYPES = ['byte', 'int', 'long', 'float', 'double', 'string', 'char', 'hex', 'base64', 'sha1'];
+
+	var methods = {
+		init : function(options) {
+			return this.each( function() {
+				// If options exist, lets merge them with our default settings
+				var settings = {
+					validator  : false,
+				};
+				if (options) settings = $.extend(settings, options);
+
+				var _this = $(this);
+				_this.data('propeditor_settings', settings);
+				_this.append(_entry());
+				_this.addremove(settings);
+			})
+		},
+		reset : function() {
+			return this.each( function() {
+				$(this).addremove('reset')
+					.find('.key').val('').end()
+					.find('.val').val('');
+			});
+		},
+		serialize : function() {
+			var self = $(this);
+			var validator = self.data('propeditor_settings').validator;
+			var result = new Array();
+			var ok = true;
+			var entries = $(this).find('div.addremove');
+			if (entries.size() == 1) {
+				var k = entries.find('.key').removeClass('ui-state-error').val();
+				var v = entries.find('.val').removeClass('ui-state-error').val();
+				if (k != '' || v != '') {
+					var data = _check_entry( entries, validator );
+					//if ( data == false ) ok = false; else result.push(data);
+					if ( data == false ) ok = false; else result = data;
+				}
+			} else {
+				entries.each(function() {
+					var data = _check_entry( $(this), validator );
+					//if ( data == false ) ok = false; else result.push(data);
+					if ( data == false ) ok = false; else result = result.concat(data);
+				});
+			}
+			return ok ? result : false;
+		},
+		setup : function(data, append) {
+			var self = $(this);
+			if (!append) self.propeditor('reset');
+			for (var i in data) {
+				self.addremove('add');
+				var d = data[i];
+				self.find('div.addremove:last')
+					.find('.key').val(d.key).end()
+					.find('.val').val(d.val).end()
+					.find('.typ').val(d.type);
+			}
+			if (!append) self.find('div.addremove:first').remove();
+		}
+	};
+
+	$.fn.propeditor = function( method ) {
+		// Method calling logic
+		if ( methods[method] ) {
+		  return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+		} else if ( typeof method === 'object' || ! method ) {
+		  return methods.init.apply( this, arguments );
+		} else {
+		  $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
+		} 
+	};
+
+	var _el = function(el, clazz) {
+		var ret = $(document.createElement(el));
+		if (clazz) ret.addClass(clazz);
+		return ret;
+	}
+
+	var _entry = function() {
+		var sel = _el('select', 'typ');
+		for(var i in TYPES) {
+			sel.append( _el('option').text( TYPES[i] ) );
+		}
+		return _el('span', 'propeditor_entry')
+			.append( _el('input', 'key') )
+			.append( _el('span').text(' = '))
+			.append( _el('input', 'val') )
+			.append( sel );
+	}
+
+	var _check_entry = function(e, validator) {
+		var k = e.find('.key').removeClass('ui-state-error');
+		var v = e.find('.val').removeClass('ui-state-error');
+		var t = e.find('.typ').val();
+		var ok = _check_field(k);
+		ok = _check_field(v) && ok;
+		ok = ok && _defaultPropertyValidator(k, v, t);
+		if (ok && typeof validator == 'function') {
+			ok = validator(k, v, t);
+		}
+		if (ok) {
+			return [
+				{ 'name' : 'key', 'value' : k.val() },
+				{ 'name' : 'val', 'value' : v.val() },
+				{ 'name' : 'type', 'value' : t }
+			];
+			/*
+			return {
+				'key': k.val(),
+				'val': v.val(),
+				'type': t
+			}*/
+		}
+		return false;
+	}
+
+	var _check_field = function(f) {
+		if (!f.val()) {
+			f.addClass('ui-state-error');
+			return false;
+		}
+		return true;
+	}
+
+	var _range = function(field, isint, min, max) {
+		var v = false;
+		if (isint) {
+			var v = parseInt(field.val());
+			var xv = parseFloat(field.val());
+			if ( isNaN(v) || isNaN(xv) || xv != v) return false;  // field is actually double
+		} else { // double
+			v = parseFloat(field.val());
+			if (isNaN(v)) return false;
+		}
+
+		return v >= min && v <= max;
+	}
+	
+	// key == element, value == element, type == type string
+	var _defaultPropertyValidator = function(key, value, type) {
+		var v = value.val();
+		var ok = true;
+		switch(type) {
+			case 'byte':
+				ok = _range(value, true, -128, 127);
+				break;
+			case 'int':
+				ok = _range(value, true, -2147483648, 2147483647);
+				break;
+			case 'long':
+				ok = _range(value, true, -9223372036854775808, 9223372036854775807);
+				break;
+			case 'float':
+				ok = _range(value, false, 1.4E-45, 3.4E38);
+				break;
+			case 'double':
+				ok = _range(value, false, 4.9E-324, 1.7E308);
+				break;
+			case 'char':
+				ok = v.length == 1;
+				break;
+		}
+		if (!ok) value.addClass('ui-state-error');
+		return ok;
+	}
+
+})( jQuery );
\ No newline at end of file