function TrajectoryView(parentElementId, streamNodes, layout, streamUtilities, model) { var thisView = this; var mStreamUtilities = streamUtilities; var mStreamNodeHash = new Array(); var mStreamToTrajectoryNode = new Array(); var mChangelistTrajectoryLinks = new Array(); var mChangeOriginationHash = new Object(); var mChanges = null; var mRowsPerPage = 15; var mAllRows = []; var mChangelistDictionary = {}; function loadStreamNodeHash() { for( var i = 0; i < streamNodes.length; i++ ) { if(streamNodes[i]) mStreamNodeHash[streamNodes[i].stream.mStream] = streamNodes[i]; } } loadStreamNodeHash(); this.streamNodes = streamNodes; var mSelectedChangelist; function changeListRowClick(e, changelist) { if( e ) { var clNum = Number(e.currentTarget.cells[0].innerText); if( !changelist ) changelist = mChangelistDictionary[clNum]; } mSelectedChangelist = changelist; if( mSelectedChangelist ) { if( mSelectedChangelist.history)//no need to go get the history from the server thisView.changeHistoryListener(mSelectedChangelist); else model.loadSingleChangeHistory(mSelectedChangelist); } } this.changeHistoryListener = function(change) { if (document.getElementById('originRadio').checked) showChangeOriginationPath(change); else showChangePropagationPath(change); } this.switchTrajectoryDirection = function() { changeListRowClick(null, mSelectedChangelist); } this.Show = function() { $( "#trajectoryDialog" ).dialog( "open" ); } function formatTable() { firstLoad = true; $("#changeTable").flexigrid({ colModel : [ {display: 'Change', name : 'Change', width : 60, sortable : true, align: 'center'}, {display: 'Description', name : 'Description', width : 200, sortable : true, align: 'left'}, {display: 'User', name : 'User', width : 130, sortable : true, align: 'left'} ], /*buttons : [ {name: 'Add', bclass: 'add', onpress : test}, {name: 'Delete', bclass: 'delete', onpress : test}, {separator: true} ],*/ searchitems : [ {display: 'Change', name : 'change'}, {display: 'User', name : 'name', isdefault: true} ], url: true, height: 220, //default height width: 'auto', //auto width striped: true, //apply odd even stripes novstripe: false, minwidth: 30, //min width of columns minheight: 80, //min height of columns resizable: true, //allow table resizing method: 'POST', //data sending method dataType: 'json', //type of data for AJAX, either xml or json errormsg: 'Connection Error', usepager: true, nowrap: true, page: 1, //current page total: 1, //total pages useRp: true, //use the results per page select box rp: mRowsPerPage, //results per page rpOptions: [10, 15, 20, 30, 50], //allowed per-page values title: false, showToggleBtn: true, //show or hide column toggle popup hideOnSubmit: false, singleSelect: true, onChangePage: changePage, onSubmit: reLoad, onSearch: function(){console.log('search');}, onRpChange: changeRowCount//the user changed the row count }); } //this flag ensures that reLoad doesn't get called the first time formatTable is called. //Seems a bit janky, but there you have it. var firstLoad = true; function reLoad() { if( !firstLoad ) model.Load(mStreamUtilities.currentDepot); firstLoad = false; } function changePage(page) { var pageHi = mRowsPerPage * page; var currentChange = pageHi - mRowsPerPage; if( pageHi > mAllRows.length ) pageHi = mAllRows.length; var pageResults = []; for( var i = currentChange; i < pageHi; i++ ) pageResults.push( mAllRows[i] ); var myData = { total: mAllRows.length , page: page, rows: pageResults }; $('#changeTable').flexAddData(eval(myData)); if( currentChange == 0 )//this fixes a bug in flexigrid where the first row doesn't have its id set. { var hmm = $('#changeTable'); $(hmm[0].rows[0]).attr('id', 'row0'); } for( var i = currentChange; i < pageHi; i++ ) $('#row' + i).click( changeListRowClick ); } function changeRowCount(rowsPerPage) { mRowsPerPage = rowsPerPage; loadTable(mChanges); } function formatChangeResults() { mAllRows = []; mChangelistDictionary = {}; for (var i = 0; i < mChanges.length; i++) { var item = mChanges[i]; mAllRows.push({ id: i, cell: [item.Change, item.Description, item.User] }); mChangelistDictionary[item.Change] = item; } } function loadTable(changes) { deleteForm();//kill the old one, it doesn't have a good way to refresh the data buildForm();//add the html elements needed for the data grid mChanges = changes; formatChangeResults();//massage the changes so the data grid likes 'em formatTable();//turn the grid into a flexigrid changePage(1);//select the first page of the grid. } function buildForm() { var trajectoryDialog = document.getElementById('trajectoryDialog'); var form = document.createElement( "form" ); trajectoryDialog.appendChild( form ); var changeTypeRadio = document.createElement("div"); changeTypeRadio.id = "changeTypeRadio"; form.appendChild(changeTypeRadio); var originRadio = document.createElement("input"); originRadio.type="radio"; originRadio.id="originRadio"; originRadio.name="radio"; originRadio.checked="true"; changeTypeRadio.appendChild(originRadio); $(originRadio).click( function(e, that) { thisView.switchTrajectoryDirection(); }); var originLabel = document.createElement('label'); originLabel.htmlFor="originRadio"; originLabel.innerHTML= "Show a change's origin"; changeTypeRadio.appendChild(originLabel); var propagateRadio = document.createElement("input"); propagateRadio.type="radio"; propagateRadio.id="propagateRadio"; propagateRadio.name="radio"; changeTypeRadio.appendChild(propagateRadio); $(propagateRadio).click( function(e, that) { thisView.switchTrajectoryDirection(); }); var propagateLabel = document.createElement('label'); propagateLabel.htmlFor="propagateRadio"; propagateLabel.innerHTML= "Show a change's propagation path"; changeTypeRadio.appendChild(propagateLabel); var progressbar = document.createElement("div"); progressbar.id = "progressbar"; form.appendChild(progressbar); var changeTable = document.createElement("table"); changeTable.id = "changeTable"; changeTable.style.display = 'none'; form.appendChild(changeTable); $( "#changeTypeRadio" ).buttonset(); } function deleteForm() { var trajectoryDialog = document.getElementById('trajectoryDialog'); //get rid of the old table, flexigrid isn't good at removing single rows for(var i = trajectoryDialog.childElementCount; i > 0 ; i--) { var child = trajectoryDialog.children[i - 1]; trajectoryDialog.removeChild(trajectoryDialog.children[i - 1]); } } this.depotChangesListener = function( data ) { loadTable( data ); } function clear() { for( var i in mStreamToTrajectoryNode ) mStreamToTrajectoryNode[i].close(); for( var i in mChangelistTrajectoryLinks ) mChangelistTrajectoryLinks[i].close(); } function showChangeOriginationPath(selectedChange) { clear(); mStreamToTrajectoryNode = new Array(); mChangelistTrajectoryLinks = new Array(); var streams = new Array(); for(var j = 0; j < selectedChange.history.length; j++ ) streams.push(selectedChange.history[j].stream); for(var i = 0; i < streamNodes.length; i++) if( streamNodes[i] ) streamNodes[i].draw(false); highlightOriginatingPaths(selectedChange); draw(); } function draw() { //console.log('layout: ' + layout); var horizontal = layout == 'ancestor'; for(var i = 0; i < mChangelistTrajectoryLinks.length; i++ ) mChangelistTrajectoryLinks[i].draw(horizontal); for(var i in mStreamToTrajectoryNode) mStreamToTrajectoryNode[i].draw(horizontal); } function highlightOriginatingPaths(changeList) { var histString = 'Changelist history: '; //each changelist has a history of changelists that it came from (derived from the command: 'changes -i changeNum@changeNum) if( changeList.history.length > 1 ) { for(var histCount = 0; histCount < changeList.history.length; histCount++) { var change = changeList.history[histCount]; //mChangeOriginationHash contains all the streams that a change is associated with, derived from the 'integrated' command if( mChangeOriginationHash[change.Change] ) { //get the node for the stream that this history change happened in var streamNode = mStreamNodeHash[change.stream]; if( streamNode ) { histString += ' [' + change.Change + ' streamNode: ' + streamNode.stream.mName; //now cycle through the stream nodes that this change has been integrated from for( var originCount = 0; originCount < mChangeOriginationHash[change.Change].length; originCount++ ) { var terminatingNode = mStreamNodeHash[ mChangeOriginationHash[change.Change][originCount] ];//hash! if( terminatingNode && streamNode.stream != terminatingNode.stream) { var arrowDirection = mStreamUtilities.ArrowDirection.bottom; if( streamNode.mRow > terminatingNode.mRow ) arrowDirection = mStreamUtilities.ArrowDirection.top; histString += ' <-- terminatingNode: ' + terminatingNode.stream.mName; //console.log('XXXXXXXXXXXXXXXXXXXXXX terminating node: ' + terminatingNode.stream.mName + ' streamNode: ' + streamNode.stream.mName + ' change: ' + change.Change); addTrajectoryDataPoint(change);//new mChangelistTrajectoryLinks.push( new TrajectoryLink(streamNode, terminatingNode, arrowDirection, change, mStreamUtilities ) ); } else//this can happen if the change occurred in a non-stream or outside of this depot { //console.log('@@@@@@@@@@@@@@@@@@@@@@@@@@@'); addTrajectoryDataPoint(change); //return; } } } } else { //console.log('***************************change without integration history: ' + change.Change); addTrajectoryDataPoint(change); } //histString += ']'; } } else { //console.log('Single change that has no history: ' + changeList.Change); addTrajectoryDataPoint(changeList); } //console.log(histString); } function setChangeOriginationData(data) { mChangeOriginationHash = data; } this.depotIntegratedListener = function(data ) { /* $( "#progressbar" ).show(); $( "#progressbar" ).progressbar({ value: 50 }); */ setChangeOriginationData(data); } function showChangePropagationPath(selectedChange) { clear(); mStreamToTrajectoryNode = new Array(); mChangelistTrajectoryLinks = new Array(); var forwardHistory = new Array(); //for each change greater than the selected change, load it's history (if we haven't already). //if the selected change is part of a change's history, add change to the propagation path (an array called forwardHistory) for(var i = 0; i < mChanges.length; i++) { //console.log(i); var change = mChanges[i]; if( change.Change >= selectedChange.Change )//we only care about the changes that happened after the selectedChange { //lazily loading the history here... if(!change.history ) model.getChangeHistoryNoCallback(change); for(var histCount = 0; histCount < change.history.length; histCount++) { if( selectedChange.Change == change.history[histCount].Change ) { forwardHistory.push(change); continue; } } } } var streams = new Array(); for(var j = 0; j < forwardHistory.length; j++ ) streams.push(forwardHistory[j].stream); for( var j = 0; j < streamNodes.length; j++ ) if( streamNodes[i] ) streamNodes[j].draw(false); var forward = ''; //create the changelist nodes and the links between them for( var i = forwardHistory.length - 1; -1 < i; i-- ) { var change = forwardHistory[i]; if( change.Change != selectedChange.Change && mChangeOriginationHash[change.Change] ) { var streamNode = mStreamNodeHash[change.stream]; if( streamNode ) { for( var originCount = 0; originCount < mChangeOriginationHash[change.Change].length; originCount++ ) { var originatingNode = mStreamNodeHash[ mChangeOriginationHash[change.Change][originCount] ]; if( streamNode && originatingNode && streamNode.stream != originatingNode.stream) { var arrowDirection = mStreamUtilities.ArrowDirection.bottom; if( streamNode.mRow > originatingNode.mRow ) arrowDirection = mStreamUtilities.ArrowDirection.top; addTrajectoryDataPoint(change);//new mChangelistTrajectoryLinks.push( new TrajectoryLink(streamNode, originatingNode, arrowDirection, change, mStreamUtilities ) ); } else { addTrajectoryDataPoint(change); } } } } else { addTrajectoryDataPoint(change); } forward += forwardHistory[i].Change + ' '; } draw(); //console.log( 'change: ' + selectedChange + ' forward: ' + forward); } function addTrajectoryDataPoint(changeList) { var streamNode = mStreamNodeHash[ changeList.stream ]; if( !streamNode ) { console.log('The attempt to show change ' + changeList.Change + ' was unsuccessful. It is associated with a stream, ' + changeList.stream + ', that does not appear to exist.' ); return; } //console.log(changeList.stream); if( !mStreamToTrajectoryNode[streamNode] ) mStreamToTrajectoryNode[streamNode] = new TrajectoryNode(parentElementId, streamNode, mStreamUtilities); var trajectoryNode = mStreamToTrajectoryNode[streamNode]; trajectoryNode.addChange(changeList); } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#9 | 8124 | David George |
Updated some of the calls to ClientKit to sync up with it's refactoring. Commented out the diagnostic time-tracking stuff in TrajectoryModel. Refactored TrajectoryView so that it once again has a asynchronous progress bar. |
||
#8 | 8123 | David George |
More optimization. This time, I took out code that didn't actually do anything but take up cycles, and removed calls to the server for changelist history if that call has previously been made. |
||
#7 | 8116 | David George | Interim checkin so Jaimen can test against ClientKit. | ||
#6 | 8110 | David George | This is a fairly unstable checkin that is a first attempt at getting the StreamGraph to work with the refactored ClientKit. | ||
#5 | 8092 | David George | Added stuff so the reload button works on the trajectory dialog. | ||
#4 | 8091 | David George | Added handling on the changelist flexigrid for changing the number of rows per page. | ||
#3 | 8090 | David George |
More optimization of the changelist trajectory dialog. Updated the third-party flexigrid. Updated jquery. Added timeTracker, a temporary object that'll assist me in tracking time for optimization purposes. |
||
#2 | 8084 | David George |
Began work on optimizing the changelist trajectory algorithm. By using lazy loading, it's now about 4x faster. Refactored TrajectoryView and TrajectoryModel so that the model doesn't know about the view (I know, should never have known about it in the first place. I figured out how to create a Listener pattern in JavaScript so that I could do this a little more elegantly). |
||
#1 | 8081 | David George |
Initial submit of JavaScript StreamGraph. Main functionality is: Change Trajectory (Change Flow), Timeline, and GitStreams. |