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; +}