提交 705ce391 编写于 作者: K kohsuke

implemented SCM repository browser support.


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@2566 71c3de6d-444a-0410-be80-ed276b4c234a
上级 a4a9be11
......@@ -23,6 +23,7 @@ import hudson.remoting.VirtualChannel;
import hudson.scm.CVSSCM;
import hudson.scm.SCM;
import hudson.scm.SCMS;
import hudson.scm.SCMDescriptor;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrappers;
......@@ -921,7 +922,7 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node
for( Descriptor<BuildWrapper> d : BuildWrappers.WRAPPERS )
result &= d.configure(req);
for( Descriptor<SCM> scmd : SCMS.SCMS )
for( SCMDescriptor scmd : SCMS.SCMS )
result &= scmd.configure(req);
for( TriggerDescriptor d : Triggers.TRIGGERS )
......
......@@ -443,35 +443,32 @@ public abstract class Job<JobT extends Job<JobT,RunT>, RunT extends Run<JobT,Run
/**
* Accepts submission from the configuration page.
*/
public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
if(!Hudson.adminCheck(req,rsp))
public synchronized void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
if (!Hudson.adminCheck(req, rsp))
return;
req.setCharacterEncoding("UTF-8");
description = req.getParameter("description");
if(req.getParameter("logrotate")!=null)
if (req.getParameter("logrotate") != null)
logRotator = LogRotator.DESCRIPTOR.newInstance(req);
else
logRotator = null;
keepDependencies = req.getParameter("keepDependencies")!=null;
keepDependencies = req.getParameter("keepDependencies") != null;
try {
properties.clear();
for (JobPropertyDescriptor d : JobPropertyDescriptor.getPropertyDescriptors(getClass())) {
JobProperty prop = d.newInstance(req);
if(prop!=null)
if (prop != null)
properties.add(prop);
}
} catch (FormException e) {
throw new ServletException(e);
}
save();
String newName = req.getParameter("name");
if(newName!=null && !newName.equals(name)) {
rsp.sendRedirect("rename?newName="+newName);
......
......@@ -118,6 +118,7 @@ public abstract class ViewJob<JobT extends ViewJob<JobT,RunT>, RunT extends Run<
super.doConfigSubmit(req,rsp);
// make sure to reload to reflect this config change.
nextUpdate = 0;
save();
}
......
......@@ -185,10 +185,23 @@ public final class CVSChangeLogSet extends ChangeLogSet<CVSChangeLog> {
private String prevrevision;
private boolean dead;
/**
* Gets the full file name in the CVS repository, like
* "/foo/bar/zot.c"
*/
public String getName() {
return name;
}
/**
* Gets just the last component of the path, like "zot.c"
*/
public String getSimpleName() {
int idx = name.lastIndexOf('/');
if(idx>0) return name.substring(idx+1);
return name;
}
public void setName(String name) {
this.name = name;
}
......@@ -239,7 +252,7 @@ public final class CVSChangeLogSet extends ChangeLogSet<CVSChangeLog> {
}
public Revision(String s) {
String[] tokens = s.split(".");
String[] tokens = s.split("\\.");
numbers = new int[tokens.length];
for( int i=0; i<tokens.length; i++ )
numbers[i] = Integer.parseInt(tokens[i]);
......@@ -249,6 +262,8 @@ public final class CVSChangeLogSet extends ChangeLogSet<CVSChangeLog> {
/**
* Returns a new {@link Revision} that represents the previous revision.
*
* For example, "1.5"->"1.4", "1.5.2.13"->"1.5.2.12", "1.5.2.1"->"1.5"
*
* @return
* null if there's no previous version, meaning this is "1.1"
*/
......
package hudson.scm;
import java.net.URL;
import java.io.IOException;
/**
* {@link RepositoryBrowser} for CVS.
*
* @author Kohsuke Kawaguchi
*/
public abstract class CVSRepositoryBrowser extends RepositoryBrowser {
/**
* Determines the link to the diff between the version
* in the {@link CVSChangeLogSet.File} to its previous version.
*
* @return
* null if the browser doesn't have any URL for diff.
*/
public abstract URL getDiffLink(CVSChangeLogSet.File file) throws IOException;
/**
* Determines the link to a single file under CVS.
* This page should display all the past revisions of this file, etc.
*
* @return
* null if the browser doesn't have any suitable URL.
*/
public abstract URL getFileLink(CVSChangeLogSet.File file) throws IOException;
}
......@@ -11,7 +11,6 @@ import hudson.model.AbstractModelObject;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.model.Job;
import hudson.model.LargeText;
......@@ -108,14 +107,23 @@ public class CVSSCM extends AbstractCVSFamilySCM implements Serializable {
*/
private boolean flatten;
private CVSRepositoryBrowser repositoryBrowser;
public CVSSCM(String cvsroot, String module,String branch,String cvsRsh,boolean canUseUpdate, boolean flatten) {
/**
* @stapler-constructor
*/
public CVSSCM(String cvsroot, String module,String branch,String cvsRsh,boolean canUseUpdate, boolean legacy) {
this.cvsroot = cvsroot;
this.module = module.trim();
this.branch = nullify(branch);
this.cvsRsh = nullify(cvsRsh);
this.canUseUpdate = canUseUpdate;
this.flatten = flatten && module.indexOf(' ')==-1;
this.flatten = !legacy && module.indexOf(' ')==-1;
}
@Override
public CVSRepositoryBrowser getBrowser() {
return repositoryBrowser;
}
private String compression() {
......@@ -697,7 +705,7 @@ public class CVSSCM extends AbstractCVSFamilySCM implements Serializable {
env.put("CVS_PASSFILE",cvspass);
}
public static final class DescriptorImpl extends Descriptor<SCM> implements ModelObject {
public static final class DescriptorImpl extends SCMDescriptor implements ModelObject {
static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
/**
......@@ -710,18 +718,17 @@ public class CVSSCM extends AbstractCVSFamilySCM implements Serializable {
*/
private String cvsExe;
/**
* Copy-on-write.
*/
private volatile Map<String,RepositoryBrowser> browsers = new HashMap<String,RepositoryBrowser>();
// compatibility only
private transient Map<String,RepositoryBrowser> browsers;
// compatibility only
class RepositoryBrowser {
String diffURL;
String browseURL;
}
DescriptorImpl() {
super(CVSSCM.class);
super(CVSSCM.class,CVSRepositoryBrowser.class);
load();
}
......@@ -733,15 +740,10 @@ public class CVSSCM extends AbstractCVSFamilySCM implements Serializable {
return "CVS";
}
public SCM newInstance(StaplerRequest req) {
return new CVSSCM(
req.getParameter("cvs_root"),
req.getParameter("cvs_module"),
req.getParameter("cvs_branch"),
req.getParameter("cvs_rsh"),
req.getParameter("cvs_use_update")!=null,
req.getParameter("cvs_legacy")==null
);
public SCM newInstance(StaplerRequest req) throws FormException {
CVSSCM scm = req.bindParameters(CVSSCM.class, "cvs.");
scm.repositoryBrowser = RepositoryBrowsers.createInstance(CVSRepositoryBrowser.class,req,"cvs.browser");
return scm;
}
public String getCvspassFile() {
......@@ -761,35 +763,10 @@ public class CVSSCM extends AbstractCVSFamilySCM implements Serializable {
save();
}
/**
* Gets the URL that shows the diff.
*/
public String getDiffURL(String cvsRoot, String pathName, String oldRev, String newRev) {
RepositoryBrowser b = browsers.get(cvsRoot);
if(b==null) return null;
return b.diffURL.replaceAll("%%P",pathName).replace("%%r",oldRev).replace("%%R",newRev);
}
public boolean configure( StaplerRequest req ) {
cvsPassFile = fixEmpty(req.getParameter("cvs_cvspass").trim());
cvsExe = fixEmpty(req.getParameter("cvs_exe").trim());
Map<String,RepositoryBrowser> browsers = new HashMap<String, RepositoryBrowser>();
int i=0;
while(true) {
String root = req.getParameter("cvs_repobrowser_cvsroot" + i);
if(root==null) break;
RepositoryBrowser rb = new RepositoryBrowser();
rb.browseURL = req.getParameter("cvs_repobrowser"+i);
rb.diffURL = req.getParameter("cvs_repobrowser_diff"+i);
browsers.put(root,rb);
i++;
}
this.browsers = browsers;
save();
return true;
......
......@@ -5,7 +5,6 @@ import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.model.TaskListener;
import org.kohsuke.stapler.StaplerRequest;
......@@ -32,7 +31,7 @@ public class NullSCM extends SCM {
return true;
}
public Descriptor<SCM> getDescriptor() {
public SCMDescriptor getDescriptor() {
return DESCRIPTOR;
}
......@@ -48,7 +47,7 @@ public class NullSCM extends SCM {
return new NullChangeLogParser();
}
static final Descriptor<SCM> DESCRIPTOR = new Descriptor<SCM>(NullSCM.class) {
static final SCMDescriptor DESCRIPTOR = new SCMDescriptor(NullSCM.class,null) {
public String getDisplayName() {
return "None";
}
......
package hudson.scm;
import hudson.ExtensionPoint;
import hudson.model.Describable;
import java.io.IOException;
import java.net.URL;
/**
* Connects Hudson to repository browsers like ViewCVS or FishEye,
* so that Hudson can generate links to them.
*
* <p>
* {@link RepositoryBrowser} instance is normally created as
* a result of job configuration, and stores immutable
* configuration information (such as the URL of the FishEye site).
*
* <p>
* {@link RepositoryBrowser} is persisted with {@link SCM}.
*
* @author Kohsuke Kawaguchi
* @since 1.89
* @see RepositoryBrowsers
*/
public abstract class RepositoryBrowser implements ExtensionPoint, Describable<RepositoryBrowser> {
/**
* Determines the link to the given change set.
*
* @return
* null if this repository browser doesn't have any meaningful
* URL for a change set (for example, ViewCVS doesn't have
* any page for a change set, whereas FishEye does.)
*/
public abstract URL getChangeSetLink(ChangeLogSet.Entry changeSet) throws IOException;
}
package hudson.scm;
import hudson.model.Descriptor;
import hudson.model.Descriptor.FormException;
import hudson.scm.browsers.ViewCVS;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import org.kohsuke.stapler.StaplerRequest;
/**
* List of all installed {@link RepositoryBrowsers}.
*
* @author Kohsuke Kawaguchi
*/
public class RepositoryBrowsers {
/**
* List of all installed {@link RepositoryBrowsers}.
*/
public static final List<Descriptor<RepositoryBrowser>> LIST = Arrays.asList(
ViewCVS.DESCRIPTOR
);
/**
* Only returns those {@link RepositoryBrowser} descriptors that extend from the given type.
*/
public static List<Descriptor<RepositoryBrowser>> filter(Class<? extends RepositoryBrowser> t) {
List<Descriptor<RepositoryBrowser>> r = new ArrayList<Descriptor<RepositoryBrowser>>();
for (Descriptor<RepositoryBrowser> d : LIST)
if(t.isAssignableFrom(d.clazz))
r.add(d);
return r;
}
/**
* Creates an instance of {@link RepositoryBrowser} from a form submission.
*/
public static <T extends RepositoryBrowser>
T createInstance(Class<T> type, StaplerRequest req, String fieldName) throws FormException {
List<Descriptor<RepositoryBrowser>> list = filter(type);
String value = req.getParameter(fieldName);
if(value==null || value.equals("auto"))
return null;
return type.cast(list.get(Integer.parseInt(value)).newInstance(req));
}
}
......@@ -10,8 +10,8 @@ import hudson.model.Describable;
import hudson.model.TaskListener;
import java.io.File;
import java.io.IOException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
/**
......@@ -25,6 +25,18 @@ import java.util.Map;
*/
public abstract class SCM implements Describable<SCM>, ExtensionPoint {
/**
* Returns the {@link RepositoryBrowser} for files
* controlled by this {@link SCM}.
*
* @return
* null to indicate that there's no configured browser
* for this SCM instance.
*/
public RepositoryBrowser getBrowser() {
return null;
}
/**
* Checks if there has been any changes to this module in the repository.
*
......@@ -103,6 +115,8 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
*/
public abstract ChangeLogParser createChangeLogParser();
public abstract SCMDescriptor getDescriptor();
protected final boolean createEmptyChangeLog(File changelogFile, BuildListener listener, String rootTag) {
try {
FileWriter w = new FileWriter(changelogFile);
......
package hudson.scm;
import hudson.model.Descriptor;
import java.util.List;
import java.util.Collections;
/**
* {@link Descriptor} for {@link SCM}.
*
* @author Kohsuke Kawaguchi
*/
public abstract class SCMDescriptor extends Descriptor<SCM> {
/**
* If this SCM has corresponding {@link RepositoryBrowser},
* that type. Otherwise this SCM will not have any repository browser.
*/
public final Class<? extends RepositoryBrowser> repositoryBrowser;
protected SCMDescriptor(Class<? extends SCM> clazz, Class<? extends RepositoryBrowser> repositoryBrowser) {
super(clazz);
this.repositoryBrowser = repositoryBrowser;
}
/**
* Returns the list of {@link RepositoryBrowser} {@link Descriptor}
* that can be used with this SCM.
*
* @return
* can be empty but never null.
*/
public List<Descriptor<RepositoryBrowser>> getBrowserDescriptors() {
if(repositoryBrowser==null) return Collections.emptyList();
return RepositoryBrowsers.filter(repositoryBrowser);
}
}
......@@ -17,7 +17,7 @@ public class SCMS {
* List of all installed SCMs.
*/
@SuppressWarnings("unchecked") // generic array creation
public static final List<Descriptor<SCM>> SCMS =
public static final List<SCMDescriptor> SCMS =
Descriptor.toList(
NullSCM.DESCRIPTOR,
CVSSCM.DescriptorImpl.DESCRIPTOR,
......
......@@ -110,6 +110,7 @@ public final class SubversionChangeLogSet extends ChangeLogSet<LogEntry> {
}
public void addPath( Path p ) {
p.entry = this;
paths.add(p);
}
......@@ -118,10 +119,18 @@ public final class SubversionChangeLogSet extends ChangeLogSet<LogEntry> {
}
}
/**
* A file in a commit.
*/
public static class Path {
private LogEntry entry;
private char action;
private String value;
public LogEntry getLogEntry() {
return entry;
}
public void setAction(String action) {
this.action = action.charAt(0);
}
......
package hudson.scm;
import java.net.URL;
import java.io.IOException;
/**
* {@link RepositoryBrowser} for Subversion.
*
* @author Kohsuke Kawaguchi
*/
public abstract class SubversionRepositoryBrowser extends RepositoryBrowser {
/**
* Determines the link to the diff between the version
* in the specified revision of {@link SubversionChangeLogSet.Path} to its previous version.
*
* @return
* null if the browser doesn't have any URL for diff.
*/
public abstract URL getDiffLink(SubversionChangeLogSet.Path path) throws IOException;
/**
* Determines the link to a single file under Subversion.
* This page should display all the past revisions of this file, etc.
*
* @return
* null if the browser doesn't have any suitable URL.
*/
public abstract URL getFileLink(SubversionChangeLogSet.Path path) throws IOException;
}
......@@ -8,7 +8,6 @@ import static hudson.Util.fixNull;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.model.TaskListener;
import hudson.remoting.Channel;
......@@ -62,8 +61,8 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Subversion.
......@@ -476,7 +475,7 @@ public class SubversionSCM extends SCM implements Serializable {
return tokens[tokens.length-1]; // return the last token
}
public static final class DescriptorImpl extends Descriptor<SCM> {
public static final class DescriptorImpl extends SCMDescriptor {
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
/**
......@@ -559,7 +558,7 @@ public class SubversionSCM extends SCM implements Serializable {
}
private DescriptorImpl() {
super(SubversionSCM.class);
super(SubversionSCM.class,null);
load();
}
......
package hudson.scm.browsers;
import hudson.model.Descriptor;
import hudson.scm.CVSChangeLogSet.File;
import hudson.scm.CVSChangeLogSet.Revision;
import hudson.scm.ChangeLogSet.Entry;
import hudson.scm.RepositoryBrowser;
import hudson.scm.CVSRepositoryBrowser;
import org.kohsuke.stapler.StaplerRequest;
import java.io.IOException;
import java.net.URL;
/**
* {@link RepositoryBrowser} for
* @author Kohsuke Kawaguchi
*/
public final class ViewCVS extends CVSRepositoryBrowser {
/**
* The URL of the top of the site.
*
* Normalized to ends with '/', like <tt>http://isscvs.cern.ch/cgi-bin/viewcvs-all.cgi/</tt>
* It may contain a query parameter like <tt>?cvsroot=foobar</tt>, so relative URL
* construction needs to be done with care.
*/
public final URL url;
/**
* @stapler-constructor
*/
public ViewCVS(URL url) {
this.url = url;
}
public URL getFileLink(File file) throws IOException {
return new URL(url,trimHeadSlash(file.getName())+param());
}
public URL getDiffLink(File file) throws IOException {
Revision r = new Revision(file.getRevision());
Revision p = r.getPrevious();
if(p==null) return null;
return new URL(getFileLink(file), file.getSimpleName()+".diff"+param().add("r1="+p).add("r2="+r));
}
/**
* No changeset support in ViewCVS.
*/
public URL getChangeSetLink(Entry changeSet) throws IOException {
return null;
}
private String trimHeadSlash(String s) {
if(s.startsWith("/")) return s.substring(1);
return s;
}
private QueryBuilder param() {
return new QueryBuilder(url.getQuery());
}
private static final class QueryBuilder {
private final StringBuilder buf = new StringBuilder();
public QueryBuilder(String s) {
add(s);
}
private QueryBuilder add(String s) {
if(buf.length()==0) buf.append('?');
else buf.append('&');
buf.append(s);
return this;
}
public String toString() {
return buf.toString();
}
}
public Descriptor<RepositoryBrowser> getDescriptor() {
return DESCRIPTOR;
}
public static final Descriptor<RepositoryBrowser> DESCRIPTOR = new Descriptor<RepositoryBrowser>(ViewCVS.class) {
public String getDisplayName() {
return "ViewCVS";
}
public RepositoryBrowser newInstance(StaplerRequest req) throws FormException {
return req.bindParameters(ViewCVS.class,"viewcvs.");
}
};
}
......@@ -8,6 +8,9 @@
<li><st:out value="${cs.msg}" /></li>
</j:forEach>
</ol>
<j:set var="browser" value="${it.build.parent.scm.browser}"/>
<table class="pane" style="border:none">
<j:forEach var="cs" items="${it.logs}" varStatus="loop">
<tr class="pane">
......@@ -16,6 +19,11 @@
<div class="changeset-message">
<b><a href="${rootURL}/${cs.author.url}/">${cs.author}</a>:</b><br/>
${cs.msgAnnotated}
<j:set var="csLink" value="${browser.getChangeSetLink(cs)}"/>
<j:if test="${csLink!=null}">
<a href="${csLink}">(${browser.descriptor.displayName})</a>
</j:if>
</div>
</td>
</tr>
......@@ -23,8 +31,11 @@
<j:forEach var="f" items="${cs.files}">
<tr>
<td><t:editTypeIcon type="${f.editType}" /></td>
<td>${f.revision}</td>
<td>${f.name}</td>
<td>
<a href="${browser.getDiffLink(f)}">${f.revision}</a>
</td>
<td>
<a href="${browser.getFileLink(f)}">${f.name}</a></td>
</tr>
</j:forEach>
</j:forEach>
......
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="CVSROOT" help="/help/_cvs/cvsroot.html">
<f:editableComboBox id="cvs_root" clazz="setting-input validated"
name="cvs_root" value="${scm.cvsRoot}"
name="cvs.cvsroot" value="${scm.cvsRoot}"
items="${app.allCvsRoots}"
checkUrl="'${rootURL}/scm/CVSSCM/cvsrootCheck?value='+escape(this.value)" />
</f:entry>
<f:entry title="Module(s)" help="/help/_cvs/modules.html">
<f:textbox name="cvs_module" value="${scm.allModules}"/>
<f:textbox name="cvs.module" value="${scm.allModules}"/>
</f:entry>
<f:entry title="Branch" help="/help/_cvs/branch.html">
<f:textbox name="cvs_branch" value="${scm.branch}"/>
<f:textbox name="cvs.branch" value="${scm.branch}"/>
</f:entry>
<f:entry title="CVS_RSH" help="/help/_cvs/cvs-rsh.html">
<f:textbox name="cvs_rsh" value="${scm.cvsRsh}"/>
<f:textbox name="cvs.cvsRsh" value="${scm.cvsRsh}"/>
</f:entry>
<f:entry title="Use update" help="/help/_cvs/update.html">
<f:checkbox name="cvs_use_update" checked="${scm.canUseUpdate}"/>
<f:checkbox name="cvs.canUseUpdate" checked="${scm.canUseUpdate}"/>
</f:entry>
<f:entry title="Legacy mode" help="/help/_cvs/legacy.html">
<f:checkbox name="cvs_legacy" checked="${scm!=null and !scm.flatten}"/>
<f:checkbox name="cvs.legacy" checked="${scm!=null and !scm.flatten}"/>
(run CVS in a way compatible with older versions of Hudson &lt;1.21)
</f:entry>
<!-- TODO: share with all SCMs -->
<f:dropdownList name="cvs.browser" title="Repository browser">
<f:dropdownListBlock value="auto" title="(Auto)" />
<j:set var="currentBrowser" value="${scm.browser}"/>
<j:forEach var="d" items="${scmd.browserDescriptors}" varStatus="loop">
<f:dropdownListBlock value="${loop.index}" title="${d.displayName}" selected="${currentBrowser.descriptor==d}">
<f:nested>
<table width="100%">
<j:set var="browser" value="${h.ifThenElse(currentBrowser.descriptor==d, currentBrowser, null)}"/>
<st:include from="${d}" page="${d.configPage}"/>
</table>
</f:nested>
</f:dropdownListBlock>
</j:forEach>
</f:dropdownList>
</j:jelly>
\ No newline at end of file
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="URL" help="/help/viewcvs/url.html">
<f:textbox name="viewcvs.url" value="${browser.url}"/>
</f:entry>
</j:jelly>
\ No newline at end of file
<!--
Foldable block expanded when the corresponding item is selected in the drop-down list.
@name (mandatory)
name of the drop-down list.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<!-- first invoke the body to collect all <dropdownListBlock> -->
<j:useList var="dropdownItems" />
<d:invokeBody />
<j:set var="id" value="${h.generateId()}"/>
<f:entry title="${title}">
<!-- create drop-down list -->
<select name="${name}" id="${id}" onchange="updateDropDownList(this)">
<j:forEach var="item" items="${dropdownItems}">
<f:option selected="${item.selected}" value="${item.value}">${item.title}</f:option>
</j:forEach>
</select>
<script>
$$('${id}').forms = [];
</script>
</f:entry>
<!-- generate the actual form entries -->
<j:forEach var="item" items="${dropdownItems}">
<!-- sandwitch them by a marker so that we now what to show/hide -->
<j:set var="sid" value="${h.generateId()}"/>
<j:set var="eid" value="${h.generateId()}"/>
<tr id="${sid}" style="display:none" />
<d:invoke script="${item.body}" />
<tr id="${eid}" style="display:none" />
<script>
$$('${id}').forms.push({
start: $$('${sid}'),
end: $$('${eid}')
});
</script>
</j:forEach>
<!-- set the initial visibility -->
<script>
updateDropDownList($$('${id}'));
</script>
</j:jelly>
<!--
Foldable block expanded when the corresponding item is selected in the drop-down list.
@value (mandatory)
value of the list item. set to <option value="...">
@title (mandatory)
human readable text displayed for this list item.
@selected (mandatory)
is this value initially selected?
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:mute>
${dropdownItems.add(attrs)}
${attrs.put('body',context.getVariable('org.apache.commons.jelly.body'))}
</j:mute>
</j:jelly>
......@@ -11,11 +11,11 @@
<td class="setting-name">
${attrs.title}
</td>
<td>
<td class="setting-main">
<d:invokeBody />
</td>
<j:if test="${attrs.help!=null}">
<td>
<td class="setting-help">
<a href="#" class="help-button" helpURL="${rootURL}${attrs.help}"><img src="${rootURL}/images/16x16/help.gif" alt="Help for feature: ${title}" /></a>
</td>
</j:if>
......
<!--
Used to display indented nested portion of the form
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<tr>
<td colspan="2"/>
<td colspan="2">
<d:invokeBody />
</td>
</tr>
</j:jelly>
\ No newline at end of file
......@@ -160,12 +160,19 @@ pre.console {
overflow: auto;
}
.setting-leftspace {
width: 2em;
}
.setting-name {
white-space: nowrap;
}
.setting-leftspace {
width: 2em;
.setting-main {
width: 100%; /* try to make this column as big as possible. */
}
.setting-help {
width: 16px;
}
.setting-input {
......@@ -316,6 +323,7 @@ th.pane {
border: 1px solid #ccb;
background: #eed;
padding: 4px;
white-space: normal;
}
.error {
......
......@@ -386,3 +386,17 @@ Form.findMatchingInput = function(base, name) {
return null; // not found
}
// used witih <dropdownList> and <dropdownListBlock> to control visibility
function updateDropDownList(sel) {
// alert('Yay! '+sel.value+' '+sel.selectedIndex);
for (var i = 0; i < sel.forms.length; i++) {
var show = sel.selectedIndex == i;
var f = sel.forms[i];
var td = f.start;
while (true) {
td.style.display = (show ? "" : "none");
if (td == f.end) break;
td = td.nextSibling;
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册