//Rev: 2010.1 (P4JsApi 1.0 Example)
//NOTE: This is an example only, and should be modified to work in
//a production environment! No warranty is expressed or implied.
//Scripts should be tested thoroughly on a test server before
//using in a production environment.
//Copyright (c) 2010, Perforce Software, Inc. All rights reserved.
//Redistribution and use in source and binary forms, with or without
//modification, are permitted provided that the following conditions are met:
//1. Redistributions of source code must retain the above copyright
//notice, this list of conditions and the following disclaimer.
//2. Redistributions in binary form must reproduce the above copyright
//notice, this list of conditions and the following disclaimer in the
//documentation and/or other materials provided with the distribution.
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
//ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY
//DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
//THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
// submitMain(): Initializes the submit dialog window and stores the various
// bits of information accordingly.
*/
Submit = {
load: function() {
var desc = localStorage["previousDescriptions"];
var maxHistory = localStorage["maxHistory"];
if( desc )
Submit.descriptions = JSON.parse(desc);
else
Submit.descriptions = [];
if( maxHistory )
Submit.maximumHistory = parseInt(maxHistory);
else
Submit.maximumHistory = 10;
Submit.get('store_size').value = Submit.maximumHistory.toString();
var descriptionDropDown = Submit.get("previous_descriptions");
var o = descriptionDropDown.options;
for( key in Submit.descriptions ) {
var opt = document.createElement("option");
descriptionDropDown.options.add(opt);
opt.text = Submit.descriptions[key].replace(/\n/g," \\n ");
opt.value = Submit.descriptions[key];
}
descriptionDropDown.onchange = function() {
if( o.selectedIndex > 0)
Submit.setDescription(o[o.selectedIndex].value);
};
},
// convert an array into an object for quick in test
contains: function (a, obj) {
var i = a.length;
while (i--) {
if (a[i] === obj) {
return true;
}
}
return false;
},
save: function() {
if( ! Submit.contains(Submit.descriptions, Submit.get("description")) ) {
Submit.descriptions.unshift( Submit.get("description").value );
if( Submit.descriptions.length > Submit.maximumHistory )
Submit.descriptions.pop();
localStorage["previousDescriptions"] = JSON.stringify( Submit.descriptions );
localStorage["maxHistory"] = Submit.maximumHistory.toString();
}
// else nothing to do, we simply reused a value
},
setDescription: function(desc) {
Submit.get('description').value = desc;
},
clearAllDescriptions: function() {
// only need the first element
var descriptionDropDown = Submit.get("previous_descriptions");
descriptionDropDown.length = 1;
Submit.descriptions = [];
},
setSize: function(size) {
var oldSize = Submit.maximumHistory;
Submit.maximumHistory = parseInt(size);
if( Submit.maximumHistory < oldSize) {
// reduce the size of the pop-up menu immediately
// but add one extra space for the header
Submit.get("previous_descriptions").length = Submit.maximumHistory + 1;
Submit.descriptions.length = Submit.maximumHistory;
}
},
get: function(id) {
return document.getElementById(id);
},
init: function() {
var theChange = Submit.getChange();
// we get files differently from the default changelist than a numbered change
var clNumber = P4JsApi.getSubmitChange();
var files;
if (clNumber === "default") {
var entries = [];
var index = 0;
var key = "Files";
var rawFiles = theChange;
while (rawFiles[key + index]) {
entries.push(rawFiles[key + index]);
index++;
}
files = entries;
}
else {
files = Submit.getEntries(theChange, "depotFile");
}
// Extract the changelist fields needed for populating the file and jobs
// checklists:
var jobs = Submit.getEntries(theChange, "job");
var types = Submit.getEntries(theChange, "type");
var actions = Submit.getEntries(theChange, "action");
// Store non-list items in the various fields in the HTML form.
// Note that the change/user/client fields are hidden -- this is
// not a requirement of the script, so you can make those values
// visible as needed:
Submit.get("description").value = theChange.Description;
Submit.get("change").value = theChange.Change;
Submit.get("user").value = theChange.User;
Submit.get("client").value = theChange.Client;
Submit.get("status").value = theChange.Status;
// Now to populate the file list using the loadFileList function:
Submit.get("filelist").innerHTML = Submit.loadFilelist(files, "Files", true, types, actions);
// We create the job listing from the list of jobs using loadJobList:
var jobList = Submit.loadJoblist(jobs, "Jobs", true);
// And then pass that value to the appropriate location in the html:
Submit.get("joblist").innerHTML = jobList;
// The submit dialog should now be ready to edit.
Submit.load();
Submit.get('clear').onclick = Submit.clearAllDescriptions
},
//
// createForm(): Creates a change form that obeys the formatting for a Perforce
// changelist.
//
createForm : function() {
// Pull all of the pertinent field information from the hidden and
// visible fields in the html document:
var change = Submit.get("change").value;
var user = Submit.get("user").value;
var client = Submit.get("client").value;
var status = Submit.get("status").value;
var myDesc = Submit.get("description").value;
// Check to see if the description is empty. This section could be used
// to qualify the description prior to submitting to the Perforce server.
// Think of this as a "pre-submit" trigger that places no load on the
// server:
if (!myDesc) {
// The field was empty, so we update the html document with an error message:
Submit.get("desc_header").innerHTML = "You must add a description to submit files:";
} else {
// Otherwise, add tabs to the beginning of each line to make it
// a legal changelist description:
myDesc = "\t" + myDesc.replace(/\n/gm, '\n\t');
// Remove any error message in the html form:
Submit.get("desc_header").innerHTML = " ";
}
// Now to get a list of files to be submitted:
var myFiles = Submit.getCheckList("Files");
// If there are no files to be submitted, update the html document with an error message:
if (!myFiles) {
Submit.get("files_header").innerHTML = "Select files to submit:";
} else {
// the getCheckList function has already added whitespace to the
// beginning of each file, so we just clear any errors in the html
// document:
Submit.get("files_header").innerHTML = " ";
}
// Now to collect any jobs:
var myJobs = Submit.getCheckList("Jobs");
// One more check to make sure we at LEAST have a description or
// files:
if (myDesc && myFiles) {
// And then we assemble the form. If an optional field is empty (jobs, for example)
// Perforce ignores the field, so we don't need to check it:
var myForm = "Change: " + change
+ "\nClient: " + client
+ "\nUser: " + user
+ "\nStatus: " + status
+ "\nDescription: \n" + myDesc
+ "\nFiles: \n" + myFiles
+ "\nJobs: \n" + myJobs;
return myForm;
} else {
return "";
}
},
/**
* addJob(): Adds a job to the end of the job listing. This is a bit of a "brute
* force" hack, as it pulls the current list and appends the new job to the end.
* A more elegant solution would sort things and allow for easy deletion of any
* job row. However, at that point you may want to consider picking up a GUI
* framework to do the heavy lifting.
*/
addJob : function() {
// First, grab the current un-ordered list contents:
var myList = Submit.get("joblist").innerHTML;
// We then create an array of job numbers using the value
// attribute from each label tag:
// We pull the job number to be added from the html document:
var myJob = Submit.get("jobAdd").value;
// Confirm there were any contents in the job add field:
if (myJob) {
// Check to see if the job was already in the list:
if (Submit.itemExists(myJob, "Jobs")) {
alert("Job is already in the changelist.");
} else {
// We use "jobs -e" followed by the job number to be added to see if the job
// already exists:
var jobCheck = P4JsApi.p4('jobs -e ' + myJob);
// Since this is a JSON object, there's a size attribute we can check. A zero
// size means nothing was returned, so anything larger means it likely already
// exists:
if (jobCheck.size > 0) {
// Pull the description and status for the job from the returned JSON object:
theDesc = jobCheck.data[0].Description;
theStatus = jobCheck.data[0].Status;
// Format the description to replace newlines with spaces:
theDesc = '- ' + theDesc.replace(/\n/gm, ' ');
// If the resulting string is longer than 163 characters, truncate the string
// and add an ellipse. This could also be done as part of the style sheet:
if (theDesc.length > 163) {
theDesc = theDesc.substring(0, 160) + '...';
}
// addListItem takes the original unordered list and adds the new job. The result
// is used to update the html document:
Submit.get("joblist").innerHTML = addListItem(myList, myJob, 'Jobs', theStatus, theDesc);
// Clear the add job field:
Submit.get("jobAdd").value = "";
} else {
// No such job, so we inform the user and exit. We leave the field untouched
// so they can tweak a typo rather than start from scratch:
alert("Job number " + myJob + ' does not exist and can not be added.');
}
}
} else {
// The add job field was empty. This could be prevented with some style sheet
// tricks to disable the "add job" button unless the field had been clicked in.
// This could also be done in the html directly:
alert("Please enter the job number to add.");
}
},
/**
* itemExists(theItem, theLabel): Checks an item (theItem) against a list collected by a label
* name (theLabel) from the html document. Returns true if the item is found.
*/
itemExists: function(theItem, theLabel) {
var boxes = document.getElementsByName(theLabel + 'Box');
for (var i = 0; i < boxes.length; i++) {
if (boxes[i].value == theItem) {
return true;
}
}
return false;
},
/**
* addListItem(theList, theItem, theLabel, theStatus, theDesc): theList is likely
* redundant here. I think I could just as easily obtained a list of job numbers
* by label name, and used the generic loadJoblist function instead. This would make
* it easier to sort or otherwise refresh the job list.
*
* As it stands, this takes the original unordered list (theList), takes the new
* job number to be added (theItem, which was verified in addJob) and creates a new
* job entry at the end of the current list.
*/
addListItem: function(theList, theItem, theLabel, theStatus, theDesc) {
// We want to alternate colors for each job entry, so we divide the
// current number of lines by 2 and take the remainder to determine if the next
// entry is even or odd:
var boxes = document.getElementsByName(theLabel + 'Box').length;
if (boxes % 2) {
var rowClass = " class=\"alt-row\" ";
} else {
var rowClass = "";
}
// This is virtually identical to the formatting in loadJobList:
theList = theList + '<li ' + rowClass + '><label for=\"R' + theLabel + (boxes + 1)
+ '\"><input id=\"R' + theLabel + (boxes + 1) + '\" type=\"checkbox\" checked=\"checked\"'
+ ' name=\"' + theLabel + 'Box\" ' + 'value=\"' + theItem + '\" />'
+ theItem + ' <font color=\"#999999\"> (' + theStatus + ') </font>'
+ theDesc + '</li>\n';
return theList;
},
/**
* getCheckList(theField): Passing the name of a checklist ('jobs' or 'files')
* returns a newline delimited list, each line padded with a tab to make it
* changelist legal.
*/
getCheckList: function(theField) {
var boxes = document.getElementsByName(theField + 'Box');
var result = "";
for (var i = 0; i < boxes.length; i++) {
if (boxes[i].checked) {
result = result + "\n\t" + boxes[i].value;
}
}
return result;
},
/**
* loadFilelist(theList, theLabel, theState, theRev, theType, theAction):
* Since the files and jobs listings have very different formatting requirements,
* they have two separate functions to create the unordered list html for
* display. theList is an array of files or jobs to be listed. theLabel is used to create
* unique id's for each line. theState is a boolean to indicate whether the checkbox
* should be checked. The remaining variables, theRev, theType, and theAction, are
* used to provide the file rev, type and changelist action for the formatted line.
*/
loadFilelist : function(theList, theLabel, theState, theType, theAction) {
var checkList = "";
if (theState) {
theCheck = "checked=\"checked\"";
} else {
theCheck = "";
}
for (var i = 0; i < theList.length; i++) {
// If you ever wondered why the modulus operator (%) would ever be useful, here's
// a great example -- by dividing the current count, 'i', by two and getting the
// remainder using the modulus operator, the result is always 0 or 1. Perfect
// for creating alternating rows based on whether the row is even (0) or odd (1):
if (i % 2) {
// It's an odd row, so let's add the alt-row class to the tag for CSS trickery:
var rowClass = " class=\"alt-row\" ";
} else {
var rowClass = "";
}
// Now to construct the unordered list entry with the structure:
//
// <li {class="alt-row"}>
// <label for="RFiles1234" \>
// <input id="RFiles1234" type="checkbox" {checked="checked"}
// name="FilesBox value="{file path}" />
// {file path}#123 <font color="#999999"> ({file type}) {file action}
// </font></label></li>
//
// Each line has a unique ID which is not currently used much -- this
// is here for future development. All lines have the same name, which
// allows for collecting the list later.
//
// I set the checkbox value so I can store the same value I'll need later to
// construct the changelist form without a lot of heavy parsing.
//
// I color the text dark grey (#999999) to differentiate it from the file
// path:
if (theType[i] == null || theAction[i] == null)
{
checkList = checkList + '<li ' + rowClass + '><label for=\"R' + theLabel + i
+ '\"><input id=\"R' + theLabel + i + '\" type=\"checkbox\" ' + theCheck
+ ' name=\"' + theLabel + 'Box\" ' + 'value=\"' + theList[i] + '\" />'
+ theList[i] + '<font color=\"#999999\">' + '</font></label></li>\n';
}
else {
checkList = checkList + '<li ' + rowClass + '><label for=\"R' + theLabel + i
+ '\"><input id=\"R' + theLabel + i + '\" type=\"checkbox\" ' + theCheck
+ ' name=\"' + theLabel + 'Box\" ' + 'value=\"' + theList[i] + '\" />'
+ theList[i] + '<font color=\"#999999\"> (' + theType[i] + ') '
+ theAction[i] + '</font></label></li>\n';
}
}
return checkList;
},
/**
* loadJoblist(theList, theLabel, theState): Unlike the file listing from the changelist
* describe, we have to pull job items using individual commands. While it's entirely
* possible to re-factor this so that loadFileList and loadJobList is combined, I noted
* that the results were not as easy to read. One of the reasons I pass "theLabel" (even
* though it could easily be hard coded) is to simplify combining both functions later.
*
* Here we pass the job list as an array (theList) with theLabel and the initial checkbox
* selection state (theCheck).
*/
loadJoblist : function(theList, theLabel, theState) {
var checkList = "";
if (theState) {
theCheck = "checked=\"checked\"";
} else {
theCheck = "";
}
for (var i = 0; i < theList.length; i++) {
if (i % 2) {
// It's an odd row, so let's add the alt-row class to the tag for CSS trickery:
var rowClass = " class=\"alt-row\" ";
} else {
var rowClass = "";
}
// By using "p4 jobs -e" we can pull the current state and Description of the
// job being added to the list. This can be modified fairly easily to add custom
// field or other output:
var theJob = P4JsApi.p4('jobs -e ' + theList[i]);
theDesc = theJob.data[0].Description;
theStatus = theJob.data[0].Status;
// Format the description to replace newlines with spaces:
theDesc = '- ' + theDesc.replace(/\n/gm, ' ');
// If the resulting string is longer than 163 characters, truncate the string
// and add an ellipse. This could also be done as part of the style sheet:
if (theDesc.length > 163) {
theDesc = theDesc.substring(0, 160) + '...';
}
// Now to construct the unordered list entry with the structure:
//
// <li {class="alt-row"}>
// <label for="RJobs1234" \>
// <input id="RJobs1234" type="checkbox" {checked="checked"}
// name="JobsBox value="{file path}" />
// {job number} <font color="#999999"> ({job status}) </font>
// {job description} </label></li>
//
// Each line has a unique ID which is not currently used much -- this
// is here for future development. All lines have the same name, which
// allows for collecting the list later.
//
// I set the checkbox value so I can store the same value I'll need later to
// construct the changelist form without a lot of heavy parsing.
//
// I color the text dark grey (#999999) to differentiate it from the job
// number:
checkList = checkList + '<li ' + rowClass + '><label for=\"R' + theLabel + i
+ '\"><input id=\"R' + theLabel + i + '\" type=\"checkbox\" ' + theCheck
+ ' name=\"' + theLabel + 'Box\" ' + 'value=\"' + theList[i] + '\" />'
+ theList[i] + ' <font color=\"#999999\"> (' + theStatus + ') </font>'
+ theDesc + '</label></li>\n';
}
return checkList;
},
/**
* checkAll(theName, theChecked): Handy utility function that takes a value returned
* from a clicked checkbox and toggles all of the checkboxes for the associated
* checklist.
*/
checkAll : function(theName, theChecked) {
var toToggle = Submit.get(theName + 'Box');
for (var i = 0; i < toToggle.length; i++) {
toToggle[i].checked = theChecked;
}
},
/**
* saveChange(): Handles the submit button.
*/
saveChange : function() {
var result = Submit.saveForm(true);
if (result.error) {
alert("Perforce error: " + result.error);
}
},
/**
* saveForm(close): This is a separate function as I hope to use it to solve the
* problem with submitting a default change. The close argument is a boolean that
* indicates whether the submit form is to be closed or not.
*/
saveForm : function(close) {
var form = Submit.createForm();
var result = P4JsApi.p4('change -i "' + form + '"');
if (result.error) {
alert("Perforce error: " + result.error);
} else {
P4JsApi.refreshAll();
if (close) {
P4JsApi.accepted();
}
}
return result;
},
/**
* setFixes(): Uses a drop down menu value to set the job state upon submit.
*/
setFixes : function() {
var myJobs = Submit.getCheckList("Jobs");
myJobs = myJobs.split("\n\t");
if (myJobs.length > 0) {
var jobOpt = Submit.get("jobstatus_submit").value;
if (jobOpt === "same") {
jobOpt = "none";
};
var chgNum = Submit.get("change").value;
for (var i = 0; i < myJobs.length; i++) {
if (myJobs[i].length > 0) {
P4JsApi.p4('fix -c ' + chgNum + ' -s ' + jobOpt + ' ' + myJobs[i]);
}
}
}
},
/**
* getSubmitOptions(): Uses both the drop down menu and the "check out files after
* submit" checkbox to create the "-f" arguments for submitting the change.
*/
getSubmitOptions : function() {
var fileOpt = "";
switch (Submit.get("fileoption_submit").value) {
case 'Submit all selected files':
fileOpt = "submitunchanged";
break;
case 'Don\'t submit unchanged files':
fileOpt = "leaveunchanged";
break;
case 'Revert unchanged files':
fileOpt = "revertunchanged";
break;
}
if (Submit.get("recheckout").checked) {
fileOpt = fileOpt + "+reopen";
}
var options = " -f " + fileOpt;
return options;
},
/**
* These are stand alone function versions of the code that can be found in
* js/dashboard/changelist.js, and are relatively unchanged from those functions.
*/
/**
* Gets all occurrences of an incrementing key sequence from the specified JSON data object.
* This method looks for properties in the specified data object starting at key+index where
* index starts at zero and increments until no property is found by key+index in data.
*
*
* @param data - Object to index into for specified key
* @param key - incrementing key sequence
*
* @return - non-null but possibly empty array of value found by the specified incrementing key
*/
getEntries : function(data, key) {
var entries = [];
if (data && key) {
if (data[key]) {
// For handling data from commands like p4 opened
entries.push(data[key]);
} else {
// For handling data from commands like p4 describe
var index = 0;
while (data[key + index]) {
entries.push(data[key + index]);
index++;
}
}
}
return entries;
},
/**
* Loads the current changelist that the submit is for into an
* object literal with the key/value pairs of the current
* changelist.
*
* @return - object literal of current changelist data
*/
getChange : function() {
var changelist = {};
var clNumber = P4JsApi.getSubmitChange();
try {
// if the change is the default change, run 'p4 change -o' to get the filelist
if (clNumber === "default") {
var defaultChangelistData = P4JsApi.p4("change -o");
if (defaultChangelistData) {
changelist = defaultChangelistData;
}
} // else run 'p4 describe'
else if (clNumber && parseInt(clNumber) > 0) {
var changelistData = P4JsApi.p4("describe " + clNumber);
if (changelistData) {
changelist = changelistData;
}
}
} catch(e) {
changelist = {};
}
if (clNumber === "default")
return (changelist.data[0]);
else
return (changelist.data && changelist.data[0]) ? changelist.data[0] : {};
},
/**
* Submit the current changelist with the specified files, jobs,
* and description and close the submit dialog.
*
* This method first saves the current changelist and then submits
* it by changelist number and closes the submit dialog.
*
* @param files - array of depot file paths
* @param jobs - array of job ids
* @param description - string changelist description
*/
submitChanges : function() {
var change = Submit.createForm();
var chgNum = Submit.get("change").value;
var onSubmit = Submit.getSubmitOptions();
Submit.setFixes();
// if change is default then first run save, retrieve the changelist number and then run submit
// if change is not default, then no need run save. Just run submit
if (chgNum === "new") {
var saveResult = Submit.saveForm(false);
if (saveResult && saveResult.data[0].info) {
// Get the changelist number from saveResult.info
chgNum = saveResult.data[0].info.split(" ", 2)[1];
P4JsApi.p4('submit -c ' + chgNum + onSubmit);
Submit.save();
P4JsApi.refreshAll();
P4JsApi.accepted();
} else {
// Do nothing. "save" should have shown the error message already.
}
} else {
P4JsApi.p4('submit -c ' + chgNum + onSubmit);
Submit.save();
P4JsApi.refreshAll();
P4JsApi.accepted();
}
}
};
P4JsApi.setWebKitDeveloperExtrasEnabled(true);