I have a CheckBox which I have extended to add a IsReadOnly property. The property has a Coerce callback, which will look up access rights for the current user, and return true, if the user does not have permissions:
private static object CoerceIsReadOnly(DependencyObject d, object baseValue)
{
var owner = (CheckBox)d;
bool isReadOnly = (bool)baseValue;
if (isReadOnly == false)
{
if (AccessControl.HasFullAccess(owner) == false)
{
return true; // If we do not have access, and read only is false, we coerce it to true
}
}
return baseValue;
}
Now I did some experiments to look at the different values:
XAML
<vc:CheckBox Content="AccessControl" x:Name="ChkBoxAccessControl" Margin="5" vc:AccessControl.OverrideAccessRights="NoAccess"/>
Code-behind
var defaultValue = CheckBox.IsReadOnlyProperty.GetMetadata(typeof(CheckBox)).DefaultValue; // False
var localValue = ChkBoxAccessControl.ReadLocalValue(CheckBox.IsReadOnlyProperty); // Unset (we didn't set a value in code or XAML)
var actualValue = ChkBoxAccessControl.GetValue(CheckBox.IsReadOnlyProperty); // True (coerced)
ChkBoxAccessControl.SetValue(CheckBox.IsReadOnlyProperty, false);
var localValue2 = ChkBoxAccessControl.ReadLocalValue(CheckBox.IsReadOnlyProperty); // False
var actualValue2 = ChkBoxAccessControl.GetValue(CheckBox.IsReadOnlyProperty); // True
ChkBoxAccessControl.ClearValue(CheckBox.IsReadOnlyProperty);
var localValue3 = ChkBoxAccessControl.ReadLocalValue(CheckBox.IsReadOnlyProperty); // Unser
var actualValue3 = ChkBoxAccessControl.GetValue(CheckBox.IsReadOnlyProperty); // false??? WTH...
So to my surprise, the last actualValue3 returns false.
Apparently when calling ClearValue, it does not call the coerce callback. Is this to be expected? Can anyone explain why this would be correct?
Is this to be expected?
Yes, see this answer. When you clear the value, you get the default non-coerced one.
Also, actualValue isn't coerced in your example but actualValue2 is.
A dependency property stores and remembers the non-coerced value. You can read more about this here.
Related
private List<Report> _reports = new List<Report>();
public Report CurrentReport
{
get { return _reports[_componentIterator]; }
set { _reports[_componentIterator] = value; }
}
I have a _reports field and it's a list of Report objects.
I use a CurrentReport property to access current Report object based on _componentIterator.
How do I bind to some of Report properties so that changing _componentIterator won't break my binding?
If I bind like this, every _componentIterator change breaks binding.
Binding designatorTextBlockBinding = new Binding(nameof(CurrentReport.Designator));
designatorTextBlockBinding.Source = CurrentReport;
_artifactControl.DesignatorTextBlock.SetBinding(Controls.TextBlock.TextProperty, designatorTextBlockBinding);
Declare the Binding like shown below, and make sure the CurrentReport property fires a change notification.
var designatorTextBlockBinding = new Binding
{
Path = new PropertyPath("CurrentReport.Designator"),
Source = this
};
or
var designatorTextBlockBinding = new Binding
{
Path = new PropertyPath(
nameof(CurrentReport) + "." + nameof(CurrentReport.Designator)),
Source = this
};
or
var designatorTextBlockBinding = new Binding(
nameof(CurrentReport) + "." + nameof(CurrentReport.Designator))
{
Source = this
};
I need to serialize an object to json in angular 2.0.0-rc1 when I found out that Typescript's private isn't private at all, and get set property are not outputted through JSON.stringify.
So I set out to decorate the class:
//method decorator
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
//property decorator
function exclude(target: any, propertyKey: string): any {
return { enumerable: false };
}
class MyClass {
test: string = "test";
#exclude
testExclude: string = "should be excluded";
#enumerable(true)
get enumerated(): string {
return "yes";
}
#enumerable(false)
get nonEnumerated(): string {
return "non enumerable"
}
}
let x = new MyClass();
//1st
console.log(JSON.stringify(x));
//2nd
console.log(JSON.stringify(x, Object.keys(MyClass.prototype)));
//3rd
console.log(JSON.stringify(x, Object.keys(x).concat(Object.keys(MyClass.prototype))));//test 3
on Typescript playground, this gives
{"test":"test"}
{"enumerated":"yes"}
{"test":"test","enumerated":"yes"}
but on my project (angular 2.0.0-rc1), this gives
{"test":"test","testExclude":"should be excluded"}
{"enumerated":"yes"}
{"test":"test","testExclude":"should be excluded","enumerated":"yes"}
What I'm really after is output #3 from the playground.
After taking a look at the transpiled code,
the only difference is reflect-metadata's code:
//snip ...
__decorate([
exclude,
__metadata('design:type', String)
], MyClass.prototype, "testExclude", void 0);
__decorate([
enumerable(true),
__metadata('design:type', String)
], MyClass.prototype, "enumerated", null);
__decorate([
enumerable(false),
__metadata('design:type', String)
], MyClass.prototype, "nonEnumerated", null);
return MyClass;
}());
none of that __metadata lines in playground.
What's happening in here? And how can I achieve playground's #3 result on my project?
Fixed it (or might be just a workaround).
Notice that in playground, Reflect-metadata is not available. Property decorators can return an object to be assigned (ORed) to the descriptor to change its behaviour. In angular environment, Reflect-metadata (specifically Reflect.decorate()) is used instead to decorate things.
After reading up on reflect-metadata doc, and this, apparently there's no way to change PropertyDescriptor on property decorator since it is tied to the constructor instead of the prototype. A solution(workaround) would be to recreate the property with the new descriptor.
function include(value: boolean) {
return function (target: any, propertyKey: string): any {
// Buffer the value
var _val = target[propertyKey];
// Delete property.
if (delete target[propertyKey]) {
// Create new property with getter and setter
Object.defineProperty(target, propertyKey, {
get: () => _val,
set: (newVal) => _val = newVal,
enumerable: value,
configurable: true
});
}
}
}
the factory is only needed so I could use #include(false) instead of #exclude.
Only drawback is the fact that the property now tied to the prototype, hence normal JSON.stringify(instance) would not serialize it.
On that note, we can further make a generic decorator usable both in property and method, as such:
//method decorator
function excludeMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = false;
return descriptor;
};
//property decorator
function excludeProperty(target: any, propertyKey: string): any {
// Buffer the value
var _val = target[propertyKey];
// Delete property.
if (delete target[propertyKey]) {
// Create new property with getter and setter
Object.defineProperty(target, propertyKey, {
get: () => _val,
set: (newVal) => _val = newVal,
enumerable: false,
configurable: true
});
}
}
function exclude(...args : any[]) {
switch(args.length) {
case 2:
return excludeProperty.apply(this, args);
case 3:
if (typeof args[2] !== "number")
return excludeMethod.apply(this, args);
default:
throw new Error("Decorators are not valid here!");
}
}
so now we can use it as such:
class MyClass {
test: string = "test";
#exclude
testExclude: string = "should be excluded";
get enumerated(): string {
return "yes";
}
#exclude
get nonEnumerated(): string {
return "non enumerable"
}
constructor() {}
}
let x = new MyClass();
//to serialize, we have to whitelist the instance and its prototype prop keys
console.log(JSON.stringify(x, Object.keys(x).concat(Object.keys(MyClass.prototype))));
So far I haven't found a cleaner way to do this.
Down the rabbit hole I fell...
so for some reason adding whitelist to JSON.stringify somehow made it not recursively serialize nested objects:
class a {
p1 = 1;
p2 = 2;
}
class b {
m1 = new a();
m2 = "test";
m3 = new Array<a>();
}
let i = new b();
i.m3.push(new a());
i.m3.push(new a());
JSON.stringify(i);
// properly gives
// {"m1":{"p1":1,"p2":2},"m2":"test","m3":[{"p1":1,"p2":2},{"p1":1,"p2":2}]}
JSON.stringify(i, Object.keys(i).concat(Object.keys(Object.getPrototypeOf(i))));
// nested class a doesn't get serialized
// {"m1":{},"m2":"test","m3":[{},{}]}
So just putting this out there, if you're like me and want to hide private variables in TS and giving it a readonly facade property:
declare it as a simple object member, then modify its propertyDescriptor in constructor as such:
//Don't do this
class a {
private _prop;
get prop() { return _prop; }
}
//do this instead
class a {
prop; //just define your public-facing property
constructor() {
let _prop; //internal variable here
Object.defineProperty(this, "prop", { //now we modify the existing prop,
get: () => _prop, //closure to outside variable
//(no set here, it's readonly after all)
enumerable: true, //make sure it's visible
configurable: false //close up access
});
}
}
now we can simply use JSON.stringify(instance). The only drawback is if you have complicated getter/setter, keep in mind that this is invoked in every instance/new.
with this pattern and #exclude decorators above, pretty much solves my use case. Hope this helps someone..
I have a scope called $scope.activities. Inside that scope are multiple objects. Each object has a string called viewed with the value of check or uncheck. I would like to check if the value uncheck is present in the scope.
I've created this, but it doesn't seem to work.
if (activities.indexOf("uncheck") == -1) {
console.log ('uncheck found')
$scope.newActivities = 'newActivities';
} else {
console.log ('no uncheck found')
}
Because in activities I have two objects, one with the uncheck value, and a object without it.
[{"id":2,", "viewed":"check"}, {"id":3,", "viewed":"uncheck"}]
You've got to loop each object and check the property - you can use Array.some
var hasValue = activities.some(function(obj) { return obj.viewed == "unchecked" });
You have to loop over each object in the array and test if the property "viewed" equal to "unchek"
var activities = [{"id":2, "viewed":"check"}, {"id":3, "viewed":"uncheck"}];
var activities2 = [{"id":2, "viewed":"check"}, {"id":3, "viewed":"check"}];
var check = function(data){
var checked = false;
for(var i in data){
if(data[i].hasOwnProperty("viewed") && (data[i]["viewed"]=="uncheck") ){
checked = true ;
}
}
return checked;
} ;
console.log(check(activities));
console.log(check(activities2));
Is there a way to stop backbone validation of colelction if one model fails validation? Currently, the code does this (taken from Backbone.js 1.1.0):
for (i = 0, l = models.length; i < l; i++) {
attrs = models[i];
if (attrs instanceof Model) {
id = model = attrs;
} else {
id = attrs[targetModel.prototype.idAttribute];
}
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
if (existing = this.get(id)) {
if (remove) modelMap[existing.cid] = true;
if (merge) {
attrs = attrs === model ? model.attributes : attrs;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
}
models[i] = existing;
// If this is a new, valid model, push it to the `toAdd` list.
} else if (add) {
model = models[i] = this._prepareModel(attrs, options);
if (!model) continue;
toAdd.push(model);
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
model.on('all', this._onModelEvent, this);
this._byId[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
}
if (order) order.push(existing || model);
}
So if _prepareModel returns false, which it will if a model is invalid, it will skip to next model and try to validate and add that.
I want to stop if one of the models fails validation? The app I'm working on is a tablet app and thousands of models are returned from the server (represented of course as JSON) so if the first model fails validation, I do not want all the others to be validated too, as the entire collection will be disguarded.
Any ideas how I can do this?
I would override the fetch method on your collection. Like this:
youCollection.fetch = function(options) {
options = options ? _.clone(options) : {};
if (options.parse === void 0) options.parse = true;
var success = options.success;
var collection = this;
options.success = function(resp) {
var method = options.reset ? 'reset' : 'set';
if (success) {
if (success(collection, resp, options))
collection[method](resp, options);
} else
collection[method](resp, options);
collection.trigger('sync', collection, resp, options);
};
wrapError(this, options);
return this.sync('read', this, options);
}
Here's what's happening. Focus on the line that starts with options.success =. When your server comes back with your requested models, a trigger invokes the function defined by options.success passing your models in as a parameter. In the original code, options.success looks like this:
options.success = function(resp) {
var method = options.reset ? 'reset' : 'set';
collection[method](resp, options);
if (success) success(collection, resp, options);
collection.trigger('sync', collection, resp, options);
};
What matters here that collection[method](resp, options); invokes set on the collection that called the fetch. And only then you have the option to invoke your own callback. Once collection.set is called, the validation loop on your models begins and there's nothing we could do.
We want to change that up. We want to first call our own success function. This function will run a for loop checking if each model.isValid(), and will return false as soon as one fails. If one does fail we never reach collection[method](resp, options); and set never happens.
To get a success function into your fetch call simply drop a reference to it in the options passed into fetch
yourCollection.fetch({ success: checkValidate });
I would additionally point you to Backbone.validate so that you can read about the 'invoke' trigger that gets fired when a model is invalid. That way you can handle another fetch or whatever.
i have a collection with models with a property called start_time in seconds (0-60..), which is undefined if no start_time is set.
i have troubles to test if this property is 0 or null/undefined, since the handlebar #if always converts it to 0
If you are fetching the data you could use collection.parse to set a new field on the model that you could test for in your view.
YourCollection = Backbone.Collection.extend({
url: "/api/foo",
parse: function(res) {
return _.map(res, function(source) {
obj = _.clone(source);
obj.no_start = !obj.hasOwnProperty('start_time');
return obj;
});
}
});
Why not just use backbone model defaults?
YourModel = Backbone.Model.extend({
defaults : {
start_time = 'no start time'
}
});
The string should evaluate to true and any null or undefined value will be set to 'no start time' when your model is initialized.
You could do a little bit of work before calling your template: make start_time a string ('0' is true).
You could also register a helper: see boolean logic within a handlebars template.
Should give something like:
Handlebars.registerHelper('if_all', function() {
var args = [].slice.apply(arguments);
var opts = args.pop();
var fn = opts.fn;
if(!args[0] && args[0] != 0) fn = opts.inverse;
return fn(this);
});