Added a totally gnarly example of how to do complex custom data views using Xcode and bindings

This commit is contained in:
Aparajita Fishman
2013-02-13 00:08:10 -05:00
parent 5a558a9d50
commit 76ff2a4492
11 changed files with 3240 additions and 0 deletions
@@ -0,0 +1,296 @@
/*
* AppController.j
* DataView
*
* Created by You on February 12, 2013.
* Copyright 2013, Your Company All rights reserved.
*/
@import <Foundation/CPObject.j>
@import <Foundation/CPArray.j>
@import <AppKit/CPTableView.j>
@import <AppKit/CPWindow.j>
var AppControllerInstance = nil;
/*
This app is a complete example of how to program complex
custom data views in a table view, using bindings in Xcode as much
as possible. Unfortunately the contents of a custom data view
in a cell-based table cannot be bound to row data. For that
we need view-based tables.
A lot of the behavior is specified in Xcode through bindings
and formatters. Inspect each object closely to see how the magic works.
*/
@implementation AppController : CPObject
{
@outlet CPWindow theWindow;
@outlet CPTableView tableView;
@outlet CustomDataView dataView;
@outlet CPArrayController rowController;
BOOL uploading @accessors;
CPArray rows;
CPArray progressIncrements;
}
+ (AppController)sharedAppController
{
return AppControllerInstance;
}
- (id)init
{
self = [super init];
if (self)
{
AppControllerInstance = self;
rows = [];
progressIncrements = [];
}
return self;
}
- (void)applicationDidFinishLaunching:(CPNotification)aNotification
{
// This is called when the application is done loading.
}
- (void)awakeFromCib
{
[[tableView tableColumns][0] setDataView:dataView];
rows = [
[CPDictionary dictionaryWithJSObject:{ id:1, filename: "Jack.png", size:1327, uploading:NO, progress:0 }],
[CPDictionary dictionaryWithJSObject:{ id:2, filename: "Jill.png", size:827193133061, uploading:NO, progress:0 }],
[CPDictionary dictionaryWithJSObject:{ id:3, filename: "Up the hill.html", size:4131964, uploading:NO, progress:0 }],
[CPDictionary dictionaryWithJSObject:{ id:4, filename: "Alice.pdf", size:72731, uploading:NO, progress:0 }],
[CPDictionary dictionaryWithJSObject:{ id:5, filename: "Wonderland.mov", size:1234567890, uploading:NO, progress:0 }],
[CPDictionary dictionaryWithJSObject:{ id:6, filename: "Mad Hatter.psd", size:3113713, uploading:NO, progress:0 }],
[CPDictionary dictionaryWithJSObject:{ id:7, filename: "Down the Rabbit Hole.mkv", size:93847229, uploading:NO, progress:0 }],
];
// Simulate different upload speeds
for (var i = 0; i < rows.length; ++i)
[progressIncrements addObject:FLOOR(RAND() * (10 - 3 + 1)) + 3]; // Random number 3-10
[rowController setContent:rows];
}
// Don't allow files to be selected during an upload
- (BOOL)tableView:(CPTableView)aTableView shouldSelectRow:(int)index
{
return !uploading;
}
- (@action)start:(id)sender
{
// Note we use setUploading:YES instead of uploading = YES.
// This allows controls bound to that value to be updated.
[self setUploading:YES];
[rowController setSelectionIndexes:[CPIndexSet indexSet]];
// Here is a good example of using a method to wrap a block of code with other code.
[self updateRowsWithBlock:function()
{
[rows setValue:YES forKey:@"uploading"];
[rows setValue:0 forKey:@"progress"];
}];
[CPTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(updateProgress:)
userInfo:nil
repeats:YES];
}
- (@action)stop:(id)sender
{
[self updateRowsWithBlock:function()
{
[rows setValue:NO forKey:@"uploading"];
[rows setValue:0 forKey:@"progress"];
}];
[self setUploading:NO];
}
- (@action)removeFiles:(id)sender
{
[rowController removeObjectsAtArrangedObjectIndexes:[rowController selectionIndexes]];
}
- (void)updateProgress:(CPTimer)timer
{
var updater = function()
{
var allDone = YES;
for (var i = 0, count = [rows count]; i < count; ++i)
{
var info = [rows objectAtIndex:i],
progress = [info valueForKey:@"progress"],
fileUploading = [info valueForKey:@"uploading"];
if (fileUploading)
{
progress = MIN(progress + progressIncrements[i], 100);
[info setValue:progress forKey:@"progress"];
if (progress < 100)
allDone = NO;
else
[info setValue:NO forKey:@"uploading"];
}
}
return allDone;
},
allDone = [self updateRowsWithBlock:updater];
if (allDone)
{
[timer invalidate];
[self setUploading:NO];
}
}
- (void)abortFileWithId:(int)anId
{
var info = [rows objectAtIndex:[rows indexOfObjectPassingTest:function(object)
{
return [object valueForKey:@"id"] === anId;
}]];
if (![info valueForKey:@"uploading"])
return;
[self updateRowsWithBlock:function()
{
[info setValue:NO forKey:@"uploading"];
[info setValue:0 forKey:@"progress"];
}
];
[[CPAlert alertWithMessageText:[CPString stringWithFormat:@"Transfer of “%@” has been aborted.", [info valueForKey:@"filename"]]
defaultButton:@"OK"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:nil] runModal];
}
- (id)updateRowsWithBlock:(Function)block
{
/*
We can't bind directly to the contents of the row array, but we did bind
the table contents to the row array. By wrapping a change to the array
with willChange/didChange, we notify observers of the array that a change has occurred.
*/
[self willChangeValueForKey:@"rows"];
var result = block();
[self didChangeValueForKey:@"rows"];
return result;
}
@end
/*
This is the data view class. The prototype for the data view
was created in Xcode and is loaded as an instance variable
of AppController. To use a data view, you must create outlets
to the views you want to update with dynamic data and connect
them in the prototype view within Xcode.
In the data view class you MUST implement the following:
- (void)setObjectValue:(id)aValue
- (id)initWithCoder:(CPCoder)aCoder
- (void)encodeWithCoder:(CPCoder)aCoder
*/
@implementation CustomDataView : CPView
{
// From row data, but not part of the view
int fileId;
// The fields we need to update with row data.
// Note the size field has a CPByteCountFormatter attached.
@outlet CPTextField filename;
@outlet CPTextField size;
@outlet CPProgressIndicator progressBar;
@outlet CPButton abortButton;
}
/*
This is called once for each row in the table's data array whenever
you modify the array through the array controller or use willChange/didChange
on the array. aValue is the array object for the current table row.
*/
- (void)setObjectValue:(id)aValue
{
fileId = [aValue valueForKey:@"id"];
[filename setStringValue:[aValue valueForKey:@"filename"]];
// Because the size field has a CPByteCountFormatter attached,
// we can use setObjectValue with a number to set a formatted string.
[size setObjectValue:[aValue valueForKey:@"size"]];
[progressBar setDoubleValue:[aValue valueForKey:@"progress"]];
[abortButton setEnabled:[aValue valueForKey:@"uploading"]];
}
/*
The abort button in connected to this method in Xcode.
*/
- (@action)abort:(id)sender
{
[[AppController sharedAppController] abortFileWithId:fileId];
}
@end
@implementation CustomDataView (CPCoding)
/*
You MUST decode every view within your custom data view.
*/
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
filename = [aCoder decodeObjectForKey:@"filename"];
size = [aCoder decodeIntForKey:@"size"];
progressBar = [aCoder decodeObjectForKey:@"progressBar"];
abortButton = [aCoder decodeObjectForKey:@"abortButton"];
}
return self;
}
/*
You MUST encode every view within your custom data view.
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeObject:filename forKey:@"filename"];
[aCoder encodeInt:size forKey:@"size"];
[aCoder encodeObject:progressBar forKey:@"progressBar"];
[aCoder encodeObject:abortButton forKey:@"abortButton"];
}
@end
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Main cib file base name</key>
<string>MainMenu.cib</string>
<key>CPBundleName</key>
<string>DataView</string>
</dict>
</plist>
+94
View File
@@ -0,0 +1,94 @@
/*
* Jakefile
* DataView
*
* Created by You on February 12, 2013.
* Copyright 2013, Your Company All rights reserved.
*/
var ENV = require("system").env,
FILE = require("file"),
JAKE = require("jake"),
task = JAKE.task,
FileList = JAKE.FileList,
app = require("cappuccino/jake").app,
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug",
OS = require("os");
app ("DataView", function(task)
{
task.setBuildIntermediatesPath(FILE.join("Build", "DataView.build", configuration));
task.setBuildPath(FILE.join("Build", configuration));
task.setProductName("DataView");
task.setIdentifier("com.yourcompany.DataView");
task.setVersion("1.0");
task.setAuthor("Your Company");
task.setEmail("feedback @nospam@ yourcompany.com");
task.setSummary("DataView");
task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**")));
task.setResources(new FileList("Resources/**"));
task.setIndexFilePath("index.html");
task.setInfoPlistPath("Info.plist");
task.setNib2CibFlags("-R Resources/");
if (configuration === "Debug")
task.setCompilerFlags("-DDEBUG -g");
else
task.setCompilerFlags("-O");
});
task ("default", ["DataView"], function()
{
printResults(configuration);
});
task ("build", ["default"]);
task ("debug", function()
{
ENV["CONFIGURATION"] = "Debug";
JAKE.subjake(["."], "build", ENV);
});
task ("release", function()
{
ENV["CONFIGURATION"] = "Release";
JAKE.subjake(["."], "build", ENV);
});
task ("run", ["debug"], function()
{
OS.system(["open", FILE.join("Build", "Debug", "DataView", "index.html")]);
});
task ("run-release", ["release"], function()
{
OS.system(["open", FILE.join("Build", "Release", "DataView", "index.html")]);
});
task ("deploy", ["release"], function()
{
FILE.mkdirs(FILE.join("Build", "Deployment", "DataView"));
OS.system(["press", "-f", FILE.join("Build", "Release", "DataView"), FILE.join("Build", "Deployment", "DataView")]);
printResults("Deployment")
});
task ("desktop", ["release"], function()
{
FILE.mkdirs(FILE.join("Build", "Desktop", "DataView"));
require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "DataView"), FILE.join("Build", "Desktop", "DataView", "DataView.app"));
printResults("Desktop")
});
task ("run-desktop", ["desktop"], function()
{
OS.system([FILE.join("Build", "Desktop", "DataView", "DataView.app", "Contents", "MacOS", "NativeHost"), "-i"]);
});
function printResults(configuration)
{
print("----------------------------");
print(configuration+" app built at path: "+FILE.join("Build", configuration, "DataView"));
print("----------------------------");
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,107 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<!--
index-debug.html
DataView
Created by You on February 12, 2013.
Copyright 2013, Your Company All rights reserved.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<link rel="apple-touch-icon" href="Resources/icon.png" />
<link rel="apple-touch-startup-image" href="Resources/default.png" />
<title>DataView</title>
<script type="text/javascript">
OBJJ_MAIN_FILE = "main.j";
OBJJ_INCLUDE_PATHS = ["Frameworks/Debug", "Frameworks"];
</script>
<script type="text/javascript" src="Frameworks/Debug/Objective-J/Objective-J.js" charset="UTF-8"></script>
<script type="text/javascript">
objj_msgSend_reset();
// DEBUG OPTIONS:
// Uncomment to enable printing of backtraces on exceptions:
//objj_msgSend_decorate(objj_backtrace_decorator);
// Uncomment to supress exceptions that take place inside a message
//objj_msgSend_decorate(objj_supress_exceptions_decorator)
// Uncomment to enable runtime type checking:
//objj_msgSend_decorate(objj_typecheck_decorator);
// Uncomment (along with both above) to print backtraces on type check errors:
//objj_typecheck_prints_backtrace = true;
// Uncomment to disable the default logger (CPLogConsole if window.console exists, CPLogPopup otherwise):
//CPLogUnregister(CPLogDefault);
// Uncomment to enable a specific logger:
//CPLogRegister(CPLogConsole);
//CPLogRegister(CPLogPopup);
// Tag view DOM elements with a "data-cappuccino-view" attribute that contains
// the class name of the view that created them. Comment this or set to false to disable.
appkit_tag_dom_elements = true;
</script>
<style type="text/css">
body{margin:0; padding:0;}
#container {position: absolute; top:50%; left:50%;}
#content {width:800px; text-align:center; margin-left: -400px; height:50px; margin-top:-25px; line-height: 50px;}
#content {font-family: "Helvetica", "Arial", sans-serif; font-size: 18px; color: black; text-shadow: 0px 1px 0px white; }
#loadgraphic {margin-right: 0.2em; margin-bottom:-2px;}
</style>
<!--[if lt IE 7]>
<STYLE type="text/css">
#container { position: relative; top: 50%; }
#content { position: relative;}
</STYLE>
<![endif]-->
</head>
<body style="">
<div id="cappuccino-body">
<div id="loadingcontainer" style="background-color: #eeeeee; overflow:hidden; width:100%; height:100%; position: absolute; top: 0; left: 0;">
<script type="text/javascript">
document.write("<div id='container'><p id='content'>" +
"<img id='loadgraphic' width='16' height='16' src='Resources/spinner.gif' /> " +
"Loading DataView...</p></div>");
</script>
<noscript>
<div id="container">
<div style="width: 440px; padding: 10px 25px 20px 25px; font-family: sans-serif; background-color: #ffffff; position: relative; left: -245px; top: -120px; text-align: center; -moz-border-radius: 20px; -webkit-border-radius: 20px; color: #555555">
<p style="line-height: 1.4em;">JavaScript is required for this site to work correctly but is either disabled or not supported by your browser.</p>
<p style="font-size:120%; padding:10px;"><a href="http://cappuccino-project.org/noscript">Show me how to enable JavaScript</a></p>
<p style="font-size:80%;">You may want to upgrade to a newer browser while you're at it:</p>
<ul style="margin:0;padding:0; text-align: center; font-size:80%;" >
<li style="display: inline;"><a href="http://www.apple.com/safari/download/">Safari</a></li>
<li style="display: inline;"><a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
<li style="display: inline;"><a href="http://www.google.com/chrome/">Chrome</a></li>
<li style="display: inline;"><a href="http://www.opera.com/download/">Opera</a></li>
<li style="display: inline;"><a href="http://www.microsoft.com/windows/downloads/ie/getitnow.mspx">Internet Explorer</a></li>
</ul>
</div>
</div>
</noscript>
</div>
</div>
</body>
</html>
@@ -0,0 +1,77 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<!--
index.html
DataView
Created by You on February 12, 2013.
Copyright 2013, Your Company All rights reserved.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<link rel="apple-touch-icon" href="Resources/icon.png" />
<link rel="apple-touch-startup-image" href="Resources/default.png" />
<title>DataView</title>
<script type="text/javascript">
OBJJ_MAIN_FILE = "main.j";
</script>
<script type="text/javascript" src="Frameworks/Objective-J/Objective-J.js" charset="UTF-8"></script>
<style type="text/css">
body{margin:0; padding:0;}
#container {position: absolute; top:50%; left:50%;}
#content {width:800px; text-align:center; margin-left: -400px; height:50px; margin-top:-25px; line-height: 50px;}
#content {font-family: "Helvetica", "Arial", sans-serif; font-size: 18px; color: black; text-shadow: 0px 1px 0px white; }
#loadgraphic {margin-right: 0.2em; margin-bottom:-2px;}
</style>
<!--[if lt IE 7]>
<STYLE type="text/css">
#container { position: relative; top: 50%; }
#content { position: relative;}
</STYLE>
<![endif]-->
</head>
<body style="">
<div id="cappuccino-body">
<div id="loadingcontainer" style="background-color: #eeeeee; overflow:hidden; width:100%; height:100%; position: absolute; top: 0; left: 0;">
<script type="text/javascript">
document.write("<div id='container'><p id='content'>" +
"<img id='loadgraphic' width='16' height='16' src='Resources/spinner.gif' /> " +
"Loading DataView...</p></div>");
</script>
<noscript>
<div id="container">
<div style="width: 440px; padding: 10px 25px 20px 25px; font-family: sans-serif; background-color: #ffffff; position: relative; left: -245px; top: -120px; text-align: center; -moz-border-radius: 20px; -webkit-border-radius: 20px; color: #555555">
<p style="line-height: 1.4em;">JavaScript is required for this site to work correctly but is either disabled or not supported by your browser.</p>
<p style="font-size:120%; padding:10px;"><a href="http://cappuccino-project.org/noscript">Show me how to enable JavaScript</a></p>
<p style="font-size:80%;">You may want to upgrade to a newer browser while you're at it:</p>
<ul style="margin:0;padding:0; text-align: center; font-size:80%;" >
<li style="display: inline;"><a href="http://www.apple.com/safari/download/">Safari</a></li>
<li style="display: inline;"><a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
<li style="display: inline;"><a href="http://www.google.com/chrome/">Chrome</a></li>
<li style="display: inline;"><a href="http://www.opera.com/download/">Opera</a></li>
<li style="display: inline;"><a href="http://www.microsoft.com/windows/downloads/ie/getitnow.mspx">Internet Explorer</a></li>
</ul>
</div>
</div>
</noscript>
</div>
</div>
</body>
</html>
+18
View File
@@ -0,0 +1,18 @@
/*
* AppController.j
* DataView
*
* Created by You on February 12, 2013.
* Copyright 2013, Your Company All rights reserved.
*/
@import <Foundation/Foundation.j>
@import <AppKit/AppKit.j>
@import "AppController.j"
function main(args, namedArgs)
{
CPApplicationMain(args, namedArgs);
}