%--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--%>
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";
}
}
);