I want to add repeatable properties to the Jenkins plugin I'm developing, and created a test plugin to make make sure I was using them correctly. My plugin seems to work fine, I can add as many properties as I want when I originally edit the config, and it saves and builds. However, when I try to edit the config a second time, the config screen shows the loading overlay endlessly. If I scroll down, I can see the properties I saved earlier are still there, but I can't edit anything.
My class looks like this: public class RepeatableTest extends Builder { private List<Prop> property = new ArrayList<Prop>(); @DataBoundConstructor public RepeatableTest(List<Prop> property) { this.property = property; } public List<Prop> getProperty() { return property; } @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException { listener.getLogger().println(property.get(0).name); listener.getLogger().println(property.size()); return true; } @Override public DescriptorImpl getDescriptor() { return (DescriptorImpl)super.getDescriptor(); } public static class Prop extends AbstractDescribableImpl<Prop> { public String name; public String getName(){ return name; } @DataBoundConstructor public Prop(String name) { this.name = name; } @Extension public static class DescriptorImpl extends Descriptor<Prop> { @Override public String getDisplayName() { return ""; } } } @Extension // This indicates to Jenkins that this is an implementation of an extension point. public static final class DescriptorImpl extends BuildStepDescriptor<Builder> { private String phpLoc; public DescriptorImpl() { load(); } public boolean isApplicable(Class<? extends AbstractProject> aClass) { // Indicates that this builder can be used with all kinds of project types return true; } public String getDisplayName() { return "Repeatable Test"; } @Override public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { phpLoc = formData.getString("phpLoc"); save(); return super.configure(req,formData); } public String getPhpLoc() { return phpLoc; } } } My config.groovy looks like this: package uitestplugin.uitest.RepeatableTest; import lib.JenkinsTagLib import lib.FormTagLib def f = namespace(lib.FormTagLib) t=namespace(JenkinsTagLib.class) f.form{ f.entry(title:"Properties"){ f.repeatableProperty(field:"property") } } and my prop/config.groovy looks like this: package uitestplugin.uitest.RepeatableTest.Prop; def f = namespace(lib.FormTagLib) f.entry(title:"Name", field:"name") { f.textbox() } The config.xml: <?xml version='1.0' encoding='UTF-8'?> <project> <actions/> <description></description> <keepDependencies>false</keepDependencies> <properties/> <scm class="hudson.scm.NullSCM"/> <canRoam>true</canRoam> <disabled>false</disabled> <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding> <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding> <triggers/> <concurrentBuild>false</concurrentBuild> <builders> <uitestplugin.uitest.RepeatableTest plugin="ui-test@1.0-SNAPSHOT"> <property> <uitestplugin.uitest.RepeatableTest_-Prop> <name>Prop1</name> </uitestplugin.uitest.RepeatableTest_-Prop> <uitestplugin.uitest.RepeatableTest_-Prop> <name>Prop2</name> </uitestplugin.uitest.RepeatableTest_-Prop> </property> </uitestplugin.uitest.RepeatableTest> </builders> <publishers/> <buildWrappers/> </project> Any ideas as to what could cause this? I based a lot of the code from the ui-samples plugin (https://wiki.jenkins-ci.org/display/JENKINS/UI+Samples+Plugin). Also, using the web developer toolbar for Firefox, I can see that there is a Javascript error on the page. The error is: Timestamp: 10/3/2014 12:58:49 PM Error: TypeError: prototypes is undefined Source File: http://localhost:8080/adjuncts/e58fb488/lib/form/hetero-list/hetero-list.js Line: 16 And the code this relates to is(I've marked line 16 with a comment at the end of the line): // @include lib.form.dragdrop.dragdrop // do the ones that extract innerHTML so that they can get their original HTML before // other behavior rules change them (like YUI buttons.) Behaviour.specify("DIV.hetero-list-container", 'hetero-list', -100, function(e) { e=$(e); if(isInsideRemovable(e)) return; // components for the add button var menu = document.createElement("SELECT"); var btns = findElementsBySelector(e,"INPUT.hetero-list-add"), btn = btns[btns.length-1]; // In case nested content also uses hetero-list YAHOO.util.Dom.insertAfter(menu,btn); var prototypes = $(e.lastChild); while(!prototypes.hasClassName("prototypes")) //LINE 16, ERROR IS HERE prototypes = prototypes.previous(); var insertionPoint = prototypes.previous(); // this is where the new item is inserted. // extract templates var templates = []; var i=0; $(prototypes).childElements().each(function (n) { var name = n.getAttribute("name"); var tooltip = n.getAttribute("tooltip"); var descriptorId = n.getAttribute("descriptorId"); menu.options[i] = new Option(n.getAttribute("title"),""+i); templates.push({html:n.innerHTML, name:name, tooltip:tooltip,descriptorId:descriptorId}); i++; }); Element.remove(prototypes); var withDragDrop = initContainerDD(e); var menuAlign = (btn.getAttribute("menualign")||"tl-bl"); var menuButton = new YAHOO.widget.Button(btn, { type: "menu", menu: menu, menualignment: menuAlign.split("-") }); $(menuButton._button).addClassName(btn.className); // copy class names $(menuButton._button).setAttribute("suffix",btn.getAttribute("suffix")); menuButton.getMenu().clickEvent.subscribe(function(type,args,value) { var item = args[1]; if (item.cfg.getProperty("disabled")) return; var t = templates[parseInt(item.value)]; var nc = document.createElement("div"); nc.className = "repeated-chunk"; nc.setAttribute("name",t.name); nc.setAttribute("descriptorId",t.descriptorId); nc.innerHTML = t.html; $(nc).setOpacity(0); var scroll = document.body.scrollTop; renderOnDemand(findElementsBySelector(nc,"TR.config-page")[0],function() { function findInsertionPoint() { // given the element to be inserted 'prospect', // and the array of existing items 'current', // and preferred ordering function, return the position in the array // the prospect should be inserted. // (for example 0 if it should be the first item) function findBestPosition(prospect,current,order) { function desirability(pos) { var count=0; for (var i=0; i<current.length; i++) { if ((i<pos) == (order(current[i])<=order(prospect))) count++; } return count; } var bestScore = -1; var bestPos = 0; for (var i=0; i<=current.length; i++) { var d = desirability(i); if (bestScore<=d) {// prefer to insert them toward the end bestScore = d; bestPos = i; } } return bestPos; } var current = e.childElements().findAll(function(e) {return e.match("DIV.repeated-chunk")}); function o(did) { if (Object.isElement(did)) did = did.getAttribute("descriptorId"); for (var i=0; i<templates.length; i++) if (templates[i].descriptorId==did) return i; return 0; // can't happen } var bestPos = findBestPosition(t.descriptorId, current, o); if (bestPos<current.length) return current[bestPos]; else return insertionPoint; } (e.hasClassName("honor-order") ? findInsertionPoint() : insertionPoint).insert({before:nc}); if(withDragDrop) prepareDD(nc); new YAHOO.util.Anim(nc, { opacity: { to:1 } }, 0.2, YAHOO.util.Easing.easeIn).animate(); Behaviour.applySubtree(nc,true); ensureVisible(nc); layoutUpdateCallback.call(); },true); }); menuButton.getMenu().renderEvent.subscribe(function() { // hook up tooltip for menu items var items = menuButton.getMenu().getItems(); for(i=0; i<items.length; i++) { var t = templates[i].tooltip; if(t!=null) applyTooltip(items[i].element,t); } }); if (e.hasClassName("one-each")) { // does this container already has a ocnfigured instance of the specified descriptor ID? function has(id) { return Prototype.Selector.find(e.childElements(),"DIV.repeated-chunk[descriptorId=\""+id+"\"]")!=null; } menuButton.getMenu().showEvent.subscribe(function() { var items = menuButton.getMenu().getItems(); for(i=0; i<items.length; i++) { items[i].cfg.setProperty("disabled",has(templates[i].descriptorId)); } }); } }); Behaviour.specify("DIV.dd-handle", 'hetero-list', -100, function(e) { e=$(e); e.on("mouseover",function() { $(this).up(".repeated-chunk").addClassName("hover"); }); e.on("mouseout",function() { $(this).up(".repeated-chunk").removeClassName("hover"); }); }); -- View this message in context: http://jenkins-ci.361315.n4.nabble.com/repeatableProperty-with-Groovy-Loading-overlay-not-dissapearing-on-config-screen-tp4722513.html Sent from the Jenkins dev mailing list archive at Nabble.com. -- You received this message because you are subscribed to the Google Groups "Jenkins Developers" group. To unsubscribe from this group and stop receiving emails from it, send an email to jenkinsci-dev+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.