diff --git a/Scrummer/Controls/CardBoardControl.cs b/Scrummer/Controls/CardBoardControl.cs
new file mode 100644
index 0000000..fbfa708
--- /dev/null
+++ b/Scrummer/Controls/CardBoardControl.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Web;
+using System.Web.UI;
+using System.Web.UI.WebControls;
+
+namespace Scrummer.Controls
+{
+ public class CardBoardControl : Control, INamingContainer
+ {
+ #region Declarations
+
+ private string _serviceUrl = "CardBoardHandler";
+
+ private int _idBoard = 0;
+
+ private string _userName = string.Empty;
+
+ private int _timePoolData = 10000;
+
+ #endregion
+
+ #region Properties
+
+ public string ServiceUrl
+ {
+ get { return _serviceUrl; }
+ set { _serviceUrl = value; }
+ }
+
+ public int IDBoard
+ {
+ get { return _idBoard; }
+ set { _idBoard = value; }
+ }
+
+ public string UserName
+ {
+ get { return _userName; }
+ set { _userName = value; }
+ }
+
+ public int TimePoolData
+ {
+ get { return _timePoolData; }
+ set { _timePoolData = value; }
+ }
+
+ #endregion
+
+ #region Control Life cycle
+
+ public CardBoardControl()
+ {
+ Init += ChatControl_Init;
+ }
+
+ void ChatControl_Init(object sender, EventArgs e)
+ {
+ InitializeControls();
+ }
+
+ #endregion
+
+ #region Private methods
+
+ private void InitializeControls()
+ {
+ string strCfgName = string.Format("{0}_cfg", this.ClientID);
+
+ Panel divBoard = new Panel { ID = "divBoard", CssClass = "divBoard" };
+ Controls.Add(divBoard);
+
+ StringBuilder sbCfg = new StringBuilder();
+ sbCfg.AppendFormat("\n");
+ LiteralControl liScript = new LiteralControl(sbCfg.ToString());
+ Controls.Add(liScript);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Scrummer/Controls/CardBoardHandler.cs b/Scrummer/Controls/CardBoardHandler.cs
new file mode 100644
index 0000000..08ac572
--- /dev/null
+++ b/Scrummer/Controls/CardBoardHandler.cs
@@ -0,0 +1,213 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Web;
+using Scrummer.Code.BusinessLogic;
+using Scrummer.Code.Entities;
+using Scrummer.Code.JSON;
+
+namespace Scrummer.Controls
+{
+ public class CardBoardHandler : IHttpHandler
+ {
+ #region Declarations
+
+ private static object _monitor = new object();
+ private static Dictionary _cardBoards = new Dictionary();
+
+ #endregion
+
+ #region IHttpHandler
+
+ public bool IsReusable
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public void ProcessRequest(HttpContext context)
+ {
+ try
+ {
+ if (context.Request.RequestType == "GET")
+ {
+ if (string.IsNullOrEmpty(GetRequestParm(context, "IDCardEvent")))
+ {
+ ProcessInitializationReciver(context);
+ }
+ else
+ {
+ ProcessEventReciver(context);
+ }
+ }
+ if (context.Request.RequestType == "POST")
+ {
+ ProcessEventSender(context);
+ }
+ }
+ catch (Exception ex)
+ {
+ ResponseObject(context, new OperationStatus { IsOK = false, Message = ex.Message, });
+ }
+ }
+
+ #endregion
+
+ #region Private methods
+
+ private void ProcessInitializationReciver(HttpContext context)
+ {
+ int idBoard = Convert.ToInt32(GetRequestParm(context, "IDBoard"));
+ CardBoard cardBoard;
+ if (_cardBoards.ContainsKey(idBoard) == false)
+ {
+ lock (_cardBoards)
+ {
+ if (_cardBoards.ContainsKey(idBoard) == false)
+ {
+ cardBoard = new CardBoard(idBoard);
+ _cardBoards[idBoard] = cardBoard;
+ }
+ }
+ }
+
+ if (_cardBoards.ContainsKey(idBoard))
+ {
+ cardBoard = _cardBoards[idBoard];
+ List listCards = cardBoard.Cards_Status();
+ List listEvents = new List();
+ int lastIDCardEvent = cardBoard.GetLastIDCardEvent();
+ int lastIDCard = cardBoard.GetLastIDCard();
+ if (listCards.Count > 0)
+ {
+ listEvents = CardBoard.ConvertCardsToEvents(listCards, lastIDCardEvent);
+ }
+ else
+ {
+ listEvents = new List();
+ }
+ ResponseObject(context, listEvents);
+ }
+ }
+
+ private CardBoard GetCardBoard(int idBoard)
+ {
+ CardBoard cardBoard = null;
+ if (_cardBoards.ContainsKey(idBoard) == false)
+ {
+ lock (_cardBoards)
+ {
+ if (_cardBoards.ContainsKey(idBoard) == false)
+ {
+ cardBoard = new CardBoard(idBoard);
+ _cardBoards[idBoard] = cardBoard;
+ }
+ }
+ }
+ cardBoard = _cardBoards[idBoard];
+ return cardBoard;
+ }
+
+ private void ProcessEventReciver(HttpContext context)
+ {
+ int idBoard = Convert.ToInt32(GetRequestParm(context, "IDBoard"));
+ int idCardEvent = Convert.ToInt32(GetRequestParm(context, "IDCardEvent"));
+ string strTimePoolData = GetRequestParm(context, "TimePoolData");
+ int timePoolData = Convert.ToInt32(string.IsNullOrEmpty(strTimePoolData) ? "0" : strTimePoolData);
+
+ CardBoard cardBoard = GetCardBoard(idBoard);
+ bool mustWait = (timePoolData > 0);
+ do
+ {
+ List listMessages = cardBoard.Cards_GetEventList(idCardEvent);
+ if (listMessages.Count > 0)
+ {
+ mustWait = false;
+ ResponseObject(context, listMessages);
+ return;
+ }
+
+ if (mustWait)
+ {
+ lock (_monitor) { Monitor.Wait(_monitor, timePoolData); }
+ }
+ } while (mustWait);
+ ResponseObject(context, new List());
+ }
+
+ private void ProcessEventSender(HttpContext context)
+ {
+ string strIDBoard = GetRequestParm(context, "IDBoard");
+ int idBoard = Convert.ToInt32(string.IsNullOrEmpty(strIDBoard) ? "0" : strIDBoard);
+ string command = GetRequestParm(context, "Command");
+
+ bool done = false;
+ CardBoard cardBoard = GetCardBoard(idBoard);
+ lock (cardBoard)
+ {
+ if (command == "Create")
+ {
+ string title = GetRequestParm(context, "Title");
+ string body = GetRequestParm(context, "Body");
+ int x = Convert.ToInt32(GetRequestParm(context, "X"));
+ int y = Convert.ToInt32(GetRequestParm(context, "Y"));
+ cardBoard.Card_Create(title, body, x, y);
+ done = true;
+ }
+ if (command == "Move")
+ {
+ int idCard = Convert.ToInt32(GetRequestParm(context, "IDCard"));
+ int x = Convert.ToInt32(GetRequestParm(context, "X"));
+ int y = Convert.ToInt32(GetRequestParm(context, "Y"));
+ cardBoard.Card_Move(idCard, x, y);
+ done = true;
+ }
+ if (command == "Edit")
+ {
+ int idCard = Convert.ToInt32(GetRequestParm(context, "IDCard"));
+ string title = GetRequestParm(context, "Title");
+ string body = GetRequestParm(context, "Body");
+ cardBoard.Card_Edit(idCard, title, body);
+ done = true;
+ }
+ if (command == "Delete")
+ {
+ int idCard = Convert.ToInt32(GetRequestParm(context, "IDCard"));
+ cardBoard.Card_Delete(idCard);
+ done = true;
+ }
+ }
+ if (done)
+ {
+ NotifyAll();
+ ResponseObject(context, new OperationStatus { IsOK = true, Message = "Update successfully" });
+ }
+ }
+
+ private void NotifyAll()
+ {
+ lock (_monitor) { Monitor.PulseAll(_monitor); }
+ }
+
+ private string GetRequestParm(HttpContext context, string parm)
+ {
+ foreach (string key in context.Request.Params.AllKeys)
+ {
+ if (string.IsNullOrEmpty(key) == false && key.EndsWith(parm))
+ {
+ return context.Request.Params[key];
+ }
+ }
+ return string.Empty;
+ }
+
+ private void ResponseObject(HttpContext context, object obj)
+ {
+ var jsonWritter = new JSONWriter(true);
+ context.Response.ContentType = "text/json";
+ string strObject = jsonWritter.Write(obj);
+ context.Response.Write(strObject);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Scrummer/Pages/FrmBoard.cs b/Scrummer/Pages/FrmBoard.cs
index 62c9979..3e732ea 100644
--- a/Scrummer/Pages/FrmBoard.cs
+++ b/Scrummer/Pages/FrmBoard.cs
@@ -15,8 +15,12 @@ namespace Scrummer.Pages
void FrmBoard_Init(object sender, EventArgs e)
{
Title = "Board";
- var lblTest = new CLabel { Text = "Hello World", Tag = "h2" };
- Controls.Add(lblTest);
+
+ CardBoardControl cardBoardControl = new CardBoardControl();
+ cardBoardControl.ID = "ctrCardBoard";
+ cardBoardControl.IDBoard = _idBoard;
+ cardBoardControl.UserName = CurrentUser.Name;
+ Controls.Add(cardBoardControl);
ChatControl chatControl = new ChatControl();
chatControl.ID = "ctrChat";
diff --git a/Scrummer/Scripts/05. Cards.js b/Scrummer/Scripts/05. Cards.js
new file mode 100644
index 0000000..3462371
--- /dev/null
+++ b/Scrummer/Scripts/05. Cards.js
@@ -0,0 +1,287 @@
+var Card = function (cfg, idCard, title, body, x, y) {
+ this.cfg = cfg;
+ this.IDCard = idCard;
+ this.Title = title;
+ this.Body = body;
+ this.X = x;
+ this.Y = y;
+
+ // Create DOM
+ this.container = null;
+ this.divCard = document.createElement("div");
+ this.divCard.className = "divCard";
+ this.divCard.style.left = x + "px";
+ this.divCard.style.top = y + "px";
+
+ this.divTitle = document.createElement("div");
+ this.divCard.appendChild(this.divTitle);
+ this.divTitle.className = "divTitle";
+ this.divTitle.innerHTML = title;
+
+ this.divBody = document.createElement("div");
+ this.divCard.appendChild(this.divBody);
+ this.divBody.className = "divBody";
+ this.divBody.innerHTML = body;
+
+ this.divOverlay = document.createElement("div");
+ this.divCard.appendChild(this.divOverlay);
+ this.divOverlay.className = "divOverlay";
+
+ // Bind mouse event handlers
+ this.bindedMouseDown = Card.prototype.MouseDown.bind(this);
+ this.bindedMouseMove = Card.prototype.MouseMove.bind(this);
+ this.bindedMouseUp = Card.prototype.MouseUp.bind(this);
+ this.divOverlay.addEventListener("mousedown", this.bindedMouseDown, false);
+
+ // Bind touch event handlers
+ this.bindedTouchStart = Card.prototype.TouchStart.bind(this);
+ this.bindedTouchMove = Card.prototype.TouchMove.bind(this);
+ this.bindedTouchEnd = Card.prototype.TouchEnd.bind(this);
+ this.divOverlay.addEventListener("touchstart", this.bindedTouchStart, false);
+
+ // temporal variables for dragging and editing
+ this.offsetX = 0;
+ this.offsetY = 0;
+ this.newX = this.X;
+ this.newY = this.Y;
+ this.newTitle = this.Title;
+ this.newBody = this.Body;
+};
+Card.prototype = {
+ InsertInContainer: function (container) {
+ this.container = container;
+ this.container.appendChild(this.divCard);
+ },
+ RemoveFromContainer: function(){
+ this.container.removeChild(this.divCard);
+ },
+ Move: function (x, y) {
+ this.X = x;
+ this.Y = y;
+ this.divCard.style.left = this.X + "px";
+ this.divCard.style.top = this.Y + "px";
+ },
+ Edit: function (title, body) {
+ this.Title = title;
+ this.Body = body;
+ this.divTitle.innerHTML = this.Title;
+ this.divBody.innerHTML = this.Body;
+ },
+ Reset: function () {
+ this.newX = this.X;
+ this.newY = this.Y;
+ this.newTitle = this.Title;
+ this.newBody = this.Body;
+ this.divCard.style.left = this.X + "px";
+ this.divCard.style.top = this.Y + "px";
+ this.divTitle.innerHTML = this.Title;
+ this.divBody.innerHTML = this.Body;
+ },
+ SetNew: function () {
+ this.X = this.newX;
+ this.Y = this.newY;
+ this.Title = this.newTitle;
+ this.Body = this.newBody;
+ this.divCard.style.left = this.X + "px";
+ this.divCard.style.top = this.Y + "px";
+ this.divTitle.innerHTML = this.Title;
+ this.divBody.innerHTML = this.Body;
+ },
+ OnMove: function () {
+ if (this.X != this.newX || this.Y != this.newY) {
+ var card = this;
+ if (this.cfg.Connected == false) {
+ card.Reset();
+ }
+ var data = {
+ "IDBoard": this.cfg.IDBoard,
+ "Command": "Move",
+ "IDCard": this.IDCard,
+ "X": this.newX,
+ "Y": this.newY,
+ "TimeStamp": new Date().getTime()
+ };
+ SendData(this.cfg.ServiceUrl, data,
+ function (responseText) {
+ try {
+ var recvData = JSON.parse(responseText);
+ if (recvData && recvData instanceof Object && recvData.IsOK == true) {
+ card.SetNew();
+ } else {
+ card.Reset();
+ }
+ } catch (e) { }
+ }, function () {
+ card.Reset();
+ });
+ }
+ },
+ GetRelativePosToContainer: function (pos) {
+ var tempElem = this.container;
+ var relPos = { x: pos.x, y: pos.y };
+ while (tempElem) {
+ relPos.x -= tempElem.offsetLeft;
+ relPos.y -= tempElem.offsetTop;
+ tempElem = tempElem.offsetParent;
+ }
+ return relPos;
+ },
+ MouseDown: function (evt) {
+ var pos = this.GetRelativePosToContainer({ x: evt.clientX, y: evt.clientY });
+ this.offsetX = pos.x - this.divCard.offsetLeft;
+ this.offsetY = pos.y - this.divCard.offsetTop;
+
+ document.addEventListener("mouseup", this.bindedMouseUp, false);
+ document.addEventListener("mousemove", this.bindedMouseMove, false);
+
+ evt.preventDefault();
+ return false;
+ },
+ MouseMove: function (evt) {
+ var pos = this.GetRelativePosToContainer({ x: evt.clientX, y: evt.clientY });
+ this.newX = parseInt(pos.x - this.offsetX);
+ this.newY = parseInt(pos.y - this.offsetY);
+ this.divCard.style.left = this.newX + "px";
+ this.divCard.style.top = this.newY + "px";
+
+ evt.preventDefault();
+ return false;
+ },
+ MouseUp: function (evt) {
+ document.removeEventListener("mouseup", this.bindedMouseUp, false);
+ document.removeEventListener("mousemove", this.bindedMouseMove, false);
+
+ this.OnMove();
+
+ evt.preventDefault();
+ return false;
+ },
+ TouchStart: function (evt) {
+ var pos = this.GetRelativePosToContainer({ x: evt.touches[0].clientX, y: evt.touches[0].clientY });
+ this.offsetX = pos.x - this.divCard.offsetLeft;
+ this.offsetY = pos.y - this.divCard.offsetTop;
+
+ document.addEventListener("touchend", this.bindedTouchEnd, false);
+ document.addEventListener("touchcancel", this.bindedTouchEnd, false);
+ document.addEventListener("touchmove", this.bindedTouchMove, false);
+
+ evt.preventDefault();
+ return false;
+ },
+ TouchMove: function (evt) {
+ var pos = this.GetRelativePosToContainer({ x: evt.touches[0].clientX, y: evt.touches[0].clientY });
+ this.newX = parseInt(pos.x - this.offsetX);
+ this.newY = parseInt(pos.y - this.offsetY);
+ this.divCard.style.left = this.newX + "px";
+ this.divCard.style.top = this.newY + "px";
+
+ evt.preventDefault();
+ return false;
+ },
+ TouchEnd: function (evt) {
+ document.removeEventListener("touchend", this.bindedTouchEnd, false);
+ document.removeEventListener("touchcancel", this.bindedTouchEnd, false);
+ document.removeEventListener("touchmove", this.bindedTouchMove, false);
+
+ this.OnMove();
+
+ evt.preventDefault();
+ return false;
+ }
+};
+
+function RunCardBoard(cfg) {
+ cfg.divBoard = GetElement(cfg.divBoard);
+
+ cfg.Connected = false;
+
+ cfg.Cards = [];
+
+ var GetCardByID = function (idCard) {
+ for (var i = 0, n = cfg.Cards.length; i < n; i++) {
+ var card = cfg.Cards[i];
+ if (card.IDCard == idCard) {
+ return card;
+ }
+ }
+ return null;
+ };
+
+ var ProcessCardCreateEvent = function(cardEvent){
+ var card = new Card(cfg, cardEvent.IDCard, cardEvent.Title, cardEvent.Body, cardEvent.X, cardEvent.Y);
+ cfg.Cards.push(card);
+ card.InsertInContainer(cfg.divBoard);
+ };
+
+ var ProcessCardMoveEvent = function (cardEvent) {
+ var card = GetCardByID(cardEvent.IDCard);
+ if (card == null) { return; }
+ card.Move(cardEvent.X, cardEvent.Y);
+ };
+
+ var ProcessCardEditEvent = function (cardEvent) {
+ var card = GetCardByID(cardEvent.IDCard);
+ if (card == null) { return; }
+ card.Edit(cardEvent.Title, cardEvent.Body);
+ };
+
+ var ProcessCardDeleteEvent = function (cardEvent) {
+ var card = GetCardByID(cardEvent.IDCard);
+ if (card == null) { return; }
+ card.RemoveFromContainer(cfg.divBoard);
+
+ };
+
+ var RequestCardEventData = function () {
+ var ReciveCardEventData = function (responseText) {
+ cfg.Connected = true;
+
+ var recvData = JSON.parse(responseText);
+ if (recvData && recvData instanceof Array) {
+ for (var i = 0, n = recvData.length; i < n; i++) {
+ var cardEvent = recvData[i];
+ if (cardEvent.IDCardEvent > cfg.IDCardEvent) {
+ cfg.IDCardEvent = cardEvent.IDCardEvent;
+ }
+ if (cardEvent.EventType == "CardCreate") {
+ ProcessCardCreateEvent(cardEvent);
+ }
+ if (cardEvent.EventType == "CardMove") {
+ ProcessCardMoveEvent(cardEvent);
+ }
+ if (cardEvent.EventType == "CardEdit") {
+ ProcessCardEditEvent(cardEvent);
+ }
+ if (cardEvent.EventType == "CardDelete") {
+ ProcessCardDeleteEvent(cardEvent);
+ }
+ }
+ }
+
+ // Reset pool
+ window.setTimeout(function () {
+ RequestCardEventData();
+ }, 20);
+ };
+ var ErrorCardEventData = function () {
+ cfg.Connected = false;
+
+ // Retry
+ window.setTimeout(function () {
+ RequestCardEventData();
+ }, 5000);
+ };
+
+ // Pool data
+ var data = {
+ "IDBoard": cfg.IDBoard,
+ "IDCardEvent": cfg.IDCardEvent,
+ "TimePoolData": ((cfg.Connected == false) ? "0" : String(cfg.TimePoolData)),
+ "TimeStamp": new Date().getTime()
+ };
+ SendRequest(cfg.ServiceUrl, data, ReciveCardEventData, ErrorCardEventData);
+ };
+ RequestCardEventData();
+}
+
+
diff --git a/Scrummer/Scrummer.csproj b/Scrummer/Scrummer.csproj
index 684b5da..f7b421f 100644
--- a/Scrummer/Scrummer.csproj
+++ b/Scrummer/Scrummer.csproj
@@ -59,8 +59,10 @@
+
+
@@ -73,6 +75,8 @@
+
+
diff --git a/Scrummer/Styles/05. Cards.css b/Scrummer/Styles/05. Cards.css
new file mode 100644
index 0000000..e4cfad7
--- /dev/null
+++ b/Scrummer/Styles/05. Cards.css
@@ -0,0 +1,25 @@
+.divCard{
+ position: absolute;
+ background-color: rgb(255,255,0);
+ box-shadow: 0 0 10px rgba(0,0,0,0.5);
+ min-width: 150px;
+ min-height: 200px;
+ padding: 5px;
+ border-radius: 2px;
+}
+
+.divCard .divTitle{
+ font-weight: bold;
+ text-align: center;
+ padding-bottom: 5px;
+}
+
+.divCard .divOverlay{
+ opacity: 0;
+ background-color: rgb(255,255,0);
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}