dojo.provide("dojotrader.widget.Portfolio"); dojo.require("dojo.widget.*"); //TODO - can this be removed dojo.require("dojo.widget.Tooltip"); dojo.require("dojo.storage.*"); dojo.require("dojo.collections.*"); dojo.require("dojo.widget.HtmlWidget"); dojo.require("dojotrader.widget.DaytraderProgressBar"); dojo.require("dojotrader.widget.BaseDaytraderPane"); dojo.widget.defineWidget( "dojotrader.widget.Portfolio", [dojo.widget.HtmlWidget, dojotrader.widget.BaseDaytraderPane], { templatePath: dojo.uri.dojoUri("/dojotrader/widget/templates/HtmlPortfolio.html"), widgetType: "Portfolio", label: "Portfolio", // backing cache for quote/holding data _holdingCache: null, _numUpdates: 0, _numSells: 0, _quoteCache: null, _toolTips: null, _updateType: "full", _refreshButton: null, _changeRefreshComboBox: null, _numQuotes: 0, _timer: null, _progressBar: null, postCreate: function() { dojotrader.widget.Portfolio.superclass.postCreate.call(this); dojo.event.topic.subscribe("/portfolio", this, "handleExternalEvents"); this._holdingCache = new dojo.collections.Dictionary(); this._quoteCache = new dojo.collections.Dictionary(); this._toolTips = new dojo.collections.Dictionary(); }, handleExternalEvents: function(args) { if (args.event == "updateHoldings") { this._updateType = "partial"; this.getHoldings(); } else if (args.event == "getHoldings") { //alert("getHoldings"); this._updateType = "full"; this.resetCaches(); this.getHoldings(); } }, fillInTemplate: function(args, frag) { this._refreshButton = dojo.widget.createWidget("Button", {caption: "Refresh Now"}, this.buttonNode); dojo.event.connect(this._refreshButton, "onClick", this, "refreshQuotes"); this._progressBar = dojo.widget.createWidget("dojotrader:DaytraderProgressBar", {cycle: true}, this.progressBar); this._progressBar.onComplete = dojo.lang.hitch(this, this.refreshQuotes); if (this.debug) { var ref = dojo.widget.createWidget("Button", {caption: "Get Holdings"}, this.holdingsButtonNode); dojo.event.connect(ref, "onClick", this, "getHoldings"); this.pfDebug.style.display = ""; } ref = dojo.widget.createWidget("Button", {caption: "Sell Holdings"}, this.sellButtonNode); dojo.event.connect(ref, "onClick", this, "sellSelectedHoldings"); }, changeTimerSettings: function() { var value = this.refreshSelectBox.value; this._progressBar.stop(); if (value == "manual") { this._progressBar.reset(); } else { this._progressBar.setPeriod(+(value) * 1000); this._progressBar.start(); } }, // ------------------------------------------------ // Population (initial and adding) to holdings cache and table // ------------------------------------------------ getHoldings: function() { var uid = dojo.storage.get("uid"); if (uid == null || uid == "") { alert("Unable to find uid in storage, using uid:0"); uid = "uid:0"; } dojo.io.bind({ method: "GET", //url: "/proxy/SoapProxy/getHoldings?p1=" + uid + "&format=json", url: "/daytraderProxy/doProxy/getHoldings?p1=" + uid, mimetype: "text/json", load: dojo.lang.hitch(this,this.handleGetHoldings), error: dojo.lang.hitch(this,this.handleError), useCache: false, preventCache: true }); }, handleGetHoldings: function(type, data, evt) { this.populateHoldingsCache(data); }, populateHoldingsCache: function(data) { if (data.getHoldingsReturn.HoldingDataBean) { for (idx=0; idx < data.getHoldingsReturn.HoldingDataBean.length; idx++) { // add holding to the holding cache if it does not exist var holding = data.getHoldingsReturn.HoldingDataBean[idx]; if (!this._holdingCache.contains(holding.holdingID)) { this._numUpdates++; this._holdingCache.add(holding.holdingID, holding); this.getQuote(holding.quoteID); } } } }, getQuote: function (quoteID) { dojo.io.bind({ method: "GET", //url: "/proxy/SoapProxy/getQuote?p1=" + quote + "&format=json", url: "/daytraderProxy/doProxy/getQuote?p1=" + quoteID, mimetype: "text/json", load: dojo.lang.hitch(this, this.handleQuote), error: dojo.lang.hitch(this, this.handleError), useCache: false, preventCache: true }); }, handleQuote: function (type, data, event) { // add quote to the cache (does it really matter if i protect the data??) if (!this._quoteCache.contains(data.getQuoteReturn.symbol)) { this._quoteCache.add(data.getQuoteReturn.symbol, data.getQuoteReturn); } this._numUpdates--; if (this._numUpdates == 0) { // holding/quote retrieval has completed // now go off and build the DOM if (this.pfHoldingsDisplay.style.display == "none") this.pfHoldingsDisplay.style.display = ""; this.addToHoldingsTable(); this.replaceTextNode(this.msLastUpdated, this.createShortTimeStampStr()); //alert("Done: " + this._holdingCache.count + " - " + this._quoteCache.count); } }, addToHoldingsTable: function() { var list = this._holdingCache.getKeyList(); for (idx=0; idx < list.length; idx++) { var holding = this._holdingCache.entry(list[idx]).value; // only add holding if it isn't in the table already if (this.pfHoldingsTable.innerHTML.indexOf(holding.holdingID + "-row") < 0) { //row = this.pfHoldingsTable.insertRow(this.pfHoldingsTable.rows.length); row = this.pfHoldingsTable.insertRow(1); this.addHoldingToTable(holding, row); } } //this.redoTableColorScheme(this.pfHoldingsTable, 1, 1); this.addHoldingStatsToTable(); }, addHoldingStatsToTable: function() { var stats = this.calculateHoldingStats(); if (this._holdingCache.count + 2 == this.pfHoldingsTable.rows.length) { // replace the stats with updated information //alert("table already exists - updating stats"); row = this.pfHoldingsTable.rows[this.pfHoldingsTable.rows.length - 1]; this.replaceTextNode(row.cells[2], this.addCommas("$" + stats.purchase.toFixed(2))); this.replaceTextNode(row.cells[3], this.addCommas("$" + stats.current.toFixed(2))); if (row.cells[4].firstChild) row.cells[4].removeChild(row.cells[4].firstChild); this.addValueWithArrow(row.cells[4], stats.gain, this.addCommas("$" + stats.gain.toFixed(2))); } else { alert("i messed up!"); } // finally update the account summary with the holdings value dojo.event.topic.publish("/accountSummary", {event: "updateHoldingsValue", value: stats.current}); }, calculateGain: function(holding, quote){ purchase = +(holding.purchasePrice); current = +(quote.price); quantity = +(holding.quantity); return quantity * (current - purchase); }, calculateHoldingStats: function() { var totalPurchase = 0; var totalCurrent = 0; var totalGain = 0; var list = this._holdingCache.getKeyList(); for (idx=0; idx < list.length; idx++) { var holding = this._holdingCache.entry(list[idx]).value; quote = this._quoteCache.entry(holding.quoteID).value; totalPurchase += +(holding.quantity) * +(holding.purchasePrice); totalCurrent += +(holding.quantity) * +(quote.price); } totalGain = totalCurrent - totalPurchase; return {purchase: totalPurchase, current: totalCurrent, gain: totalGain}; }, addHoldingToTable: function(holding, row) { row.id = holding.holdingID + "-row"; if (this.pfHoldingsTable.rows.length % 2 == 0) row.className = "row-even"; else row.className = "row-odd"; cell = row.insertCell(0); //TODO - Stan this is the attempt to add Tooltip tooltipText = "HoldingID: " + holding.holdingID + " PurchaseDate: " + holding.purchaseDate; cell.innerHTML = "" + ""; span = cell.childNodes[1]; tooltip = dojo.widget.createWidget("ToolTip", {id: "tip-"+holding.holdingID, connectId: holding.holdingID + "-row", toggle: "explode", caption: tooltipText}, span); this._toolTips.add(holding.holdingID, tooltip); cell = row.insertCell(1); this.appendTextNode(cell, holding.quoteID); cell = row.insertCell(2); this.appendTextNode(cell, holding.quantity); cell = row.insertCell(3); this.appendTextNode(cell, this.addCommas("$" + holding.purchasePrice)); // handle the current quote price and calculate gain/loss quote = this._quoteCache.entry(holding.quoteID).value; cell = row.insertCell(4); this.appendTextNode(cell, this.addCommas("$" + quote.price)); cell = row.insertCell(5); gain = this.calculateGain(holding, quote); this.addValueWithArrow(cell, gain, this.addCommas("$" + gain.toFixed(2))); }, // ------------------------------------------------ // Sell holdings // ------------------------------------------------ sellSelectedHoldings: function() { // figure out which holdings were selected var checkboxes = document.getElementsByName("holdings-chkbox"); var uid = dojo.storage.get("uid"); if (uid == null || uid == "") { alert("Unable to find uid in storage, using uid:0"); uid = "uid:0"; } for (idx=0; idx < checkboxes.length; idx++) { if (checkboxes[idx].checked) { // use numSells variable to "batch" requests this._numSells++; dojo.io.bind({ method: "GET", //url: "/proxy/SoapProxy/sell?p1=" + uid + "&p2=" + checkboxes[idx].id + "&p3=0&format=json", url: "/daytraderProxy/doProxy/sell?p1=" + uid + "&p2=" + checkboxes[idx].id + "&p3=0", mimetype: "text/json", load: dojo.lang.hitch(this, this.handleSellHolding), error: dojo.lang.hitch(this, this.handleError), useCache: false, preventCache: true, holdingid: checkboxes[idx].id }); } } }, handleSellHolding: function(type, data, event, xhr) { var order = data.sellReturn; var message = "Sell order completed (OrderId: " + order.orderID + " - $" + order.price + ")" //alert("HoldingID: " + xhr.holdingid); //addMessage(message); //displayStatusMessage(message); dojo.event.topic.publish("/messages", {event: "addMessage", message: message}); // remove holding from cache this._holdingCache.remove(xhr.holdingid); // remove the tooltip tooltip = this._toolTips.entry(xhr.holdingid).value; tooltip.destroy(); // remove row from table rows = this.pfHoldingsTable.rows; for (idx = 1; idx < rows.length - 1; idx++) { row = rows[idx]; holdingID = row.id.substring(0,row.id.indexOf("-")); if (holdingID == xhr.holdingid) this.pfHoldingsTable.deleteRow(idx); } this._numSells--; // don't refresh holdings table until all sells are completed if (this._numSells == 0) { //this.resetCaches(); //this.getHoldings(); this.addHoldingStatsToTable(); this.redoTableColorScheme(this.pfHoldingsTable, 1, 1); this.replaceTextNode(this.msLastUpdated, this.createShortTimeStampStr()); } }, // ------------------------------------------------ // Update current quote prices // ------------------------------------------------ refreshQuotes: function () { this._numQuotes = 0; keys = this._quoteCache.getKeyList(); //alert("Refresh Quotes: " + keys); for (idx = 0; idx < keys.length; idx++) { //alert(keys[idx]); this.updateQuote(keys[idx]); } this.replaceTextNode(this.msLastUpdated, this.createShortTimeStampStr()); }, updateQuote: function (symbol) { dojo.io.bind({ method: "GET", //url: "/proxy/SoapProxy/getQuote?p1=" + symbol.value + "&format=json", url: "/daytraderProxy/doProxy/getQuote?p1=" + symbol, mimetype: "text/json", load: dojo.lang.hitch(this, this.handleUpdateQuote), error: dojo.lang.hitch(this, this.handleError), useCache: false, preventCache: true }); }, handleUpdateQuote: function (type, data, evt) { //alert("handleQuoteUpate"); newQuote = data.getQuoteReturn; oldQuote = this._quoteCache.entry(newQuote.symbol).value; // replace quote in the quote cache if (oldQuote.price != newQuote.price || oldQuote.change != newQuote.change) { //alert("replaced item in cache"); this._quoteCache.add(newQuote.symbol, newQuote); this.updateQuoteRow(newQuote); } this._numQuotes++; if (this._quoteCache.count == this._numQuotes) { //alert("Refresh Complete: " + this._quoteCache.count + " - " + this._numQuotes); //this.addHoldingStatsToTable(); } }, updateQuoteRow: function (quote) { //alert("updateQuoteRow"); rows = this.pfHoldingsTable.rows; for (idx=1; idx < rows.length; idx++) { //alert(rows[idx].cells[1].innerHTML); row = rows[idx]; if (row.cells[1].innerHTML == quote.symbol) { dojo.lfx.html.highlight(row,"red",2000,5).play(); this.replaceTextNode(row.cells[4], this.addCommas("$" + quote.price)); holdingID = row.id.substring(0,row.id.indexOf("-")); holding = this._holdingCache.entry(holdingID).value; gain = this.calculateGain(holding, quote); row.cells[5].removeChild(row.cells[5].firstChild); this.addValueWithArrow(row.cells[5], gain, this.addCommas("$" + gain.toFixed(2))); } } this.addHoldingStatsToTable(); }, resetCaches: function () { //alert("resetCaches"); this._holdingCache.clear(); //this._quoteCache.clear(); //this._numUpdates = 0; var list = this._toolTips.getKeyList(); for (idx=0; idx < list.length; idx++) { tooltip = this._toolTips.entry(list[idx]).value; tooltip.destroy(); } this._toolTips.clear(); var count = this.pfHoldingsTable.rows.length - 1; for (idx=1; idx < count; idx++) { this.pfHoldingsTable.deleteRow(1); } this.addHoldingStatsToTable(); this.resetHoldingsPane(); }, resetHoldingsPane: function () { // this method is used to cleanup the holdings pane when a user logs out var display = this.pfHoldingsDisplay; if (display.style.display == "") display.style.display = "none"; } } );