Accessing XPages Data Source in a Plugin - xpages

For a plugin (like the extension library) I try to access the datasource with a given "var" name. Accessing the Datasource object is very easy with the following code:
m_DataSourceName contains the name (var) of the datasource.
public DataSource getDataSource() {
if (StringUtil.isNotEmpty(m_DataSourceName)) {
UIViewRoot vrCurrent = getFacesContext().getViewRoot();
if (vrCurrent instanceof UIViewRootEx) {
for (DataSource dsCurrent : ((UIViewRootEx) vrCurrent)
.getData()) {
if (m_DataSourceName.equals(dsCurrent.getVar())) {
return dsCurrent;
}
}
}
}
System.out.println("Datasource name:" + m_DataSourceName);
return null;
}
I'm getting the datasource back and I can cast this datasource:
private TabularDataModel getTDM(DataSource dsCurrent, FacesContext context) {
try {
if (dsCurrent instanceof ModelDataSource) {
ModelDataSource mds = (ModelDataSource) dsCurrent;
AbstractDataSource ads = (AbstractDataSource) mds;
ads.load(context);
System.out.println(ads.getBeanId());
if (ads.getBeanId() == null) {
}
DataModel tdm = mds.getDataModel();
if (tdm instanceof TabularDataModel) {
TabularDataModel tds = (TabularDataModel) tdm;
return tds;
}
}
return null;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
And now I wanna access the TDM.getRowCount() and this point I'm getting a nullpointer exception. The datasource contains a notes view. Did I miss anything to initialize the datasource?

Here is a solution for your problem:
This will give you all lines of a view, not the entry count*. F.e. if you have a categorized view with 5 categories and 1 entry for each category, this will result in 10 lines. The entry count is 5.
First, create a dummy class which implements FacesDataIterator
public class DummyDataIterator implements com.ibm.xsp.component.FacesDataIterator{
public DataModel getDataModel() {
return null;
}
public int getFirst() {
return 0;
}
public int getRowIndex() {
return 0;
}
public int getRows() {
return 0;
}
public void setFirst(int paramInt) {}
public void setRows(int paramInt) {}
}
And then you have to do the following:
Set the data iterator
tdm.setDataControl( new DummyDataIterator() );
Init the row counter for the first time
tdm.getRowCount();
Calculate the exact row count with a navigator
(( com.ibm.xsp.model.domino.viewnavigator.NOIViewNavigatorEx) tdm.getDominoViewDataContainer().getNavigator()).calculateExactCount(tdm.getView());
Now your row count is initialized, you can get the result with a normal getRowCount:
System.out.println("Rows: " + tdm.getRowCount() );
Hope this helps!
*:
tdm.getView().getAllEntries().getCount()

Related

rich:dataScroller seems to still load all the data without pagination

I'm trying to implement pagination for a project that loads a large set of data from the database.
I've done a lot of searching on the internet already to get db-pagination to work, but for some reason I still don't get it working the way I want.
I've followed the example as described in this topic:
JSF, RichFaces, pagination
The data is loaded, so that works; cache also seems to work. However, it seems to still load all the data. The sr.getRows() in the walk-method is always -1, so the call to the database also uses -1 for maxResults. I get all my data, but no pagination.
I don't want to introduce another dependency if I can avoid it.
Some of my data:
BatchDataModel
public abstract class BatchDataModel<T> extends ExtendedDataModel<T> {
private SequenceRange cachedRange;
private Integer cachedRowCount;
private List<T> cachedList;
private Object rowKey;
public abstract List<T> getDataList(int firstRow, int numRows);
public abstract Object getKey(T t);
public abstract int getTotalCount();
#Override
public void walk(FacesContext ctx, DataVisitor dv, Range range, Object argument) {
SequenceRange sr = (SequenceRange) range;
if (cachedList == null || !equalRanges(cachedRange, sr)) {
cachedList = getDataList(sr.getFirstRow(), sr.getRows());
cachedRange = sr;
}
for (T t : cachedList) {
if (getKey(t) == null) {
/*
* the 2nd param is used to build the client id of the table
* row, i.e. mytable:234:inputname, so don't let it be null.
*/
throw new IllegalStateException("found null key");
}
dv.process(ctx, getKey(t), argument);
}
}
/*
* The rowKey is the id from getKey, presumably obtained from
* dv.process(...).
*/
#Override
public void setRowKey(Object rowKey) {
this.rowKey = rowKey;
}
#Override
public Object getRowKey() {
return rowKey;
}
#Override
public boolean isRowAvailable() {
return (getRowData() != null);
}
#Override
public int getRowCount() {
if (cachedRowCount == null) {
cachedRowCount = getTotalCount();
}
return cachedRowCount;
}
#Override
public T getRowData() {
for (T t : cachedList) {
if (getKey(t).equals(this.getRowKey())) {
return t;
}
}
return null;
}
protected static boolean equalRanges(SequenceRange range1, SequenceRange range2) {
if (range1 == null || range2 == null) {
return range1 == null && range2 == null;
} else {
return range1.getFirstRow() == range2.getFirstRow() && range1.getRows() == range2.getRows();
}
}
/*
* get/setRowIndex are used when doing multiple select in an
* extendedDataTable, apparently. Not tested. Actually, the get method is
* used when using iterationStatusVar="it" & #{it.index}.
*/
#Override
public int getRowIndex() {
if (cachedList != null) {
ListIterator<T> it = cachedList.listIterator();
while (it.hasNext()) {
T t = it.next();
if (getKey(t).equals(this.getRowKey())) {
return it.previousIndex() + cachedRange.getFirstRow();
}
}
}
return -1;
}
#Override
public void setRowIndex(int rowIndex) {
int upperBound = cachedRange.getFirstRow() + cachedRange.getRows();
if (rowIndex >= cachedRange.getFirstRow() && rowIndex < upperBound) {
int index = rowIndex % cachedRange.getRows();
T t = cachedList.get(index);
setRowKey(getKey(t));
}
}
#Override
public Object getWrappedData() {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public void setWrappedData(Object data) {
throw new UnsupportedOperationException("Not supported yet.");
}
public List<T> getCachedList() {
return cachedList;
}
Bean (part)
private ListState state;
private BatchDataModel<Batch> model;
public BatchDataModel<Batch> getModel(){
return model;
}
public int getCurrentPage() {
return state.getPage();
}
public void setCurrentPage(int page) {
state.setPage(page);
}
public void setBatchService(BatchService batchService) {
this.batchService = batchService;
}
/**
* Initialize the variables, before the page is shown.
*/
#PostConstruct
private void init() {
filter = new Filter();
sorter = new Sorter();
state = getFromSession("batchList", null);
if (state == null) {
state = new ListState();
storeInSession("batchList", state);
}
}
public void loadBatches(boolean search) {
BatchDataModel<Batch> model = new BatchDataModel<Batch>(){
#Override
public List<Batch> getDataList(int firstRow, int numRows) {
try {
List <Batch> test;
test = batchService.selectBatches(userBean.getUser(), firstRow, numRows);
return test;
} catch (NozemException e) {
LOGGER.error(e.getMessage());
sendMessage(e.getMessage(), true);
return null;
}
}
#Override
public Object getKey(Batch batch) {
return batch.getBatchId();
}
#Override
public int getTotalCount() {
try {
return batchService.countBatches(userBean.getUser());
} catch (NozemException e) {
LOGGER.error(e.getMessage());
sendMessage(e.getMessage(), true);
return 0;
}
}
};
}
xhtml (part)
<rich:dataTable id="batchesTable" rows="2"
value="#{batchBean.model}" var="batch" first="#{batchBean.currentPage}"
styleClass="table" rowClasses="odd-row, even-row"
onrowmouseover="this.style.backgroundColor='#88B5F9'"
onrowmouseout="this.style.backgroundColor='#{a4jSkin.rowBackgroundColor}'">
(...)
<f:facet name="footer">
<rich:dataScroller page="#{batchBean.currentPage}" />
</f:facet>
ArrangeableModel is the key. This class needs to be implemented in the BatchDataModel class, together with a method and function. This way the same state is used and the walk-method gets the correct values in SequenceRange.
public abstract class BatchDataModel<T> extends ExtendedDataModel<T> implements Arrangeable {
private ArrangeableState arrangeableState;
public void arrange(FacesContext context, ArrangeableState state) {
arrangeableState = state;
}
protected ArrangeableState getArrangeableState() {
return arrangeableState;
}
}

Entity Framework The context cannot be used while the model is being created

My unit of work class is mentioned below and I am using Ninject and I have tried injecting IUnitOfWork per request per thread scope, transient etc. but I am still getting error which is:
"Message":"An error has occurred.","ExceptionMessage":"The context cannot be used while the model is being created. This exception may be thrown if the context is used inside the OnModelCreating method or if the same context instance is accessed by multiple threads concurrently. Note that instance members of DbContext and related classes are not guaranteed to be thread safe.","ExceptionType":"System.InvalidOperationException
I get this error when i make two web API (get) calls at the same time using angularJS and it shows error at the point _context.Set<TEntity>().FirstOrDefault(match);
public class UnitOfWork : IUnitOfWork, IDisposable
{
private My_PromotoolEntities _uowDbContext = new My_PromotoolEntities();
private Dictionary<string, object> _repositories;
// Do it like this if no specific class file
private GenericRepository<MysPerson> _personRepository;
//private GenericRepository<MysDataSource> dataSourcesRepository;
//private GenericRepository<MysCountry> countryMasterRepository;
// Or like this if with specific class file.
private DataSourceRepository _dataSourcesRepository;
private CustomerRepository _customerRepository;
private DeviceRepository _deviceRepository;
private DeviceRegistrationRepository _deviceRegistrationRepository;
private EmailQueueRepository _emailQueueRepository;
public void SetContext(My_PromotoolEntities context)
{
_uowDbContext = context;
}
public void CacheThis(object cacheThis, string keyName, TimeSpan howLong)
{
Cacheing.StaticData.CacheStaticData(cacheThis, keyName, howLong);
}
public object GetFromCache(string keyName)
{
return Cacheing.StaticData.GetFromCache(keyName);
}
public GenericRepository<T> GenericRepository<T>() where T : BaseEntity
{
if (_repositories == null)
{
_repositories = new Dictionary<string, object>();
}
var type = typeof(T).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), _uowDbContext);
_repositories.Add(type, repositoryInstance);
}
return (GenericRepository<T>)_repositories[type];
}
public GenericRepository<MysPerson> PersonRepository
{
get
{
if (this._personRepository == null)
{
this._personRepository = new GenericRepository<MysPerson>(_uowDbContext);
}
return _personRepository;
}
}
public DataSourceRepository DataSourcesRepository
{
get
{
if (this._dataSourcesRepository == null)
{
this._dataSourcesRepository = new DataSourceRepository(_uowDbContext);
}
return _dataSourcesRepository;
}
}
public CustomerRepository CustomerRepository
{
get
{
if (this._customerRepository == null)
{
this._customerRepository = new CustomerRepository(_uowDbContext);
}
return _customerRepository;
}
}
public DeviceRepository DeviceRepository
{
get
{
if (this._deviceRepository == null)
{
this._deviceRepository = new DeviceRepository(_uowDbContext);
}
return _deviceRepository;
}
}
public DeviceRegistrationRepository DeviceRegistrationRepository
{
get
{
if (this._deviceRegistrationRepository == null)
{
this._deviceRegistrationRepository = new DeviceRegistrationRepository(_uowDbContext);
}
return _deviceRegistrationRepository;
}
}
public EmailQueueRepository emailQueueRepository
{
get
{
if (this._emailQueueRepository == null)
{
this._emailQueueRepository = new EmailQueueRepository(_uowDbContext);
}
return _emailQueueRepository;
}
}
/// <summary>
/// Commits all changes to the db. Throws exception if fails. Call should be in a try..catch.
/// </summary>
public void Save()
{
try
{
_uowDbContext.SaveChanges();
}
catch (DbEntityValidationException dbevex)
{
// Entity Framework specific errors:
StringBuilder sb = new StringBuilder();
var eve = GetValidationErrors();
if (eve.Count() > 0)
{
eve.ForEach(error => sb.AppendLine(error));
}
ClearContext();
// Throw a new exception with original as inner.
var ex = new Exception(sb.ToString(), dbevex);
ex.Source = "DbEntityValidationException";
throw ex;
}
catch (Exception)
{
ClearContext();
throw;
}
}
private void ClearContext()
{
DetachAll();
}
private void DetachAll()
{
foreach (DbEntityEntry dbEntityEntry in _uowDbContext.ChangeTracker.Entries())
{
if (dbEntityEntry.Entity != null)
{
dbEntityEntry.State = EntityState.Detached;
}
}
}
/// <summary>
/// Checks for EF DbEntityValidationException(s).
/// </summary>
/// <returns>Returns a List of string containing the EF DbEntityValidationException(s).</returns>
public List<string> GetValidationErrors()
{
if (_uowDbContext.GetValidationErrors().Count() != 0)
{
return _uowDbContext.GetValidationErrors().Select(e => string.Join(Environment.NewLine, e.ValidationErrors.Select(v => string.Format("{0} - {1}", v.PropertyName, v.ErrorMessage)))).ToList();
}
return null;
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_uowDbContext.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
You should never use a context in 2 places at the same time, that's exactly why you are getting this error. From the MSDN documentation:
Thread Safety: Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
It is a little hard to make suggestions without a repro but there is a brute force approach that should resolve the issue. If you have an interception point before/during DI setup then you can cause all the context initialization etc to happen by creating an instance of your context and calling ctx.Database.Initialize(force: false); Passing 'force: false' will ensure that the initialization still only happens once per AppDomain

Loading overlay never disappears in config of Jenkins plugin

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).
EDIT: The current status of this is, well, I still haven't figured it out. I've done more research and tried tons of different examples, but the farthest I ever get is what I described above. It almost seems like you can't use repeatable through groovy. Anyways, I have one more piece of information to add. 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");
});
});
I hope this is enough information to solve the problem. Any suggestions (even if they aren't complete answers) are really appreciated.
While not an exact answer, I did find a way to get this working. For some reason, putting the repeatableProperty in an advanced block stopped the javascript error from happening, so everything loaded fine.
So, my config.groovy for RepeatableTest looked like this:
package uitestplugin.uitest.RepeatableTest;
f = namespace(lib.FormTagLib)
f.advanced{
f.entry(title:"Properties"){
f.repeatableProperty(field:"property", minimum:"1"){
}
}
}
My config.groovy for Prop1 looked like this:
package uitestplugin.uitest.Prop1;
def f = namespace(lib.FormTagLib)
f.entry(title:"Name",field:"name") {
f.textbox()
}
f.entry {
div(align:"left") {
input(type:"button",value:"Delete",class:"repeatable-delete")
}
}
My prop 1 looked like this:
public class Prop1 extends AbstractDescribableImpl<Prop1> {
private final String name;
public String getName(){
return name;
}
#DataBoundConstructor
public Prop1( String name) {
this.name = name;
}
#Extension
public static class DescriptorImpl extends Descriptor<Prop1> {
#Override
public String getDisplayName() {
return "";
}
}
}
And my RepeatableTest.java looked like this:
public class RepeatableTest extends Builder {
private final List<Prop1> property;
// Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
#DataBoundConstructor
public RepeatableTest(List<Prop1> property) {
this.property = property;
}
public List<Prop1> getProperty() {
return property;
}
#Override
public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException {
//Doesn't matter
}
#Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl)super.getDescriptor();
}
#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;
}
}
}

