Bindable data controller or underlying collection class (maybe CPArrayController) for REST endpoint

32 views
Skip to first unread message

Luke

unread,
May 3, 2019, 2:32:49 PM5/3/19
to Cappuccino & Objective-J
Just wondering what people think about the best way to connect a Cappuccino UI to a REST endpoint. 

My prototype has a table bound to an array controller with an underlying CPArray as the data model.  
Now, I need the contents of that array (or some other data representation) to come from a REST endpoint.  Ideally, this should lazily fetch items and cache them in an array in the client, but I suppose it could just GET the entire list of items at initialization.  It should however, do PUT operations when the user edits table cells and the data is updated in the CPDictionary representation in the controller and pushed down the the model and it should do DELETE when the row is removed and CREATE when a new row is added.  I have the standard REST operations implemented on the resource at the server.

Has anyone implemented something similar?  This is surely a very common thing with Cappuccino solutions.  Clearly there's support for the fundamentals, but is there a nice way to (for instance) just define the REST interface as the model in the regular cocoa MVC pattern and have all the REST calls (and client caching) happen automagically, driven by events in the regular controller binding.

I've thought about the possibility of writing an index addressable to-many KVC compliant REST model class that would have a contained array as a cache and would do the underlying REST operations as the controller requests/modifies items. 



  

Keary Suska

unread,
May 4, 2019, 10:41:08 AM5/4/19
to objec...@googlegroups.com
Nothing that you mention sounds difficult though there may be a few tricky points. As long as the data your are requesting in in JSON, you can use CPURLConnection delegate methods to make the API calls and use JSON.parse in -connection:didReceiveData:. Delegate calls are the same as Cocoa, I am pretty sure, but if you have issues I can share my usage.

One main tricky point is how to usefully detect changes. If you use cell-based table views you may find that it is easier to use data source methods rather than bindings. In view based tables you can use target-action to trigger the upstream process, so that might be a better approach. If a user might need to change multiple values in short succession you may need to consider some trickery to keep the UI responsive.

In either case of retrieving or pushing you need to deal with delays of the network operations, which could be lengthy in some cases, and network failures, which will happen. My approach to delays may not be elegant, but works: when the network process is initiated I throw up an invisible modal window so no changes can be made until the operation is complete. I have to “freeze” the UI because otherwise the user could initiate other actions that could interfere. I also have a timer that throws up a dialog if the process takes 2 seconds or more so there is some feedback to the user about the delay. In your case error handling might be a bit more tricky, as you might have to revert the value or return the field into editing mode, whatever works best for your UI.

HTH,

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

daboe01

unread,
May 4, 2019, 10:44:00 AM5/4/19
to Cappuccino & Objective-J

Martin Carlberg

unread,
May 4, 2019, 11:20:43 AM5/4/19
to objec...@googlegroups.com
Hi!

Yes, Ratatosk is an excellent framework for this.

You can also take a look at the LightObject framework. https://github.com/mrcarlberg/LightObject

It has a very easy tutorial in code that use bindings and a small backend written in Objective-J running against a Postgres database. You can actually do the same tutorial with Interface Builder in Xcode without any code at all. 

- Martin

--
You received this message because you are subscribed to the Google Groups "Cappuccino & Objective-J" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objectivej+...@googlegroups.com.
To post to this group, send email to objec...@googlegroups.com.
Visit this group at https://groups.google.com/group/objectivej.
For more options, visit https://groups.google.com/d/optout.

Luke

unread,
May 7, 2019, 4:47:55 PM5/7/19
to Cappuccino & Objective-J
This looks great.

Does anyone have a quick bootstrap example for this though?  The documentation is rather sparse.
I've declared a subclass of WLRemoteObject called Activity
and I've tried:

[WLRemoteLink setDefaultBaseURL:@"http://localhost:3000/"];
[[WLRemoteLink sharedRemoteLink] setShouldFlushActions:YES];
var allActivities = [Activity allObjects];

This doesn't call my web server at all (listening on port 3000) and I'm not even sure if this code would be expected to (as WLRemoteObject's allObjects just seems to look up in a local map).
I'm thinking that I can probably get into this once I've done enough to have the framework start emitting the GET queries, but perhaps this is insufficient to initialize it.





On Saturday, May 4, 2019 at 7:44:00 AM UTC-7, daboe01 wrote:

daboe01

unread,
May 9, 2019, 4:13:54 AM5/9/19
to Cappuccino & Objective-J
search github for "WLRemoteObject" and you will find some code examples.

Luke

unread,
May 13, 2019, 6:41:00 PM5/13/19
to Cappuccino & Objective-J
Making some progress now.  Specifically, from examples like https://github.com/ahankinson/TableViewTutorial

One surprise is that I've had to have my REST POST endpoint for creation return the value of the object as well as just a 201 Created response (with the Location header set).  
It seems that Ratatosk complains about not being about to parse the body unless I do that.

Anyway, what I'm currently stuck on is this:
I have my resources loaded from a GET endpoint and I populate a local model with CPArrayController's -addObjects, per the TableViewTutorial linked above.
My table refreshes with all this data, which is great.  However, I clearly need to invoke a Ratatosk PUT to update an object when the user edits it with the table's cell editor.  
So... what hook is there (KVO/KVB) to find out when a model object is changed through whatever method, so I can have this object flush its change to the backend? 

I don't want to observe every individual key path for my fields bound to columns in my table.  I want to be told when any field of any object has been altered.  
Is there a way to do this?

daboe01

unread,
May 15, 2019, 12:58:20 PM5/15/19
to Cappuccino & Objective-J
try to use bindings directly the table column.

Luke

unread,
May 15, 2019, 1:27:32 PM5/15/19
to Cappuccino & Objective-J
Thanks.  

I have my bindings on the table column. 
When I run the program I overtly create the GET request to fetch the data and the result of this is parsed with -objectsFromJson and then added to my array controller.  
The items then appear correctly in my table.

However, when I use the table cell editor to change values in these items, the table seems to do this correctly, but there doesn't appear to be any attempt by Ratatosk to send the change to the server (I have WLRemoteActionPutType configured on the sharedRemoteLink), so I'm expecting the change to be observed by the Ratatosk machinery and for it to schedule a PUT for the object to synchronize it with the server.  

This is the only part that I can't get working (i.e. I have loading of the data initially, adding an item, deleting an item, but so far nothing that reacts to a change of an item's properties). 
There are no console errors. 

Presumably the properties are being observed correctly, but maybe I'm missing some magic setting that actually causes changes to be flushed out automatically.
It looks like this should happen by default, but I have: [[WLRemoteLink sharedRemoteLink] setShouldFlushActions:YES]; anyway.

It looks like I can't avoid really trying to understand how Ratatosk is supposed to work and tracing why it isn't generating the PUT after my WLRemoteObject has a property changed via the table cell edit. 

Luke

unread,
May 16, 2019, 2:41:50 PM5/16/19
to Cappuccino & Objective-J
OK, I found the problem. 

In WLRemoteObject.j, -activateRemotePropertiesObservation sets up the observation of the remote properties to detect changes.
However, the line:
[self addObserver:self forKeyPath:[property localName] options:nil context:property]; 

does not request the correct options for sending the old an new values to the observer.

I made the change:
[self addObserver:self forKeyPath:[property localName] options:(CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew) context:property];

and now the properties correctly get set dirty and the PUT is issued correctly.

I'll log a bug on the Ratatosk project.
Reply all
Reply to author
Forward
0 new messages