Made a video showing how to create a file navigation widget:
Walk through this here: https://gist.github.com/justinbmeyer/cab04443d02f3ca36f126ae54ab276b8
Post other training topics you’d like to see here: https://github.com/bitovi/training/issues
Made a video showing how to create a file navigation widget:
Walk through this here: https://gist.github.com/justinbmeyer/cab04443d02f3ca36f126ae54ab276b8
Post other training topics you’d like to see here: https://github.com/bitovi/training/issues
We are working on something like this - a tree component.
The difference is that we should be able to add folders (to the root or the child folders).
But we can’t make it work, if we add a folder to an existing folder.
newFolder.save() keeps pushing the item to the EntitiesList instead of the current folder.
var self = this;
Entity.getList({parentId: "0"}).then(function(entities){
self.entities = entities;
})
// ...
var newFolder = new Entity({
parentId : self.currentFolder.id,
title : 'New Folder',
type : 'folder',
hasChildren : false
});
newFolder.save(successData => {
self.currentFolder.children.push(successData);
}, errorData => {
console.error(errorData);
});
We’ve tried something like this:
self.currentFolder.children.push(newFolder);
But the new folder is also added to the EntitiesList. I’ve tried doing a pop() on the list and it works, but that doesn’t feel correct.
I think we need something like canMergeBehaviour.
But I can’t wrap my head around it…
Do you have a JSBin?
If you are mimicing the behavior of the “advanced” tutorial, where is children
coming from?
If I were doing the advanced tutorial, you shouldn’t have to use .push
at all … can-connect/real-time should handle adding new entries into their appropriate list automatically.
That’s what it should be doing. it should add them to the entities list auto-magically. Why isn’t this the desired behavior?
It adds it at the end of the original list, and not the selected folder’s children.
What do you mean by “original list”? If the algebra is right, it should work out. Checkout some debugging tips on algebras: http://canjs.com/doc/can-set.html#SolvingCommonIssues
The original list is this:
Entity.getList({parentId: "0"}).then(function(entities){
self.entities = entities;
});
If we create a new folder like this:
var newFolder = new Entity({
parentId : self.currentFolder.id,
title : 'New Folder',
type : 'folder',
hasChildren : false
});
The newFolder doesn’t know it should get pushed inside currentFolder.children but it get’s pushed inside self.entities.
I will have a look at the Algebra, but I found it very confusing.
This seems to work: http://jsbin.com/vasixip/edit?html,js,output
I HACKED in a makeFolder
method:
makeFolder: function(parentId){
var newFolder = new Entity({
parentId : parentId,
name : 'New Folder',
type : 'folder',
hasChildren : false
}).save()
}
If you open some folders, and write the ID of an openned folder, then hit enter
, the new folder will appear in the list in the correct spot.
I had to change your new Entity
code to use"name" instead of “title”.
If you want the new folders to show up in the right spot, you’ll need a configured set.prop.sort
. You’ll also need to change the .getList({ sort: ... })
to include it.
There is no currentFolder.children
as far as I can tell. Where do you see that?
// Stores the next entity id to use.
var entityId = 1;
// Returns an array of entities for the given `parentId`.
// Makes sure the `depth` of entities doesn't exceed 5.
var makeEntities = function(parentId, depth){
if(depth > 5) {
return [];
}
// The number of entities to create.
var entitiesCount = can.fixture.rand(10);
// The array of entities we will return.
var entities = [];
for(var i = 0 ; i< entitiesCount; i++) {
// The id for this entity
var id = ""+(entityId++),
// If the entity is a folder or file
isFolder = Math.random() > 0.3,
// The children for this folder.
children = isFolder ? makeEntities(id, depth+1) : [];
var entity = {
id: id,
name: (isFolder ? "Folder" : "File")+" "+id,
parentId: parentId,
type: (isFolder ? "folder" : "file"),
hasChildren: children.length ? true : false
};
entities.push(entity);
// Add the children of a folder
[].push.apply(entities, children)
}
return entities;
};
// Make the entities for the demo
var entities = makeEntities("0", 0);
// Add them to a client-like DB store
var entitiesStore = can.fixture.store(entities);
// Trap requests to /api/entities to read items from the entities store.
can.fixture("/api/entities", entitiesStore);
// Make requests to /api/entities take 1 second
can.fixture.delay = 1000;
var Entity = can.DefineMap.extend({
id: "string",
name: "string",
parentId: "string",
hasChildren: "boolean",
type: "string",
makeFolder: function(parentId){
var newFolder = new Entity({
parentId : parentId,
name : 'New Folder',
type : 'folder',
hasChildren : false
}).save()
}
});
can.connect.baseMap({
Map: Entity,
url: "/api/entities"
});
var folder = new Entity({
id: "0",
name: "ROOT/",
hasChildren: true,
type: "folder"
});
var FolderVM = can.DefineMap.extend({ // ADDED
folder: Entity,
entitiesPromise: {
value: function(){
return Entity.getList({parentId: this.folder.id});
}
},
isOpen: {type: "boolean", value: false},
toggleOpen: function(){
this.isOpen = !this.isOpen;
}
});
can.Component.extend({ // ADDED
tag: "a-folder",
ViewModel: FolderVM,
view: can.stache.from("folder-template")
});
var template = can.stache.from("app-template"),
frag = template(folder); // CHANGED
document.body.appendChild( frag );
No there isn’t, that was in our component
No there isn’t, that was in our component
Ok, how does .children
get created?
They come from the backend (fixture). We don’t have lazy-loading, we receive the complete three the first time from the backend.
So I would either:
drop the real-time
behavior in your connection. This can be done by creating one manually like is done here: https://github.com/canjs/can-connect/blob/master/can/base-map/base-map.js … but don’t use real-time
. Removing real-time will prevent the automatic insertion / removal.
Decorate your children
lists with a .__listSet
. This can be done like: https://github.com/donejs/bitballs/blob/cffe472b63282efbf7d9b7bb7d9e0367da8a354f/public/models/game.js#L128. I’m assuming you have a recursive definition like is done in the beginner guide:
var Entity = can.DefineMap.extend("Entity",{
id: "string",
name: "string",
parentId: "string",
hasChildren: "boolean",
type: "string",
children: [{
type: function(entity){
return new Entity(entity)
}
}],
isOpen: {type: "boolean", value: false}, // ADDED
toggleOpen: function(){ // ADDED
this.isOpen = !this.isOpen;
}
});
This should change to something more like:
var Entity = can.DefineMap.extend("Entity",{
id: "string",
name: "string",
parentId: "string",
hasChildren: "boolean",
type: "string",
children: {
type: function(entities){
if(! (entities instanceof Entity.List) {
return new Entity.List(entities);
} else {
return entities;
}
},
set: function(entities){
entities.__listSet = {parentId: this.id}
}
},
isOpen: {type: "boolean", value: false}, // ADDED
toggleOpen: function(){ // ADDED
this.isOpen = !this.isOpen;
}
});
Entity.List = DefineList.extend({
"#": {
type: Entity
},
__listSet: "any"
})
For 100% correctness, I’d probably have a setter on the id
property that changed children.__listSet
if changed.
id: {
set: function(id){
if(this.children) {
this.childen.__listSet = {parentId: id}
}
return id;
}
}
Essentially, setting __listSet informs the set algebra what set
that list belongs to. It uses that to match any created or updated instances. If you are going to be creating and updating folders, especially if you have a real-time connection, it is probably worth doing.
Docs around the listSet: https://canjs.com/doc/can-connect/base/base.listSet.html
Thx, Justin. We will have a look into this.
Things are getting complicated.
Things are getting complicated.
I’m sorry you feel that way. Like I said, you can remove the real-time behavior and you’ll have to handle updating things on your own.
Or, you can decorate your lists in a way where CanJS knows how to update them for you. Conceptually, you are simply adding a property that describes what belongs in the list. Specifically, a list has every entity where it’s parentId
is some value.
Imo, it’s not that complex related to the benefit it provides (automatic real-time behavior).
this.childen.__listSet = {parentId: id}
So if a list has a __listSet
of {parentId: “5”}, entities like:
{id: 66, name: "something", parentId: "5"}
{id: 67, name: "something else", parentId: "5"}
{id: 68, name: "something new", parentId: "5"}
belong in that list.
{id: 76, name: "something", parentId: "6"}
{id: 77, name: "something else", parentId: "7"}
{id: 78, name: "something new", parentId: "8"}
do not.
real-time
monitors all creates, updates, and deletes of data globally and updates lists to look right, in a memory safe way.
Thx for the response!
The second option (with the __listSet) did the trick.
It was important though that our initial request passed the set as well:
EntityModel.getList({where : {parentId : 0}});