Upgrading custom view list mvvmcross touch

Hi I am trying to upgrade our ios app from mvvmcross v1 to v3. I can't figure out how to make my custom buttonrow work.
My view ViewDidLoad it is the button items that binds to the button row
public override void ViewDidLoad()
{
base.ViewDidLoad();
SortingView.ViewModel = ViewModel;
_shown = false;
// Setup View Animatons
Buttons.OnClick = () => { AnimationTransition = ViewTransitionAnimation.TransitionFade; };
TopRightButton.TouchDown +=
(sender, args) => {
AnimationTransition = ViewTransitionAnimation.TransitionCrossDissolve;
};
// Setup Bindings
this.AddBindings(
new Dictionary<object, string>
{
{this.BackgroundImage, "{'ImageData':{'Path':'BackgroundImage','Converter':'ImageItem'}}"},
{this.TopbarBackground, "{'ImageData':{'Path':'TopBarImage','Converter':'ImageItem'}}"},
{this.TopLogo, "{'ImageData':{'Path':'Logo','Converter':'ImageItem'}}"},
{this.Buttons, "{'ItemsSource':{'Path':'ButtonItems'}}"},
{this.SlideMenu, "{'ItemsSource':{'Path':'VisibleViews'}}"},
{
this.SortingView,
"{'ItemsSource':{'Path':'CategoriesName'},'SelectedGroups':{'Path':'SelectedGroups'},'ForceUbracoUpdateAction':{'Path':'ForceUbracoUpdateAction'}}"
},
{this.SettingsButton, "{'TouchDown':{'Path':'TopRightButtonClick'},'Hide':{'Path':'HideTopbarButton'},'ImageData':{'Path':'TopButtonImage','Converter':'ImageItem'}}" },
{this.TopRightButton, "{'TouchDown':{'Path':'SecondaryButtonButtonPushed'},'Hide':{'Path':'HideTopbarButton2'},'ImageData':{'Path':'SettingsButtonImage','Converter':'ImageItem'}}" }
});
// Perform any additional setup after loading the view, typically from a nib.
NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.DidBecomeActiveNotification, ReEnableSlideMenu);
this.SortingView.Hidden=true;
ViewModel.SettingButtonEvent += HandleSettingButtonPushed;
}
Here is my custom control "ButtonRow
[Register("ButtonRow")]
public class ButtonRow : CustomListViewController
{
private int _width = 0;
private UIImage _backgroundImage = null;
public ButtonRow(IntPtr handle)
: base(handle)
{
_width = (int)this.Frame.Width;
UseImageAsIcon = false;
FontSize=0;
}
public bool UseImageAsIcon { get; set; }
public UIImage BackgroundImage
{
get { return _backgroundImage; }
set { _backgroundImage = value; }
}
public int FontSize
{
get;set;
}
private Action _onClickAction;
private int _spacing = 0;
public Action OnClick
{
get
{
return _onClickAction;
}
set
{
_onClickAction = value;
}
}
/// <summary>
/// The add views.
/// </summary>
/// custom implementation for adding views to the button row
protected override void AddViews()
{
if (ItemsSource == null)
{
Hidden = true;
return;
}
base.AddViews();
foreach (UIView uiView in Subviews)
{
uiView.RemoveFromSuperview();
}
if (ItemsSource.Count == 0)
{
Hidden = true;
return;
}
if (_backgroundImage != null)
{
var frame = new RectangleF(0, 0, Frame.Width, Frame.Height);
var background = new UIImageView(frame) { Image = _backgroundImage };
AddSubview(background);
}
_width = _width - ((ItemsSource.Count - 1) * Spacing);
var buttonWidth = (int)Math.Ceiling (((double)_width) / ItemsSource.Count);
int index = 0;
foreach (ViewItemModel item in ItemsSource)
{
// creating custom button with needed viewmodel, nib etc is loaded in the class constructor
var button = new ButtonWithLabel(item, OnClick);
if (FontSize > 0)
{
button.FontSize(FontSize);
}
if (UseImageAsIcon)
{
button.AddBindings(
new Dictionary<object, string>
{
{ button, "{'IconLabel':{'Path':'Title'},'TitleFontColor':{'Path':'TitleFontColor'}}" },
{ button.icon, "{'ImageData':{'Path':'ImageIcon','Converter':'ImageItem'}}" }
});
}
else
{
// bindings created between the button and its viewmodel
button.AddBindings(
new Dictionary<object, string>
{
{button, "{'Label':{'Path':'Title'},'TitleFontColor':{'Path':'TitleFontColor'},'BackgroundColor':{'Path':'BackgroundColor'}}" },
{button.Background, "{'ImageData':{'Path':'ImageIcon','Converter':'ImageItem'}}" }
});
button.icon.Hidden = true;
}
// new frame of button is set, as the number of buttons is dynamic
int x = index == 0 ? 0 : index * (buttonWidth + Spacing);
button.SetFrame(new RectangleF(x, 0, buttonWidth, Frame.Height));
// the view of the button is added to the buttonrow view
AddSubview(button.View);
index++;
}
}
public int Spacing
{
get
{
return this._spacing;
}
set
{
this._spacing = value;
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
public override void Cleanup()
{
if(Subviews!=null)
{
foreach (var view in Subviews)
{
view.RemoveFromSuperview();
}
}
if(_backgroundImage!=null)
{
_backgroundImage.Dispose();
}
ItemsSource = null;
}
}
Here is my CustomListViewController
public class CustomListViewController: UIView
{
private IList _itemsSource;
private CustomViewSource _viewSource;
public CustomListViewController(MvxShowViewModelRequest showRequest)
{
ShowRequest = showRequest;
}
protected CustomListViewController()
{
}
public CustomListViewController(IntPtr handle)
: base(handle)
{
}
public bool IsVisible
{
get
{
return this.IsVisible;
}
}
public IList ItemsSource
{
get { return _itemsSource; }
set { _itemsSource = value; if(value!=null){CreateViewSource(_itemsSource); }}
}
public virtual void CreateViewSource(IList items)
{
if (_viewSource == null)
{
_viewSource = new CustomViewSource();
_viewSource.OnNewViewsReady += FillViews;
}
_viewSource.ItemsSource = items;
}
private void FillViews(object sender, EventArgs e)
{
AddViews();
}
protected virtual void AddViews()
{
// get views from source and do custom allignment
}
public virtual void Cleanup()
{
if(_viewSource!=null)
{
_itemsSource.Clear();
_itemsSource=null;
_viewSource.OnNewViewsReady -= FillViews;
_viewSource.ItemsSource.Clear();
_viewSource.ItemsSource = null;
_viewSource=null;
}
}
public MvxShowViewModelRequest ShowRequest { get;
private set;
}
}
And My CustomViewSource
public class CustomViewSource
{
private IList _itemsSource;
private List<UIView> _views=new List<UIView>();
public event EventHandler<EventArgs> OnNewViewsReady;
public CustomViewSource ()
{
}
public List<UIView> Views { get { return _views; } }
public virtual IList ItemsSource
{
get { return _itemsSource; }
set
{
// if (_itemsSource == value)
// return;
var collectionChanged = _itemsSource as INotifyCollectionChanged;
if (collectionChanged != null)
collectionChanged.CollectionChanged -= CollectionChangedOnCollectionChanged;
_itemsSource = value;
collectionChanged = _itemsSource as INotifyCollectionChanged;
if (collectionChanged != null)
collectionChanged.CollectionChanged += CollectionChangedOnCollectionChanged;
ReloadViewData();
}
}
protected object GetItemAt(int position)
{
if (ItemsSource == null)
return null;
return ItemsSource[position];
}
protected void CollectionChangedOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
ReloadViewData();
}
protected virtual void ReloadViewData()
{
if(ItemsSource==null){return;}
foreach (var VARIABLE in ItemsSource)
{
//call create view and add it to Views
}
//event new views created
if(OnNewViewsReady!=null)
OnNewViewsReady(this,new EventArgs());
}
protected virtual UIView CreateUIView(int position)
{
UIView view = null;
/*
//create view from nib
UIView newView=null;
return newView;
* */
return view;
}
}
Any one have any clues on how to make this work in mvvmcross v3 ?
I would like to make it so i can add x number of buttons and load the buttons from nib files. Have looked at the Kittens collection view example, but have not figured out how to make it work for my buttonRow, not sure if the collectionView is the right one to use as base.
The most common way to show a list is to use a UITableView.
There are quite a few samples around that show how to load these in MvvmCross:
n=2 and n=2.5 in http://mvvmcross.wordpress.com/
n=6 and n=6.5 in http://mvvmcross.wordpress.com/
Working with Collections in https://github.com/MvvmCross/MvvmCross-Tutorials/tree/master/Working%20With%20Collections
In your case, it looks like the previous coder has implemented some sort of custom control with custom layout. Adapting this to v3 shouldn't be particularly difficult, but you are going to need to read through and understand the previous code and how it works (this is nothing to do with MvvmCross - it's just app UI code).
One sample on dealing with custom views like this in iOS is the N=32 tutorial - http://slodge.blogspot.co.uk/2013/06/n32-truth-about-viewmodels-starring.html

