I'm trying to modify the Todos Examples to get a better understanding of the framework.
I'm trying to modify the todosController to add a 'completed' computed property that returns all the completed todos. On top of this, I'm trying to get the 'areAllCompleted' property to update.
I have this code, which does not update 'areAllCompleted' when 'completed' has changed.
TodosThree.todosController = SC.ArrayController.create({
completed: function(){
if(this.get('content')){
return this.get('content').find(
SC.Query.local(
TodosThree.Todo,
'isCompleted = true'
)
);
}
else {
return [];
}
}.property('content').cacheable(),
areAllCompleted: function (k, v) {
console.log('get');
if (v !== undefined) {
this.setEach('isCompleted', v);
}
return this.getPath('completed.length') === this.get('length');
# This .property definition doesn't work with .*completed.length .. ?
}.property('length','.*completed.length')
});
However, if I change the code slightly to add a binding, it works:
TodosThree.todosController = SC.ArrayController.create({
completed: function(){
if(this.get('content')){
return this.get('content').find(
SC.Query.local(
TodosThree.Todo,
'isCompleted = true'
)
);
}
else {
return [];
}
}.property('content').cacheable(),
# For some reason, this binding works ...
completedLengthBinding: SC.Binding.oneWay('.*completed.length'),
areAllCompleted: function (k, v) {
console.log('get');
if (v !== undefined) {
this.setEach('isCompleted', v);
}
return this.getPath('completed.length') === this.get('length');
# If I reference the binding that references completed, this now works ...
}.property('length','completedLength')
});
Why does this subtle difference suddenly make it work?
Thanks.
When you are using .property() method, the parameters are expected to be direct properties of the object; so, it doesn't expand upon the property path that you are passing (.*completed.length). When you setup the binding, you are basically telling SproutCore that you want the path (.*completed.length) bound to a property of the object, which is why the second one works; since it has become a simple property.
Since you are setting both of these based off of the completion, another way that you could do it is by using a single function with .observes() which does follow property paths, but that is a bit complex. Following is how I would probably handle this:
/*
* Note that using a store.find will auto-update when new todos are pushed
* into the store, so there is no need to reset this every time.
*/
completed: TodosThree.store.find(
SC.Query.local('TodosThree.Todo', 'isCompleted = true')
),
/*
* Go ahead and grab the length so we can use it as a dependent property below
*/
completedLengthBinding: '*completed.length',
/*
* Create the property function
*/
allAreCompleted: function(key, value) {
if (value !== undefined) {
this.setEach('isCompleted', value);
}
return this.everyProperty('isCompleted');
}.property('completed', 'completedLength')
A couple of things to note: since you're wanting to call allAreCompleted() and pass a value, you DO want this as a property, not just an observer. You could technically do it with a function that acts as both an observer and a property updater, but I think this is more clear.
Also, note the use of the everyProperty() method, which will iterate over each todo and ensure that the passed property is true for all todos.
Hope this helps! Please ask if you need clarification on anything :-D
Related
I have implemented a jqgrid in grouping method. By default I have kept the groups collapsed using groupCollapse:true parameter of jqgrid. My grid works well but When I expand the group and sort a column, the whole grid is reloaded and the expanded state of the column is not retained. How can I retain the expanded state while sorting?
Please write always which version of jqGrid, which you use (can use), and from which fork (free jqGrid, commercial Guriddo jqGrid JS or an old jqGrid in version <=4.7).
Your requirements could be easy realized in "free jqGrid", which I develop. It allows to use groupCollapse as callback function, which returns Boolean (see the issue). In combination with onClickGroup callback or jqGridGroupingClickGroup event one can easy persist the grouping state.
UPDATED: I created the demo https://jsfiddle.net/92da8xhq/, which demonstrates how one can persist the collapsing state in the grouping grid. Below I describe shortly the code. The demo uses one level of grouping to make the code more simple for understanding.
I added custom collapsedGroups: {} parameter to jqGrid. We will use the parameter to hold the list of collapsed groups. I used collapsedGroups: { "test2": true } in the demo to demonstrated that we can create the grid with some collapsed groups at the beginning. We don't use the value of the property of collapsedGroups object. Just the existence of the property test2 for example means that the group with the value test2 has collapsed state.
The demo uses groupCollapse property of groupingView defined as the callback function. The function tests whether the group is in the list of collapsed groups (has collapsedGroups property with some value)
groupingView: {
groupField: ["name"],
groupCollapse: function (options) {
var collapsedGroups = $(this).jqGrid("getGridParam", "collapsedGroups") || {};
// options looks like { group: number, rowid: string }
if (collapsedGroups.hasOwnProperty(options.group.value)) {
return true;
}
return false;
}
}
We adjust additionally the properties of the custom collapsedGroups parameter after expanding/collapsing of the group. We use the following onClickGroup callback:
onClickGroup: function (hid, isCollapsed) {
var p = $(this).jqGrid("getGridParam"),
iGroup = $(this).jqGrid("getGroupHeaderIndex", hid),
group = p.groupingView.groups[iGroup];
if (p.collapsedGroups == null) {
// be sure that the custom parameter is initialized as an empty object
p.collapsedGroups = {};
}
if (isCollapsed) {
// we place group.value in the p.collapsedGroups object as a property
if (!p.collapsedGroups.hasOwnProperty(group.value)) {
// create the property group.value in with some value
p.collapsedGroups[group.value] = true;
}
} else if (p.collapsedGroups.hasOwnProperty(group.value)) {
// remove group.value property from the p.collapsedGroups object
delete p.collapsedGroups[group.value];
}
}
groupingView: {
groupCollapse: true,
groupField: ["name"],
plusicon: 'ace-icon fa fa-plus-square purple',
minusicon: 'ace-icon fa fa-edit red'
}
this question is basically the same question I asked a few weeks ago... how to tap into mappingcontext.processAnonType... I marked the question as answered by mistake and since then have not been able to get any follow up.
Basically what I am trying to figure out is a location within the breeze pipeline that i can set a non entity object's prototype when the object is materialized from server results... when breeze processes results from the servers that are not entities, it ends up calling the method below of the helper MappingContext class... this method is as follows:
function processAnonType(mc, node) {
// node is guaranteed to be an object by this point, i.e. not a scalar
var keyFn = mc.metadataStore.namingConvention.serverPropertyNameToClient;
var result = {};
__objectForEach(node, function (key, value) {
var newKey = keyFn(key);
var nodeContext = { nodeType: "anonProp", propertyName: newKey };
visitNode(value, mc, nodeContext, result, newKey);
});
return result;
}
up above the value of "results" is what the client ends up receiving from breeze... this is a perfect place that I could do what it is i want to do just because i have access to the final object ("results") AND the node.$type property... i basically want to parse the node.$type property in order to figure out the prototype of the non entity object... unfortunately it does not appear processAnonType is an interception point within the pipeline... in the previous question i asked, i was directed to look at a custom jsonresultsadapter... i did that but i don't think it will work simply because the jsonresultsadapter does not ever appear to be in the position of changing the value of "results" (the final object returned)... so even if i implement a custom jsonresulsadapter and return new nodes, the value of "results" up above continues to be the same... can anyone please clue me in? thank you
EDIT #1: I already tried using a custom jsonresultsadapter… but this does NOT work for what I am SPECIFICALLY trying to do, UNLESS I am using a very old version of breeze (unlikely) or am missing something really obvious (more likely)... down below I have provided two snippets of breeze code that will hopefully help me explain my conclusion... the first snippet is the “visitandmerge” method of the mappingcontext… towards the bottom of that method you’ll see a call made to “jra.visitnode”… that’s great… it calls my custom jra implementation which returns a “node” property in the result such that the following line will use that node rather than the original one… so far so good… then at the end you’ll see that a call is made to “processmeta” that passes in my custom node… ok fine… but then if you look the “processmeta” code in my case the last “else” block ends up being invoked and a call is made to “processanontype”… this is where the problem is… at this point my custom node is discarded for purposes of creating/instantiating the final object returned to the client… I understand my custom node will be used to create properties for the final object but that's not what I am after... instead I need to manipulate the final object myself by setting its prototype… as I mentioned previously, if you look at the “processanontype” method it creates a new object (var result = {};) and that object is returned to the client, NOT my custom node, which is inline with what the documentation says… please see all the comments I left in the previous post... do you understand what my problem is? I am probably missing something here really obvious… can you please clue me in? thanks again
proto.visitAndMerge = function (nodes, nodeContext) {
var query = this.query;
var jra = this.jsonResultsAdapter;
nodeContext = nodeContext || {};
var that = this;
return __map(nodes, function (node) {
if (query == null && node.entityAspect) {
// don't bother merging a result from a save that was not returned from the server.
if (node.entityAspect.entityState.isDeleted()) {
that.entityManager.detachEntity(node);
} else {
node.entityAspect.acceptChanges();
}
return node;
}
var meta = jra.visitNode(node, that, nodeContext) || {};
node = meta.node || node;
if (query && nodeContext.nodeType === "root" && !meta.entityType) {
meta.entityType = query._getToEntityType && query._getToEntityType(that.metadataStore);
}
return processMeta(that, node, meta);
});
};
function processMeta(mc, node, meta, assignFn) {
// == is deliberate here instead of ===
if (meta.ignore || node == null) {
return null;
} else if (meta.nodeRefId) {
var refValue = resolveEntityRef(mc, meta.nodeRefId);
if (typeof refValue === "function" && assignFn != null) {
mc.deferredFns.push(function () {
assignFn(refValue);
});
return undefined; // deferred and will be set later;
}
return refValue;
} else if (meta.entityType) {
var entityType = meta.entityType;
if (mc.mergeOptions.noTracking) {
node = processNoMerge(mc, entityType, node);
if (entityType.noTrackingFn) {
node = entityType.noTrackingFn(node, entityType);
}
if (meta.nodeId) {
mc.refMap[meta.nodeId] = node;
}
return node;
} else {
if (entityType.isComplexType) {
// because we still need to do serverName to client name processing
return processNoMerge(mc, entityType, node);
} else {
return mergeEntity(mc, node, meta);
}
}
} else {
if (typeof node === 'object' && !__isDate(node)) {
node = processAnonType(mc, node);
}
// updating the refMap for entities is handled by updateEntityRef for entities.
if (meta.nodeId) {
mc.refMap[meta.nodeId] = node;
}
return node;
}
}
You should NOT need to modify the processAnonType method.
The parameters to the visitNode method in the jsonResultsAdapter have all of the information regarding the node being visited that you say you need. (See the link at the bottom of this post). The result from the visitNode is an object with the following properties:
entityType: you should return null for an anonymous type
nodeId and nodeRefId: ( probably not needed for an anonymous objects unless you plan to return multiple refs to the same object)
ignore: boolean - (if you want to completely ignore the node).
node: This is where you can take the incoming node ( the first parameter in the visitNode parameter list) and modify it, or return a completely new node object that represents your anonType instance. This object will be returned to the client unchanged, so you can create an new instance of your object with whatever prototype you want. If you don't set this property then the original incoming node will be used.
passThru: (avail in breeze versions > v 1.5.4) boolean - you should return true to return the node (above) intact without ANY further processing.
So your visitNode will look something like this:
visitNode: function(node, mappingContext, nodeContext) {
// 'isAnonType' is your method that determines if this is an anon type
var isAnon = isAnonType(node.$type);
if (isAnonType) {
// 'createCustomAnonNode' is your method where you create a copy of the node with whatever prototype you want.
// prototype you want.
var newNode = createCustomAnonNode(node);
return {
return { passThru: true, node: newNode };
}
} else {
// assuming that you kept track of the default JsonResultsAdapter;
return defaultAdapter.visitNode(node, mappingContext, nodeContext);
}
}
For more detail, see:
http://www.getbreezenow.com/documentation/jsonresultsadapters
How can I make a (two-way) property that is an alias to model.someProperty if model is not empty, and otherwise is an alias to default.someProperty? Currently I wrap two versions of a <select> in an {{#if model}}, one bound to model and one bound to defaults, but I would like to remove the logic from the template. Also there are certain circumstances where the set fires before the select gets swapped, leading to an error because model is null.
When setting model, the new model should get someProperty set to default.someProperty. Currently I do that by:
modelChanged: function(){
if(default && default.get('someProperty')){
this.set('model.someProperty', default.get('someProperty'));
}
}.observes('model')
and I'm also curious to know if this is the Best way to do that.
DS.attr() can take a default value. Does that help?
var Post = DS.Model.extend({
title: DS.attr('string', { defaultValue: "Work in progress" })
I don't know if this is possible, using few lines. But, it's possible to create a computed property macro:
var get = Ember.get, set = Ember.set;
Ember.computed.aliasWithDefaultTo = function(dependentKey, defaultPath) {
return Ember.computed(dependentKey, defaultPath, function(key, value) {
if (arguments.length > 1) {
set(this, dependentKey, value || get(this, defaultPath));
return value;
} else {
return get(this, dependentKey) || get(this, defaultPath);
}
});
};
Live demo http://jsfiddle.net/marciojunior/3LD6a/
I would like to know how to define the data type and how to return the object (record) using getObject(). Currently, the only way that I have been able to use the result (record) outside of the function that obtains it is to call another function with the result. That way, the data-type does not need to be specified. However if I want to return the value, I need to define the data-type and I can't find what it is. I tried "dynamic" but that didn't appear to work. For example ":
fDbSelectOneClient(String sKey, Function fSuccess, String sErmes) {
try {
idb.Transaction oDbTxn = ogDb1.transaction(sgTblClient, 'readwrite');
idb.ObjectStore oDbTable = oDbTxn.objectStore(sgTblClient);
idb.Request oDbReqGet = oDbTable.getObject(sKey);
oDbReqGet.onSuccess.listen((val){
if (oDbReqGet.result == null) {
window.alert("Record $sKey was not found - $sErmes");
} else {
///////return oDbReqGet.result; /// THIS IS WHAT i WANT TO DO
fSuccess(oDbReqGet.result); /// THIS IS WHAT i'm HAVING TO DO
}});
oDbReqGet.onError.first.then((e){window.alert(
"Error reading single Client. Key = $sKey. Error = ${e}");});
} catch (oError) {
window.alert("Error attempting to read record for Client $sKey.
Error = ${oError}");
}
}
fAfterAddOrUpdateClient(oDbRec) {
/// this is one of the functions used as "fSuccess above
As someone else once said (can't remember who), once you start using an async API, everything needs to be async.
A typical "Dart" pattern to do this would be to use a Future + Completer pair (although there's nothing inherently wrong with what you've done in your question above - it's more a question of style...).
Conceptually, the fDbSelectOneClient function creates a completer object, and the function returns the completer.future. Then, when the async call completes, you call completer.complete, passing the value in.
A user of the function would call fDbSelectOneClient(...).then((result) => print(result)); to make use of the result in an async way
Your code above could be refactored as follows:
import 'dart:async'; // required for Completer
Future fDbSelectOneClient(String sKey) {
var completer = new Completer();
try {
idb.Transaction oDbTxn = ogDb1.transaction(sgTblClient, 'readwrite');
idb.ObjectStore oDbTable = oDbTxn.objectStore(sgTblClient);
idb.Request oDbReqGet = oDbTable.getObject(sKey);
oDbReqGet.onSuccess.listen((val) => completer.complete(oDbReqGet.result));
oDbReqGet.onError.first.then((err) => completer.completeError(err));
}
catch (oError) {
completer.completeError(oError);
}
return completer.future; // return the future
}
// calling code elsewhere
foo() {
var key = "Mr Blue";
fDbSelectOneClient(key)
.then((result) {
// do something with result (note, may be null)
})
..catchError((err) { // note method chaining ..
// do something with error
};
}
This future/completer pair only works for one shot (ie, if the onSuccess.listen is called multiple times, then the second time you will get a "Future already completed" error. (I've made an assumption on the basis of the function name fDbSelectOneClient that you are only expecting to select a single record.
To return a value from a single future multiple times, you'll probably have to use the new streams feature of the Future - see here for more details: http://news.dartlang.org/2012/11/introducing-new-streams-api.html
Note also, that Futures and Completers support generics, so you can strongly type the return type as follows:
// strongly typed future
Future<SomeReturnType> fDbSelectOneClient(String sKey) {
var completer = new Completer<SomeReturnType>();
completer.complete(new SomeReturnType());
}
foo() {
// strongly typed result
fDbSelectOneClient("Mr Blue").then((SomeReturnType result) => print(result));
}
I've an individualStore (extends from Em.ArrayController), whose task is to keep an array of individual objects. There are several APIs that my application calls, and they return individual objects which are sent to the store. Think about it as the database of cached individual records in my application.
App.individualStore = App.ArrayController.create({
allIndividuals: function () {
return this.get('content').sort(function (a, b) {
return (b.votes_count - a.votes_count);
});
}.property('#each.votes_count').cacheable(),
aliveIndividuals: function () {
return this.get('content').filter(function (individual) {
return (!!individual.living);
}).sort(function (a, b) {
return (b.votes_count - a.votes_count);
});
}.property('#each.living', '#each.votes_count').cacheable(),
deceasedIndividuals: function () {
return this.get('content').filter(function (individual) {
return (!individual.living);
}).sort(function (a, b) {
return (b.votes_count - a.votes_count);
});
}.property('#each.living', '#each.votes_count').cacheable()
});
My view has a `individualsBinding: 'App.individualStore.allIndividuals', which renders up as intended perfectly.
I want to add filtering buttons, e.g. Show: All | Alive | Deceased. What would be the right way to change the filtering here? Keep in mind that whatever the criteria is, I'd like it to keep in sync with individualStore always.
Someone suggested to change bindings on runtime,
this.bind('users', Ember.Binding.from('App.individualStore.aliveIndividuals'));
This works in my first two-three clicks on these buttons, but then it freezes the browser (sort of infinite loop?).
This also doesn't feel like the best option to me. I'm new to ember, so anything you say would be helpful. Thanks in advance.
I would make the filter function itself a property and by changing a filterName on the controller, you are notified and accordingly update the filtered content, see http://jsfiddle.net/pangratz666/ypcLq/
App.controller = Ember.ArrayProxy.create({
content: [],
filterName: 'all',
allFilter: function() {
return true;
},
aliveFilter: function(individual) {
return ( !! individual.living);
},
deceasedFilter: function(individual) {
return (!individual.living);
},
filtered: function() {
var filterName = this.get('filterName');
var filterFunc = this.get(filterName + 'Filter');
return this.filter(filterFunc).sort(function(a, b) {
return (b.votes_count - a.votes_count);
});
}.property('content.#each', 'filterName').cacheable()
});
So you can later in your view set the filter which shall be used via App.controller.set('filterName', 'alive').
Just as a note: you can chain filters via this.filter(filterFunc1).filter(filterFunc2) so you could for example filter all alive individuals of a specific age, ...