Note: All code/live demo can be found at ChartPlotter
This app has 4 ways of viewing the details of the objects
- View details in the bottom right hand view for selected(one-click) object
- View details in a separate window for selected(double-click) object
- View details in a separate window for a selected collection of plots(highlight, then click “Plot Selected”)
- View details of all the plots listed in a separate window(click “Plot All”)
In this case the details come from a website within a webview, but you could generate the details however you like, ie the details could be images or objj generated content. Let’s start with #1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| - (void)tableViewSelectionDidChange:(CPNotification)aNotification
{
if(groupView === [aNotification object]){
var i = [[[aNotification object] selectedRowIndexes] firstIndex];
[listDS getList:[[groupDS objs] objectAtIndex:i]];
[searchField setStringValue:@""];
[self hideFilterBar:nil];
}
else{
var i = [[[aNotification object] selectedRowIndexes] firstIndex];
if(i > -1){
var row = [[listDS objsToDisplay] objectAtIndex:i];
[webView setMainFrameURL:@"php/tradeReport.php?group="+[row objectForKey:groupColHeaderName]+"&file="+[row objectForKey:"Name"]];
}
}
} |
The CPTableView class automatically create a notification for tableViewSelectionDidChange:, all wee need to do is implement it. Remember though that there are two tableviews in this app. One for the listview and one for the groupview. Line 3 checks which object is sending the notification, if it is groupview then the function tells listDS to get a new list from the server of only the files from within the selected group. You could, however, just filter the objects without a server call if you have a way to tell which group each file belongs to. This app does, but I chose to make a new server call because the backend is dynamic, and this forces a sort-of auto refresh every time the group changes. Also the call is generally fast enough that the user can’t even tell. Just be aware of my choice, so you can make your own. After it sends the call to get a new list, it resets the searchfield and raises the filterbar, again a choice. Now let’s move on to #2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| - (@action)openDetailInNewWindow:(id)sender
{
var newWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(100, 100, 800, 600) styleMask:CPTitledWindowMask|CPClosableWindowMask|CPMiniaturizableWindowMask|CPResizableWindowMask];
[newWindow setMinSize:CGSizeMake(300, 300)];
var platformWindow = [[CPPlatformWindow alloc] initWithContentRect:CGRectMake(100, 100, 800, 600)];
[newWindow setPlatformWindow:platformWindow];
[newWindow setFullBridge:YES];
var contentView = [newWindow contentView],
webViewWin = [[DetailsWebView alloc] initWithFrame:[contentView bounds]];
[webViewWin setAutoresizingMask:CPViewWidthSizable|CPViewHeightSizable];
[contentView addSubview:webViewWin];
[newWindow orderFront:self];
[newWindow setDelegate:webViewWin];
var i = [[tableView selectedRowIndexes] firstIndex];
var row = [[listDS objsToDisplay] objectAtIndex:i];
[webViewWin setMainFrameURL:@"php/tradeReport.php?group="+[row objectForKey:groupColHeaderName]+"&file="+[row objectForKey:"Name"]];
} |
There is alot going on this function, and it is a great example of the power of Cappuccino. First we create a newWindow to hold the webView with the details. Remember, this is a content window, not a platform window or “bridge” in Cappuccino speak. So we then make a platformWindow to contain the newWindow. In lines 7,8 we are placing the newWindow in the platformWindow rather than the current bridge, then we are telling the window to take on the full size of the bridge. Now we have a new bridge with a window, so let’s place some content in this newWindow. We create a new webViewWin in line 11, then add it to the newWindow. Then we just bring the window up in line 16. The last step, of course, is to set the URL of the webViewWin. This is unique to this app, but in short my php page accepts the group and file names as parameters to look up the plot I need. So I am just creating a URL based on the group/file in the selected row. Finally, let’s tackle #3,#4 together:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| - (@action)openDetailsInNewWindow:(id)sender
{
var platformWindow = [[CPPlatformWindow alloc] initWithContentRect:CGRectMake(0, 0, 600, 800)];
var newWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask];
[newWindow setPlatformWindow:platformWindow];
[newWindow setFullBridge:YES];
[newWindow orderFront:self];
var contentView = [newWindow contentView],
bounds = [contentView bounds];
var detsView = [[CPCollectionView alloc] initWithFrame:bounds];
[detsView setAutoresizingMask:CPViewWidthSizable];
[detsView setMinItemSize:CGSizeMake(collViewWidth, collViewHeight)];
[detsView setMaxItemSize:CGSizeMake(collViewWidth, collViewHeight)];
var itemPrototype = [[CPCollectionViewItem alloc] init],
detView = [[DetailsWebView alloc] initWithFrame:CGRectMakeZero()];
[itemPrototype setView:detView];
[detsView setItemPrototype:itemPrototype];
var scrollViewDetails = [[CPScrollView alloc] initWithFrame:bounds];
[scrollViewDetails setDocumentView:detsView];
[scrollViewDetails setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[scrollViewDetails setAutohidesScrollers:YES];
[contentView addSubview:scrollViewDetails];
var urls = [];
if([sender title] == showAll){
for(var i=0;i < [[listDS objsToDisplay] count];i++){
var row = [[listDS objsToDisplay] objectAtIndex:i];
urls[i] = @"php/tradeReport.php?group="+[row objectForKey:groupColHeaderName]+"&file="+[row objectForKey:"Name"];
}
}
else{
var indices = [tableView selectedRowIndexes];
var index = [indices firstIndex];
for(var i=0;i < [indices count];i++){
var row = [[listDS objsToDisplay] objectAtIndex:index];
urls[i] = @"php/tradeReport.php?group="+[row objectForKey:groupColHeaderName]+"&file="+[row objectForKey:"Name"];
index = [indices indexGreaterThanIndex:index];
}
}
[detsView setContent:urls];
} |
There are quite a few Cappuccino idiosyncrasies here, so pay close attention to the details. Up to line 9 everything is pretty much the same as before, just creating a new bridge/window, but now comes the good stuff. In order to display multiple plots properly I have chosen to place them all inside a CPCollectionView. This is a choice, and the reason for it is the positioning power of a collectionview. The collectionview can organizes the objects into columns, and as the browser expand it will automatically add or remove columns. This allows the user to see the most possible plots for his/her resolution/bridge size without any more effort on my part. In lines 11-14 we are creating the collectionview, making it autoresize properly, and setting the size of each object. In lines 22-26 we create a scrollview to place the collectionview in so the page scrolls with nice bars.
In lines 16,17 we create a prototype for the items in the collectionview. This is tricky because this is not just a way of telling the collectionview what is going inside it, but rather a way of telling the collectionview what to create each time an object is added. In this example we use a webview as a prototype. This means we cannot fill the collectionview with webviews because it does that for us. Rather, we need to just tell the collectionview how many webviews we want, then set the URLs for each webview. So what we do is create an array of the URLs we want. Then we set the content of the collectionview to this array. The content refers to what makes each webview different, not the views themselves, in this case it is obviously the URLs. What setContent does is create a prototype based object for each object in the array. Then it calls setRepresentedObject:(id) once for each item in the collectionview. The question is, where is setRepresentedObject:(id)? Well that is a function that the collectionview assumes is within the class of the prototype object. But ofosho, there this function does not exist in CPWebView? Correct, we need to subclass CPWebView, which we did with DetailsWebView. I put this subclass within the AppController.j file because it is so short and not very useful again, but you can put it in a separate file. All setRepresentedObject: does is set the mainFrameURL of the webview. Take a second and explore the code, all this should click.
One last interesting note is the creation of the URL array. In the case of “Plot All” we just traverse all of objsToDisplay and create a URL for each object, but how do we traverse just the selected objects? Cappuccino has a nice class CPIndexSet that is just a collection of selected indices. So we set the first index to the firstIndex in the CPIndexSet. Then on each iteration from 0 to the amount of indices we have we set the index to the next index in the list using, indexGreaterThanIndex:index, where index is our current index. I am not sure if CPIndexSet is sorted in general, but in this case it is because table rows are ordered. If there is a better way, let me know, but this works just fine.
Well that concludes the posts on the ChartPlotter that I can think would be useful. However, if there are alot of questions or interest, I will gladly add more, just ask! I hope that at this point you are comfortable with Cappuccino and ready to start creating your own apps.
-O
PS, Let me know if you use/fork this app!