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";
}
}
);