I'm modifying an example of the built-in-ds. The way this example works is that you need to select a datasource, and then a table gets filled with data from there. The idea is to show how the same component can adapt to multiple data sources. I've managed to run the example and it works, but I'm trying to modify it so you skip the first step - there's only one data source that gets loaded "into" the table. What puzzles me is that this should be trivial but for some reason it's not. I'll paste just the different parts of code, this one works:
// we create a list of datasources
ListGrid grid = new ListGrid();
grid.setLeft(20);
grid.setTop(75);
grid.setWidth(130);
grid.setLeaveScrollbarGap(false);
grid.setShowSortArrow(SortArrow.NONE);
grid.setCanSort(false);
grid.setFields(new ListGridField("dsTitle", "Select a DataSource"));
// I'm just loading the one I need
grid.setData(new ListGridRecord[] { new DSRecord("predmeti", "predmeti")});
grid.setSelectionType(SelectionStyle.SINGLE);
grid.addRecordClickHandler(new RecordClickHandler() {
public void onRecordClick(RecordClickEvent event) {
DSRecord record = (DSRecord) event.getRecord();
bindComponents(record.getDsName());
}
});
grid.draw();
and this is the bindComponents method:
private void bindComponents(String dsName) {
DataSource ds = DataSource.get(dsName);
boundList.setDataSource(ds);
boundViewer.setDataSource(ds);
boundForm.setDataSource(ds);
boundList.fetchData();
newBtn.enable();
saveBtn.disable();
}
and this works as it should. Now, since I only have one data source, I can skip the grid and just make the call to bindComponents:
bindComponents();
And bindComponents looks like this:
private void bindComponents() {
DataSource ds = DataSource.get("predmeti");
boundList.setDataSource(ds);
boundViewer.setDataSource(ds);
boundForm.setDataSource(ds);
boundList.fetchData();
newBtn.enable();
saveBtn.disable();
}
I can't see why the second doesn't work, it breaks on boundList.setDataSource(ds);. I inspected it in Debug mode, an object is returned and it looks "the same" in both cases but for some reason it doesn't work in the second example, so I'm guessing I'm instancing the data source too early or, somehow, just plain wrong :)
Everything was fine with the datasources, the problem is trivial and I must have just been tired - the boundList object at the time of this call boundList.setDataSource(ds); was not yet instantiated. I kept getting Null pointer exception, but I thought there was something wrong with the DataSource.
Related
I am trying to implement 2 AutoCompleteTextView. First one holds the value, on click of one of them will populate the data in second AutoCompleteTextView, all works and data gets loaded correctly however when I try to call notifyDataSetChanged on the second adapter which populates the second field that does not get changed.
I also wrote that piece of code inside a Handler too, however that does not seems to work either.
branchAdapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,branchNames);
et7.setAdapter(branchAdapter);//et7 is the AutoCompleteTextView //branchname is the ArrayList
in some method I clean up the ArrayList and add the new data into it and then call notifyDataSetChanged like below
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
branchAdapter.notifyDataSetChanged();
}
});
Which is not updating the list. What should be the correct way here to update data?
#Saty, I guess i am a little late to the party. I am answering, keeping in mind that, it may help somebody in future.
Cleanup the adapter as well using branchAdapter.clear() method. Then add new items to the adapter using branchAdapter.addAll(String... items) method. Now, call branchAdapter.notifyDataSetChanged() method. This will work.
Try resetting listview instead.
ListView lv = (ListView)findViewById(R.id.lv);
ArrayAdapter adapter = new ArrayAdapter(/*with all parameters*/);
lv.setAdapter(adapter);
My adapter uses findHasMany to load child records for a hasMany relationship.
My findHasMany adapter method is directly based on the test case for findHasMany. It retrieves the contents of the hasMany on demand, and eventually does the following two operations:
store.loadMany(type, hashes);
// ...
store.loadHasMany(record, relationship.key, ids);
(The full code for the findHasMany is below, in case the issue is there, but I don't think so.)
The really strange behavior is: it seems that somewhere within loadHasMany (or in some subsequent async process) only the first and last child records get their inverse belongsTo property set, even though all the child records are added to the hasMany side. I.e., if posts/1 has 10 comments, this is what I get, after everything has loaded:
var post = App.Posts.find('1');
post.get('comments').objectAt(0).get('post'); // <App.Post:ember123:1>
post.get('comments').objectAt(1).get('post'); // null
post.get('comments').objectAt(2).get('post'); // null
// ...
post.get('comments').objectAt(8).get('post'); // null
post.get('comments').objectAt(9).get('post'); // <App.Post:ember123:1>
My adapter is a subclass of DS.RESTAdapter, and I don't think I'm overloading anything in my adapter or serializer that would cause this behavior.
Has anybody seen something like this before? It's weird enough I though someone might know why it's happening.
Extra
Using findHasMany lets me load the contents of the hasMany only when the property is accessed (valuable in my case because calculating the array of IDs would be expensive). So say I have the classic posts/comments example models, the server returns for posts/1:
{
post: {
id: 1,
text: "Linkbait!"
comments: "/posts/1/comments"
}
}
Then my adapter can retrieve /posts/1/comments on demand, which looks like this:
{
comments: [
{
id: 201,
text: "Nuh uh"
},
{
id: 202,
text: "Yeah huh"
},
{
id: 203,
text: "Nazi Germany"
}
]
}
Here is the code for the findHasMany method in my adapter:
findHasMany: function(store, record, relationship, details) {
var type = relationship.type;
var root = this.rootForType(type);
var url = (typeof(details) == 'string' || details instanceof String) ? details : this.buildURL(root);
var query = relationship.options.query ? relationship.options.query(record) : {};
this.ajax(url, "GET", {
data: query,
success: function(json) {
var serializer = this.get('serializer');
var pluralRoot = serializer.pluralize(root);
var hashes = json[pluralRoot]; //FIXME: Should call some serializer method to get this?
store.loadMany(type, hashes);
// add ids to record...
var ids = [];
var len = hashes.length;
for(var i = 0; i < len; i++){
ids.push(serializer.extractId(type, hashes[i]));
}
store.loadHasMany(record, relationship.key, ids);
}
});
}
Solution
Override the DS.RelationshipChange.getByReference method by inserting the following code into your app:
DS.RelationshipChange.prototype.getByReference = function(reference) {
var store = this.store;
// return null or undefined if the original reference was null or undefined
if (!reference) { return reference; }
if (reference.record) {
return reference.record;
}
return store.materializeRecord(reference);
};
Yes, this is overriding a private, internal method in Ember Data. Yes, it may break at any time with any update. I'm pretty sure this is a bug in Ember Data, but I'm not 100% certain this is the right solution. But it does solve this problem, and possibly other relationship-related problems.
This fix is designed to be applied to Ember Data master as of 29 Apr 2013.
Reason
DS.Store.loadHasMany calls DS.Model.hasManyDidChange, which retrieves references for all the child records and then sets the hasMany's content to the array of references. This kicks off a chain of observers., eventually calling DS.ManyArray.arrayContentDidChange, in which the first line is this._super.apply(this, arguments);, calling the superclass method Ember.Array.arrayContentDidChange. That Ember.Array method includes an optimization that caches the first and last object in the array and calls objectAt on only those two array members. So there's the part that singles out the first and last record.
Next, since DS.RecordArray implements an objectAtContent method (from Ember.ArrayProxy), the objectAtContent implementation calls DS.Store.recordForReference, which in turn calls DS.Store.materializeRecord. This last function adds a record property to the reference that is passed in as a side effect.
Now we get to what I think is a bug. In DS.ManyArray.arrayContentDidChange, after calling the superclass method, it loops through all the new references and creates a DS.RelationshipChangeAdd instance that encapsulates the owner and child record references. But the first line inside the loop is:
var reference = get(this, 'content').objectAt(i);
Unlike what happens above to the first and last record, this calls objectAt directly on the Ember.NativeArray and bypasses the ArrayProxy methods including the objectAtContent hook, which means that DS.Store.materializeRecord--which adds the record property on the reference object--may have never been called on some references.
Next, the relationship changes created in the loop are immediately afterward (in the same run loop) applied with this call tree: DS.RelationshipChangeAdd.sync -> DS.RelationshipChange.getFirstRecord -> DS.RelationshipChange.getByReference. This last method expects the reference object to have a record property. However, the record property is only set on the first and last reference objects, for reasons explained above. Therefore, for all but the first and last records, the relationship fails to be established because it doesn't have access to the child record object!
The above fix calls DS.Store.materializeRecord whenever the record property doesn't exist on the reference. The last line in the function is the only thing added. On the one hand, it looks like this was the original intention: that var store = this.store; line in the original declares a variable that isn't otherwise used in the function, so what's it there for? Also, without the added line, the function doesn't always return a value, which is a little unusual for a function which is expected to do so. On the other hand, this could lead to mass materialization in some cases where that would be undesirable (but, the relationships just won't work without it in some cases, it seems).
Possibly related
The "chain of observers" I mentioned takes a bit of an odd path. The initiating event was setting the content property on a DS.ManyArray, which extends Ember.ArrayProxy--therefore the content property has a dependent property arrangedContent. Importantly, the observers on arrangedContent are executed before observers on content are executed (see Ember.propertyDidChange). However, the default implementation of Ember.ArrayProxy.arrangedContentArrayDidChange simply calls Ember.Array.arrayContentDidChange, which DS.ManyArray implements! The point being, this looks like a recipe for some code to execute in an unintended order. That is, I think Ember.ManyArray.arrayContentDidChange may getting executed earlier than expected. If this is the case, the above mentioned code that expects the record property to already exist on all references may have been expecting this reasonably, as one of the observers directly on the content property may call DS.Store.materializeRecord on each reference. But I haven't dug deep enough to find out if this is true.
I have a document that has a ReferenceMany attribute to another document. The reference is setup fine, and the data is returned from the query fine, but each document in the arraycollection is returned as a proxy. On this site, I saw it was mentioned I should add ->prime(true) in order to return the actual referenced documents.
When that ArrayCollection of documents is returned, I am running a loop of ids I have submitted to the server to remove them from the referenced collection. The removeElement method is not working b/c the returned documents are proxies, and I am comparing an actual document vs. those proxies. So basically I am trying to:
Look up a single document
Force all documents in the ReferenceMany attribute to be actual documents and not Proxy documents
Loop through my array of id's and load each document
Send the document to the removeElement method
On the first getSingleResult query method below, I am getting an error cannot modify cursor after beginning iteration. I saw a thread on this site mention you should prime the results in order to get actual documents back instead of proxies, and in his example, he used getSingleResult.
$q = $this->dm->createQueryBuilder('\FH\Document\Person')->field('target')->prime(true)->field('id')->equals($data->p_id);
$person = $q->getQuery()->getSingleResult();
foreach($data->loc_id as $loc) {
$location = $this->dm->createQueryBuilder('\FH\Document\Location')->field('id')->equals(new \MongoId($loc))->getQuery()->getSingleResult();
$person->removeTarget($location);
}
....
....
....
public function removeTarget($document)
{
$this->target->removeElement($document);
return $this;
}
If I remove ->prime(true) from the first query, it doesn't throw an error, yet it doesn't actually remove any elements even though I breakpoint on the method, compare the two documents, and the data is exactly the same, except in $this->target they are Location Proxy documents, and the loaded one is an actual Location Document.
Can I prime the single result somehow so I can use the ArrayCollection methods properly, or do I need to just do some for loop and compare ids?
UPDATE
So here is an update showing the problem I am having. While the solution below would work just using the MongoId(s), when I submit an actual Document class, it never actually removes the document. The ArrayCollection comes back from Doctrine as a PersistentCollection. Each element in $this->coll is of this Document type:
DocumentProxy\__CG__\FH\Document\Location
And the actual Document is this:
FH\Document\Location
The removeElement method does an array_search like this:
public function removeElement($element)
{
$key = array_search($element, $this->_elements, true);
if ($key !== false) {
unset($this->_elements[$key]);
return true;
}
return false;
}
So because the object types are not exactly the same, even though the proxy object should be inheriting from the actual Document I created, $key always returns 0 (false), so the element is not removed. Everything between the two documents are exactly the same, except the object type.
Like I said, I guess I can do it by MongoId, but why isn't it working by submitting the entire object?
Don't worry about the prime(true) stuff for just now. All that does is tell doctrine to pull the referenced data now, so it doesn't have to make multiple calls to the database when you iterate over the cursor.
What I would do is change your removeTarget method to do the following.
$this->dm->createQueryBuilder('\FH\Document\Person')->field('id')->equals($data->p_id);
$person = $q->getQuery()->getSingleResult();
$person->removeTargets($data->loc_id);
Person.php
public function removeTargets($targets)
{
foreach ($targets as $target) {
$this->removeTarget($target);
}
}
public function removeTarget($target)
{
if ($target instanceof \FH\Document\Location) {
return $this->targets->removeElement($target);
}
foreach ($this->targets as $t) {
if ($t->getId() == $target) {
return $this->targets->removeElement($t);
}
}
return $this;
}
This would mean you don't have to perform the second query manually as doctrine will know it needs to pull the data on that reference when you iterate over it. Then you can make this operation quicker by using the prime(true) call to make it pull the information it needs in one call rather than doing it dynamically when you request the object.
I have a file/class::method (HelperAdmin.php/HelperAdmin::menuItem()) which extracts data from DB to generate main menu and submenu.
I have to get this data after the menu being generated but I don't want to call this method twice.
So I have created a static array in HelperAdmin class. It looks like this:
class HelperAdmin {
static $arrMenuItems;
...
public static function menuItem() {
....get $items....
self::$arrMenuItems = $items;
return $items;
}
....
}
But here is a problem. If I call the METHOD again:
$items=HelperAdmin::menuItem();
...I can get data.
In other hand if I try to get data through a static array:
$items=HelperAdmin::$arrMenuItems;
...it just returns null.
I hope to see some ideas. After all, if your opinion is that using static variable here (from Yii architecture view) is not the best solution, I'd like to get your advice!
#bool.dev:
OK, imagine the following scheme:
1. We have the main file
/modules/admin/views/layouts/admin.php
which is in essence our backend template.
2. We have the helper here:
/modules/admin/components/HelperAdmin.php
which contains:
* a class HelperAdmin,
* menuItem() method and
* a class static array $arrMenuItems.
A content of this ARRAY returned by calling HelperAdmin::menuItem().
We want get data from the HelperAdmin class twice:
1. While generating a menu in admin.php;
2. As part of content which we get with variable $content putting it at this file.
$content in turn is generated in another file:
/modules/admin/views/generator/index.php
So as you can see, our page compounds itself from the template file /modules/admin/views/layouts/admin.php and data ($content) getting from /modules/admin/views/generator/index.php.
First I get data for menu:
HelperAdmin::menuItem();
$items=HelperAdmin::$arrMenuItems;
$this->widget(....
'items'=>$items,
...),
));
That's OK. Notice that after this a static array $arrMenuItems already is generated in HelperAdmin.
Next I'm trying to get the same data ($arrMenuItems generated earlier) in file /modules/admin/views/generator/index.php to place it as $content:
$items=HelperAdmin::$arrMenuItems
And here I can't get it as described above.
Well, I hope it made a situation more clear (?).
when using a cellbrowser and adding that widget to a flowpanel (to be placed wherever, downstream), for some reason the end result is dead (a blank screen)...vs if I add directly to the rootpanel (or layout panel)
Also had the same problem. I had to use a <g:HTMLPanel> as the parent of the CellBrowser (as seen in the GWT Showcase).
Do you have some sample code that will reproduce this?
below is the code for the composite...essentially, what I'd like to do is in another class, attached this composite to a flowpanel and do whatever with it...but, the reality is, I have to attach is directly to the RootPanel (or RootLayoutPanel)...any other abstraction causes it to bail
for example
FlowPanel fp = new FlowPanel();
V2_M76Rolodex v = new V2_M76Rolodex();
fp.add(v); // not going to work
RootPanel.get.add(v) works
public class V2_M76Rolodex extends Composite {
/*
a bunch of code here for getting data and
populating the tree - works, not at issue or relevant
*/
public V2_M76Rolodex() {
TreeViewModel model = new CustomTreeModel();
CellBrowser browser = new CellBrowser(model, null);
browser.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);
browser.addStyleName("rolodex_cell_browser");
initWidget(browser);
}
}