diff --git a/.gitignore b/.gitignore
index 9af9dbc..2007561 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,4 @@ tmp
bin
build
*.zip
-.DS_Store
-.metadata
+.*
diff --git a/.gitmodules b/.gitmodules
index 53f6efd..1179dd0 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,4 +1,4 @@
[submodule "mobile/ios/vendor/couchbase-lite-ios"]
path = mobile/ios/vendor/couchbase-lite-ios
url = https://github.com/couchbase/couchbase-lite-ios.git
- branch = release/1.0.0
+ branch = release/1.0.2
diff --git a/mobile/android/.gitignore b/mobile/android/.gitignore
index f8715a9..8adbbe7 100644
--- a/mobile/android/.gitignore
+++ b/mobile/android/.gitignore
@@ -7,3 +7,5 @@ build
build.properties
dist
libs
+example
+documentation
diff --git a/mobile/android/build.xml b/mobile/android/build.xml
index a78421b..ab645e4 100644
--- a/mobile/android/build.xml
+++ b/mobile/android/build.xml
@@ -9,7 +9,6 @@
-
diff --git a/mobile/android/example b/mobile/android/example
deleted file mode 120000
index f307990..0000000
--- a/mobile/android/example
+++ /dev/null
@@ -1 +0,0 @@
-../noarch/example
\ No newline at end of file
diff --git a/mobile/android/lib/LICENSE.txt b/mobile/android/lib/LICENSE.txt
new file mode 100644
index 0000000..bdf0aa1
--- /dev/null
+++ b/mobile/android/lib/LICENSE.txt
@@ -0,0 +1,17 @@
+Couchbase, Inc. Community Edition License Agreement
+
+IMPORTANT-READ CAREFULLY: BY CLICKING THE “I ACCEPT” BOX OR INSTALLING, DOWNLOADING OR OTHERWISE USING THIS SOFTWARE AND ANY ASSOCIATED DOCUMENTATION, YOU, ON BEHALF OF YOURSELF OR AS AN AUTHORIZED REPRESENTATIVE ON BEHALF OF AN ENTITY (“LICENSEE”) AGREE TO ALL THE TERMS OF THIS COMMUNITY EDITION LICENSE AGREEMENT (THE “AGREEMENT”) REGARDING YOUR USE OF THE SOFTWARE. YOU REPRESENT AND WARRANT THAT YOU HAVE FULL LEGAL AUTHORITY TO BIND THE LICENSEE TO THIS AGREEMENT. IF YOU DO NOT AGREE WITH ALL OF THESE TERMS, DO NOT SELECT THE “I ACCEPT” BOX AND DO NOT INSTALL, DOWNLOAD OR OTHERWISE USE THE SOFTWARE. THE EFFECTIVE DATE OF THIS AGREEMENT IS THE DATE ON WHICH YOU CLICK “I ACCEPT” OR OTHERWISE INSTALL, DOWNLOAD OR USE THE SOFTWARE.
+
+1. License Grant. Couchbase Inc. hereby grants Licensee, free of charge, the non-exclusive right to use, copy, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to Licensee including the following copyright notice in all copies or substantial portions of the Software:
+
+Couchbase ®
+http://www.couchbase.com
+Copyright 2011 Couchbase, Inc.
+
+As used in this Agreement, “Software” means the object code version of the applicable elastic data management server software provided by Couchbase, Inc.
+
+2. Restrictions. Licensee will not: (a) reverse engineer, disassemble, or decompile the Software (except to the extent such restrictions are prohibited by law);
+
+3. Support. Couchbase, Inc. will provide Licensee with access to, and use of, the Couchbase, Inc. support forum available at the following URL: http://forums.Couchbase.org. Couchbase, Inc. may, at its discretion, modify, suspend or terminate support at any time.
+
+4. Warranty Disclaimer and Limitation of Liability. THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL COUCHBASE INC. OR THE AUTHORS OR COPYRIGHT HOLDERS IN THE SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES (INCLUDING, WITHOUT LIMITATION, DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES) OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/mobile/android/lib/cbl_collator_so-1.0.0.jar b/mobile/android/lib/cbl_collator_so-1.0.2.jar
similarity index 96%
rename from mobile/android/lib/cbl_collator_so-1.0.0.jar
rename to mobile/android/lib/cbl_collator_so-1.0.2.jar
index bb9e100..92b50f7 100644
Binary files a/mobile/android/lib/cbl_collator_so-1.0.0.jar and b/mobile/android/lib/cbl_collator_so-1.0.2.jar differ
diff --git a/mobile/android/lib/couchbase-lite-android-1.0.0.jar b/mobile/android/lib/couchbase-lite-android-1.0.2.jar
similarity index 63%
rename from mobile/android/lib/couchbase-lite-android-1.0.0.jar
rename to mobile/android/lib/couchbase-lite-android-1.0.2.jar
index 23cd705..b804ca3 100644
Binary files a/mobile/android/lib/couchbase-lite-android-1.0.0.jar and b/mobile/android/lib/couchbase-lite-android-1.0.2.jar differ
diff --git a/mobile/android/lib/couchbase-lite-java-core-1.0.0.jar b/mobile/android/lib/couchbase-lite-java-core-1.0.0.jar
deleted file mode 100644
index e8d05fb..0000000
Binary files a/mobile/android/lib/couchbase-lite-java-core-1.0.0.jar and /dev/null differ
diff --git a/mobile/android/lib/couchbase-lite-java-core-1.0.2.jar b/mobile/android/lib/couchbase-lite-java-core-1.0.2.jar
new file mode 100644
index 0000000..c05d9df
Binary files /dev/null and b/mobile/android/lib/couchbase-lite-java-core-1.0.2.jar differ
diff --git a/mobile/android/lib/couchbase-lite-java-javascript-1.0.0.jar b/mobile/android/lib/couchbase-lite-java-javascript-1.0.2.jar
similarity index 82%
rename from mobile/android/lib/couchbase-lite-java-javascript-1.0.0.jar
rename to mobile/android/lib/couchbase-lite-java-javascript-1.0.2.jar
index fba988c..11ed216 100644
Binary files a/mobile/android/lib/couchbase-lite-java-javascript-1.0.0.jar and b/mobile/android/lib/couchbase-lite-java-javascript-1.0.2.jar differ
diff --git a/mobile/android/lib/couchbase-lite-java-listener-1.0.2.jar b/mobile/android/lib/couchbase-lite-java-listener-1.0.2.jar
new file mode 100644
index 0000000..d9aee44
Binary files /dev/null and b/mobile/android/lib/couchbase-lite-java-listener-1.0.2.jar differ
diff --git a/mobile/android/lib/webserver-2-3.jar b/mobile/android/lib/webserver-2-3.jar
new file mode 100644
index 0000000..ea31895
Binary files /dev/null and b/mobile/android/lib/webserver-2-3.jar differ
diff --git a/mobile/android/manifest b/mobile/android/manifest
index bd362ef..ed4d8fa 100644
--- a/mobile/android/manifest
+++ b/mobile/android/manifest
@@ -2,7 +2,7 @@
# this is your module manifest and used by Titanium
# during compilation, packaging, distribution, etc.
#
-version: 1.0.1
+version: 1.1.0
apiversion: 2
description: TouchDB for Titanium
author: Paul Mietz Egli
diff --git a/mobile/android/src/com/obscure/titouchdb/AttachmentProxy.java b/mobile/android/src/com/obscure/titouchdb/AttachmentProxy.java
index 4f658d2..45f68fc 100644
--- a/mobile/android/src/com/obscure/titouchdb/AttachmentProxy.java
+++ b/mobile/android/src/com/obscure/titouchdb/AttachmentProxy.java
@@ -12,6 +12,8 @@
import android.util.Log;
import com.couchbase.lite.Attachment;
+import com.couchbase.lite.BlobStore;
+import com.couchbase.lite.BlobStoreWriter;
import com.couchbase.lite.CouchbaseLiteException;
@Kroll.proxy(parentModule = TitouchdbModule.class)
@@ -65,6 +67,12 @@ public TiBlob getContent() {
public String getContentType() {
return attachment.getContentType();
}
+
+ @Kroll.getProperty(name = "contentURL")
+ public String getContentURL() {
+ BlobStore store = new BlobStore(attachment.getDocument().getDatabase().getAttachmentStorePath());
+ return "file:/" + store.pathForKey(BlobStore.keyForBlob(getContent().getBytes()));
+ }
@Kroll.getProperty(name = "document")
public DocumentProxy getDocument() {
diff --git a/mobile/android/src/com/obscure/titouchdb/AuthenticatorProxy.java b/mobile/android/src/com/obscure/titouchdb/AuthenticatorProxy.java
new file mode 100644
index 0000000..2bff399
--- /dev/null
+++ b/mobile/android/src/com/obscure/titouchdb/AuthenticatorProxy.java
@@ -0,0 +1,23 @@
+package com.obscure.titouchdb;
+
+import org.appcelerator.kroll.KrollProxy;
+import org.appcelerator.kroll.annotations.Kroll;
+
+import com.couchbase.lite.auth.Authenticator;
+
+@Kroll.proxy(parentModule = TitouchdbModule.class)
+public class AuthenticatorProxy extends KrollProxy {
+
+ private static final String LCAT = "AuthenticatorProxy";
+
+ private Authenticator authenticator;
+
+ public AuthenticatorProxy(Authenticator authenticator) {
+ this.authenticator = authenticator;
+ }
+
+ public Authenticator getAuthenticator() {
+ return authenticator;
+ }
+
+}
diff --git a/mobile/android/src/com/obscure/titouchdb/DatabaseProxy.java b/mobile/android/src/com/obscure/titouchdb/DatabaseProxy.java
index af79322..0411712 100644
--- a/mobile/android/src/com/obscure/titouchdb/DatabaseProxy.java
+++ b/mobile/android/src/com/obscure/titouchdb/DatabaseProxy.java
@@ -124,6 +124,12 @@ public ReplicationProxy createPushReplication(String url) {
return result;
}
+ @Kroll.method
+ public QueryProxy createSlowQuery(KrollFunction map) {
+ lastError = null;
+ return new QueryProxy(this, database.slowQuery(new KrollMapper(map)));
+ }
+
@Kroll.method
public boolean deleteDatabase() {
lastError = null;
diff --git a/mobile/android/src/com/obscure/titouchdb/ReplicationProxy.java b/mobile/android/src/com/obscure/titouchdb/ReplicationProxy.java
index 482d8c0..5b214f3 100644
--- a/mobile/android/src/com/obscure/titouchdb/ReplicationProxy.java
+++ b/mobile/android/src/com/obscure/titouchdb/ReplicationProxy.java
@@ -21,6 +21,8 @@ public class ReplicationProxy extends KrollProxy implements ChangeListener {
private static final String LCAT = "ReplicationProxy";
+ private AuthenticatorProxy authenticatorProxy;
+
private DatabaseProxy databaseProxy;
private KrollDict lastError = null;
@@ -36,6 +38,20 @@ public ReplicationProxy(DatabaseProxy databaseProxy, Replication replicator) {
replicator.addChangeListener(this);
}
+ @Override
+ public void changed(ChangeEvent e) {
+ KrollDict params = new KrollDict();
+ params.put("source", this);
+ params.put("status", replicator.getStatus().ordinal());
+
+ fireEvent("change", params);
+ }
+
+ @Kroll.getProperty(name="authenticator")
+ public AuthenticatorProxy getAuthenticator() {
+ return authenticatorProxy;
+ }
+
@Kroll.getProperty(name = "changesCount")
public int getChangesCount() {
return replicator.getChangesCount();
@@ -113,6 +129,12 @@ public void restart() {
replicator.restart();
}
+ @Kroll.setProperty(name="authenticator")
+ public void setAuthenticator(AuthenticatorProxy authenticatorProxy) {
+ this.authenticatorProxy = authenticatorProxy;
+ replicator.setAuthenticator(authenticatorProxy != null ? authenticatorProxy.getAuthenticator() : null);
+ }
+
@Kroll.setProperty(name = "continuous")
public void setContinuous(boolean continuous) {
replicator.setContinuous(continuous);
@@ -124,7 +146,7 @@ public void setCreateTarget(boolean createTarget) {
}
@Kroll.method
- public void setCredential(@Kroll.argument(optional=true) KrollDict credential) {
+ public void setCredential(@Kroll.argument(optional = true) KrollDict credential) {
if (credential == null) {
replicator.setAuthenticator(null);
}
@@ -167,13 +189,4 @@ public void stop() {
replicator.stop();
}
- @Override
- public void changed(ChangeEvent e) {
- KrollDict params = new KrollDict();
- params.put("source", this);
- params.put("status", replicator.getStatus().ordinal());
-
- fireEvent("status", params);
- }
-
}
\ No newline at end of file
diff --git a/mobile/android/src/com/obscure/titouchdb/TitouchdbModule.java b/mobile/android/src/com/obscure/titouchdb/TitouchdbModule.java
index 3947a66..523b476 100644
--- a/mobile/android/src/com/obscure/titouchdb/TitouchdbModule.java
+++ b/mobile/android/src/com/obscure/titouchdb/TitouchdbModule.java
@@ -22,6 +22,7 @@
import com.couchbase.lite.Query;
import com.couchbase.lite.SavedRevision;
import com.couchbase.lite.Status;
+import com.couchbase.lite.auth.AuthenticatorFactory;
@Kroll.module(name = "Titouchdb", id = "com.obscure.titouchdb")
public class TitouchdbModule extends KrollModule {
@@ -116,11 +117,26 @@ public TitouchdbModule() {
Log.i(LCAT, this.toString() + " loaded");
}
+ @Kroll.method
+ public AuthenticatorProxy createBasicAuthenticator(String username, String password) {
+ return new AuthenticatorProxy(AuthenticatorFactory.createBasicAuthenticator(username, password));
+ }
+
+ @Kroll.method
+ public AuthenticatorProxy createFacebookAuthenticator(String token) {
+ return new AuthenticatorProxy(AuthenticatorFactory.createFacebookAuthenticator(token));
+ }
+
+ @Kroll.method
+ public AuthenticatorProxy createPersonaAuthenticator(String assertion, @Kroll.argument(optional=true) String email) {
+ return new AuthenticatorProxy(AuthenticatorFactory.createPersonaAuthenticator(assertion, email));
+ }
+
@Kroll.getProperty(name = "databaseManager")
public DatabaseManagerProxy getDatabaseManager() {
return this.databaseManagerProxy;
}
-
+
@Override
protected void initActivity(Activity activity) {
super.initActivity(activity);
diff --git a/mobile/ios/.gitignore b/mobile/ios/.gitignore
index 12dd3c2..1549bc6 100644
--- a/mobile/ios/.gitignore
+++ b/mobile/ios/.gitignore
@@ -3,3 +3,5 @@ bin
build
*.zip
example
+documentation
+.*
diff --git a/mobile/ios/Classes/ComObscureTitouchdbModule.m b/mobile/ios/Classes/ComObscureTitouchdbModule.m
index a3fd7c6..4b2e57c 100644
--- a/mobile/ios/Classes/ComObscureTitouchdbModule.m
+++ b/mobile/ios/Classes/ComObscureTitouchdbModule.m
@@ -14,6 +14,7 @@
#import "TiUtils.h"
#import "TiProxy+Errors.h"
#import "TDDatabaseManagerProxy.h"
+#import "TDAuthenticatorProxy.h"
extern BOOL EnableLog(BOOL enable);
@@ -39,7 +40,7 @@ -(NSString*)moduleId {
-(void)startup {
[super startup];
- EnableLog(YES);
+ [CBLManager enableLogging:nil];
NSLog(@"[INFO] %@ loaded", self);
@@ -122,6 +123,33 @@ - (id)stopListener:(id)args {
[self.listener stop];
}
+#pragma mark -
+#pragma mark CBLAuthenticator
+
+- (id)createBasicAuthenticator:(id)args {
+ NSString * user;
+ NSString * pass;
+ ENSURE_ARG_AT_INDEX(user, args, 0, NSString)
+ ENSURE_ARG_AT_INDEX(pass, args, 1, NSString)
+
+ return [TDAuthenticatorProxy proxyWithAuthenticator:[CBLAuthenticator basicAuthenticatorWithName:user password:pass]];
+}
+
+- (id)createFacebookAuthenticator:(id)args {
+ NSString * token;
+ ENSURE_ARG_AT_INDEX(token, args, 0, NSString)
+
+ return [TDAuthenticatorProxy proxyWithAuthenticator:[CBLAuthenticator facebookAuthenticatorWithToken:token]];
+}
+
+- (id)createPersonaAuthenticator:(id)args {
+ NSString * assertion;
+ ENSURE_ARG_AT_INDEX(assertion, args, 0, NSString)
+
+ return [TDAuthenticatorProxy proxyWithAuthenticator:[CBLAuthenticator personaAuthenticatorWithAssertion:assertion]];
+}
+
+
#pragma mark -
#pragma mark Constants
diff --git a/mobile/ios/Classes/TDAuthenticatorProxy.h b/mobile/ios/Classes/TDAuthenticatorProxy.h
new file mode 100644
index 0000000..0199916
--- /dev/null
+++ b/mobile/ios/Classes/TDAuthenticatorProxy.h
@@ -0,0 +1,15 @@
+//
+// TDAuthenticatorProxy.h
+// titouchdb
+//
+// Created by Paul Mietz Egli on 7/14/14.
+//
+//
+
+#import "TiProxy.h"
+#import "CBLAuthenticator.h"
+
+@interface TDAuthenticatorProxy : TiProxy
+@property (nonatomic, strong) id authenticator;
++ (instancetype)proxyWithAuthenticator:(id)authenticator;
+@end
diff --git a/mobile/ios/Classes/TDAuthenticatorProxy.m b/mobile/ios/Classes/TDAuthenticatorProxy.m
new file mode 100644
index 0000000..ef76efe
--- /dev/null
+++ b/mobile/ios/Classes/TDAuthenticatorProxy.m
@@ -0,0 +1,24 @@
+//
+// TDAuthenticatorProxy.m
+// titouchdb
+//
+// Created by Paul Mietz Egli on 7/14/14.
+//
+//
+
+#import "TDAuthenticatorProxy.h"
+
+@implementation TDAuthenticatorProxy
+
++ (instancetype)proxyWithAuthenticator:(id)authenticator {
+ return [[TDAuthenticatorProxy alloc] initWithAuthenticator:authenticator];
+}
+
+- (id)initWithAuthenticator:(id)authenticator {
+ if (self = [super init]) {
+ self.authenticator = authenticator;
+ }
+ return self;
+}
+
+@end
diff --git a/mobile/ios/Classes/TDDatabaseManagerProxy.m b/mobile/ios/Classes/TDDatabaseManagerProxy.m
index 42ff21a..e092383 100644
--- a/mobile/ios/Classes/TDDatabaseManagerProxy.m
+++ b/mobile/ios/Classes/TDDatabaseManagerProxy.m
@@ -182,4 +182,13 @@ - (id)error {
return self.lastError ? [self errorDict:self.lastError] : nil;
}
+/** specify whether the CouchbaseLite directory should be backed up or not */
+- (void)setExcludedFromBackup:(id)value {
+ [self.databaseManager setExcludedFromBackup:[value boolValue]];
+}
+
+- (id)excludedFromBackup {
+ return NUMBOOL(self.databaseManager.excludedFromBackup);
+}
+
@end
diff --git a/mobile/ios/Classes/TDDatabaseProxy.m b/mobile/ios/Classes/TDDatabaseProxy.m
index 35447aa..3a833c2 100644
--- a/mobile/ios/Classes/TDDatabaseProxy.m
+++ b/mobile/ios/Classes/TDDatabaseProxy.m
@@ -189,7 +189,7 @@ - (id)createAllDocumentsQuery:(id)args {
return [TDQueryProxy proxyWithDatabase:self query:query];
}
-- (id)slowQueryWithMap:(id)args {
+- (id)createSlowQuery:(id)args {
KrollCallback * callback;
ENSURE_ARG_AT_INDEX(callback, args, 0, KrollCallback);
diff --git a/mobile/ios/Classes/TDQueryProxy.m b/mobile/ios/Classes/TDQueryProxy.m
index efa1e0b..a9c0377 100644
--- a/mobile/ios/Classes/TDQueryProxy.m
+++ b/mobile/ios/Classes/TDQueryProxy.m
@@ -46,6 +46,11 @@ - (id)initWithExecutionContext:(id)context CBLQuery:(CBLQuery *)que
return self;
}
+- (void)dealloc {
+ self.query = nil;
+ [super dealloc];
+}
+
#pragma mark Properties
- (id)limit {
@@ -187,6 +192,11 @@ - (id)initWithQuery:(TDQueryProxy *) query queryEnumerator:(CBLQueryEnumerator *
return self;
}
+- (void)dealloc {
+ self.enumerator = nil;
+ [super dealloc];
+}
+
- (id)count {
return NUMLONG(self.enumerator.count);
}
@@ -238,6 +248,11 @@ - (id)initWithQueryEnumerator:(TDQueryEnumeratorProxy *)enumerator queryRow:(CBL
return self;
}
+- (void)dealloc {
+ self.row = nil;
+ [super dealloc];
+}
+
- (id)database {
return self.queryEnumerator.query.database;
}
diff --git a/mobile/ios/Classes/TDReplicationProxy.m b/mobile/ios/Classes/TDReplicationProxy.m
index 3482212..d7853f8 100644
--- a/mobile/ios/Classes/TDReplicationProxy.m
+++ b/mobile/ios/Classes/TDReplicationProxy.m
@@ -8,6 +8,7 @@
#import "TDReplicationProxy.h"
#import "TDDatabaseProxy.h"
+#import "TDAuthenticatorProxy.h"
#import "TiProxy+Errors.h"
extern NSString * CBL_ReplicatorProgressChangedNotification;
@@ -16,6 +17,7 @@
@interface TDReplicationProxy ()
@property (nonatomic, assign) TDDatabaseProxy * database;
@property (nonatomic, strong) CBLReplication * replication;
+@property (nonatomic, strong) TDAuthenticatorProxy * authenticatorProxy;
- (void)startObservingReplication:(CBLReplication*)repl;
- (void)stopObservingReplication:(CBLReplication*)repl;
@end
@@ -120,6 +122,15 @@ - (void)setNetwork:(id)value {
#pragma mark Authentication
+- (void)setAuthenticator:(id)value {
+ self.authenticatorProxy = value;
+ self.replication.authenticator = self.authenticatorProxy.authenticator;
+}
+
+- (id)authenticator {
+ return self.authenticatorProxy;
+}
+
- (void)setCredential:(id)value {
ENSURE_DICT(value)
NSDictionary * cred = value;
@@ -163,34 +174,28 @@ - (id)status {
#pragma mark Notifications
#define kReplicationChangedEventName @"change"
-#define kReplicationStoppedEventName @"status"
- (void)startObservingReplication:(CBLReplication*)repl {
[repl addObserver:self forKeyPath:@"completedChangesCount" options:0 context:NULL];
[repl addObserver:self forKeyPath:@"changesCount" options:0 context:NULL];
[repl addObserver:self forKeyPath:@"status" options:0 context:NULL];
+ [repl addObserver:self forKeyPath:@"running" options:0 context:NULL];
+ [repl addObserver:self forKeyPath:@"lastError" options:0 context:NULL];
}
- (void)stopObservingReplication:(CBLReplication*)repl {
[repl removeObserver:self forKeyPath:@"completedChangesCount"];
[repl removeObserver:self forKeyPath:@"changesCount"];
[repl removeObserver:self forKeyPath:@"status"];
+ [repl removeObserver:self forKeyPath:@"running"];
+ [repl removeObserver:self forKeyPath:@"lastError"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
CBLReplication * repl = (CBLReplication *)object;
- if ([@"status" isEqualToString:keyPath]) {
- // fire status event
- TiThreadPerformOnMainThread(^{
- [self fireEvent:kReplicationStoppedEventName withObject:@{@"status":NUMINT(repl.status)} propagate:YES];
- }, NO);
- }
- else {
- // fire change event
- TiThreadPerformOnMainThread(^{
- [self fireEvent:kReplicationChangedEventName withObject:nil propagate:YES];
- }, NO);
- }
+ TiThreadPerformOnMainThread(^{
+ [self fireEvent:kReplicationChangedEventName withObject:@{@"property": keyPath, @"source": self} propagate:YES];
+ }, NO);
}
@end
diff --git a/mobile/ios/Classes/TiProxy+Errors.m b/mobile/ios/Classes/TiProxy+Errors.m
index be75a1f..71d88cf 100644
--- a/mobile/ios/Classes/TiProxy+Errors.m
+++ b/mobile/ios/Classes/TiProxy+Errors.m
@@ -10,15 +10,15 @@
@implementation TiProxy (Errors)
-- (NSDictionary *)errorDict:(NSError *)error {
- NSMutableDictionary * result = nil;
+- (id)errorDict:(NSError *)error {
+ NSDictionary * result = nil;
if (error) {
- result = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- NUMBOOL(YES), @"error",
- NUMINT(error.code), @"code",
- error.domain, @"domain",
- error.localizedDescription, @"description",
- nil];
+ result = @{
+ @"error": NUMBOOL(YES),
+ @"code": NUMLONG(error.code),
+ @"domain": error.domain,
+ @"description": error.localizedDescription
+ };
/*
// who knows what evil lurks in the hearts of error.userInfo?
// whatever it is, it can't be serialized to javascript...
@@ -27,12 +27,7 @@ - (NSDictionary *)errorDict:(NSError *)error {
}
*/
}
- /*
- else {
- result = [NSMutableDictionary dictionaryWithObject:NUMBOOL(NO) forKey:@"error"];
- }
- */
- return result;
+ return result ? [result autorelease] : [NSNull null];
}
@end
diff --git a/mobile/ios/dist/com.obscure.titouchdb-iphone-1.0.zip b/mobile/ios/dist/com.obscure.titouchdb-iphone-1.0.zip
deleted file mode 100644
index de284c7..0000000
Binary files a/mobile/ios/dist/com.obscure.titouchdb-iphone-1.0.zip and /dev/null differ
diff --git a/mobile/ios/manifest b/mobile/ios/manifest
index e05898d..4d9fbc5 100644
--- a/mobile/ios/manifest
+++ b/mobile/ios/manifest
@@ -2,7 +2,7 @@
# this is your module manifest and used by Titanium
# during compilation, packaging, distribution, etc.
#
-version: 1.0.2
+version: 1.1.0
apiversion: 2
description: Titanium wrapper for Couchbase Mobile
author: Paul Mietz Egli
diff --git a/mobile/ios/titouchdb.xcodeproj/project.pbxproj b/mobile/ios/titouchdb.xcodeproj/project.pbxproj
index d7d41bd..d4f41b1 100644
--- a/mobile/ios/titouchdb.xcodeproj/project.pbxproj
+++ b/mobile/ios/titouchdb.xcodeproj/project.pbxproj
@@ -46,6 +46,8 @@
DAA36892167F9BB6004A514D /* TDReplicationProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4919431676B78A00ECCB83 /* TDReplicationProxy.m */; };
DAA36893167F9BB6004A514D /* TDAttachmentProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DABFA05116779B4100A58019 /* TDAttachmentProxy.m */; };
DABFA05216779B4200A58019 /* TDAttachmentProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DABFA05016779B4100A58019 /* TDAttachmentProxy.h */; };
+ DAD03A8A197449FA00886829 /* TDAuthenticatorProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DAD03A88197449FA00886829 /* TDAuthenticatorProxy.h */; };
+ DAD03A8B197449FA00886829 /* TDAuthenticatorProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD03A89197449FA00886829 /* TDAuthenticatorProxy.m */; };
DAE72A37152E66C900622013 /* TiProxy+Errors.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE72A35152E66C800622013 /* TiProxy+Errors.h */; };
DAEE1D1A1932850500ABEF1D /* libCouchbaseLite.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA77FA4B1932849500D85AEE /* libCouchbaseLite.a */; };
DAEE1D1B1932850500ABEF1D /* libCouchbaseLiteListener.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA77FA571932849500D85AEE /* libCouchbaseLiteListener.a */; };
@@ -192,6 +194,8 @@
DA77FA351932849400D85AEE /* CouchbaseLite.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CouchbaseLite.xcodeproj; path = "vendor/couchbase-lite-ios/CouchbaseLite.xcodeproj"; sourceTree = ""; };
DABFA05016779B4100A58019 /* TDAttachmentProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TDAttachmentProxy.h; path = Classes/TDAttachmentProxy.h; sourceTree = ""; };
DABFA05116779B4100A58019 /* TDAttachmentProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TDAttachmentProxy.m; path = Classes/TDAttachmentProxy.m; sourceTree = ""; };
+ DAD03A88197449FA00886829 /* TDAuthenticatorProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TDAuthenticatorProxy.h; path = Classes/TDAuthenticatorProxy.h; sourceTree = ""; };
+ DAD03A89197449FA00886829 /* TDAuthenticatorProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TDAuthenticatorProxy.m; path = Classes/TDAuthenticatorProxy.m; sourceTree = ""; };
DAE72A35152E66C800622013 /* TiProxy+Errors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "TiProxy+Errors.h"; path = "Classes/TiProxy+Errors.h"; sourceTree = ""; };
DAE72A36152E66C800622013 /* TiProxy+Errors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "TiProxy+Errors.m"; path = "Classes/TiProxy+Errors.m"; sourceTree = ""; };
DAF6E66D1680F84D003217C3 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
@@ -279,6 +283,8 @@
DA4919431676B78A00ECCB83 /* TDReplicationProxy.m */,
DABFA05016779B4100A58019 /* TDAttachmentProxy.h */,
DABFA05116779B4100A58019 /* TDAttachmentProxy.m */,
+ DAD03A88197449FA00886829 /* TDAuthenticatorProxy.h */,
+ DAD03A89197449FA00886829 /* TDAuthenticatorProxy.m */,
);
name = Classes;
sourceTree = "";
@@ -329,6 +335,7 @@
DA49193C1676ADF100ECCB83 /* TDViewProxy.h in Headers */,
DA4919401676B32800ECCB83 /* TDRevisionProxy.h in Headers */,
DA4919441676B78B00ECCB83 /* TDReplicationProxy.h in Headers */,
+ DAD03A8A197449FA00886829 /* TDAuthenticatorProxy.h in Headers */,
DABFA05216779B4200A58019 /* TDAttachmentProxy.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -505,6 +512,7 @@
DAA3688D167F9BB6004A514D /* TDDocumentProxy.m in Sources */,
DAA3688E167F9BB6004A514D /* TDQueryProxy.m in Sources */,
DAA3688F167F9BB6004A514D /* TDBridge.m in Sources */,
+ DAD03A8B197449FA00886829 /* TDAuthenticatorProxy.m in Sources */,
DAA36890167F9BB6004A514D /* TDViewProxy.m in Sources */,
DAA36891167F9BB6004A514D /* TDRevisionProxy.m in Sources */,
DAA36892167F9BB6004A514D /* TDReplicationProxy.m in Sources */,
diff --git a/mobile/ios/vendor/couchbase-lite-ios b/mobile/ios/vendor/couchbase-lite-ios
index 2d3a97d..b3c92e2 160000
--- a/mobile/ios/vendor/couchbase-lite-ios
+++ b/mobile/ios/vendor/couchbase-lite-ios
@@ -1 +1 @@
-Subproject commit 2d3a97dd4a2881a81cc6d46bd75e8f0646ea32f8
+Subproject commit b3c92e25d0e883d72d7144c15642a531f39e526d
diff --git a/mobile/noarch/alloy/sync/titouchdb.js b/mobile/noarch/alloy/sync/titouchdb.js
index c4fa773..438fb21 100755
--- a/mobile/noarch/alloy/sync/titouchdb.js
+++ b/mobile/noarch/alloy/sync/titouchdb.js
@@ -3,6 +3,7 @@
*/
var _ = require('alloy/underscore'),
+ moment = require('alloy/moment'),
titouchdb = require('com.obscure.titouchdb'),
manager = titouchdb.databaseManager,
db;
@@ -26,7 +27,7 @@ function query_view(db, name, options) {
}
return null;
}
-
+
if (_.isBoolean(opts.prefetch)) { query.prefetch = opts.prefetch; }
if (_.isFinite(opts.limit)) { query.limit = opts.limit; }
if (_.isFinite(opts.skip)) { query.skip = opts.skip; }
@@ -65,10 +66,11 @@ function InitAdapter(config) {
db = manager.getDatabase(config.adapter.dbname);
// register views
- _.each(config.adapter.views, function(view) {
- var v = db.getView(view.name);
- v.setMapReduce(view.map, view.reduce, view.version || '1');
- Ti.API.info("defined "+view.name);
+ _.each(_.keys(config.adapter.views), function(name) {
+ var def = config.adapter.views[name];
+ var v = db.getView(name);
+ v.setMapReduce(def.map, def.reduce, def.version || '1');
+ Ti.API.info("defined "+name);
});
return {};
@@ -82,7 +84,10 @@ function Sync(method, model, options) {
switch (method) {
case 'create':
var props = model.toJSON();
- _.extend(props, model.config.adapter.static_properties || {});
+ props = _.defaults(props,
+ model.config.adapter.static_properties || {},
+ model.config.adapter.modelname ? { modelname: model.config.adapter.modelname } : {}
+ );
var doc = model.id ? db.getDocument(model.id) : db.createDocument();
doc.putProperties(props);
err = doc.error;
@@ -111,12 +116,16 @@ function Sync(method, model, options) {
var collection = model; // just to clear things up
// collection
- var view = opts.view || collection.config.adapter.views[0]["name"];
+ var view = collection.config.adapter.views[opts.view];
+ if (!view) {
+ err = "missing view named " + opts.view;
+ break;
+ }
collection.view = view;
// add default view options from model
- opts = _.defaults(opts, collection.config.adapter.view_options);
- var query = query_view(db, view, opts);
+ opts = _.defaults(opts, view.query_options || {}, collection.config.adapter.default_query_options);
+ var query = query_view(db, opts.view, opts);
if (!query) {
err = { error: 'missing view' };
break;
@@ -144,7 +153,10 @@ function Sync(method, model, options) {
case 'update':
var props = model.toJSON();
- _.extend(props, model.config.adapter.static_properties || {});
+ props = _.defaults(props,
+ model.config.adapter.static_properties || {},
+ model.config.adapter.modelname ? { modelname: model.config.adapter.modelname } : {}
+ );
var doc = db.getDocument(model.id);
doc.putProperties(props);
err = doc.error;
@@ -180,21 +192,139 @@ function Sync(method, model, options) {
module.exports.sync = Sync;
-module.exports.beforeModelCreate = function(config) {
- config = config || {};
+// MIGRATIONS
+
+var migration_doc_name = 'titouchdb_migrations';
+
+function Migrator(config) {
+ this.dbname = config.adapter.db_name;
+ this.idAttribute = config.adapter.idAttribute;
+ this.database = db; // TODO ensure db is set?
- InitAdapter(config);
+ // ensure that the properties defined in the model config are set
+ this.createModel = function(props) {
+ var doc = props.id ? this.database.getDocument(props.id) : this.database.createDocument();
+ props = _.defaults(props,
+ config.adapter.static_properties || {},
+ config.adapter.modelname ? { modelname: config.adapter.modelname } : {}
+ );
+ doc.putProperties(props);
+ };
+}
+
+// Gets the current saved migration
+function GetLatestMigration(database) {
+ var mdoc = database.getExistingDocument(migration_doc_name);
+ return mdoc ? mdoc.userProperties.id : null;
+}
+
+function Migrate(Model) {
+ // get list of migrations for this model
+ var migrations = Model.migrations || [];
+
+ // get a reference to the last migration
+ var lastMigration = {};
+ if (migrations.length) { migrations[migrations.length-1](lastMigration); }
+
+ // Get config reference
+ var config = Model.prototype.config;
+
+ // Set up the migration obejct
+ var migrator = new Migrator(config);
+
+ // Get the migration number from the config, or use the number of
+ // the last migration if it's not present. If we still don't have a
+ // migration number after that, that means there are none. There's
+ // no migrations to perform.
+ var targetNumber = typeof config.adapter.migration === 'undefined' ||
+ config.adapter.migration === null ? lastMigration.id : config.adapter.migration;
+ if (typeof targetNumber === 'undefined' || targetNumber === null) {
+ return;
+ }
+ targetNumber = targetNumber + ''; // ensure that it's a string
+
+ // Get the current saved migration number.
+ var currentNumber = GetLatestMigration(db);
+
+ // If the current and requested migrations match, the data structures
+ // match and there is no need to run the migrations.
+ var direction;
+ if (currentNumber === targetNumber) {
+ return;
+ } else if (currentNumber && currentNumber > targetNumber) {
+ direction = 0; // rollback
+ migrations.reverse();
+ } else {
+ direction = 1; // upgrade
+ }
+
+ migrator.database = db;
+
+ // iterate through all migrations based on the current and requested state,
+ // applying all appropriate migrations, in order, to the database.
+ var lastContext;
+ if (migrations.length) {
+ for (var i = 0; i < migrations.length; i++) {
+ // create the migration context
+ var migration = migrations[i];
+ var context = {};
+ migration(context);
+ // if upgrading, skip migrations higher than the target
+ // if rolling back, skip migrations lower than the target
+ if (direction) {
+ if (context.id > targetNumber) { break; }
+ if (context.id <= currentNumber) { continue; }
+ } else {
+ if (context.id <= targetNumber) { break; }
+ if (context.id > currentNumber) { continue; }
+ }
+
+ // execute the appropriate migration function
+ var funcName = direction ? 'up' : 'down';
+ if (_.isFunction(context[funcName])) {
+ context[funcName](migrator);
+ lastContext = context;
+ }
+ }
+ }
+
+ // insert a doc to track this migration
+ if (lastContext) {
+ var mdoc = db.getDocument(migration_doc_name);
+ mdoc.putProperties({
+ id: lastContext.id,
+ name: lastContext.name,
+ applied: moment().unix()
+ });
+ }
+
+ migrator.db = null;
+}
+
+// EXPORTED FUNCTIONS
+
+var cache = {
+ config: {},
+ Model: {}
+};
+
+module.exports.beforeModelCreate = function(config, name) {
+ if (cache.config[name]) return cache.config[name];
+ config = config || {};
+ InitAdapter(config);
+ cache.config[name] = config;
return config;
};
-module.exports.afterModelCreate = function(Model) {
- Model = Model || {};
+module.exports.afterModelCreate = function(Model, name) {
+ if (cache.Model[name]) {
+ return cache.Model[name];
+ }
+ Model = Model || {};
Model.prototype.idAttribute = '_id'; // true for all TouchDB documents
- Model.prototype.config.Model = Model; // needed for fetch operations to initialize the collection from persistent store
- Model.prototype.database = db;
-
+
Model.prototype.attachmentNamed = function(name) {
var doc = db.getDocument(this.id);
if (doc) {
@@ -225,6 +355,19 @@ module.exports.afterModelCreate = function(Model) {
return doc ? doc.currentRevision.attachmentNames : [];
};
+ Migrate(Model);
+
+ cache.Model[name] = Model;
+
return Model;
};
+/*
+module.exports.afterCollectionCreate = function(Collection) {
+ Collection = Collection || {};
+
+ Collection.prototype.database = db;
+
+ return Collection;
+};
+*/
\ No newline at end of file
diff --git a/mobile/noarch/documentation/changes.md b/mobile/noarch/documentation/changes.md
index 74ffeb6..4f3c64c 100644
--- a/mobile/noarch/documentation/changes.md
+++ b/mobile/noarch/documentation/changes.md
@@ -1,3 +1,18 @@
+2014-08-26
+
+* Updated to Couchbase Mobile 1.0.2 release for Android and iOS
+* Fixed memory leak of CBL query objects (issue 80)
+* Modified the Alloy sync adapter configuration to allow specifying
+ query properties with the view definition.
+* Added data model migration support to the sync adapter.
+
+
+2014-07-30
+
+### Database
+
+* added `createSlowQuery(mapfn)` method.
+
2014-06-10
Initial release of module based on [couchbase-lite-ios](https://github.com/couchbase/couchbase-lite-ios)
diff --git a/mobile/noarch/documentation/index.md b/mobile/noarch/documentation/index.md
index aa5f345..591182b 100644
--- a/mobile/noarch/documentation/index.md
+++ b/mobile/noarch/documentation/index.md
@@ -216,6 +216,14 @@ object. The returned object can be customized prior to the start of replication
Set up a one-time replication from this database to a remote target database and return a [`replication`](#replication)
object. The returned object can be customized prior to the start of replication.
+**createSlowQuery**(map)
+
+* map (function(document)): the map function used to create the query
+
+Create a new [`query`](#query) object with the provided map function. The query index will be created once
+when the query is run but not persisted. This function is best used for development only or in cases where
+creating a persistent view is not desirable.
+
**deleteDatabase**()
Permanently delete this database and all of its documents.
diff --git a/mobile/noarch/example/.gitignore b/mobile/noarch/example/.gitignore
index ce392ae..bfdbfc5 100644
--- a/mobile/noarch/example/.gitignore
+++ b/mobile/noarch/example/.gitignore
@@ -1,3 +1,3 @@
-replication_config.json
+replication_config.json*
LiteServ/bin/*
LiteServ/Frameworks/*
diff --git a/mobile/noarch/example/001_module.js b/mobile/noarch/example/001_module.js
index 059cc9d..d0bf566 100644
--- a/mobile/noarch/example/001_module.js
+++ b/mobile/noarch/example/001_module.js
@@ -3,8 +3,9 @@ require('ti-mocha');
var should = require('should');
module.exports = function() {
+ var titouchdb = require('com.obscure.titouchdb');
+
describe('module', function() {
- var titouchdb = require('com.obscure.titouchdb');
it('must exist', function() {
should.exist(titouchdb);
@@ -36,4 +37,17 @@ module.exports = function() {
});
+ describe('module (authenticators)', function() {
+ it('must have a createBasicAuthenticator method', function() {
+ should(titouchdb.createBasicAuthenticator).be.a.Function;
+ });
+
+ it('must have a createFacebookAuthenticator method', function() {
+ should(titouchdb.createFacebookAuthenticator).be.a.Function;
+ });
+
+ it('must have a createPersonaAuthenticator method', function() {
+ should(titouchdb.createPersonaAuthenticator).be.a.Function;
+ });
+ });
};
\ No newline at end of file
diff --git a/mobile/noarch/example/002_databaseManager.js b/mobile/noarch/example/002_databaseManager.js
index 273b1a9..fa1cd5e 100644
--- a/mobile/noarch/example/002_databaseManager.js
+++ b/mobile/noarch/example/002_databaseManager.js
@@ -65,7 +65,7 @@ module.exports = function() {
should(manager.getExistingDatabase).be.a.Function;
var db = manager.getExistingDatabase('test_does_not_exist');
should.not.exist(db);
- should(manager.error).be.an.Object
+ should(manager.error).be.an.Object;
});
it('must return a previously created database', function() {
diff --git a/mobile/noarch/example/004_all_documents_query.js b/mobile/noarch/example/004_all_documents_query.js
index b164291..2119aa6 100644
--- a/mobile/noarch/example/004_all_documents_query.js
+++ b/mobile/noarch/example/004_all_documents_query.js
@@ -56,4 +56,27 @@ module.exports = function() {
});
+ describe('database (slow query)', function() {
+ var db;
+
+ before(function() {
+ utils.delete_nonsystem_databases(manager);
+ db = utils.install_elements_database(manager);
+ });
+
+ it('must run a slow query', function() {
+ var q = db.createSlowQuery(function(doc) {
+ if (['Pd', 'Ag', 'Pt', 'Au'].indexOf(doc.symbol) !== -1) {
+ emit(doc.symbol, null);
+ }
+ });
+
+ var e = q.run();
+ e.count.should.eql(4);
+ e.getRow(0).key.should.eql('Ag');
+ e.getRow(1).key.should.eql('Au');
+ e.getRow(2).key.should.eql('Pd');
+ e.getRow(3).key.should.eql('Pt');
+ });
+ });
};
diff --git a/mobile/noarch/example/005_database_validation.js b/mobile/noarch/example/005_database_validation.js
index aeb9248..8dded03 100644
--- a/mobile/noarch/example/005_database_validation.js
+++ b/mobile/noarch/example/005_database_validation.js
@@ -13,7 +13,7 @@ module.exports = function() {
var dummy_fn = function() {};
before(function() {
- utils.delete_nonsystem_databases(manager)
+ utils.delete_nonsystem_databases(manager);
db = manager.getDatabase('test005_validation');
db.setValidation('require_tag', function(rev, context) {
if (rev.properties.tag == null) {
@@ -66,7 +66,7 @@ module.exports = function() {
it('must allow validation functions that call other functions', function() {
var is_int = function(n, v) {
return n != null && v != null && !isNaN(parseInt(v));
- }
+ };
db.setValidation('tag_must_be_int', function(rev, context) {
if (!is_int('tag', rev.properties.tag)) {
diff --git a/mobile/noarch/example/008_attachments.js b/mobile/noarch/example/008_attachments.js
index fb54f25..6e453d1 100644
--- a/mobile/noarch/example/008_attachments.js
+++ b/mobile/noarch/example/008_attachments.js
@@ -53,4 +53,24 @@ module.exports = function() {
});
});
+
+ // properties and methods that are not part of the common API
+ describe('attachment (extended)', function() {
+ var db, doc, att;
+
+ before(function() {
+ utils.delete_nonsystem_databases(manager);
+ db = utils.install_elements_database(manager);
+
+ doc = db.getExistingDocument('Bi');
+ att = doc.currentRevision.getAttachment('image.jpg');
+ });
+
+ it('must have a contentURL property', function() {
+ should(att).have.property('contentURL');
+ var f = Ti.Filesystem.getFile(att.contentURL);
+ should.exist(f);
+ f.exists().should.be.ok;
+ });
+ });
};
diff --git a/mobile/noarch/example/011_query_enumerator.js b/mobile/noarch/example/011_query_enumerator.js
index b73e594..dfc9e99 100644
--- a/mobile/noarch/example/011_query_enumerator.js
+++ b/mobile/noarch/example/011_query_enumerator.js
@@ -109,6 +109,6 @@ module.exports = function() {
e.reset();
var r3 = e.next();
r3.key.should.eql(0);
- })
+ });
});
};
\ No newline at end of file
diff --git a/mobile/noarch/example/013_replication.js b/mobile/noarch/example/013_replication.js
index bee46e9..e0a2408 100644
--- a/mobile/noarch/example/013_replication.js
+++ b/mobile/noarch/example/013_replication.js
@@ -90,15 +90,15 @@ module.exports = function() {
it.skip('must have a addChangeListener method', function() {
should(repl.addChangeListener).be.a.Function;
- })
+ });
it.skip('must have a removeChangeListener method', function() {
should(repl.removeChangeListener).be.a.Function;
- })
+ });
it('must have a restart method', function() {
should(repl.restart).be.a.Function;
- })
+ });
it('must have a setCredential method', function() {
should(repl.setCredential).be.a.Function;
@@ -106,11 +106,11 @@ module.exports = function() {
it('must have a start method', function() {
should(repl.start).be.a.Function;
- })
+ });
it('must have a stop method', function() {
should(repl.stop).be.a.Function;
- })
+ });
});
@@ -126,9 +126,11 @@ module.exports = function() {
it('must replicate an entire db', function(done) {
this.timeout(10000);
var db = manager.getDatabase('repl1');
+ var hasStopped = false;
repl = db.createPullReplication('http://'+conf.host+':'+conf.port+'/'+conf.dbname);
- repl.addEventListener('status', function(e) {
- if (e.status == titouchdb.REPLICATION_MODE_STOPPED) {
+ repl.addEventListener('change', function(e) {
+ if (!hasStopped && e.source.status == titouchdb.REPLICATION_MODE_STOPPED) {
+ hasStopped = true;
should.not.exist(repl.lastError);
db.documentCount.should.eql(118);
repl.isRunning.should.eql(false);
@@ -152,10 +154,12 @@ module.exports = function() {
it('must replicate an entire db', function(done) {
this.timeout(10000);
var dbname = "repl2_" + Ti.Platform.createUUID().substring(0, 8).toLowerCase();
+ var hasStopped = false;
repl = db.createPushReplication('http://'+conf.host+':'+conf.port+'/'+dbname);
repl.createTarget = true;
- repl.addEventListener('status', function(e) {
- if (e.status == titouchdb.REPLICATION_MODE_STOPPED) {
+ repl.addEventListener('change', function(e) {
+ if (!hasStopped && e.source.status == titouchdb.REPLICATION_MODE_STOPPED) {
+ hasStopped = true;
should.not.exist(repl.lastError);
repl.completedChangesCount.should.eql(12);
repl.isRunning.should.eql(false);
@@ -175,13 +179,43 @@ module.exports = function() {
});
// currently returning a 400 error due to a request for /elements/_session
- it.skip('must replicate with credentials', function(done) {
+ it('must replicate with credentials', function(done) {
this.timeout(10000);
var db = manager.getDatabase('repl3');
+ var hasStopped = false;
repl = db.createPullReplication('http://'+conf.host+':'+conf.port+'/'+conf.dbname);
repl.setCredential({ user: 'scott', pass: 'tiger' });
- repl.addEventListener('status', function(e) {
- if (e.status == titouchdb.REPLICATION_MODE_STOPPED) {
+ repl.addEventListener('change', function(e) {
+ if (!hasStopped && e.source.status == titouchdb.REPLICATION_MODE_STOPPED) {
+ hasStopped = true;
+ should.not.exist(repl.lastError);
+ db.documentCount.should.eql(118);
+ repl.isRunning.should.eql(false);
+ done();
+ }
+ });
+ repl.start();
+ });
+ });
+
+ describe('pull replication with authenticator', function() {
+ var conf, repl;
+
+ before(function(done) {
+ utils.delete_nonsystem_databases(manager);
+ conf = utils.verify_couchdb_server('replication_config.json', done);
+ });
+
+ // currently returning a 400 error due to a request for /elements/_session
+ it('must replicate with a basic authenticator', function(done) {
+ this.timeout(10000);
+ var db = manager.getDatabase('repl4');
+ var hasStopped = false;
+ repl = db.createPullReplication('http://'+conf.host+':'+conf.port+'/'+conf.dbname);
+ repl.authenticator = titouchdb.createBasicAuthenticator('scott', 'tiger');
+ repl.addEventListener('change', function(e) {
+ if (!hasStopped && e.source.status == titouchdb.REPLICATION_MODE_STOPPED) {
+ hasStopped = true;
should.not.exist(repl.lastError);
db.documentCount.should.eql(118);
repl.isRunning.should.eql(false);
diff --git a/mobile/noarch/example/014_filtered_replication.js b/mobile/noarch/example/014_filtered_replication.js
new file mode 100644
index 0000000..549c829
--- /dev/null
+++ b/mobile/noarch/example/014_filtered_replication.js
@@ -0,0 +1,60 @@
+require('ti-mocha');
+
+var should = require('should');
+var utils = require('test_utils');
+
+module.exports = function() {
+ var titouchdb = require('com.obscure.titouchdb'),
+ manager = titouchdb.databaseManager;
+
+ describe('filtered replication (push)', function() {
+ var conf, repl, db;
+
+ before(function(done) {
+ utils.delete_nonsystem_databases(manager);
+ db = utils.install_elements_database(manager);
+ conf = utils.verify_couchdb_server('replication_config.json', done);
+ });
+
+ it('must push with a filter', function(done) {
+ this.timeout(10000);
+
+ var target_url = 'http://'+conf.host+':'+conf.port+'/noble_gases';
+
+ db.setFilter('noble_gases', function(doc, req) {
+ return [2, 10, 18, 36, 54, 86].indexOf(doc.atomic_number) != -1;
+ });
+
+ repl = db.createPushReplication(target_url);
+ repl.filter = 'noble_gases';
+ repl.createTarget = true;
+
+ var hasStopped = false;
+ repl.addEventListener('change', function(e) {
+ if (!hasStopped && e.source.status == titouchdb.REPLICATION_MODE_STOPPED) {
+ hasStopped = true;
+ should.not.exist(repl.lastError);
+ repl.isRunning.should.eql(false);
+
+ var client = Ti.Network.createHTTPClient({
+ onload: function(e) {
+ var resp = JSON.parse(this.responseText);
+ should.exist(resp);
+ should.exist(resp.doc_count);
+ resp.doc_count.should.eql(6);
+ done();
+ },
+ onerror: function(e) {
+ throw e;
+ }
+ });
+ client.open("GET", target_url);
+ client.send();
+ }
+ });
+ repl.start();
+ });
+
+ });
+
+};
\ No newline at end of file
diff --git a/mobile/noarch/example/app.js b/mobile/noarch/example/app.js
index 687260f..afea2ff 100644
--- a/mobile/noarch/example/app.js
+++ b/mobile/noarch/example/app.js
@@ -18,6 +18,7 @@ require('010_queries')();
require('011_query_enumerator')();
require('012_query_row')();
require('013_replication')();
+require('014_filtered_replication')();
// create a window and run the tests
var window = Ti.UI.createWindow({
diff --git a/samples/CannedDatabase/Resources/app.js b/samples/CannedDatabase/Resources/app.js
index 956a084..fcf2592 100644
--- a/samples/CannedDatabase/Resources/app.js
+++ b/samples/CannedDatabase/Resources/app.js
@@ -11,14 +11,14 @@ var TiTouchDB = require('com.obscure.titouchdb');
* technique would not be suitable for large datasets.
*/
function generateDB() {
- var db = TiTouchDB.databaseManager.createDatabaseNamed('elements');
+ var db = TiTouchDB.databaseManager.getDatabase('elements');
var elements = [[1, "Hydrogen", "H"], [2, "Helium", "He"], [3, "Lithium", "Li"], [4, "Beryllium", "Be"], [5, "Boron", "B"], [6, "Carbon", "C"], [7, "Nitrogen", "N"], [8, "Oxygen", "O"], [9, "Fluorine", "F"], [10, "Neon", "Ne"], [11, "Sodium", "Na"], [12, "Magnesium", "Mg"], [13, "Aluminium", "Al", "http://0.tqn.com/d/chemistry/1/6/q/V/1/Aluminium.jpg"], [14, "Silicon", "Si"], [15, "Phosphorus", "P"], [16, "Sulfur", "S"], [17, "Chlorine", "Cl"], [18, "Argon", "Ar"], [19, "Potassium", "K"], [20, "Calcium", "Ca"], [21, "Scandium", "Sc"], [22, "Titanium", "Ti"], [23, "Vanadium", "V"], [24, "Chromium", "Cr"], [25, "Manganese", "Mn"], [26, "Iron", "Fe"], [27, "Cobalt", "Co", "http://0.tqn.com/d/chemistry/1/6/I/Z/1/cobalt.jpg"], [28, "Nickel", "Ni"], [29, "Copper", "Cu"], [30, "Zinc", "Zn"], [31, "Gallium", "Ga", "http://0.tqn.com/d/chemistry/1/6/H/Q/gallium.jpg"], [32, "Germanium", "Ge"], [33, "Arsenic", "As"], [34, "Selenium", "Se"], [35, "Bromine", "Br"], [36, "Krypton", "Kr"], [37, "Rubidium", "Rb"], [38, "Strontium", "Sr"], [39, "Yttrium", "Y"], [40, "Zirconium", "Zr"], [41, "Niobium", "Nb"], [42, "Molybdenum", "Mo"], [43, "Technetium", "Tc"], [44, "Ruthenium", "Ru"], [45, "Rhodium", "Rh"], [46, "Palladium", "Pd"], [47, "Silver", "Ag"], [48, "Cadmium", "Cd"], [49, "Indium", "In"], [50, "Tin", "Sn"], [51, "Antimony", "Sb"], [52, "Tellurium", "Te"], [53, "Iodine", "I"], [54, "Xenon", "Xe"], [55, "Caesium", "Cs"], [56, "Barium", "Ba"], [71, "Lutetium", "Lu"], [72, "Hafnium", "Hf"], [73, "Tantalum", "Ta"], [74, "Tungsten", "W"], [75, "Rhenium", "Re"], [76, "Osmium", "Os"], [77, "Iridium", "Ir"], [78, "Platinum", "Pt"], [79, "Gold", "Au"], [80, "Mercury", "Hg"], [81, "Thallium", "Tl"], [82, "Lead", "Pb"], [83, "Bismuth", "Bi", "http://0.tqn.com/d/chemistry/1/6/m/Q/bismuth.jpg"], [84, "Polonium", "Po"], [85, "Astatine", "At"], [86, "Radon", "Rn"], [87, "Francium", "Fr"], [88, "Radium", "Ra"], [103, "Lawrencium", "Lr"], [104, "Rutherfordium", "Rf"], [105, "Dubnium", "Db"], [106, "Seaborgium", "Sg"], [107, "Bohrium", "Bh"], [108, "Hassium", "Hs"], [109, "Meitnerium", "Mt"], [110, "Darmstadtium", "Ds"], [111, "Roentgenium", "Rg"], [112, "Copernicium", "Cn"], [113, "Ununtrium", "Uut"], [114, "Ununquadium", "Uuq"], [115, "Ununpentium", "Uup"], [116, "Ununhexium", "Uuh"], [117, "Ununseptium", "Uus"], [118, "Ununoctium", "Uuo"], [57, "Lanthanum", "La"], [58, "Cerium", "Ce"], [59, "Praseodymium", "Pr"], [60, "Neodymium", "Nd"], [61, "Promethium", "Pm"], [62, "Samarium", "Sm"], [63, "Europium", "Eu"], [64, "Gadolinium", "Gd"], [65, "Terbium", "Tb"], [66, "Dysprosium", "Dy"], [67, "Holmium", "Ho"], [68, "Erbium", "Er"], [69, "Thulium", "Tm"], [70, "Ytterbium", "Yb"], [89, "Actinium", "Ac"], [90, "Thorium", "Th"], [91, "Protactinium", "Pa"], [92, "Uranium", "U"], [93, "Neptunium", "Np"], [94, "Plutonium", "Pu"], [95, "Americium", "Am"], [96, "Curium", "Cm"], [97, "Berkelium", "Bk"], [98, "Californium", "Cf"], [99, "Einsteinium", "Es"], [100, "Fermium", "Fm"], [101, "Mendelevium", "Md"], [102, "Nobelium", "No"]];
function saveAttachment(doc, url) {
- ++attcount''
+ ++attcount;
var client = Ti.Network.createHTTPClient({
onload: function(e) {
- var rev = doc.newRevision();
+ var rev = doc.createRevision();
rev.addAttachment('image.jpg', 'image/jpeg', client.responseData);
rev.save();
}
@@ -30,7 +30,7 @@ function generateDB() {
var attcount = 0;
for (i=0; i < elements.length; i++) {
var e = elements[i];
- var doc = db.untitledDocument();
+ var doc = db.createDocument();
doc.putProperties({
type: 'element',
atomic_number: e[0],
@@ -107,6 +107,15 @@ function copyDatabaseFiles() {
copydir(srcdir, destdir);
}
+function installDatabase() {
+ var basedir = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'assets', 'CouchbaseLite').path;
+ var dbfile = [basedir, 'elements.cblite'].join(Ti.Filesystem.separator);
+ var attdir = [basedir, 'elements attachments'].join(Ti.Filesystem.separator);
+ if (!TiTouchDB.databaseManager.replaceDatabase('elements', dbfile, attdir)) {
+ throw 'could not install elements database';
+ }
+}
+
var win = Ti.UI.createWindow({
backgroundColor: 'white'
});
@@ -116,22 +125,22 @@ var tableView = Ti.UI.createTableView({
win.add(tableView);
win.addEventListener('open', function(e) {
- var db = TiTouchDB.databaseManager.databaseNamed('elements');
+ var db = TiTouchDB.databaseManager.getExistingDatabase('elements');
if (!db) {
- copyDatabaseFiles();
- db = TiTouchDB.databaseManager.databaseNamed('elements');
+ installDatabase();
+ db = TiTouchDB.databaseManager.getExistingDatabase('elements');
if (!db) {
- alert("Error copying database files!");
+ alert("Error creating database!");
return;
}
Ti.API.info("copied database files");
}
- var query = db.queryAllDocuments();
+ var query = db.createAllDocumentsQuery();
query.prefetch = true;
- var rows = query.rows();
+ var rows = query.run();
var data = [];
- while (row = rows.nextRow()) {
+ while (row = rows.next()) {
var props = row.documentProperties;
data.push({
title: String.format("%s (%s)", props.name, props.symbol)
diff --git a/samples/CannedDatabase/Resources/assets/CouchbaseLite/elements.touchdb b/samples/CannedDatabase/Resources/assets/CouchbaseLite/elements.cblite
similarity index 100%
rename from samples/CannedDatabase/Resources/assets/CouchbaseLite/elements.touchdb
rename to samples/CannedDatabase/Resources/assets/CouchbaseLite/elements.cblite
diff --git a/samples/CannedDatabase/Resources/assets/CouchbaseLite/elements.touchdb-shm b/samples/CannedDatabase/Resources/assets/CouchbaseLite/elements.touchdb-shm
deleted file mode 100644
index fe9ac28..0000000
Binary files a/samples/CannedDatabase/Resources/assets/CouchbaseLite/elements.touchdb-shm and /dev/null differ
diff --git a/samples/CannedDatabase/Resources/assets/CouchbaseLite/elements.touchdb-wal b/samples/CannedDatabase/Resources/assets/CouchbaseLite/elements.touchdb-wal
deleted file mode 100644
index e69de29..0000000
diff --git a/samples/CannedDatabase/tiapp.xml b/samples/CannedDatabase/tiapp.xml
index 2a73f82..c1d7fdb 100644
--- a/samples/CannedDatabase/tiapp.xml
+++ b/samples/CannedDatabase/tiapp.xml
@@ -38,14 +38,15 @@
default
- com.obscure.titouchdb
+ com.obscure.titouchdb
+ false
false
true
true
true
false
- 3.0.2.GA
+ 3.2.3.GA
diff --git a/samples/Migration/.gitignore b/samples/Migration/.gitignore
new file mode 100644
index 0000000..71ac564
--- /dev/null
+++ b/samples/Migration/.gitignore
@@ -0,0 +1,8 @@
+.*
+Resources
+build.log
+modules/android/com.obscure.titouchdb
+modules/iphone/com.obscure.titouchdb
+plugins/ti.alloy
+app/config.json
+app/assets/alloy
diff --git a/samples/Migration/LICENSE.txt b/samples/Migration/LICENSE.txt
new file mode 100644
index 0000000..388d0f5
--- /dev/null
+++ b/samples/Migration/LICENSE.txt
@@ -0,0 +1,13 @@
+Copyright 2014 Paul Mietz Egli
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/samples/Migration/README.md b/samples/Migration/README.md
new file mode 100644
index 0000000..cf2216b
--- /dev/null
+++ b/samples/Migration/README.md
@@ -0,0 +1,7 @@
+# Migration sample project
+
+This project shows how to use the migrations feature of the TiTouchDB sync
+adapter for Alloy.
+
+See also:
+* [Docs for SQL migration feature](http://docs.appcelerator.com/titanium/3.0/#!/guide/Alloy_Sync_Adapters_and_Migrations-section-36739597_AlloySyncAdaptersandMigrations-Migrations)
diff --git a/samples/Migration/app/alloy.js b/samples/Migration/app/alloy.js
new file mode 100644
index 0000000..a439f3b
--- /dev/null
+++ b/samples/Migration/app/alloy.js
@@ -0,0 +1,11 @@
+// The contents of this file will be executed before any of
+// your view controllers are ever executed, including the index.
+// You have access to all functionality on the `Alloy` namespace.
+//
+// This is a great place to do any initialization for your app
+// or create any global variables/functions that you'd like to
+// make available throughout your app. You can easily make things
+// accessible globally by attaching them to the `Alloy.Globals`
+// object. For example:
+//
+// Alloy.Globals.someGlobalFunction = function(){};
diff --git a/samples/Migration/app/assets/android/MarketplaceArtwork.png b/samples/Migration/app/assets/android/MarketplaceArtwork.png
new file mode 100644
index 0000000..fffab1b
Binary files /dev/null and b/samples/Migration/app/assets/android/MarketplaceArtwork.png differ
diff --git a/samples/Migration/app/assets/android/appicon.png b/samples/Migration/app/assets/android/appicon.png
new file mode 100644
index 0000000..7e73d18
Binary files /dev/null and b/samples/Migration/app/assets/android/appicon.png differ
diff --git a/samples/Migration/app/assets/android/default.png b/samples/Migration/app/assets/android/default.png
new file mode 100644
index 0000000..2578dc1
Binary files /dev/null and b/samples/Migration/app/assets/android/default.png differ
diff --git a/samples/Migration/app/assets/android/images/res-long-land-hdpi/default.png b/samples/Migration/app/assets/android/images/res-long-land-hdpi/default.png
new file mode 100644
index 0000000..289320d
Binary files /dev/null and b/samples/Migration/app/assets/android/images/res-long-land-hdpi/default.png differ
diff --git a/samples/Migration/app/assets/android/images/res-long-land-ldpi/default.png b/samples/Migration/app/assets/android/images/res-long-land-ldpi/default.png
new file mode 100644
index 0000000..ed0cbf3
Binary files /dev/null and b/samples/Migration/app/assets/android/images/res-long-land-ldpi/default.png differ
diff --git a/samples/Migration/app/assets/android/images/res-long-port-hdpi/default.png b/samples/Migration/app/assets/android/images/res-long-port-hdpi/default.png
new file mode 100644
index 0000000..15dd8a7
Binary files /dev/null and b/samples/Migration/app/assets/android/images/res-long-port-hdpi/default.png differ
diff --git a/samples/Migration/app/assets/android/images/res-long-port-ldpi/default.png b/samples/Migration/app/assets/android/images/res-long-port-ldpi/default.png
new file mode 100644
index 0000000..f472001
Binary files /dev/null and b/samples/Migration/app/assets/android/images/res-long-port-ldpi/default.png differ
diff --git a/samples/Migration/app/assets/android/images/res-notlong-land-hdpi/default.png b/samples/Migration/app/assets/android/images/res-notlong-land-hdpi/default.png
new file mode 100644
index 0000000..289320d
Binary files /dev/null and b/samples/Migration/app/assets/android/images/res-notlong-land-hdpi/default.png differ
diff --git a/samples/Migration/app/assets/android/images/res-notlong-land-ldpi/default.png b/samples/Migration/app/assets/android/images/res-notlong-land-ldpi/default.png
new file mode 100644
index 0000000..6cdb7d0
Binary files /dev/null and b/samples/Migration/app/assets/android/images/res-notlong-land-ldpi/default.png differ
diff --git a/samples/Migration/app/assets/android/images/res-notlong-land-mdpi/default.png b/samples/Migration/app/assets/android/images/res-notlong-land-mdpi/default.png
new file mode 100644
index 0000000..ff7e57d
Binary files /dev/null and b/samples/Migration/app/assets/android/images/res-notlong-land-mdpi/default.png differ
diff --git a/samples/Migration/app/assets/android/images/res-notlong-port-hdpi/default.png b/samples/Migration/app/assets/android/images/res-notlong-port-hdpi/default.png
new file mode 100644
index 0000000..15dd8a7
Binary files /dev/null and b/samples/Migration/app/assets/android/images/res-notlong-port-hdpi/default.png differ
diff --git a/samples/Migration/app/assets/android/images/res-notlong-port-ldpi/default.png b/samples/Migration/app/assets/android/images/res-notlong-port-ldpi/default.png
new file mode 100644
index 0000000..06d0921
Binary files /dev/null and b/samples/Migration/app/assets/android/images/res-notlong-port-ldpi/default.png differ
diff --git a/samples/Migration/app/assets/android/images/res-notlong-port-mdpi/default.png b/samples/Migration/app/assets/android/images/res-notlong-port-mdpi/default.png
new file mode 100644
index 0000000..2578dc1
Binary files /dev/null and b/samples/Migration/app/assets/android/images/res-notlong-port-mdpi/default.png differ
diff --git a/samples/Migration/app/assets/iphone/Default-568h@2x.png b/samples/Migration/app/assets/iphone/Default-568h@2x.png
new file mode 100644
index 0000000..e525fdb
Binary files /dev/null and b/samples/Migration/app/assets/iphone/Default-568h@2x.png differ
diff --git a/samples/Migration/app/assets/iphone/Default-Landscape.png b/samples/Migration/app/assets/iphone/Default-Landscape.png
new file mode 100644
index 0000000..45bcaa2
Binary files /dev/null and b/samples/Migration/app/assets/iphone/Default-Landscape.png differ
diff --git a/samples/Migration/app/assets/iphone/Default-Landscape@2x.png b/samples/Migration/app/assets/iphone/Default-Landscape@2x.png
new file mode 100644
index 0000000..4fd45d0
Binary files /dev/null and b/samples/Migration/app/assets/iphone/Default-Landscape@2x.png differ
diff --git a/samples/Migration/app/assets/iphone/Default-Portrait.png b/samples/Migration/app/assets/iphone/Default-Portrait.png
new file mode 100644
index 0000000..67996fd
Binary files /dev/null and b/samples/Migration/app/assets/iphone/Default-Portrait.png differ
diff --git a/samples/Migration/app/assets/iphone/Default-Portrait@2x.png b/samples/Migration/app/assets/iphone/Default-Portrait@2x.png
new file mode 100644
index 0000000..c67b596
Binary files /dev/null and b/samples/Migration/app/assets/iphone/Default-Portrait@2x.png differ
diff --git a/samples/Migration/app/assets/iphone/Default.png b/samples/Migration/app/assets/iphone/Default.png
new file mode 100644
index 0000000..6ad4820
Binary files /dev/null and b/samples/Migration/app/assets/iphone/Default.png differ
diff --git a/samples/Migration/app/assets/iphone/Default@2x.png b/samples/Migration/app/assets/iphone/Default@2x.png
new file mode 100644
index 0000000..62add74
Binary files /dev/null and b/samples/Migration/app/assets/iphone/Default@2x.png differ
diff --git a/samples/Migration/app/assets/iphone/appicon-72.png b/samples/Migration/app/assets/iphone/appicon-72.png
new file mode 100644
index 0000000..8fdf5a9
Binary files /dev/null and b/samples/Migration/app/assets/iphone/appicon-72.png differ
diff --git a/samples/Migration/app/assets/iphone/appicon-72@2x.png b/samples/Migration/app/assets/iphone/appicon-72@2x.png
new file mode 100644
index 0000000..b3ef1ca
Binary files /dev/null and b/samples/Migration/app/assets/iphone/appicon-72@2x.png differ
diff --git a/samples/Migration/app/assets/iphone/appicon-Small-50.png b/samples/Migration/app/assets/iphone/appicon-Small-50.png
new file mode 100644
index 0000000..244b8ec
Binary files /dev/null and b/samples/Migration/app/assets/iphone/appicon-Small-50.png differ
diff --git a/samples/Migration/app/assets/iphone/appicon-Small.png b/samples/Migration/app/assets/iphone/appicon-Small.png
new file mode 100644
index 0000000..f74c286
Binary files /dev/null and b/samples/Migration/app/assets/iphone/appicon-Small.png differ
diff --git a/samples/Migration/app/assets/iphone/appicon-Small@2x.png b/samples/Migration/app/assets/iphone/appicon-Small@2x.png
new file mode 100644
index 0000000..b94865e
Binary files /dev/null and b/samples/Migration/app/assets/iphone/appicon-Small@2x.png differ
diff --git a/samples/Migration/app/assets/iphone/appicon.png b/samples/Migration/app/assets/iphone/appicon.png
new file mode 100644
index 0000000..f7bde5b
Binary files /dev/null and b/samples/Migration/app/assets/iphone/appicon.png differ
diff --git a/samples/Migration/app/assets/iphone/appicon@2x.png b/samples/Migration/app/assets/iphone/appicon@2x.png
new file mode 100644
index 0000000..05ee350
Binary files /dev/null and b/samples/Migration/app/assets/iphone/appicon@2x.png differ
diff --git a/samples/Migration/app/assets/iphone/iTunesArtwork b/samples/Migration/app/assets/iphone/iTunesArtwork
new file mode 100644
index 0000000..a98a73a
Binary files /dev/null and b/samples/Migration/app/assets/iphone/iTunesArtwork differ
diff --git a/samples/Migration/app/config.json b/samples/Migration/app/config.json
new file mode 100644
index 0000000..07c1f1c
--- /dev/null
+++ b/samples/Migration/app/config.json
@@ -0,0 +1,11 @@
+{
+ "global": {},
+ "env:development": {},
+ "env:test": {},
+ "env:production": {},
+ "os:android": {},
+ "os:blackberry": {},
+ "os:ios": {},
+ "os:mobileweb": {},
+ "dependencies": {}
+}
\ No newline at end of file
diff --git a/samples/Migration/app/controllers/index.js b/samples/Migration/app/controllers/index.js
new file mode 100644
index 0000000..6dd9a2d
--- /dev/null
+++ b/samples/Migration/app/controllers/index.js
@@ -0,0 +1,13 @@
+
+function window_open(e) {
+ Alloy.Collections.contact.fetch();
+}
+
+function transform(model) {
+ var result = model.toJSON();
+ result.full_name = String.format("%s, %s", result.last, result.first);
+ Ti.API.info(JSON.stringify(result));
+ return result;
+}
+
+$.index.open();
diff --git a/samples/Migration/app/migrations/201405140800_Contact.js b/samples/Migration/app/migrations/201405140800_Contact.js
new file mode 100644
index 0000000..a56bb66
--- /dev/null
+++ b/samples/Migration/app/migrations/201405140800_Contact.js
@@ -0,0 +1,37 @@
+// http://www.generatedata.com
+var preload_data = [
+ { "last": "Hall", "first": "Xena", "street": "5444 Cras Rd.", "city": "Carapicuíba", "state": "São Paulo" },
+ { "last": "Grant", "first": "Macaulay", "street": "462 Curae; Av.", "city": "Racine", "state": "WI" },
+ { "last": "Perez", "first": "Orlando", "street": "P.O. Box 588", "city": "Ellesmere", "state": "OH" },
+ { "last": "Tucker", "first": "Wayne", "street": "386-1375 Lorem Ave", "city": "Fresno", "state": "CA" },
+ { "last": "Owen", "first": "Fuller", "street": "889 Nulla Ave", "city": "Lincoln", "state": "NB" },
+ { "last": "Jimenez", "first": "Blair", "street": "4732 Turpis. St.", "city": "Campbelltown", "state": "GA" },
+ { "last": "Maxwell", "first": "Ashton", "street": "118 Pellentesque Av. Apt 4", "city": "Knoxville", "state": "TN" },
+ { "last": "Glover", "first": "Candice", "street": "7281 Integer Rd.", "city": "Vienna", "state": "TX" },
+ { "last": "Pace", "first": "Joan", "street": "3455 Sodales Rd.", "city": "Bellary", "state": "KY" },
+ { "last": "Carney", "first": "Lacota", "street": "5413 Elementum Avenue", "city": "Whitehorse", "state": "YT" }
+];
+
+migration.up = function(migrator) {
+ var db = migrator.database;
+ _.each(preload_data, function(contact) {
+ migrator.createModel(contact);
+ });
+};
+
+migration.down = function(migrator) {
+ var db = migrator.database;
+ var q = db.createSlowQuery(function(doc) {
+ emit([doc.last, doc.first], null);
+ });
+ var e = q.run();
+ while (row = e.next()) {
+ var key = row.key;
+ if (_.find(preload_data, function(contact) {
+ return contact.last === key[0] && contact.first === key[1];
+ })) {
+ row.getDocument().deleteDocument();
+ }
+ }
+
+};
diff --git a/samples/Migration/app/migrations/201406110800_Contact.js b/samples/Migration/app/migrations/201406110800_Contact.js
new file mode 100644
index 0000000..98e9163
--- /dev/null
+++ b/samples/Migration/app/migrations/201406110800_Contact.js
@@ -0,0 +1,35 @@
+// http://www.generatedata.com
+var preload_data = [
+ { "first": "Xaviera", "last": "Stafford", "street": "449 Lobortis St.", "city": "Missoula", "state": "MT" },
+ { "first": "Alexander", "last": "Strickland", "street": "2851 Consequat Av.", "city": "Fuenlabrada", "state": "MA" },
+ { "first": "Oren", "last": "Fletcher", "street": "Ap #192-4259 Velit Avenue", "city": "Kraków", "state": "MP" },
+ { "first": "Rachel", "last": "Fowler", "street": "6764 Sit Rd.", "city": "Raymond", "state": "AB" },
+ { "first": "Yoko", "last": "Stewart", "street": "62022 Ornare Road", "city": "Providence", "state": "RI" },
+ { "first": "Brent", "last": "Winters", "street": "6075 Donec St.", "city": "Atwater", "state": "CA" }
+];
+
+migration.up = function(migrator) {
+ var db = migrator.database;
+ _.each(preload_data, function(contact) {
+ migrator.createModel(contact);
+ });
+};
+
+migration.down = function(migrator) {
+ Ti.API.info("down");
+ var db = migrator.database;
+ var q = db.createSlowQuery(function(doc) {
+ emit([doc.last, doc.first], null);
+ });
+ var e = q.run();
+ while (row = e.next()) {
+ var key = row.key;
+ if (_.find(preload_data, function(contact) {
+ return contact.last === key[0] && contact.first === key[1];
+ })) {
+ row.getDocument().deleteDocument();
+ }
+ }
+
+};
+
diff --git a/samples/Migration/app/models/Contact.js b/samples/Migration/app/models/Contact.js
new file mode 100644
index 0000000..a94465e
--- /dev/null
+++ b/samples/Migration/app/models/Contact.js
@@ -0,0 +1,48 @@
+/*
+ * To test down-migrations, run the app once to get both migration datasets
+ * inserted, then uncomment the "migration" property below and run again. The
+ * adapter should remove the data added in the 201406110800 migration file.
+ */
+
+exports.definition = {
+
+ config: {
+ adapter: {
+ type: "titouchdb",
+ dbname: "contacts",
+ // migration: "201405140800",
+ views: [
+ {
+ name: "by_lastname",
+ version: '1',
+ map: function(doc) {
+ if (doc.modelname == 'contact' && doc.last) {
+ emit(doc.last, null);
+ }
+ }
+ }
+ ],
+ view_options: {
+ prefetch: true
+ },
+ modelname: 'contact'
+ }
+ },
+
+ extendModel: function(Model) {
+ _.extend(Model.prototype, {
+ });
+ return Model;
+ },
+
+ extendCollection: function(Collection) {
+ _.extend(Collection.prototype, {
+ map_row: function(Model, row) {
+ var result = new Model(row.documentProperties);
+ // add custom properties here, if any
+ return result;
+ }
+ });
+ return Collection;
+ }
+};
diff --git a/samples/Migration/app/styles/index.tss b/samples/Migration/app/styles/index.tss
new file mode 100644
index 0000000..174db0b
--- /dev/null
+++ b/samples/Migration/app/styles/index.tss
@@ -0,0 +1,4 @@
+"Window": {
+ backgroundColor:"white"
+},
+
diff --git a/samples/Migration/app/views/index.xml b/samples/Migration/app/views/index.xml
new file mode 100644
index 0000000..87d03ef
--- /dev/null
+++ b/samples/Migration/app/views/index.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/Migration/manifest b/samples/Migration/manifest
new file mode 100644
index 0000000..ddd14fd
--- /dev/null
+++ b/samples/Migration/manifest
@@ -0,0 +1,8 @@
+#appname:Migration
+#publisher:Paul Mietz Egli
+#url:https://github.com/pegli/ti_touchdb
+#image:appicon.png
+#appid:com.obscure.migration
+#desc:not specified
+#type:mobile
+#guid:9140e913-ec8e-4f9c-8f47-346a0e8cbfc3
diff --git a/samples/Migration/tiapp.xml b/samples/Migration/tiapp.xml
new file mode 100644
index 0000000..82e5e0c
--- /dev/null
+++ b/samples/Migration/tiapp.xml
@@ -0,0 +1,65 @@
+
+
+ com.obscure.migration
+ Migration
+ 1.0
+ Paul Mietz Egli
+ https://github.com/pegli/ti_touchdb
+ not specified
+ 2014 by Paul Mietz Egli
+ appicon.png
+ false
+ false
+ true
+ 9140e913-ec8e-4f9c-8f47-346a0e8cbfc3
+ dp
+
+
+
+ UISupportedInterfaceOrientations~iphone
+
+ UIInterfaceOrientationPortrait
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIRequiresPersistentWiFi
+
+ UIPrerenderedIcon
+
+ UIStatusBarHidden
+
+ UIStatusBarStyle
+ UIStatusBarStyleDefault
+
+
+
+
+
+
+
+ true
+ true
+
+ default
+
+
+ com.obscure.titouchdb
+
+
+ true
+ false
+ false
+ true
+ false
+ false
+
+ 3.3.0.GA
+
+ ti.alloy
+
+
diff --git a/samples/ToDoLite/.gitignore b/samples/ToDoLite/.gitignore
new file mode 100644
index 0000000..7536cba
--- /dev/null
+++ b/samples/ToDoLite/.gitignore
@@ -0,0 +1,7 @@
+build.log
+build
+npm-debug.log
+tmp
+modules
+Resources
+.*
diff --git a/samples/ToDoLite/LICENSE.txt b/samples/ToDoLite/LICENSE.txt
new file mode 100644
index 0000000..1374352
--- /dev/null
+++ b/samples/ToDoLite/LICENSE.txt
@@ -0,0 +1,13 @@
+ Copyright 2014 Paul Mietz Egli
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/samples/ToDoLite/README.md b/samples/ToDoLite/README.md
new file mode 100644
index 0000000..04930ca
--- /dev/null
+++ b/samples/ToDoLite/README.md
@@ -0,0 +1,25 @@
+# ToDoLite Sample App
+
+This sample app is designed to be a functional equivalent of the [ToDoLite](https://github.com/couchbaselabs/ToDoLite-iOS)
+example provided by Couchbase.
+
+## Setup
+
+1. Install the TiTouchDB module using gittio or by copying the module ZIP files to this directory.
+1. Create a directory named `app/assets/alloy/sync` and copy the `titouchdb.js` sync adapter file
+ to that directory. If you have cloned this project from Github, you can make a symlink by
+ running `ln -s ../../../../mobile/noarch/alloy app/assets/`.
+1. TODO set up remote database to sync to.
+1. Build and run as usual.
+
+## Known Issues
+
+TODO list differences between Couchbase ToDoLite and this app
+
+## FAQ
+
+*Why are the event handler names so weird? It's almost like I'm reading Objective-C!*
+
+I named views, methods, and classes based on their correspondance with the original ToDoLite
+application. For example, `detail.js` contains an event handler named `shareButtonAction`,
+which performs the same function as `shareButtonAction:` in `DetailViewController`.
diff --git a/samples/ToDoLite/app/alloy.js b/samples/ToDoLite/app/alloy.js
new file mode 100644
index 0000000..21e9775
--- /dev/null
+++ b/samples/ToDoLite/app/alloy.js
@@ -0,0 +1,54 @@
+
+
+(function() {
+ var sync = require('lib/sync');
+ var cblSync;
+
+ /*
+ * Configure sync and trigger it if the user is already logged in.
+ */
+
+ function updateMyLists(userID, userData) {
+ // create a new profile document
+ // TODO figure out a way to construct the _id value in the sync adapter
+ var profile = Alloy.createModel('profile', { name: userData.name, user_id: userID });
+ profile.id = "p:" + userID;
+ Alloy.Collections.list.updateAllListsWithOwner(profile.id);
+ profile.save();
+ }
+
+ // public
+ Alloy.Globals.loginAndSync = function(cb) {
+ if (cblSync.userID) {
+ _.isFunction(cb) && cb();
+ }
+ else {
+ cblSync.beforeFirstSync(cb);
+ cblSync.start();
+ }
+ };
+
+ // run at startup to load up the sync manager for the database and remote URL
+ cblSync = sync.createSyncManager({
+ database: Alloy.CFG.dbname,
+ url: Ti.App.Properties.getString('com.couchbase.todolite.syncurl', 'http://localhost:4984/todos'),
+ });
+
+ // set the authenticator on the sync manager
+ cblSync.setAuthenticator(sync.createFacebookAuthenticator({
+ appid: Ti.App.Properties.getString('ti.facebook.appid')
+ }));
+
+ if (cblSync.userID) {
+ cblSync.start();
+ }
+ else {
+ cblSync.beforeFirstSync(function(userID, userData, err) {
+ updateMyLists(userID, userData);
+ });
+ }
+
+ // equivalent to adding a property to AppDelegate
+ Alloy.Globals.cblSync = cblSync;
+
+})();
diff --git a/samples/ToDoLite/app/assets/alloy b/samples/ToDoLite/app/assets/alloy
new file mode 120000
index 0000000..5c3eb63
--- /dev/null
+++ b/samples/ToDoLite/app/assets/alloy
@@ -0,0 +1 @@
+../../../../mobile/noarch/alloy
\ No newline at end of file
diff --git a/samples/ToDoLite/app/assets/android/MarketplaceArtwork.png b/samples/ToDoLite/app/assets/android/MarketplaceArtwork.png
new file mode 100644
index 0000000..fffab1b
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/MarketplaceArtwork.png differ
diff --git a/samples/ToDoLite/app/assets/android/appicon.png b/samples/ToDoLite/app/assets/android/appicon.png
new file mode 100644
index 0000000..7e73d18
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/appicon.png differ
diff --git a/samples/ToDoLite/app/assets/android/default.png b/samples/ToDoLite/app/assets/android/default.png
new file mode 100644
index 0000000..2578dc1
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/default.png differ
diff --git a/samples/ToDoLite/app/assets/android/images/res-long-land-hdpi/default.png b/samples/ToDoLite/app/assets/android/images/res-long-land-hdpi/default.png
new file mode 100644
index 0000000..289320d
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/images/res-long-land-hdpi/default.png differ
diff --git a/samples/ToDoLite/app/assets/android/images/res-long-land-ldpi/default.png b/samples/ToDoLite/app/assets/android/images/res-long-land-ldpi/default.png
new file mode 100644
index 0000000..ed0cbf3
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/images/res-long-land-ldpi/default.png differ
diff --git a/samples/ToDoLite/app/assets/android/images/res-long-port-hdpi/default.png b/samples/ToDoLite/app/assets/android/images/res-long-port-hdpi/default.png
new file mode 100644
index 0000000..15dd8a7
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/images/res-long-port-hdpi/default.png differ
diff --git a/samples/ToDoLite/app/assets/android/images/res-long-port-ldpi/default.png b/samples/ToDoLite/app/assets/android/images/res-long-port-ldpi/default.png
new file mode 100644
index 0000000..f472001
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/images/res-long-port-ldpi/default.png differ
diff --git a/samples/ToDoLite/app/assets/android/images/res-notlong-land-hdpi/default.png b/samples/ToDoLite/app/assets/android/images/res-notlong-land-hdpi/default.png
new file mode 100644
index 0000000..289320d
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/images/res-notlong-land-hdpi/default.png differ
diff --git a/samples/ToDoLite/app/assets/android/images/res-notlong-land-ldpi/default.png b/samples/ToDoLite/app/assets/android/images/res-notlong-land-ldpi/default.png
new file mode 100644
index 0000000..6cdb7d0
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/images/res-notlong-land-ldpi/default.png differ
diff --git a/samples/ToDoLite/app/assets/android/images/res-notlong-land-mdpi/default.png b/samples/ToDoLite/app/assets/android/images/res-notlong-land-mdpi/default.png
new file mode 100644
index 0000000..ff7e57d
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/images/res-notlong-land-mdpi/default.png differ
diff --git a/samples/ToDoLite/app/assets/android/images/res-notlong-port-hdpi/default.png b/samples/ToDoLite/app/assets/android/images/res-notlong-port-hdpi/default.png
new file mode 100644
index 0000000..15dd8a7
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/images/res-notlong-port-hdpi/default.png differ
diff --git a/samples/ToDoLite/app/assets/android/images/res-notlong-port-ldpi/default.png b/samples/ToDoLite/app/assets/android/images/res-notlong-port-ldpi/default.png
new file mode 100644
index 0000000..06d0921
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/images/res-notlong-port-ldpi/default.png differ
diff --git a/samples/ToDoLite/app/assets/android/images/res-notlong-port-mdpi/default.png b/samples/ToDoLite/app/assets/android/images/res-notlong-port-mdpi/default.png
new file mode 100644
index 0000000..2578dc1
Binary files /dev/null and b/samples/ToDoLite/app/assets/android/images/res-notlong-port-mdpi/default.png differ
diff --git a/samples/ToDoLite/app/assets/images/Camera-Light@2x.png b/samples/ToDoLite/app/assets/images/Camera-Light@2x.png
new file mode 100644
index 0000000..5d4b5c2
Binary files /dev/null and b/samples/ToDoLite/app/assets/images/Camera-Light@2x.png differ
diff --git a/samples/ToDoLite/app/assets/images/Camera@2x.png b/samples/ToDoLite/app/assets/images/Camera@2x.png
new file mode 100644
index 0000000..71d9958
Binary files /dev/null and b/samples/ToDoLite/app/assets/images/Camera@2x.png differ
diff --git a/samples/ToDoLite/app/assets/images/README.md b/samples/ToDoLite/app/assets/images/README.md
new file mode 100644
index 0000000..b9d0d73
--- /dev/null
+++ b/samples/ToDoLite/app/assets/images/README.md
@@ -0,0 +1,3 @@
+Some images copyright (c) 2011-2013 Couchbase, Inc.
+Released under the Apache License 2.0
+
diff --git a/samples/ToDoLite/app/assets/images/task_image_mask@2x.png b/samples/ToDoLite/app/assets/images/task_image_mask@2x.png
new file mode 100644
index 0000000..8019a9b
Binary files /dev/null and b/samples/ToDoLite/app/assets/images/task_image_mask@2x.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/Default-568h@2x.png b/samples/ToDoLite/app/assets/iphone/Default-568h@2x.png
new file mode 100644
index 0000000..e525fdb
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/Default-568h@2x.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/Default-Landscape.png b/samples/ToDoLite/app/assets/iphone/Default-Landscape.png
new file mode 100644
index 0000000..45bcaa2
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/Default-Landscape.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/Default-Landscape@2x.png b/samples/ToDoLite/app/assets/iphone/Default-Landscape@2x.png
new file mode 100644
index 0000000..4fd45d0
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/Default-Landscape@2x.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/Default-Portrait.png b/samples/ToDoLite/app/assets/iphone/Default-Portrait.png
new file mode 100644
index 0000000..67996fd
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/Default-Portrait.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/Default-Portrait@2x.png b/samples/ToDoLite/app/assets/iphone/Default-Portrait@2x.png
new file mode 100644
index 0000000..c67b596
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/Default-Portrait@2x.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/Default.png b/samples/ToDoLite/app/assets/iphone/Default.png
new file mode 100644
index 0000000..6ad4820
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/Default.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/Default@2x.png b/samples/ToDoLite/app/assets/iphone/Default@2x.png
new file mode 100644
index 0000000..62add74
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/Default@2x.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/appicon-72.png b/samples/ToDoLite/app/assets/iphone/appicon-72.png
new file mode 100644
index 0000000..8fdf5a9
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/appicon-72.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/appicon-72@2x.png b/samples/ToDoLite/app/assets/iphone/appicon-72@2x.png
new file mode 100644
index 0000000..b3ef1ca
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/appicon-72@2x.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/appicon-Small-50.png b/samples/ToDoLite/app/assets/iphone/appicon-Small-50.png
new file mode 100644
index 0000000..244b8ec
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/appicon-Small-50.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/appicon-Small.png b/samples/ToDoLite/app/assets/iphone/appicon-Small.png
new file mode 100644
index 0000000..f74c286
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/appicon-Small.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/appicon-Small@2x.png b/samples/ToDoLite/app/assets/iphone/appicon-Small@2x.png
new file mode 100644
index 0000000..b94865e
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/appicon-Small@2x.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/appicon.png b/samples/ToDoLite/app/assets/iphone/appicon.png
new file mode 100644
index 0000000..f7bde5b
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/appicon.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/appicon@2x.png b/samples/ToDoLite/app/assets/iphone/appicon@2x.png
new file mode 100644
index 0000000..05ee350
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/appicon@2x.png differ
diff --git a/samples/ToDoLite/app/assets/iphone/iTunesArtwork b/samples/ToDoLite/app/assets/iphone/iTunesArtwork
new file mode 100644
index 0000000..a98a73a
Binary files /dev/null and b/samples/ToDoLite/app/assets/iphone/iTunesArtwork differ
diff --git a/samples/ToDoLite/app/assets/lib/sync.js b/samples/ToDoLite/app/assets/lib/sync.js
new file mode 100644
index 0000000..2adde9d
--- /dev/null
+++ b/samples/ToDoLite/app/assets/lib/sync.js
@@ -0,0 +1,231 @@
+/**
+ * Synchronization manager module.
+ *
+ * This module keeps track of the push and pull replications for a
+ * database and holds references to any long-lived objects related
+ * to sync.
+ */
+
+var titouchdb = require('com.obscure.titouchdb'),
+ manager = titouchdb.databaseManager;
+
+var fb = require('facebook');
+
+// SYNC MANAGER
+
+function SyncManager(dbname, url, userID) {
+ this.database = manager.getDatabase(dbname);
+ this.userID = userID || Ti.App.Properties.getString('sync_manager.userid');
+ this.replicationURL = url;
+
+ // privileged methods
+
+ this.defineSync = _defineSync;
+ this.setupNewUser = _setupNewUser;
+ this.launchSync = _launchSync;
+ this.runBeforeSyncStart = _runBeforeSyncStart;
+ this.replicationProgress = _replicationProgress;
+
+ this.beforeFirstSync(function(uid, userData) {
+ Ti.App.Properties.setString('sync_manager.userid', uid);
+ Ti.API.info("stored userid "+uid);
+ });
+}
+
+// PUBLIC METHODS
+
+SyncManager.prototype.start = function() {
+ if (!this.userID) {
+ var self = this;
+ this.setupNewUser(function() {
+ self.launchSync();
+ });
+ }
+ else {
+ this.launchSync();
+ }
+};
+
+SyncManager.prototype.restartSync = function() {
+ this.pull.stop();
+ this.pull.start();
+ this.push.stop();
+ this.push.start();
+ Ti.API.info("restartSync");
+};
+
+SyncManager.prototype.beforeFirstSync = function(cb) {
+ this.beforeSyncBlocks = (this.beforeSyncBlocks || []).concat([cb]);
+};
+
+SyncManager.prototype.onSyncConnected = function(cb) {
+ this.onSyncStartedBlocks = (this.onSyncStartedBlocks || []).concat([cb]);
+};
+
+SyncManager.prototype.setAuthenticator = function(authenticator) {
+ this.authenticator = authenticator;
+ authenticator.setSyncManager(this);
+ if (this.lastAuthError) {
+ this.runAuthenticator();
+ }
+};
+
+// PRIVATE METHODS
+
+function _setupNewUser(cb) {
+ if (this.userID) return;
+
+ var self = this;
+ this.authenticator && this.authenticator.getCredentials(function(uid, userData) {
+ self.userID = uid;
+ var err = self.runBeforeSyncStart(uid, userData);
+ if (err) {
+ Ti.API.error(err);
+ }
+ else {
+ _.isFunction(cb) && cb();
+ }
+ });
+}
+
+function _runBeforeSyncStart(uid, userData) {
+ var err;
+ _.each(this.beforeSyncBlocks, function(b) {
+ if (_.isFunction(b)) {
+ err = b(uid, userData);
+ }
+ if (err) return err;
+ });
+
+ return err;
+}
+
+function _runAuthenticator() {
+ this.authenticator && this.authenticator.getCredentials(function(uid, userData) {
+ if (uid !== this.userID) {
+ throw("cannot change userID from " + this.userID + " to " + uid + "; need to reinstall");
+ }
+ this.restartSync();
+ });
+}
+
+function _launchSync() {
+ this.defineSync();
+ if (this.lastAuthError) {
+ this.runAuthenticator();
+ }
+ else {
+ this.restartSync();
+ }
+}
+
+function _defineSync() {
+ this.pull = this.database.createPullReplication(this.replicationURL);
+ this.pull.continuous = true;
+ this.pull.addEventListener('change', this.replicationProgress);
+
+ this.push = this.database.createPushReplication(this.replicationURL);
+ this.push.continuous = true;
+ this.push.addEventListener('change', this.replicationProgress);
+
+ this.authenticator.registerCredentialsWithReplications([this.pull, this.push]);
+}
+
+function _replicationProgress(e) {
+ // this is run for pull and push independently
+ var active = false;
+ var completed = 0, total = 0;
+ var status = titouchdb.REPLICATION_MODE_STOPPED;
+ var error;
+
+ var repl = e.source;
+ status = Math.max(status, repl.status);
+ if (!error) {
+ error = repl.lastError;
+ }
+ if (repl.status === titouchdb.REPLICATION_MODE_ACTIVE) {
+ active = true;
+ completed += repl.completedChangesCount;
+ total += repl.changesCount;
+ }
+
+ if (error && error.code === 401) {
+ if (!this.authenticator) {
+ this.lastAuthError = error;
+ return;
+ }
+ this.runAuthenticator();
+ }
+
+ if (active !== this.active || completed !== this.completed || total !== this.total || error !== this.error) {
+ this.active = active;
+ this.completed = completed;
+ this.total = total;
+ this.error = error;
+ this.progress = (completed / Math.max(total, 1));
+
+ Ti.API.info(String.format("SyncManager: active=%s; status=%d; %d/%d; " + (error ? JSON.stringify(error) : ""), active ? "true" : "false", status, completed, total));
+
+ // fire an event to notify the app that the data may have changed
+ Ti.App.fireEvent('sync:change', {});
+ }
+
+}
+
+// FACEBOOK AUTHENTICATOR
+
+function FacebookAuthenticator(appid) {
+ fb.appid = appid;
+ fb.permissions = ["public_profile", "user_friends", "email"];
+ fb.forceDialogAuth = false;
+}
+
+FacebookAuthenticator.prototype.setSyncManager = function(syncManager) {
+ this.syncManager = syncManager;
+};
+
+
+FacebookAuthenticator.prototype.getCredentials = function(cb) {
+ // if the user is logged in, the Ti Facebook module skips the
+ // call to authorize(). In this case, we make a graph API call
+ // to get the user data.
+ if (fb.loggedIn) {
+ fb.requestWithGraphPath('/me', { fields: "id,name,email" }, 'GET', function(e) {
+ if (e.success) {
+ var data = JSON.parse(e.result);
+ _.isFunction(cb) && cb(data.email, data);
+ }
+ });
+ }
+ else {
+ var f = function(e) {
+ if (e.success) {
+ _.isFunction(cb) && cb(e.data.email, e.data);
+ }
+ fb.removeEventListener('login', f);
+ };
+ fb.addEventListener('login', f);
+ fb.authorize();
+ }
+};
+
+FacebookAuthenticator.prototype.registerCredentialsWithReplications = function(repls) {
+ if (fb.loggedIn) {
+ _.each(repls, function(r) {
+ r.authenticator = titouchdb.createFacebookAuthenticator(fb.accessToken);
+ });
+ }
+ else {
+ Ti.API.warn("could not set authenticators for replications: not logged in");
+ }
+};
+
+// PUBLIC API
+
+exports.createSyncManager = function(opts) {
+ return new SyncManager(opts.database, opts.url, opts.user);
+};
+
+exports.createFacebookAuthenticator = function(opts) {
+ return new FacebookAuthenticator(opts.appid);
+};
diff --git a/samples/ToDoLite/app/config.json b/samples/ToDoLite/app/config.json
new file mode 100644
index 0000000..031d1f8
--- /dev/null
+++ b/samples/ToDoLite/app/config.json
@@ -0,0 +1,13 @@
+{
+ "global": {
+ "dbname": "todos4"
+ },
+ "env:development": {},
+ "env:test": {},
+ "env:production": {},
+ "os:android": {},
+ "os:blackberry": {},
+ "os:ios": {},
+ "os:mobileweb": {},
+ "dependencies": {}
+}
\ No newline at end of file
diff --git a/samples/ToDoLite/app/controllers/detail.js b/samples/ToDoLite/app/controllers/detail.js
new file mode 100644
index 0000000..5259443
--- /dev/null
+++ b/samples/ToDoLite/app/controllers/detail.js
@@ -0,0 +1,166 @@
+var args = arguments[0] || {};
+
+var imageForNewTask = null;
+var list = null;
+
+// data binding
+
+function transform(model) {
+ var result = model.toJSON();
+ var att = model.attachmentNamed('image.jpg');
+ result.image = (att && att.content) || "/images/Camera-Light.png";
+ result.template = result.checked ? 'complete' : 'incomplete';
+ return result;
+}
+
+// helper functions
+
+function displayAddImageActionSheet(listItem) {
+ var task = listItem ? $.tasks.at(listItem.itemIndex) : null;
+
+ var options = [];
+ if (Ti.Media.isCameraSupported) {
+ options.push("Take Picture");
+ }
+ options.push("Choose Existing");
+ if (imageForNewTask || (task && task.attachmentNamed('image.jpg'))) {
+ options.push("Delete");
+ }
+ options.push("Cancel");
+ var dialog = Ti.UI.createOptionDialog({
+ options: options,
+ cancel: options.length - 1,
+ });
+ dialog.addEventListener('click', function(e) {
+ var selected = options[e.index];
+ if (selected === 'Take Picture') {
+ takePicture(listItem);
+ }
+ else if (selected == 'Choose Existing') {
+ chooseExistingPhoto(listItem);
+ }
+ else if (selected == 'Delete') {
+ if (task) {
+ task.removeAttachment('image.jpg');
+ }
+ else {
+ imageForNewTask = null;
+ updateAddImageButtonWithImage(null);
+ }
+ }
+ });
+ dialog.show();
+}
+
+function updateAddImageButtonWithImage(img) {
+ $.addImageButton.image = img || '/images/Camera.png';
+}
+
+function takePicture(listItem) {
+ Ti.Media.showCamera({
+ success: function(e) {
+ if (listItem) {
+ var task = $.tasks.at(listItem.itemIndex);
+ task.addAttachment('image.jpg', e.media.mimeType, e.media);
+ listItem.image = e.media;
+ }
+ else {
+ imageForNewTask = e.media;
+ updateAddImageButtonWithImage(imageForNewTask);
+ }
+ }
+ });
+}
+
+function chooseExistingPhoto(listItem) {
+ Ti.Media.openPhotoGallery({
+ success: function(e) {
+ if (listItem) {
+ var task = $.tasks.at(listItem.itemIndex);
+ task.addAttachment('image.jpg', e.media.mimeType, e.media);
+ listItem.image = e.media;
+ }
+ else {
+ imageForNewTask = e.media;
+ updateAddImageButtonWithImage(imageForNewTask);
+ }
+ }
+ });
+}
+
+function updateModels() {
+ $.tasks.fetch({ startKey: [args.list_id], endKey: [args.list_id, {}] });
+ list = Alloy.createModel('list');
+ list.fetch({ id: args.list_id });
+}
+
+// event handlers
+
+function windowOpen(e) {
+ updateAddImageButtonWithImage();
+ updateModels();
+}
+
+function windowClose(e) {
+ $.destroy();
+}
+
+function shareButtonAction(e) {
+ Alloy.Globals.loginAndSync(function() {
+ Ti.App.fireEvent('list:share', { list_id: args.list_id });
+ });
+}
+
+// set image for new task
+function addImageButtonAction(e) {
+ e.cancelBubble = true;
+ displayAddImageActionSheet();
+}
+
+// set image for existing task
+function imageButtonAction(e) {
+ e.cancelBubble = true; // doesn't work: https://jira.appcelerator.org/browse/TIMOB-16898
+ var task = $.tasks.at(e.itemIndex);
+ if (task.attachmentNamed('image.jpg')) {
+ var controller = Alloy.createController('image', { task_id: task.id });
+ controller.getView().open({ modal:true });
+ }
+ else {
+ displayAddImageActionSheet(task);
+ }
+}
+
+function textFieldShouldReturn(e) {
+ var title = e.value;
+ if (title.length == 0) {
+ return;
+ }
+
+ // create and save a new task
+ var task = list.addTask(title, imageForNewTask);
+ if (task) {
+ imageForNewTask = null;
+ updateAddImageButtonWithImage(null);
+ $.addItemTextField.value = '';
+ $.tasks.add(task);
+ }
+}
+
+function didSelectRow(e) {
+ var task = $.tasks.at(e.itemIndex);
+ task.set({ checked: !task.get('checked') });
+ task.save();
+
+ var checked = task.get('checked');
+
+ var listItem = e.section.items[e.itemIndex];
+ listItem.template = checked ? 'complete' : 'incomplete';
+ e.section.updateItemAt(e.itemIndex, listItem, { animated: true });
+}
+
+function didDelete(e) {
+ alert('delete '+e.itemId);
+ var doomed = $.tasks.at(e.itemIndex);
+ doomed.deleteTask();
+ updateModels();
+}
diff --git a/samples/ToDoLite/app/controllers/image.js b/samples/ToDoLite/app/controllers/image.js
new file mode 100644
index 0000000..0393915
--- /dev/null
+++ b/samples/ToDoLite/app/controllers/image.js
@@ -0,0 +1,21 @@
+var args = arguments[0] || {};
+
+function windowOpen(e) {
+ var task = Alloy.createModel('task');
+ task.fetch({
+ id: args.task_id,
+ success: function() {
+ var att = task.attachmentNamed('image.jpg');
+ if (att) {
+ $.imageView.image = att.content;
+ }
+ else {
+ // TODO close, error?
+ }
+ }
+ });
+}
+
+function dismissWindow(e) {
+ $.win.close();
+}
diff --git a/samples/ToDoLite/app/controllers/index.js b/samples/ToDoLite/app/controllers/index.js
new file mode 100644
index 0000000..a8d5ff1
--- /dev/null
+++ b/samples/ToDoLite/app/controllers/index.js
@@ -0,0 +1,12 @@
+
+Ti.App.addEventListener('list:select', function(e) {
+ var controller = Alloy.createController('detail', e);
+ $.index.openWindow(controller.getView());
+});
+
+Ti.App.addEventListener('list:share', function(e) {
+ var controller = Alloy.createController('share', e);
+ $.index.openWindow(controller.getView());
+});
+
+$.index.open();
diff --git a/samples/ToDoLite/app/controllers/master.js b/samples/ToDoLite/app/controllers/master.js
new file mode 100644
index 0000000..f704aab
--- /dev/null
+++ b/samples/ToDoLite/app/controllers/master.js
@@ -0,0 +1,73 @@
+var lists = Alloy.Collections.list;
+
+function createListWithTitle(title) {
+ var list = Alloy.createModel('List');
+ // TODO if there is a userID set, add it to the list
+ list.save({ title: title }, {
+ success: function() {
+ lists.fetch();
+ },
+ error: function(e) {
+ Ti.UI.createAlertDialog({
+ title: "Error",
+ message: "Cannot create a new list.",
+ }).show();
+ }
+ });
+}
+
+// event handlers
+
+function insertNewObject(e) {
+ // TODO use androidView property for Android
+ var dialog = Ti.UI.createAlertDialog({
+ title: "New To-Do List",
+ message: "Title for new list:",
+ style: Ti.UI.iPhone.AlertDialogStyle.PLAIN_TEXT_INPUT,
+ buttonNames: ['Cancel', 'Create'],
+ cancel: 0,
+ });
+ dialog.addEventListener('click', function(e) {
+ if (e.index !== e.source.cancel) {
+ if (e.text && e.text.length > 0) {
+ createListWithTitle(e.text);
+ }
+ }
+ });
+ dialog.show();
+}
+
+function didSelectRow(e) {
+ Ti.App.fireEvent('list:select', { list_id: e.itemId });
+}
+
+function didDelete(e) {
+ var doomed = lists.get(e.itemId);
+ doomed.deleteList();
+ lists.fetch();
+}
+
+function windowOpen(e) {
+ if (!Alloy.Globals.cblSync.userID) {
+ var loginButton = Ti.UI.createButton({
+ title: "Login",
+ });
+ loginButton.addEventListener('click', function() {
+ Alloy.Globals.loginAndSync(function() {
+ $.master.leftNavButton = null;
+ });
+ });
+ $.master.leftNavButton = loginButton;
+ }
+ Ti.App.addEventListener('sync:change', syncChanged);
+ lists.fetch();
+}
+
+function windowClose(e) {
+ Ti.App.removeEventListener('sync:change', syncChanged);
+ $.destroy();
+}
+
+function syncChanged(e) {
+ lists.fetch();
+}
diff --git a/samples/ToDoLite/app/controllers/share.js b/samples/ToDoLite/app/controllers/share.js
new file mode 100644
index 0000000..9ff7979
--- /dev/null
+++ b/samples/ToDoLite/app/controllers/share.js
@@ -0,0 +1,59 @@
+var args = arguments[0] || {};
+
+var list;
+var myDocId;
+
+// corresponds to couchTableSource:willUseCell:forRow:
+function transform(model) {
+ var result = model.toJSON();
+ var personId = model.id;
+ var member = false;
+
+ if (myDocId === personId) {
+ member = true;
+ }
+ else {
+ member = _.contains(list.members || [], personId);
+ }
+
+ result.accessoryType = member ? Ti.UI.LIST_ACCESSORY_TYPE_CHECKMARK : Ti.UI.LIST_ACCESSORY_TYPE_NONE;
+
+ return result;
+}
+
+function windowOpen() {
+ if (!Alloy.Globals.cblSync.userID) {
+ throw('no userID');
+ }
+
+ myDocId = 'p:' + Alloy.Globals.cblSync.userID;
+ configureView();
+}
+
+function windowClose() {
+ $.destroy();
+}
+
+function didSelectRow(e) {
+ var toggleMemberId = e.itemId;
+ var members = list.get('members') || [];
+ var x = members.indexOf(toggleMemberId);
+ if (x < 0) {
+ // add to array
+ members.push(toggleMemberId);
+ }
+ else {
+ // remove from array
+ members.splice(x, 1);
+ }
+ list.save({ members: members });
+
+ // don't need to call configureView() again
+}
+
+function configureView() {
+ list = Alloy.createModel('list');
+ list.fetch({ id: args.list_id });
+
+ $.profiles.fetch();
+}
diff --git a/samples/ToDoLite/app/models/List.js b/samples/ToDoLite/app/models/List.js
new file mode 100644
index 0000000..1fc7ff8
--- /dev/null
+++ b/samples/ToDoLite/app/models/List.js
@@ -0,0 +1,87 @@
+exports.definition = {
+
+ config: {
+ adapter: {
+ type: 'titouchdb',
+ dbname: Alloy.CFG.dbname,
+ views: [
+ {
+ name: 'lists',
+ version: '1',
+ map: function(doc) {
+ if (doc.type == 'list') {
+ emit(doc.title, null);
+ }
+ }
+ },
+ {
+ name: 'tasks_by_list',
+ version: '1',
+ map: function(doc) {
+ if (doc.type == 'task') {
+ emit(doc.list_id, null);
+ }
+ }
+ }
+ ],
+ view_options: {
+ prefetch: true
+ },
+ static_properties: {
+ type: 'list'
+ }
+ }
+ },
+
+ extendModel: function(Model) {
+ _.extend(Model.prototype, {
+ addTask: function(title, image) {
+ var list_id = this.id;
+ var task = Alloy.createModel('task', {
+ title: title,
+ created_at: new Date().getTime(),
+ list_id: list_id
+ });
+ task.save();
+ if (image) {
+ task.addAttachment('image.jpg', image.mimeType, image);
+ }
+ return task;
+ },
+ deleteList: function() {
+ // delete the list document and all task documents associated with it
+ var view = this.database.getExistingView('tasks_by_list');
+ var query = view.createQuery();
+ query.startKey = this.id;
+ query.endKey = this.id + '\uFFFF';
+ var rows = query.run();
+ while (row = rows.next()) {
+ row.getDocument().deleteDocument();
+ }
+ this.database.getExistingDocument(this.id).deleteDocument();
+ }
+ });
+ return Model;
+ },
+
+ extendCollection: function(Collection) {
+ _.extend(Collection.prototype, {
+ map_row: function(Model, row) {
+ var result = new Model(row.documentProperties);
+ // add custom properties here, if any
+ return result;
+ },
+ updateAllListsWithOwner: function(owner) {
+ var view = this.database.getExistingView('lists');
+ var query = view.createQuery();
+ var rows = query.run();
+ while (row = rows.next()) {
+ var doc = row.getDocument();
+ doc.putProperties(_.extend(doc.properties, { owner: owner }));
+ }
+ }
+ });
+ return Collection;
+ }
+};
+
diff --git a/samples/ToDoLite/app/models/Profile.js b/samples/ToDoLite/app/models/Profile.js
new file mode 100644
index 0000000..a6fc4b2
--- /dev/null
+++ b/samples/ToDoLite/app/models/Profile.js
@@ -0,0 +1,45 @@
+exports.definition = {
+
+ config: {
+ adapter: {
+ type: "titouchdb",
+ dbname: Alloy.CFG.dbname,
+ views: [
+ {
+ name: 'profiles',
+ version: '1',
+ map: function(doc) {
+ if (doc.type == 'profile') {
+ emit(doc.name, null);
+ }
+ }
+ }
+ ],
+ view_options: {
+ prefetch: true
+ },
+ static_properties: {
+ type: 'profile'
+ }
+ }
+ },
+
+ extendModel: function(Model) {
+ _.extend(Model.prototype, {
+ // TODO maybe set all tasks and lists to this profile?
+ });
+ return Model;
+ },
+
+ extendCollection: function(Collection) {
+ _.extend(Collection.prototype, {
+ map_row: function(Model, row) {
+ var result = new Model(row.documentProperties);
+ // add custom properties here, if any
+ return result;
+ }
+ });
+ return Collection;
+ }
+};
+
diff --git a/samples/ToDoLite/app/models/Task.js b/samples/ToDoLite/app/models/Task.js
new file mode 100644
index 0000000..429242f
--- /dev/null
+++ b/samples/ToDoLite/app/models/Task.js
@@ -0,0 +1,47 @@
+exports.definition = {
+
+ config: {
+ adapter: {
+ type: "titouchdb",
+ dbname: Alloy.CFG.dbname,
+ views: [
+ {
+ name: "tasksByDate",
+ version: '1',
+ map: function(doc) {
+ if (doc.type == 'task') {
+ emit([doc.list_id, doc.created_at], null);
+ }
+ }
+ }
+ ],
+ view_options: {
+ prefetch: true
+ },
+ static_properties: {
+ type: 'task'
+ }
+ }
+ },
+
+ extendModel: function(Model) {
+ _.extend(Model.prototype, {
+ deleteTask: function() {
+ this.database.getExistingDocument(this.id).deleteDocument();
+ }
+ });
+ return Model;
+ },
+
+ extendCollection: function(Collection) {
+ _.extend(Collection.prototype, {
+ map_row: function(Model, row) {
+ var result = new Model(row.documentProperties);
+ // add custom properties here, if any
+ return result;
+ }
+ });
+ return Collection;
+ }
+};
+
diff --git a/samples/ToDoLite/app/styles/app.tss b/samples/ToDoLite/app/styles/app.tss
new file mode 100644
index 0000000..dbdf1dc
--- /dev/null
+++ b/samples/ToDoLite/app/styles/app.tss
@@ -0,0 +1,4 @@
+"Window": {
+ backgroundColor: "white"
+}
+
diff --git a/samples/ToDoLite/app/styles/detail.tss b/samples/ToDoLite/app/styles/detail.tss
new file mode 100644
index 0000000..20d84ba
--- /dev/null
+++ b/samples/ToDoLite/app/styles/detail.tss
@@ -0,0 +1,9 @@
+
+
+".complete": {
+ color: 'grey',
+}
+
+".incomplete": {
+ color: 'black',
+}
diff --git a/samples/ToDoLite/app/views/detail.xml b/samples/ToDoLite/app/views/detail.xml
new file mode 100644
index 0000000..d2f81a3
--- /dev/null
+++ b/samples/ToDoLite/app/views/detail.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ToDoLite/app/views/image.xml b/samples/ToDoLite/app/views/image.xml
new file mode 100644
index 0000000..6ce9779
--- /dev/null
+++ b/samples/ToDoLite/app/views/image.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/samples/ToDoLite/app/views/index.xml b/samples/ToDoLite/app/views/index.xml
new file mode 100644
index 0000000..fcf29ca
--- /dev/null
+++ b/samples/ToDoLite/app/views/index.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/ToDoLite/app/views/master.xml b/samples/ToDoLite/app/views/master.xml
new file mode 100644
index 0000000..522d010
--- /dev/null
+++ b/samples/ToDoLite/app/views/master.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ToDoLite/app/views/share.xml b/samples/ToDoLite/app/views/share.xml
new file mode 100644
index 0000000..dda3a76
--- /dev/null
+++ b/samples/ToDoLite/app/views/share.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ToDoLite/manifest b/samples/ToDoLite/manifest
new file mode 100644
index 0000000..e8fb51e
--- /dev/null
+++ b/samples/ToDoLite/manifest
@@ -0,0 +1,8 @@
+#appname:ToDoLite
+#publisher:Paul Mietz Egli
+#url:http://github.com/pegli/ti_touchdb
+#image:appicon.png
+#appid:com.obscure.todolite
+#desc:not specified
+#type:ipad
+#guid:e3a3af0d-f278-4470-a207-fefb2ed69b37
diff --git a/samples/ToDoLite/plugins/ti.alloy/hooks/alloy.js b/samples/ToDoLite/plugins/ti.alloy/hooks/alloy.js
new file mode 100644
index 0000000..cb66ba0
--- /dev/null
+++ b/samples/ToDoLite/plugins/ti.alloy/hooks/alloy.js
@@ -0,0 +1,180 @@
+/**
+ * Alloy
+ * Copyright (c) 2012 by Appcelerator, Inc. All Rights Reserved.
+ * See LICENSE for more information on licensing.
+ */
+
+exports.cliVersion = '>=3.X';
+
+exports.init = function (logger, config, cli, appc) {
+ var path = require('path'),
+ fs = require('fs'),
+ afs = appc.fs,
+ i18n = appc.i18n(__dirname),
+ __ = i18n.__,
+ __n = i18n.__n,
+ pkginfo = appc.pkginfo.package(module),
+ exec = require('child_process').exec,
+ spawn = require('child_process').spawn,
+ parallel = appc.async.parallel;
+
+ function run(deviceFamily, deployType, target, finished) {
+ var appDir = path.join(cli.argv['project-dir'], 'app');
+ if (!afs.exists(appDir)) {
+ logger.info(__('Project not an Alloy app, continuing'));
+ finished();
+ return;
+ }
+ logger.info(__('Found Alloy app in %s', appDir.cyan));
+
+ // TODO: Make this check specific to a TiSDK version
+ // create a .alloynewcli file to tell old plugins not to run
+ var buildDir = path.join(cli.argv['project-dir'], 'build');
+ if (!afs.exists(buildDir)) {
+ fs.mkdirSync(buildDir);
+ }
+ fs.writeFileSync(path.join(buildDir, '.alloynewcli'), '');
+
+ var cRequire = afs.resolvePath(__dirname, '..', 'Alloy', 'commands', 'compile', 'index.js'),
+ config = {
+ platform: /(?:iphone|ipad)/.test(cli.argv.platform) ? 'ios' : cli.argv.platform,
+ version: '0',
+ simtype: 'none',
+ devicefamily: /(?:iphone|ios)/.test(cli.argv.platform) ? deviceFamily : 'none',
+ deploytype: deployType || cli.argv['deploy-type'] || 'development',
+ target: target
+ };
+
+ config = Object.keys(config).map(function (c) {
+ return c + '=' + config[c];
+ }).join(',');
+
+ if (afs.exists(cRequire)) {
+ // we're being invoked from the actual alloy directory!
+ // no need to subprocess, just require() and run
+ var origLimit = Error.stackTraceLimit;
+ Error.stackTraceLimit = Infinity;
+ try {
+ require(cRequire)({}, {
+ config: config,
+ outputPath: cli.argv['project-dir'],
+ _version: pkginfo.version
+ });
+ } catch (e) {
+ logger.error(__('Alloy compiler failed'));
+ e.toString().split('\n').forEach(function (line) {
+ if (line) { logger.error(line); }
+ });
+ process.exit(1);
+ }
+ Error.stackTraceLimit = origLimit;
+ finished();
+ } else {
+ // we have no clue where alloy is installed, so we're going to subprocess
+ // alloy and hope it's in the system path or a well known place
+ var paths = {};
+ parallel(this, ['alloy', 'node'].map(function (bin) {
+ return function (done) {
+ var envName = 'ALLOY_' + (bin === 'node' ? 'NODE_' : '') + 'PATH';
+
+ paths[bin] = process.env[envName];
+ if (paths[bin]) {
+ done();
+ } else if (process.platform === 'win32') {
+ paths.alloy = 'alloy.cmd';
+ done();
+ } else {
+ exec('which ' + bin, function (err, stdout, strerr) {
+ if (!err) {
+ paths[bin] = stdout.trim();
+ done();
+ } else {
+ parallel(this, [
+ '/usr/local/bin/' + bin,
+ '/opt/local/bin/' + bin,
+ path.join(process.env.HOME, 'local/bin', bin),
+ '/opt/bin/' + bin,
+ '/usr/bin/' + bin
+ ].map(function (p) {
+ return function (cb) {
+ if (afs.exists(p)) { paths[bin] = p; }
+ cb();
+ };
+ }), done);
+ }
+ });
+ }
+ };
+ }), function () {
+ var cmd = [paths.node, paths.alloy, 'compile', appDir, '--config', config];
+ if (cli.argv['no-colors'] || cli.argv['color'] === false) { cmd.push('--no-colors'); }
+ if (process.platform === 'win32') { cmd.shift(); }
+ logger.info(__('Executing Alloy compile: %s', cmd.join(' ').cyan));
+
+ var child = (process.platform === 'win32') ? spawn(cmd.shift(), cmd, { stdio: 'inherit' }) : spawn(cmd.shift(), cmd);
+
+ function checkLine(line) {
+ var re = new RegExp(
+ '(?:\u001b\\[\\d+m)?\\[?(' +
+ logger.getLevels().join('|') +
+ ')\\]?\s*(?:\u001b\\[\\d+m)?(.*)', 'i'
+ );
+ if (line) {
+ var m = line.match(re);
+ if (m) {
+ logger[m[1].toLowerCase()](m[2].trim());
+ } else {
+ logger.debug(line);
+ }
+ }
+ }
+
+ child.stdout !== null && child.stdout.on('data', function (data) {
+ data.toString().split('\n').forEach(function (line) {
+ checkLine(line);
+ });
+ });
+ child.stderr !== null && child.stderr.on('data', function (data) {
+ data.toString().split('\n').forEach(function (line) {
+ checkLine(line);
+ });
+ });
+ child !== null && child.on('exit', function (code) {
+ if (code) {
+ logger.error(__('Alloy compiler failed'));
+ process.exit(1);
+ } else {
+ logger.info(__('Alloy compiler completed successfully'));
+ }
+ finished();
+ });
+ });
+ }
+ }
+
+ cli.addHook('build.pre.compile', function (build, finished) {
+ // TODO: Remove this workaround when the CLI reports the right deploy type for android
+ var deployType = build.deployType;
+ var target = build.target;
+
+ if (cli.argv.platform === 'android') {
+ switch(target) {
+ case 'dist-playstore':
+ deployType = 'production';
+ break;
+ case 'device':
+ deployType = 'test';
+ break;
+ case 'emulator':
+ default:
+ deployType = 'development';
+ break;
+ }
+ }
+ run(build.deviceFamily, deployType, target, finished);
+ });
+
+ cli.addHook('codeprocessor.pre.run', function (build, finished) {
+ run('none', 'development', undefined, finished);
+ });
+};
diff --git a/samples/ToDoLite/plugins/ti.alloy/plugin.py b/samples/ToDoLite/plugins/ti.alloy/plugin.py
new file mode 100644
index 0000000..ffc44d1
--- /dev/null
+++ b/samples/ToDoLite/plugins/ti.alloy/plugin.py
@@ -0,0 +1,123 @@
+import os, sys, subprocess, hashlib
+
+import subprocess
+
+def check_output(*popenargs, **kwargs):
+ r"""Run command with arguments and return its output as a byte string.
+
+ Backported from Python 2.7 as it's implemented as pure python on stdlib.
+
+ >>> check_output(['/usr/bin/python', '--version'])
+ Python 2.6.2
+ """
+ process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
+ output, unused_err = process.communicate()
+ retcode = process.poll()
+ if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = popenargs[0]
+ error = subprocess.CalledProcessError(retcode, cmd)
+ error.output = output
+ raise error
+ return output
+
+def compile(config):
+ paths = {}
+ binaries = ["alloy","node"]
+
+ dotAlloy = os.path.abspath(os.path.join(config['project_dir'], 'build', '.alloynewcli'))
+ if os.path.exists(dotAlloy):
+ print "[DEBUG] build/.alloynewcli file found, skipping plugin..."
+ os.remove(dotAlloy)
+ else:
+ for binary in binaries:
+ try:
+ # see if the environment variable is defined
+ paths[binary] = os.environ["ALLOY_" + ("NODE_" if binary == "node" else "") + "PATH"]
+ except KeyError as ex:
+ # next try PATH, and then our guess paths
+ if sys.platform == "darwin" or sys.platform.startswith('linux'):
+ userPath = os.environ["HOME"]
+ guessPaths = [
+ "/usr/local/bin/"+binary,
+ "/opt/local/bin/"+binary,
+ userPath+"/local/bin/"+binary,
+ "/opt/bin/"+binary,
+ "/usr/bin/"+binary,
+ "/usr/local/share/npm/bin/"+binary
+ ]
+
+ try:
+ binaryPath = check_output(["which",binary], stderr=subprocess.STDOUT).strip()
+ print "[DEBUG] %s installed at '%s'" % (binary,binaryPath)
+ except:
+ print "[WARN] Couldn't find %s on your PATH:" % binary
+ print "[WARN] %s" % os.environ["PATH"]
+ print "[WARN]"
+ print "[WARN] Checking for %s in a few default locations:" % binary
+ for p in guessPaths:
+ sys.stdout.write("[WARN] %s -> " % p)
+ if os.path.exists(p):
+ binaryPath = p
+ print "FOUND"
+ break
+ else:
+ print "not found"
+ binaryPath = None
+
+ if binaryPath is None:
+ print "[ERROR] Couldn't find %s" % binary
+ sys.exit(1)
+ else:
+ paths[binary] = binaryPath
+
+ # no guesses on windows, just use the PATH
+ elif sys.platform == "win32":
+ paths["alloy"] = "alloy.cmd"
+
+ f = os.path.abspath(os.path.join(config['project_dir'], 'app'))
+ if os.path.exists(f):
+ print "[INFO] alloy app found at %s" % f
+ rd = os.path.abspath(os.path.join(config['project_dir'], 'Resources'))
+
+ devicefamily = 'none'
+ simtype = 'none'
+ version = '0'
+ deploytype = 'development'
+
+ if config['platform']==u'ios':
+ version = config['iphone_version']
+ devicefamily = config['devicefamily']
+ deploytype = config['deploytype']
+ if config['platform']==u'android':
+ builder = config['android_builder']
+ version = builder.tool_api_level
+ deploytype = config['deploy_type']
+ if config['platform']==u'mobileweb':
+ builder = config['mobileweb_builder']
+ deploytype = config['deploytype']
+
+ cfg = "platform=%s,version=%s,simtype=%s,devicefamily=%s,deploytype=%s," % (config['platform'],version,simtype,devicefamily,deploytype)
+
+ if sys.platform == "win32":
+ cmd = [paths["alloy"], "compile", f, "--no-colors", "--config", cfg]
+ else:
+ cmd = [paths["node"], paths["alloy"], "compile", f, "--no-colors", "--config", cfg]
+
+ print "[INFO] Executing Alloy compile:"
+ print "[INFO] %s" % " ".join(cmd)
+
+ try:
+ print check_output(cmd, stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as ex:
+ if hasattr(ex, 'output'):
+ print ex.output
+ print "[ERROR] Alloy compile failed"
+ retcode = 1
+ if hasattr(ex, 'returncode'):
+ retcode = ex.returncode
+ sys.exit(retcode)
+ except EnvironmentError as ex:
+ print "[ERROR] Unexpected error with Alloy compiler plugin: %s" % ex.strerror
+ sys.exit(2)
diff --git a/samples/ToDoLite/server/.gitignore b/samples/ToDoLite/server/.gitignore
new file mode 100644
index 0000000..ff6ddad
--- /dev/null
+++ b/samples/ToDoLite/server/.gitignore
@@ -0,0 +1 @@
+sync_gateway
diff --git a/samples/ToDoLite/server/README.md b/samples/ToDoLite/server/README.md
new file mode 100644
index 0000000..1ff7da4
--- /dev/null
+++ b/samples/ToDoLite/server/README.md
@@ -0,0 +1,6 @@
+
+Download the sync gateway from http://www.couchbase.com/download#cb-mobile and run
+as follows:
+
+ sync-gateway todolite-config.json
+
diff --git a/samples/ToDoLite/server/todolite-config.json b/samples/ToDoLite/server/todolite-config.json
new file mode 100644
index 0000000..ac77c98
--- /dev/null
+++ b/samples/ToDoLite/server/todolite-config.json
@@ -0,0 +1,50 @@
+{
+ "log": ["CRUD", "REST+", "Access"],
+ "facebook": { "register": true },
+ "databases": {
+ "todos": {
+ "server": "walrus:",
+ "users": {
+ "GUEST": {"disabled": true}
+ },
+ "sync": `
+function(doc, oldDoc) {
+ // NOTE this function is the same across the iOS, Android, and PhoneGap versions.
+ if (doc.type == "task") {
+ if (!doc.list_id) {
+ throw({forbidden : "items must have a list_id"})
+ }
+ channel("list-"+doc.list_id);
+ } else if (doc.type == "list") {
+ channel("list-"+doc._id);
+ if (!doc.owner) {
+ throw({forbidden : "list must have an owner"})
+ }
+ if (oldDoc) {
+ var oldOwnerName = oldDoc.owner.substring(oldDoc.owner.indexOf(":")+1);
+ requireUser(oldOwnerName)
+ }
+ var ownerName = doc.owner.substring(doc.owner.indexOf(":")+1);
+ access(ownerName, "list-"+doc._id);
+ if (Array.isArray(doc.members)) {
+ var memberNames = [];
+ for (var i = doc.members.length - 1; i >= 0; i--) {
+ memberNames.push(doc.members[i].substring(doc.members[i].indexOf(":")+1))
+ };
+ access(memberNames, "list-"+doc._id);
+ }
+ } else if (doc.type == "profile") {
+ channel("profiles");
+ var user = doc._id.substring(doc._id.indexOf(":")+1);
+ if (user !== doc.user_id) {
+ throw({forbidden : "profile user_id must match docid"})
+ }
+ requireUser(user);
+ access(user, "profiles"); // TODO this should use roles
+ }
+}
+
+`
+ }
+ }
+}
diff --git a/samples/ToDoLite/tiapp.xml b/samples/ToDoLite/tiapp.xml
new file mode 100644
index 0000000..6d00bad
--- /dev/null
+++ b/samples/ToDoLite/tiapp.xml
@@ -0,0 +1,75 @@
+
+
+ com.obscure.todolite
+ ToDoLite
+ 1.0
+ Paul Mietz Egli
+ http://github.com/pegli/ti_touchdb
+ not specified
+ 2014 by Paul Mietz Egli
+ appicon.png
+ false
+ false
+ true
+ e3a3af0d-f278-4470-a207-fefb2ed69b37
+ dp
+
+ 501518809925546
+ http://sync.couchbasecloud.com:4984/todos4
+
+
+
+
+ UISupportedInterfaceOrientations~iphone
+
+ UIInterfaceOrientationPortrait
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIRequiresPersistentWiFi
+
+ UIPrerenderedIcon
+
+ UIStatusBarHidden
+
+ UIStatusBarStyle
+ UIStatusBarStyleDefault
+
+
+
+
+
+
+
+ true
+ true
+
+ default
+
+
+ com.obscure.titouchdb
+ com.obscure.titouchdb
+ facebook
+ facebook
+
+
+ true
+ false
+ true
+ true
+ false
+ false
+
+ 3.3.0.GA
+
+ ti.alloy
+
+
diff --git a/samples/TouchBooksAlloy/app/alloy.js b/samples/TouchBooksAlloy/app/alloy.js
index 8b2d3f4..5568dfe 100644
--- a/samples/TouchBooksAlloy/app/alloy.js
+++ b/samples/TouchBooksAlloy/app/alloy.js
@@ -10,7 +10,7 @@ Ti.App.Properties.setBool("LogSyncVerbose", true);
var server = require('com.obscure.titouchdb'),
db = server.databaseManager.getDatabase(Alloy.CFG.books_db_name || 'books');
-db.setFilter('books_only', function(doc,req) {
+db.setFilter('books_only', function(doc, req) {
return doc.modelname === "book";
});
@@ -20,18 +20,23 @@ if (Alloy.CFG.remote_couchdb_server) {
pull.continuous = true;
pull.addEventListener('change', function(e) {
- Ti.API.info(String.format("pull: running: %d, total: %d, completed: %d", !!pull.running, pull.total, pull.completed));
- // if (pull.total > 0 && pull.completed === pull.total) {
+ Ti.API.info(String.format("pull: running: %d, total: %d, completed: %d", !!pull.isRunning, pull.changesCount, pull.completedChangesCount));
+ if (pull.status == server.REPLICATION_MODE_IDLE) {
Ti.App.fireEvent('books:update_from_server');
- // }
+ Ti.API.info("idle pull replication; fired update event");
+ }
});
pull.start();
push.continuous = true;
- // push.filter = 'books_only'; // TODO push filter not working?
+ push.filter = 'books_only'; // TODO push filter not working?
push.addEventListener('change', function(e) {
- Ti.API.info(String.format("push: running: %d, total: %d, completed: %d", !!pull.running, pull.total, pull.completed));
+ Ti.API.info(String.format("push: running: %d, total: %d, completed: %d", !!pull.isRunning, pull.changesCount, pull.completedChangesCount));
+ if (pull.status == server.REPLICATION_MODE_IDLE) {
+ Ti.App.fireEvent('books:update_from_server');
+ Ti.API.info("idle push replication; fired update event");
+ }
});
push.start();
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/Default-568h@2x.png b/samples/TouchBooksAlloy/app/assets/iphone/Default-568h@2x.png
new file mode 100644
index 0000000..e525fdb
Binary files /dev/null and b/samples/TouchBooksAlloy/app/assets/iphone/Default-568h@2x.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/Default-Landscape.png b/samples/TouchBooksAlloy/app/assets/iphone/Default-Landscape.png
index 082688a..45bcaa2 100644
Binary files a/samples/TouchBooksAlloy/app/assets/iphone/Default-Landscape.png and b/samples/TouchBooksAlloy/app/assets/iphone/Default-Landscape.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/Default-Landscape@2x.png b/samples/TouchBooksAlloy/app/assets/iphone/Default-Landscape@2x.png
new file mode 100644
index 0000000..4fd45d0
Binary files /dev/null and b/samples/TouchBooksAlloy/app/assets/iphone/Default-Landscape@2x.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/Default-Portrait.png b/samples/TouchBooksAlloy/app/assets/iphone/Default-Portrait.png
index 426e1f0..67996fd 100644
Binary files a/samples/TouchBooksAlloy/app/assets/iphone/Default-Portrait.png and b/samples/TouchBooksAlloy/app/assets/iphone/Default-Portrait.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/Default-Portrait@2x.png b/samples/TouchBooksAlloy/app/assets/iphone/Default-Portrait@2x.png
new file mode 100644
index 0000000..c67b596
Binary files /dev/null and b/samples/TouchBooksAlloy/app/assets/iphone/Default-Portrait@2x.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/Default.png b/samples/TouchBooksAlloy/app/assets/iphone/Default.png
index b41d4a9..6ad4820 100644
Binary files a/samples/TouchBooksAlloy/app/assets/iphone/Default.png and b/samples/TouchBooksAlloy/app/assets/iphone/Default.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/Default@2x.png b/samples/TouchBooksAlloy/app/assets/iphone/Default@2x.png
index 11e4e4d..62add74 100644
Binary files a/samples/TouchBooksAlloy/app/assets/iphone/Default@2x.png and b/samples/TouchBooksAlloy/app/assets/iphone/Default@2x.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/appicon-72.png b/samples/TouchBooksAlloy/app/assets/iphone/appicon-72.png
new file mode 100644
index 0000000..8fdf5a9
Binary files /dev/null and b/samples/TouchBooksAlloy/app/assets/iphone/appicon-72.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/appicon-72@2x.png b/samples/TouchBooksAlloy/app/assets/iphone/appicon-72@2x.png
new file mode 100644
index 0000000..b3ef1ca
Binary files /dev/null and b/samples/TouchBooksAlloy/app/assets/iphone/appicon-72@2x.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/appicon-Small-50.png b/samples/TouchBooksAlloy/app/assets/iphone/appicon-Small-50.png
new file mode 100644
index 0000000..244b8ec
Binary files /dev/null and b/samples/TouchBooksAlloy/app/assets/iphone/appicon-Small-50.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/appicon-Small.png b/samples/TouchBooksAlloy/app/assets/iphone/appicon-Small.png
new file mode 100644
index 0000000..f74c286
Binary files /dev/null and b/samples/TouchBooksAlloy/app/assets/iphone/appicon-Small.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/appicon-Small@2x.png b/samples/TouchBooksAlloy/app/assets/iphone/appicon-Small@2x.png
new file mode 100644
index 0000000..b94865e
Binary files /dev/null and b/samples/TouchBooksAlloy/app/assets/iphone/appicon-Small@2x.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/appicon.png b/samples/TouchBooksAlloy/app/assets/iphone/appicon.png
index ac74d27..f7bde5b 100644
Binary files a/samples/TouchBooksAlloy/app/assets/iphone/appicon.png and b/samples/TouchBooksAlloy/app/assets/iphone/appicon.png differ
diff --git a/samples/TouchBooksAlloy/app/assets/iphone/appicon@2x.png b/samples/TouchBooksAlloy/app/assets/iphone/appicon@2x.png
new file mode 100644
index 0000000..05ee350
Binary files /dev/null and b/samples/TouchBooksAlloy/app/assets/iphone/appicon@2x.png differ
diff --git a/samples/TouchBooksAlloy/app/controllers/book_list.js b/samples/TouchBooksAlloy/app/controllers/book_list.js
index 4e3bae8..89a73c8 100644
--- a/samples/TouchBooksAlloy/app/controllers/book_list.js
+++ b/samples/TouchBooksAlloy/app/controllers/book_list.js
@@ -2,7 +2,7 @@
var currentView = 'by_author';
var books = Alloy.createCollection("Book");
-books.on('fetch', function(e) {
+books.on('reset', function(e) {
$.tableView.refresh(books);
});
diff --git a/samples/TouchBooksAlloy/app/controllers/edit_book.js b/samples/TouchBooksAlloy/app/controllers/edit_book.js
index 3af87dd..c7b993d 100644
--- a/samples/TouchBooksAlloy/app/controllers/edit_book.js
+++ b/samples/TouchBooksAlloy/app/controllers/edit_book.js
@@ -23,11 +23,10 @@ book.on('update', function() {
});
exports.set_book_id = function(id) {
- book.id = id;
$.isbn.touchEnabled = false;
$.isbn.borderStyle = Ti.UI.INPUT_BORDERSTYLE_NONE;
- book.fetch();
+ book.fetch({ id: id });
};
function changePublished(e) {
diff --git a/samples/TouchBooksAlloy/app/models/Book.js b/samples/TouchBooksAlloy/app/models/Book.js
index 98921d9..74fd084 100644
--- a/samples/TouchBooksAlloy/app/models/Book.js
+++ b/samples/TouchBooksAlloy/app/models/Book.js
@@ -5,8 +5,24 @@ exports.definition = {
type: "titouchdb",
dbname: "books",
views: [
- { name: "by_author", map: function(doc) { if (doc.author) { emit(doc.author, null); } } },
- { name: "by_published", map: function(doc) { if (doc.published && doc.published.length > 0) { emit(doc.published[0], null); } } }
+ {
+ name: "by_author",
+ version: '2',
+ map: function(doc) {
+ if (doc.modelname == 'book' && doc.author) {
+ emit(doc.author, null);
+ }
+ }
+ },
+ {
+ name: "by_published",
+ version: '2',
+ map: function(doc) {
+ if (doc.modelname == 'book' && doc.published && doc.published.length > 0) {
+ emit(doc.published[0], null);
+ }
+ }
+ }
],
view_options: {
prefetch: true
diff --git a/samples/TouchBooksAlloy/app/styles/edit_book.tss b/samples/TouchBooksAlloy/app/styles/edit_book.tss
index ddd04c4..1c383c7 100644
--- a/samples/TouchBooksAlloy/app/styles/edit_book.tss
+++ b/samples/TouchBooksAlloy/app/styles/edit_book.tss
@@ -1,6 +1,5 @@
"Window": {
backgroundColor: "white",
- fullscreen: true,
},
"ScrollView": {
@@ -19,7 +18,7 @@
".label": {
left: "2%",
right: "2%",
- top: "10dp",
+ top: 10,
textAlign: 'left',
font: {
fontWeight: "bold"
@@ -29,17 +28,17 @@
".value": {
left: "2%",
right: "2%",
- top: "6dp",
+ top: 6,
},
"#published": {
- top: "6dp",
+ top: 6,
type: Ti.UI.PICKER_TYPE_DATE
},
"#coverImage": {
- width: "64dp",
- height: "64dp",
+ width: 64,
+ height: 64,
left: "2%",
right: "2%",
top: "2%",
@@ -50,9 +49,9 @@
"#save": {
left: "2%",
right: "2%",
- top: "6dp",
- bottom: "20dp",
- height: "32dp",
+ top: 6,
+ bottom: 20,
+ height: 32,
color: 'white',
backgroundColor: '#0c0',
borderRadius: 8,