29 var exposedBindingsMap = @{},
32 var CPBindingOperationAnd = 0,
33 CPBindingOperationOr = 1;
40 JSObject _suppressedNotifications;
41 JSObject _placeholderForMarker;
44 + (void)exposeBinding:(
CPString)aBinding forClass:(Class)aClass
46 var bindings = [exposedBindingsMap objectForKey:[aClass UID]];
51 [exposedBindingsMap setObject:bindings forKey:[aClass UID]];
54 bindings.push(aBinding);
57 + (CPArray)exposedBindingsForClass:(Class)aClass
59 return [[exposedBindingsMap objectForKey:[aClass UID]] copy];
64 return [[bindingsMap objectForKey:[anObject UID]] objectForKey:aBinding];
69 var theBinding = [
self getBinding:aBinding forObject:anObject];
72 return theBinding._info;
79 return [bindingsMap objectForKey:[anObject UID]];
82 + (void)unbind:(
CPString)aBinding forObject:(
id)anObject
84 var bindings = [bindingsMap objectForKey:[anObject UID]];
89 var theBinding = [bindings objectForKey:aBinding];
94 var info = theBinding._info,
95 observedObject = [info objectForKey:CPObservedObjectKey],
96 keyPath = [info objectForKey:CPObservedKeyPathKey];
98 [observedObject removeObserver:theBinding forKeyPath:keyPath];
99 [bindings removeObjectForKey:aBinding];
102 + (void)unbindAllForObject:(
id)anObject
104 var bindings = [bindingsMap objectForKey:[anObject UID]];
109 var allKeys = [bindings allKeys],
110 count = allKeys.length;
113 [anObject unbind:[bindings objectForKey:allKeys[count]]];
115 [bindingsMap removeObjectForKey:[anObject UID]];
128 CPObservedObjectKey: aDestination,
129 CPObservedKeyPathKey: aKeyPath,
131 _suppressedNotifications = {};
132 _placeholderForMarker = {};
135 [_info setObject:options forKey:CPOptionsKey];
137 [
self _updatePlaceholdersWithOptions:options forBinding:aName];
139 [aDestination addObserver:self forKeyPath:aKeyPath options:CPKeyValueObservingOptionNew context:aBinding];
141 var bindings = [bindingsMap objectForKey:[_source UID]];
146 [bindingsMap setObject:bindings forKey:[_source UID]];
149 [bindings setObject:self forKey:aName];
150 [
self setValueFor:aBinding];
156 - (void)raiseIfNotApplicable:(
id)aValue forKeyPath:(
CPString)keyPath options:(
CPDictionary)options
158 if (aValue === CPNotApplicableMarker && [options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
161 reason:@"Cannot transform non-applicable key on: " + _source + " key path: " + keyPath + " value: " + aValue];
165 - (void)setValueFor:(
CPString)theBinding
167 var destination = [_info objectForKey:CPObservedObjectKey],
168 keyPath = [_info objectForKey:CPObservedKeyPathKey],
169 options = [_info objectForKey:CPOptionsKey],
170 newValue = [destination valueForKeyPath:keyPath];
172 if (CPIsControllerMarker(newValue))
174 [
self raiseIfNotApplicable:newValue forKeyPath:keyPath options:options];
176 var value = [
self _placeholderForMarker:newValue];
177 [
self setPlaceholderValue:value withMarker:newValue forBinding:theBinding];
181 var value = [
self transformValue:newValue withOptions:options];
182 [
self setValue:value forBinding:theBinding];
186 - (void)setPlaceholderValue:(
id)aValue withMarker:(
CPString)aMarker forBinding:(
CPString)aBinding
188 [_source setValue:aValue forKey:aBinding];
191 - (void)setValue:(
id)aValue forBinding:(
CPString)aBinding
193 [_source setValue:aValue forKey:aBinding];
196 - (void)reverseSetValueFor:(
CPString)aBinding
198 var destination = [_info objectForKey:CPObservedObjectKey],
199 keyPath = [_info objectForKey:CPObservedKeyPathKey],
200 options = [_info objectForKey:CPOptionsKey],
201 newValue = [
self valueForBinding:aBinding];
203 newValue = [
self reverseTransformValue:newValue withOptions:options];
205 [
self suppressSpecificNotificationFromObject:destination keyPath:keyPath];
206 [destination setValue:newValue forKeyPath:keyPath];
207 [
self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
210 - (id)valueForBinding:(
CPString)aBinding
212 return [_source valueForKeyPath:aBinding];
215 - (void)observeValueForKeyPath:(
CPString)aKeyPath ofObject:(
id)anObject change:(
CPDictionary)changes context:(
id)context
220 var objectSuppressions = _suppressedNotifications[[anObject UID]];
222 if (objectSuppressions && objectSuppressions[aKeyPath])
225 [
self setValueFor:context];
228 - (id)transformValue:(
id)aValue withOptions:(
CPDictionary)options
230 var valueTransformerName = [options
objectForKey:CPValueTransformerNameBindingOption],
233 if (valueTransformerName)
237 if (!valueTransformer)
241 if (valueTransformerClass)
243 valueTransformer = [[valueTransformerClass alloc] init];
244 [valueTransformerClass setValueTransformer:valueTransformer forName:valueTransformerName];
249 valueTransformer = [options
objectForKey:CPValueTransformerBindingOption];
251 if (valueTransformer)
252 aValue = [valueTransformer transformedValue:aValue];
257 if ((aValue === undefined || aValue === nil || aValue === [
CPNull null])
258 && ![_source respondsToSelector:
@selector(setPlaceholderString:)])
259 aValue = [options
objectForKey:CPNullPlaceholderBindingOption] || nil;
264 - (id)reverseTransformValue:(
id)aValue withOptions:(
CPDictionary)options
266 var valueTransformerName = [options
objectForKey:CPValueTransformerNameBindingOption],
269 if (valueTransformerName)
272 valueTransformer = [options
objectForKey:CPValueTransformerBindingOption];
274 if (valueTransformer && [[valueTransformer
class] allowsReverseTransformation])
275 aValue = [valueTransformer reverseTransformedValue:aValue];
280 - (BOOL)continuouslyUpdatesValue
282 var options = [_info objectForKey:CPOptionsKey];
283 return [[options objectForKey:CPContinuouslyUpdatesValueBindingOption] boolValue];
286 - (BOOL)handlesContentAsCompoundValue
288 var options = [_info objectForKey:CPOptionsKey];
289 return [[options objectForKey:CPHandlesContentAsCompoundValueBindingOption] boolValue];
295 - (void)suppressSpecificNotificationFromObject:(
id)anObject keyPath:(
CPString)aKeyPath
300 var uid = [anObject UID],
301 objectSuppressions = _suppressedNotifications[uid];
303 if (!objectSuppressions)
304 _suppressedNotifications[uid] = objectSuppressions = {};
306 objectSuppressions[aKeyPath] = YES;
312 - (void)unsuppressSpecificNotificationFromObject:(
id)anObject keyPath:(
CPString)aKeyPath
317 var uid = [anObject UID],
318 objectSuppressions = _suppressedNotifications[uid];
320 if (!objectSuppressions)
323 delete objectSuppressions[aKeyPath];
326 - (void)_updatePlaceholdersWithOptions:(
CPDictionary)options
328 var count = [CPBinderPlaceholderMarkers count];
332 var marker = CPBinderPlaceholderMarkers[count],
333 optionName = CPBinderPlaceholderOptions[count],
335 placeholder = isExplicit ? [options
objectForKey:optionName] : nil;
337 [
self _setPlaceholder:placeholder forMarker:marker isDefault:!isExplicit];
343 [
self _updatePlaceholdersWithOptions:options];
346 - (JSObject)_placeholderForMarker:(
id)aMarker
348 var placeholder = _placeholderForMarker[[aMarker UID]];
351 return placeholder.value;
356 - (void)_setPlaceholder:(
id)aPlaceholder forMarker:(
id)aMarker isDefault:(BOOL)isDefault
360 var existingPlaceholder = _placeholderForMarker[[aMarker UID]];
363 if (existingPlaceholder && !existingPlaceholder.isDefault)
367 _placeholderForMarker[[aMarker UID]] = {
'isDefault': isDefault,
'value': aPlaceholder };
372 @implementation CPObject (KeyValueBindingCreation)
374 + (void)exposeBinding:(
CPString)aBinding
376 [
CPBinder exposeBinding:aBinding forClass:[
self class]];
379 + (Class)_binderClassForBinding:(
CPString)aBinding
384 - (CPArray)exposedBindings
386 var exposedBindings = [],
387 theClass = [
self class];
391 var temp = [
CPBinder exposedBindingsForClass:theClass];
394 [exposedBindings addObjectsFromArray:temp];
396 theClass = [theClass superclass];
399 return exposedBindings;
402 - (Class)valueClassForBinding:(
CPString)binding
409 if (!anObject || !aKeyPath)
410 return CPLog.error(
"Invalid object or path on " +
self +
" for " + aBinding);
415 var binderClass = [[
self class] _binderClassForBinding:aBinding];
417 [
self unbind:aBinding];
418 [[binderClass alloc] initWithBinding:[
self _replacementKeyPathForBinding:aBinding] name:aBinding to:anObject keyPath:aKeyPath options:options from:self];
423 return [
CPBinder infoForBinding:aBinding forObject:self];
428 var binderClass = [[
self class] _binderClassForBinding:aBinding];
429 [binderClass unbind:aBinding forObject:self];
447 @implementation _CPValueBinder :
CPBinder
452 - (void)setValueFor:(
CPString)theBinding
454 [
super setValueFor:@"objectValue"];
457 - (void)reverseSetValueFor:(
CPString)theBinding
459 [
super reverseSetValueFor:@"objectValue"];
464 @implementation _CPMultipleValueBooleanBinding :
CPBinder
466 CPBindingOperationKind _operation;
469 - (void)setValueFor:(
CPString)aBinding
471 var bindings = [bindingsMap valueForKey:[_source UID]];
476 var baseBinding = aBinding.replace(/\d$/,
"");
478 [_source setValue:[
self resolveMultipleValuesForBinding:baseBinding bindings:bindings booleanOperation:_operation] forKey:baseBinding];
481 - (void)reverseSetValueFor:(
CPString)theBinding
486 - (void)observeValueForKeyPath:(
CPString)aKeyPath ofObject:(
id)anObject change:(
CPDictionary)changes context:(
id)context
488 [
self setValueFor:context];
491 - (BOOL)resolveMultipleValuesForBinding:(
CPString)aBinding bindings:(
CPDictionary)bindings booleanOperation:(CPBindingOperationKind)operation
493 var bindingName = aBinding,
497 while (theBinding = [bindings objectForKey:bindingName])
499 var info = theBinding._info,
500 object = [info objectForKey:CPObservedObjectKey],
501 keyPath = [info objectForKey:CPObservedKeyPathKey],
502 options = [info objectForKey:CPOptionsKey],
503 value = [object valueForKeyPath:keyPath];
505 if (CPIsControllerMarker(value))
507 [
self raiseIfNotApplicable:value forKeyPath:keyPath options:options];
508 value = [theBinding _placeholderForMarker:value];
511 value = [theBinding transformValue:value withOptions:options];
513 if (operation === CPBindingOperationOr)
524 bindingName = aBinding + (count++);
528 return operation === CPBindingOperationOr ? NO : YES;
539 if (
self = [super init])
540 _operation = CPBindingOperationAnd;
553 if (
self = [super init])
554 _operation = CPBindingOperationOr;
561 @implementation _CPMultipleValueActionBinding :
CPBinder
567 - (void)setValueFor:(
CPString)theBinding
570 [
self checkForNullBinding:theBinding initializing:YES];
573 - (void)reverseSetValueFor:(
CPString)theBinding
578 - (void)observeValueForKeyPath:(
CPString)aKeyPath ofObject:(
id)anObject change:(
CPDictionary)changes context:(
id)context
581 [
self checkForNullBinding:context initializing:NO];
592 - (void)checkForNullBinding:(
CPString)theBinding initializing:(BOOL)isInitializing
595 if (![_source isKindOfClass:
CPButton])
600 if (isInitializing && theBinding === CPArgumentBinding)
601 [_source setEnabled:YES];
603 var bindings = [bindingsMap valueForKey:[_source UID]],
604 binding = [bindings objectForKey:theBinding],
605 info = binding._info,
606 options = [info objectForKey:CPOptionsKey];
608 if (![options valueForKey:CPAllowsNullArgumentBindingOption])
610 var
object = [info objectForKey:CPObservedObjectKey],
611 keyPath = [info objectForKey:CPObservedKeyPathKey],
612 value = [object valueForKeyPath:keyPath];
614 if (value === nil || value === undefined)
616 [_source setEnabled:NO];
623 [_source setEnabled:YES];
628 var bindings = [bindingsMap valueForKey:[_source UID]],
629 theBinding = [bindings objectForKey:CPTargetBinding],
631 info = theBinding._info,
632 object = [info objectForKey:CPObservedObjectKey],
633 keyPath = [info objectForKey:CPObservedKeyPathKey],
634 options = [info objectForKey:CPOptionsKey],
636 target = [object valueForKeyPath:keyPath],
637 selector = [options
objectForKey:CPSelectorNameBindingOption];
639 if (!target || !selector)
643 bindingName = CPArgumentBinding,
646 while (theBinding = [bindings objectForKey:bindingName])
648 info = theBinding._info;
649 object = [info objectForKey:CPObservedObjectKey];
650 keyPath = [info objectForKey:CPObservedKeyPathKey];
652 [invocation setArgument:[object valueForKeyPath:keyPath] atIndex:++count];
654 bindingName = CPArgumentBinding + count;
657 [invocation setSelector:selector];
658 [invocation invokeWithTarget:target];
669 if (
self = [super init])
671 _argumentBinding = CPArgumentBinding;
672 _targetBinding = CPTargetBinding;
686 if (
self = [super init])
688 _argumentBinding = CPArgumentBinding;
689 _targetBinding = CPTargetBinding;
700 @implementation _CPPatternBinding :
CPBinder
706 - (void)setValueFor:(
CPString)aBinding
708 var bindings = [bindingsMap valueForKey:[_source UID]];
714 var baseBinding = aBinding.replace(/\d$/,
""),
715 result = [
self resolveMultipleValuesForBindings:bindings];
717 if (result.isPlaceholder)
718 [
self setPlaceholderValue:result.value withMarker:result.marker forBinding:baseBinding];
720 [
self setValue:result.value forBinding:baseBinding];
723 - (void)reverseSetValueFor:(
CPString)theBinding
728 - (void)observeValueForKeyPath:(
CPString)aKeyPath ofObject:(
id)anObject change:(
CPDictionary)changes context:(
id)context
730 [
self setValueFor:context];
733 - (JSObject)resolveMultipleValuesForBindings:(
CPDictionary)bindings
736 result = { value:
@"", isPlaceholder:NO, marker:nil };
738 for (var count = 1; theBinding = [bindings
objectForKey:_bindingKey + count]; ++count)
740 var info = theBinding._info,
741 object = [info objectForKey:CPObservedObjectKey],
742 keyPath = [info objectForKey:CPObservedKeyPathKey],
743 options = [info objectForKey:CPOptionsKey],
744 value = [object valueForKeyPath:keyPath];
747 result.value = [options objectForKey:CPDisplayPatternBindingOption];
749 if (CPIsControllerMarker(value))
751 [
self raiseIfNotApplicable:value forKeyPath:keyPath options:options];
753 result.isPlaceholder = YES;
754 result.marker = value;
756 value = [theBinding _placeholderForMarker:value];
759 value = [theBinding transformValue:value withOptions:options];
761 if (value === nil || value === undefined)
764 result.value = result.value.replace(
"%{" + _patternPlaceholder + count +
"}@", [value description]);
785 if (
self = [super init])
787 _bindingKey = CPDisplayPatternValueBinding;
788 _patternPlaceholder =
@"value";
809 if (
self = [super init])
811 _bindingKey = CPDisplayPatternTitleBinding;
812 _patternPlaceholder =
@"title";
820 @implementation _CPStateMarker :
CPObject
827 if (
self = [super init])
835 return "<" + _name + ">";
844 CPObservedObjectKey =
@"CPObservedObjectKey";
845 CPObservedKeyPathKey =
@"CPObservedKeyPathKey";
846 CPOptionsKey =
@"CPOptionsKey";
849 CPNoSelectionMarker = [[_CPStateMarker alloc] initWithName:@"NO SELECTION MARKER"];
850 CPMultipleValuesMarker = [[_CPStateMarker alloc] initWithName:@"MULTIPLE VALUES MARKER"];
851 CPNotApplicableMarker = [[_CPStateMarker alloc] initWithName:@"NOT APPLICABLE MARKER"];
852 CPNullMarker = [[_CPStateMarker alloc] initWithName:@"NULL MARKER"];
855 CPAlignmentBinding =
@"alignment";
856 CPArgumentBinding =
@"argument";
857 CPContentArrayBinding =
@"contentArray";
858 CPContentBinding =
@"content";
859 CPContentObjectBinding =
@"contentObject";
860 CPContentObjectsBinding =
@"contentObjects";
861 CPContentValuesBinding =
@"contentValues";
862 CPDisplayPatternTitleBinding =
@"displayPatternTitle";
863 CPDisplayPatternValueBinding =
@"displayPatternValue";
864 CPDoubleClickArgumentBinding =
@"doubleClickArgument";
865 CPDoubleClickTargetBinding =
@"doubleClickTarget";
866 CPEditableBinding =
@"editable";
867 CPEnabledBinding =
@"enabled";
868 CPFontBinding =
@"font";
869 CPFontNameBinding =
@"fontName";
870 CPFontBoldBinding =
@"fontBold";
871 CPHiddenBinding =
@"hidden";
872 CPFilterPredicateBinding =
@"filterPredicate";
873 CPMaxValueBinding =
@"maxValue";
874 CPMinValueBinding =
@"minValue";
875 CPPredicateBinding =
@"predicate";
876 CPSelectedIndexBinding =
@"selectedIndex";
877 CPSelectedLabelBinding =
@"selectedLabel";
878 CPSelectedObjectBinding =
@"selectedObject";
879 CPSelectedObjectsBinding =
@"selectedObjects";
880 CPSelectedTagBinding =
@"selectedTag";
881 CPSelectedValueBinding =
@"selectedValue";
882 CPSelectedValuesBinding =
@"selectedValues";
883 CPSelectionIndexesBinding =
@"selectionIndexes";
884 CPTargetBinding =
@"target";
885 CPTextColorBinding =
@"textColor";
886 CPTitleBinding =
@"title";
887 CPToolTipBinding =
@"toolTip";
888 CPValueBinding =
@"value";
889 CPValueURLBinding =
@"valueURL";
890 CPValuePathBinding =
@"valuePath";
891 CPDataBinding =
@"data";
894 CPAllowsEditingMultipleValuesSelectionBindingOption =
@"CPAllowsEditingMultipleValuesSelection";
895 CPAllowsNullArgumentBindingOption =
@"CPAllowsNullArgument";
896 CPConditionallySetsEditableBindingOption =
@"CPConditionallySetsEditable";
897 CPConditionallySetsEnabledBindingOption =
@"CPConditionallySetsEnabled";
898 CPConditionallySetsHiddenBindingOption =
@"CPConditionallySetsHidden";
899 CPContinuouslyUpdatesValueBindingOption =
@"CPContinuouslyUpdatesValue";
900 CPCreatesSortDescriptorBindingOption =
@"CPCreatesSortDescriptor";
901 CPDeletesObjectsOnRemoveBindingsOption =
@"CPDeletesObjectsOnRemove";
902 CPDisplayNameBindingOption =
@"CPDisplayName";
903 CPDisplayPatternBindingOption =
@"CPDisplayPattern";
904 CPHandlesContentAsCompoundValueBindingOption =
@"CPHandlesContentAsCompoundValue";
905 CPInsertsNullPlaceholderBindingOption =
@"CPInsertsNullPlaceholder";
906 CPInvokesSeparatelyWithArrayObjectsBindingOption =
@"CPInvokesSeparatelyWithArrayObjects";
907 CPMultipleValuesPlaceholderBindingOption =
@"CPMultipleValuesPlaceholder";
908 CPNoSelectionPlaceholderBindingOption =
@"CPNoSelectionPlaceholder";
909 CPNotApplicablePlaceholderBindingOption =
@"CPNotApplicablePlaceholder";
910 CPNullPlaceholderBindingOption =
@"CPNullPlaceholder";
911 CPPredicateFormatBindingOption =
@"CPPredicateFormat";
912 CPRaisesForNotApplicableKeysBindingOption =
@"CPRaisesForNotApplicableKeys";
913 CPSelectorNameBindingOption =
@"CPSelectorName";
914 CPSelectsAllWhenSettingContentBindingOption =
@"CPSelectsAllWhenSettingContent";
915 CPValidatesImmediatelyBindingOption =
@"CPValidatesImmediately";
916 CPValueTransformerNameBindingOption =
@"CPValueTransformerName";
917 CPValueTransformerBindingOption =
@"CPValueTransformer";
919 CPIsControllerMarker =
function(anObject)
921 return anObject === CPMultipleValuesMarker || anObject === CPNoSelectionMarker || anObject === CPNotApplicableMarker || anObject === CPNullMarker;
924 var CPBinderPlaceholderMarkers = [CPMultipleValuesMarker, CPNoSelectionMarker, CPNotApplicableMarker, CPNullMarker],
925 CPBinderPlaceholderOptions = [CPMultipleValuesPlaceholderBindingOption, CPNoSelectionPlaceholderBindingOption, CPNotApplicablePlaceholderBindingOption, CPNullPlaceholderBindingOption];
928 @implementation CPBinder (CPSynthesizedAccessors)