-
Notifications
You must be signed in to change notification settings - Fork 5
/
LOArrayController.j
282 lines (249 loc) · 11.2 KB
/
LOArrayController.j
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/*
* LOArrayController.j
*
* Created by Martin Carlberg on Feb 27, 2012.
* Copyright 2012, All rights reserved.
*/
@import <Foundation/CPObject.j>
@import <AppKit/CPArrayController.j>
@class LOInsertEvent
@class LODeleteEvent
@class LOObjectContext
@class LOFetchSpecification
@implementation LOArrayController : CPArrayController
{
@outlet LOObjectContext objectContext @accessors;
CPArray prepareContentBlocksToRunWhenModelIsReceived;
}
- (id)init {
self = [super init];
if (self) {
prepareContentBlocksToRunWhenModelIsReceived = [];
}
return self;
}
- (id)initWithCoder:(CPCoder)aCoder {
prepareContentBlocksToRunWhenModelIsReceived = [];
self = [super initWithCoder:aCoder];
if (self) {
}
return self;
}
- (void)awakeFromCib {
// In 'prepareContent' the bindings will not be ready so blocks are added to this array and executed here.
if (prepareContentBlocksToRunWhenModelIsReceived)
{
var objectStore = [objectContext objectStore];
[prepareContentBlocksToRunWhenModelIsReceived enumerateObjectsUsingBlock:function(aBlock) {
[objectStore addBlockToRunWhenModelIsReceived:aBlock];
}];
prepareContentBlocksToRunWhenModelIsReceived = nil;
}
}
// TODO: We should advertise a 'real' binding for objectContext, not piggyback on managedObjectContext.
- (void)setManagedObjectContext:(id)aContext {
objectContext = aContext;
}
// TODO: We should advertise a 'real' binding for objectContext, not piggyback on managedObjectContext.
- (id)managedObjectContext {
return objectContext;
}
- (void)prepareContent {
var entityName = [self entityName];
if (entityName != nil) {
[prepareContentBlocksToRunWhenModelIsReceived addObject:function() {
[self fetch:nil];
}];
} else {
[super prepareContent];
}
}
/*!
Fetches the objects using fetchPredicate.
@param id sender - The sender of the message.
*/
- (@action)fetch:(id)sender {
var entityName = [self entityName];
if (entityName != nil) {
var aFetchSpecification = [LOFetchSpecification fetchSpecificationForEntityNamed:entityName qualifier:[self fetchPredicate]];
if ([self usesLazyFetching]) {
[aFetchSpecification setOperator:@"lazy"];
}
[objectContext requestObjectsWithFetchSpecification:aFetchSpecification withCompletionHandler:function(resultArray, statusCode) {
if (statusCode === 200)
[self setContent:resultArray];
}];
}
}
/*!
Creates and adds a new object to the receiver's content and arranged objects.
@param id sender - The sender of the message.
*/
- (void)add:(id)sender {
if (![self canAdd])
return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject];
var entityName = [self entityName];
if (entityName) newObject._loObjectType = entityName;
[self insertAndRegisterObject:newObject atArrangedObjectIndex:nil];
}
/*!
Creates a new object and inserts it into the receiver's content array.
@param id sender - The sender of the message.
*/
- (@action)insert:(id)sender {
if (![self canInsert])
return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject];
var entityName = [self entityName];
var lastSelectedIndex = [_selectionIndexes lastIndex];
if (entityName) newObject._loObjectType = entityName;
[self insertAndRegisterObject:newObject atArrangedObjectIndex:lastSelectedIndex];
}
- (id)_defaultNewObject {
var objectClass;
if (objectContext) {
var objectStore = [objectContext objectStore];
if (objectStore) {
var entityName = [self entityName];
var entityDescription = [objectStore entityForName:entityName];
if (entityDescription) {
var classname = [entityDescription externalName];
if (classname) {
objectClass = CPClassFromString(classname);
}
}
}
}
if (objectClass == nil) {
objectClass = [self objectClass];
}
return [[objectClass alloc] init];
}
- (void)insertAndRegisterObject:(id)newObject atArrangedObjectIndex:(int)index {
if (![self canInsert])
return;
if (index != nil && index !== CPNotFound)
[self insertObject:newObject atArrangedObjectIndex:index];
else
[self addObject:newObject];
// Ok, now we need to tell the object context that we have this new object and it is a new relationship for the owner object.
// This might not be the best way to do this but it will do for now.
// We check the contentArray bindings to get hold of the owner object.
// TODO: Use model instead of bindings to find owner object if possible
var info = [self infoForBinding:@"contentArray"];
var bindingKeyPath = [info objectForKey:CPObservedKeyPathKey];
var keyPathComponents = [bindingKeyPath componentsSeparatedByString:@"."];
var lastbindingKeyPath = [keyPathComponents objectAtIndex:[keyPathComponents count] - 1];
var bindToObject = [info objectForKey:CPObservedObjectKey];
var selectedOwnerObjects = [bindToObject selectedObjects];
var registeredOwnerObjects = [CPMutableArray array];
var selectedOwnerObjectsSize = [selectedOwnerObjects count];
for (var i = 0; i < selectedOwnerObjectsSize; i++) {
var selectedOwnerObject = [selectedOwnerObjects objectAtIndex:i];
if ([selectedOwnerObject isKindOfClass:CPControllerSelectionProxy]) {
selectedOwnerObject = [[selectedOwnerObject._controller selectedObjects] objectAtIndex:0];
}
if ([objectContext isObjectRegistered:selectedOwnerObject]) {
[registeredOwnerObjects addObject:selectedOwnerObject];
[objectContext _add:newObject toRelationshipWithKey:lastbindingKeyPath forObject:selectedOwnerObject];
}
}
var insertEvent = [LOInsertEvent insertEventWithObject:newObject arrayController:self ownerObjects:[registeredOwnerObjects count] ? registeredOwnerObjects : nil ownerRelationshipKey:lastbindingKeyPath];
[objectContext registerEvent:insertEvent];
[objectContext _insertObject:newObject];
if ([objectContext autoCommit]) [objectContext saveChanges];
}
- (id) unInsertObject:(id)object ownerObjects:(CPArray) ownerObjects ownerRelationshipKey:(CPString) ownerRelationshipKey {
[self _removeObjects:[object]];
if (ownerObjects && ownerRelationshipKey) {
var size = [ownerObjects count];
for (var i = 0; i < size; i++) {
var ownerObject = [ownerObjects objectAtIndex:i];
[objectContext _unAdd:object toRelationshipWithKey:ownerRelationshipKey forObject:ownerObject];
}
}
}
- (void)removeObjects:(CPArray)objectsToDelete {
var objectsToDeleteIndexes = [CPMutableIndexSet indexSet];
[objectsToDelete enumerateObjectsUsingBlock:function(aCandidate) {
var anIndex = [[self arrangedObjects] indexOfObjectIdenticalTo:aCandidate];
if (anIndex === CPNotFound) {
[CPException raise:CPInvalidArgumentException reason:@"Can't delete object not in array controller: " + aCandidate];
}
[objectsToDeleteIndexes addIndex:anIndex];
}];
[self _removeObjects:objectsToDelete atIndexes:objectsToDeleteIndexes shouldRegisterEvent:YES];
}
- (void)_removeObjects:(CPArray)objectsToDelete {
var objectsToDeleteIndexes = [CPMutableIndexSet indexSet];
[objectsToDelete enumerateObjectsUsingBlock:function(aCandidate) {
var anIndex = [[self arrangedObjects] indexOfObjectIdenticalTo:aCandidate];
if (anIndex === CPNotFound) {
[CPException raise:CPInvalidArgumentException reason:@"Can't delete object not in array controller: " + aCandidate];
}
[objectsToDeleteIndexes addIndex:anIndex];
}];
[self _removeObjects:objectsToDelete atIndexes:objectsToDeleteIndexes shouldRegisterEvent:NO];
}
- (void)remove:(id)sender {
var selectedObjectsIndexes = [[self selectionIndexes] copy];
var selectedObjects = [self selectedObjects];
[self _removeObjects:selectedObjects atIndexes:selectedObjectsIndexes shouldRegisterEvent:YES];
}
- (void)_removeObjects:(CPArray)objectsToDelete atIndexes:(CPIndexSet)objectsToDeleteIndexes shouldRegisterEvent:(BOOL)shouldRegisterEvent {
// Note: assumes objectsToDeleteIndexes corresponds to objectsToDelete.
[self removeObjectsAtArrangedObjectIndexes:objectsToDeleteIndexes];
// Ok, now we need to tell the object context that we have this removed object and it is a removed relationship for the owner object.
// This might not be the best way to do this but it will do for now.
var registeredOwnerObjects = [CPMutableArray array];
var lastbindingKeyPath = nil;
[objectsToDelete enumerateObjectsUsingBlock:function(deletedObject) {
var info = [self infoForBinding:@"contentArray"];
var bindingKeyPath = [info objectForKey:CPObservedKeyPathKey];
var keyPathComponents = [bindingKeyPath componentsSeparatedByString:@"."];
lastbindingKeyPath = [keyPathComponents objectAtIndex:[keyPathComponents count] - 1];
var bindToObject = [info objectForKey:CPObservedObjectKey];
[[bindToObject selectedObjects] enumerateObjectsUsingBlock:function(selectedOwnerObject) {
if ([objectContext isObjectRegistered:selectedOwnerObject]) {
[registeredOwnerObjects addObject:selectedOwnerObject];
[objectContext _delete:deletedObject withRelationshipWithKey:lastbindingKeyPath forObject:selectedOwnerObject];
}
}];
}];
if (shouldRegisterEvent) {
var deleteEvent = [LODeleteEvent deleteEventWithObjects:objectsToDelete atArrangedObjectIndexes:objectsToDeleteIndexes arrayController:self ownerObjects:[registeredOwnerObjects count] ? registeredOwnerObjects : nil ownerRelationshipKey:lastbindingKeyPath];
[objectContext registerEvent:deleteEvent];
[objectContext deleteObjects: objectsToDelete]; // this will commit if auto commit is enabled
}
}
- (id) unDeleteObjects:(id)objects atArrangedObjectIndexes:(CPIndexSet)indexSet ownerObjects:(CPArray) ownerObjects ownerRelationshipKey:(CPString) ownerRelationshipKey {
var objectSize = [objects count];
var index = [indexSet firstIndex];
for (var i = 0; i < objectSize; i++) {
var object = [objects objectAtIndex:i];
[self insertObject:object atArrangedObjectIndex:index];
if (ownerObjects && ownerRelationshipKey) {
var size = [ownerObjects count];
for (var j = 0; j < size; j++) {
var ownerObject = [ownerObjects objectAtIndex:j];
[objectContext _unAdd:object toRelationshipWithKey:ownerRelationshipKey forObject:ownerObject];
}
}
index = [indexSet indexGreaterThanIndex:index];
}
}
/*
- (CPArray) arrangeObjects: (CPArray) objects {
var testArray = [[_CPKVCArray alloc] init];
var testArrayCopy = [testArray copy];
//CPLog.trace(@"tracing: arrangeObjects: class = " + [objects class]);
if ([objects className] === @"_CPKVCArray") {
debugger;
}
var copy = [objects copy];
//CPLog.trace(@"tracing: arrangeObjects: " + [CPString JSONFromObject:copy]);
[super arrangeObjects:objects];
}
*/
@end