//////////////////////////////////////////////////////////////////////////// // 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. //////////////////////////////////////////////////////////////////////////// // Uncomment the following for debug: // P4JsApi.setWebKitDeveloperExtrasEnabled(true) ; /** // submitMain(): Initializes the submit dialog window and stores the various // bits of information accordingly. */ function submitMain() { // Get the changelist being submitted. Note that this version of the submit // dialog will not work well with the default change. See the getChange // function for more information: var theChange = getChange(); // Extract the changelist fields needed for populating the file and jobs // checklists: var status = theChange.Status; if (status == "new") { var files = getEntries(theChange, "Files"); } else { var files = getEntries(theChange, "depotFile"); } var jobs = getEntries(theChange, "job"); var types = getEntries(theChange, "type"); var revs = getEntries(theChange, "rev"); var actions = 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: document.getElementById("description").value = theChange.Description; document.getElementById("change").value = theChange.Change; document.getElementById("user").value = theChange.User; document.getElementById("client").value = theChange.Client; document.getElementById("status").value = theChange.Status; // Now to populate the file list using the loadFileList function: document.getElementById("filelist").innerHTML = loadFilelist(files, "Files", true, revs, types, actions); // We create the job listing from the list of jobs using loadJobList: var jobList = loadJoblist(jobs, "Jobs", true); // And then pass that value to the appropriate location in the html: document.getElementById("joblist").innerHTML = jobList; // The submit dialog should now be ready to edit. } /** * createForm(): Creates a change form that obeys the formatting for a Perforce * changelist. */ function createForm() { // Pull all of the pertinent field information from the hidden and // visible fields in the html document: var change = document.getElementById("change").value; var user = document.getElementById("user").value; var client = document.getElementById("client").value; var status = document.getElementById("status").value; var myDesc = document.getElementById("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: document.getElementById("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: document.getElementById("desc_header").innerHTML = " "; } // Now to get a list of files to be submitted: var myFiles = getCheckList("Files"); // If there are no files to be submitted, update the html document with an error message: if (!myFiles) { document.getElementById("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: document.getElementById("files_header").innerHTML = " "; } // Now to collect any jobs: var myJobs = 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. */ function addJob() { // First, grab the current un-ordered list contents: var myList = document.getElementById("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 = document.getElementById("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 (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: document.getElementById("joblist").innerHTML = addListItem(myList, myJob, 'Jobs', theStatus, theDesc); // Clear the add job field: document.getElementById("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. */ function itemExists(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. */ function addListItem(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. */ function getCheckList(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. */ function loadFilelist(theList, theLabel, theState, theRev, 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: 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] + '#' + theRev[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). */ function loadJoblist(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. */ function checkAll(theName, theChecked) { var toToggle = document.getElementsByName(theName + 'Box'); for (var i = 0; i < toToggle.length; i++) { toToggle[i].checked = theChecked; } } /** * saveChange(): Handles the submit button. */ function saveChange() { var result = 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. */ function saveForm(close) { var form = 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. */ function setFixes() { var myJobs = getCheckList("Jobs"); myJobs = myJobs.split("\n\t"); if (myJobs.length > 0) { var jobOpt = document.getElementById("jobstatus_submit").value; if (jobOpt === "same") { jobOpt = "none"; }; var chgNum = document.getElementById("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. */ function getSubmitOptions() { var fileOpt = ""; switch (document.getElementById("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 (document.getElementById("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 */ function getEntries(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 */ function getChange() { var changelist = {}; try { var clNumber = P4JsApi.getSubmitChange(); if (clNumber == "default") { var changelistData = P4JsApi.p4("change -o"); // alert(JSON.stringify(changelistData)); if (changelistData) { changelist = changelistData; } } if (clNumber && parseInt(clNumber) > 0) { var changelistData = P4JsApi.p4("describe " + clNumber); if (changelistData) { changelist = changelistData; } } } catch(e) { changelist = {}; } 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 */ function submitChanges() { var chgNum = document.getElementById("change").value; var onSubmit = getSubmitOptions(); 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 = saveForm(false); try { var chgText = saveResult.data[0]['p4INFO']['args']['change']; chgNum = chgText.split(' ', 2)[1]; } catch (err) { alert(JSON.stringify(err)); } } alert(chgNum); P4JsApi.p4('submit -c ' + chgNum + onSubmit); P4JsApi.refreshAll(); P4JsApi.accepted(); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#5 | 8161 | jhalbig |
Merging //guest/lester_cheung/p4jsapi/examples/js/submitNoFramework.js //guest/lester_cheung/p4jsapi/examples/submitNoFramework.html to //public/perforce/p4jsapi/examples/... Includes "default change" submit fix -- thank you, Lester! |
||
#4 | 7663 | dscheirer | Rollback //public/perforce/p4jsapi to changelist 7643 | ||
#3 | 7660 | jhalbig |
As per meeting to resolve issue with sync calls made from within async calls hanging P4V (job039138) pulling example code from Public Depot until it can be re-worked and confirmed to function correctly with pending P4JsApi changes. |
||
#2 | 7643 | jhalbig |
Cleaned up any remaining discrepancies in the code. Removed all internal references and debugging code. Cleaned up formatting, added more commenting for submit dialog code. Ready for 2010.1 Beta. |
||
#1 | 7641 | jhalbig | Rename/move file(s) | ||
//guest/perforce_software/p4jsapi/examples/js/submit_basic.js | |||||
#1 | 7638 | jhalbig | Initial Addition of P4JsApi samples for 2010.1 Beta |