How to reflect changes in viewmodel to tableviewcell view with binding in MVVMcross

Am a little stuck with getting changes reflected from the ViewModel to the View when used in a MvxBindableTableViewCell. I am using the vNext branch of MvvmCross on iOS.
Everything is set up properly and the initial values are visible when loading/showing the list for the first time. The list is a ObservableCollection<T> and the ViewModels inherit from MvxViewModel (thus implements INotifyPropertyChanged).
The main ViewModel looks like this:
public abstract class BaseViewModel : MvxViewModel, IMvxServiceConsumer
{
//... just regular implementation
}
public class UploadListViewModel: BaseViewModel
{
private readonly IUploadItemTasks uploadItemTasks;
private readonly IPhotoPickerService photoPickerService;
public IObservableCollection<UploadItemViewModel> Uploads { get { return this.LoadUploadItems(); } }
public UploadListViewModel()
{
this.uploadItemTasks = this.GetService<IUploadItemTasks>();
this.photoPickerService = this.GetService<IPhotoPickerService>();
}
private IObservableCollection<UploadItemViewModel> LoadUploadItems()
{
using (var unitOfWork = UnitOfWork.Start ())
{
return new SimpleObservableCollection<UploadItemViewModel>(uploadItemTasks.GetAll());
}
}
public void StartUpload ()
{
if (this.Uploads == null || this.Uploads.Count == 0) {
ReportError("Error", "No images to upload");
return;
}
this.Uploads.ForEach (uploadItem => PostCallback (uploadItem));
}
private void PostCallback (UploadItemViewModel uploadAsset)
{
IProgressReporter progressReporter = uploadAsset;
this.photoPickerService.GetAssetFullImage(uploadAsset.ImageUrl,
(image) => {
UIImage fullImage = image;
NSData jpeg = fullImage.AsJPEG();
byte[] jpegBytes = new byte[jpeg.Length];
System.Runtime.InteropServices.Marshal.Copy(jpeg.Bytes, jpegBytes, 0, Convert.ToInt32(jpeg.Length));
MemoryStream stream = new MemoryStream(jpegBytes);
Uri destinationUrl = new Uri(uploadAsset.DestinationUrl + "&name=" + uploadAsset.Name + "&contentType=image%2FJPEG");
//TO DO: Move this to plugin
var uploader = new Uploader().UploadPicture (destinationUrl, stream, UploadComplete, progressReporter);
uploader.Host = uploadAsset.Host;
ThreadPool.QueueUserWorkItem (delegate {
uploader.Upload ();
jpeg = null;
});
});
}
private void UploadComplete (string name)
{
if (name == null){
ReportError("Error","There was an error uploading the media.");
} else
{
//ReportError("Succes", name);
}
}
The item ViewModel looks like:
public interface IProgressReporter
{
float Progress { get; set;}
}
public abstract class BaseAssetViewModel: BaseViewModel, IBaseAssetViewModel
{
//... just regular properties
}
public class UploadItemViewModel: BaseAssetViewModel, IProgressReporter
{
public UploadItemViewModel(): base()
{
}
private float progress;
public float Progress {
get {
return this.progress;
}
set {
this.progress = value;
this.RaisePropertyChanged(() => Progress);
}
}
}
The View for the items inherits from MvxBindableTableViewCell and has the property:
private float progress;
public float ProgressMarker {
get {
return progress;
}
set {
progress = value;
// change progressbar or textfield here
}
}
The tableviewcell is bounded to the UploadItemViewModel via the BindingText:
public const string BindingText = #"ProgressMarker Progress, Converter=Float;";
The Uploader class mentioned in the snippet of UploadListViewModel implements a private method which tries to set the progress on the IProgressReporter.
float progressValue;
void SetProgress (float newvalue)
{
progressValue = newvalue;
this.dispatcher.InvokeOnMainThread (delegate {
if (ProgressReporter != null)
ProgressReporter.Progress = progressValue;
});
}
During the first viewing of the list I can see that the properties in both the ViewModel and View are being hit but when I update the ViewModel via the interface IProgressReporter with a new value in Progress the View in the tableviewcell is not updated nor the property is being called.
What am I doing wrong or what am I missing here?
UPDATE: Check the answer to this question.
I found why the binding didn't work. I was replacing the ObservableCollection over and over again.. I changed that piece of code as stated below and now it reflects the changes made to the UploadItemViewModel in the View of the cell.
private IObservableCollection<UploadItemViewModel> uploads;
private IObservableCollection<UploadItemViewModel> LoadUploadItems()
{
if (uploads == null)
{
using (var unitOfWork = UnitOfWork.Start ())
{
uploads = new SimpleObservableCollection<UploadItemViewModel>(uploadItemTasks.FindAll());
}
}
return uploads;
}

Resources