Loading overlay never disappears in config of Jenkins plugin - groovy

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;
}
}
}

Related

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;
}

Accessing XPages Data Source in a Plugin

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()

Multithreading and file I/O , ThreadLocal issues

I have this base class structure:
Base:
public abstract class BackgroundTask
{
protected readonly Logger Logger = LogManager.GetCurrentClassLogger();
protected virtual void Initialize()
{
// initialize database access
}
public void Run()
{
Initialize();
try
{
Execute();
// insert to database or whatever
}
catch (Exception ex)
{
Logger.ErrorException(string.Format("Error proccesing task: {0}\r\n", ToString()), ex);
Exceptions.Add(ex);
}
finally
{
TaskExecuter.Discard();
}
}
protected abstract void Execute();
public abstract override string ToString();
public IList<Exception> Exceptions = new List<Exception>();
}
Task executor:
public static class TaskExecuter
{
private static readonly ThreadLocal<IList<BackgroundTask>> TasksToExecute
= new ThreadLocal<IList<BackgroundTask>>(() => new List<BackgroundTask>());
public static void ExecuteLater(BackgroundTask task)
{
TasksToExecute.Value.Add(task);
}
public static void StartExecuting()
{
foreach (var backgroundTask in TasksToExecute.Value)
{
Task.Factory.StartNew(backgroundTask.Run);
}
}
public static void Discard()
{
TasksToExecute.Value.Clear();
TasksToExecute.Dispose();
}
}
FileTask:
public class FileTask : BackgroundTask
{
protected static string BaseFolder = #"C:\ASCII\";
private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
private readonly string _folder;
private IHistoryRepository _historyRepository;
public string Folder
{
get { return _folder; }
}
public FileTask(string folder)
{
_folder = string.Format("{0}{1}", BaseFolder, folder);
}
protected override void Initialize()
{
_historyRepository = new HistoryRepository();
}
protected override void Execute()
{
// todo: Get institute that are active,
var institute = MockInstitute(); // todo: uncomment _historyRepository.FindInstituteByFolderName(Folder);
// todo: Update institute, lastupdate - [date] | [files amount] | [phonenumbers amount]
if (institute == null)
{
Logger.Warn("Not found data", Folder);
return;
}
// todo: read file get encoding | type and parse it
Task.Factory.StartNew(ReadFile);
}
private void ReadFile()
{
var list = GetFilesByFolder();
StreamReader sr = null;
try
{
Lock.EnterReadLock();
foreach (var fi in list)
{
var fileName = fi.FullName;
Logger.Info("Line: {0}:=> Content: {1}", fileName, Thread.CurrentThread.ManagedThreadId);
sr = new StreamReader(fileName, DetectEncoding(fileName));
string currentLine;
while ((currentLine = sr.ReadLine()).ReturnSuccess())
{
if (string.IsNullOrEmpty(currentLine)) continue;
Logger.Info("Line: {0}:=> Content: {1}", fileName, currentLine);
}
}
Lock.ExitReadLock();
}
finally
{
if (sr != null) sr.Dispose();
Logger.Info("Finished working" + Folder);
}
}
protected IEnumerable<FileInfo> GetFilesByFolder()
{
return Directory.GetFiles(Folder).Select(fileName => new FileInfo(fileName));
}
protected Encoding DetectEncoding(string file)
{
using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
var cdet = new Ude.CharsetDetector();
cdet.Feed(fs);
cdet.DataEnd();
return cdet.With(x => x.Charset)
.Return(x => Encoding.GetEncoding(cdet.Charset),
Encoding.GetEncoding("windows-1255"));
}
}
private Institute MockInstitute()
{
return new Institute
{
FromFolderLocation = string.Format("{0}{1}", BaseFolder, Folder)
};
}
public override string ToString()
{
return string.Format("Folder: {0}", Folder);
}
}
When don't read the file every thing ok, the Log is populated and every thing runs smooth,
but when i attach the Task.Factory.StartNew(ReadFile); method i have an exception.
Exception:
Cannot access a disposed object.
Object name: 'The ThreadLocal object has been disposed.'.
How do i solve that issue? might i need to change the LocalThread logic, or what - i have been trying to handle that issue, for almost a day.
BTW: It's an MVC4 project, and C# 5.0 and i'm trying to TDD it all.
You shouldn't be calling TasksToExecute.Dispose();
there.

Using Three20 TTPhotoViewController with MonoTouch

I'm trying to use the Three20 TTPhotoViewController with MonoTouch. I've derived FacebookPhoto from TTPhoto and FacebookPhotoSource from TTPhotoSource and am now trying to invoke the TTPhotoViewController but I get the following exception when pushing the view controller:
Objective-C exception thrown. Name: NSInvalidArgumentException Reason: * -[NSPlaceholderString initWithFormat:locale:arguments:]: nil argument
I noticed that the monotouch bindings in this github project: https://github.com/mono/monotouch-bindings/tree/492f68c3c2007f0638452cc8a5a762556db224ba/Three20/binding were missing the photoAtIndex binding, so I added that and recompiled them, but I haven't been able to figure out why I am getting this exception.
Here is how I'm invoking the TTPhotoViewController:
List<Photo> photoList = FacebookGraphApi.Instance.GetAlbumPhotos(album.id);
List<FacebookPhoto> fbPhotoList = photoList.Select(x => new FacebookPhoto(x)).ToList();
var photos = new TTPhotoViewController();
photos.PhotoSource = new FacebookPhotoSource(fbPhotoList);
NavController.PushViewController(photos, true);
Here is the definition of the TTPhotoSource
class FacebookPhotoSource : TTPhotoSource
{
List<FacebookPhoto> _photoList;
public FacebookPhotoSource (List<FacebookPhoto> photoList)
{
_photoList = photoList;
int i = 0;
foreach (FacebookPhoto photo in photoList) {
photo.PhotoSource = this;
photo.Index = i++;
}
}
public override string Title {
get {
return "Facebook Photos";
}
set {
throw new NotImplementedException();
}
}
public override int NumberOfPhotos {
get {
return _photoList.Count;
}
}
public override int MaxPhotoIndex {
get {
return _photoList.Count -1;
}
}
public override TTPhoto PhotoAtIndex(int photoIndex)
{
return _photoList[photoIndex];
}
}
and here is the definition of the FacebookPhoto:
class FacebookPhoto : TTPhoto
{
Photo _photo;
public FacebookPhoto(Photo photo)
{
_photo = photo;
}
public override string Caption {
get {
if(_photo.name == null)
return "";
return _photo.name;
}
set {
throw new NotImplementedException();
}
}
public override TTPhotoSource PhotoSource { get; set; }
public override int Index { get; set; }
public override SizeF Size {
get {
return new SizeF(_photo.width, _photo.height);
}
set {
throw new NotImplementedException();
}
}
public override string URLForVersion (int version)
{
switch (version) {
case 4:
return _photo.picture;
default:
return _photo.source;
}
}
}

Resources