I am trying to populate a set of lists, using only a certain number of elements that specify which row they need to be added to.
Example: Each node item has a number that corresponds to a specific UL and which position to get added to, but the "total" number of elements is already known.
Simplified example data (assume pre-determined array size is 5):
nodes = new Entities.NodeCollection([
{ listID: 1, position: 2, name: "node2" },
{ listID: 1, position: 5, name: "node5" },
{ listID: 2, position: 4, name: "node4" }
]);
Expected Output: Each node is inserted at the correct location, with empty LI's filling in the remaining locations.
<ul id="list1">
<li></li>
<li><div class="template-class" id="node2"></div></li>
<li></li>
<li></li>
<li><div class="template-class" id="node5"></div></li>
</ul>
<ul id="list2">
<li></li>
<li></li>
<li></li>
<li><div class="template-class" id="node4"></div></li>
<li></li>
</ul>
Expected output:
How do I create a template that lets me insert the data into the correct spot and auto-fills the missing pieces?
I could do this with jQuery easily, (pre-fill the right length and just select the child #) but I'm required to use Marionette/Backbone, to build out the event management and database queries side of the app.
Worst case, the ability to loop through the known # of elements and check if there is a data point that should go there, filling in the correct html (or empty one) as necessary, but that doesn't seem like a common Marionette/Backbone paradigm.
I think I have the template for the actual row correct;
<script type="text/template" id="node-list-item">
<div class="template-class" id="<%- name %></div>
</script>
and the views:
RowView = Backbone.Marionette.ItemView.extend({
tagName: "li",
template: "#node-list-item"
//something goes here to correctly handle empty pieces?
});
ListView = Backbone.Marionette.CompositeView.extend({
itemView: RowView,
itemViewContainer: "ul",
//I don't think template implementation isn't important for this example
template: "#list-layout"
});
Am I just going about this wrong, and I should modify the data set before adding to the collection to include the empty parts? (If so, I would need to switch the template depending on if the source array item is empty, but that seems like a different can of worms, with excessive empty data points.)
Ultimately, the output is like a table, but the data is structured in a column oriented fashion, which I've found a little disorienting when trying to build it out. The empty rows ensure that the items all line up, and hopefully will help simplify jQuery calls further along development.
Full intended output example snippet:
#main-content {
background: #ccc;
position: relative;
}
#iconrow {
border-bottom: solid 1px #999
}
.col-xs-0 {
width: 55px;
padding: 0px;
position: relative;
float: left;
min-height: 1px;
}
.icon {
width: 100%;
height: 0;
padding-bottom: 99%;
margin: 15px 0px;
border-radius: 100%;
border: 1px solid #999;
background: white
}
.timeline {
padding: 30px 0px;
position: relative;
}
.timeline ul {
padding: 0px;
list-style: none;
margin: 0px;
position: relative;
}
.timeline li {
position: relative;
width: 100%;
height: 50px;
display: block;
}
.timeline li:before {
width: 6px;
height: 100%;
margin-left: -3px;
position: absolute;
top: 0;
left: 50%;
display: block;
background: #cc7264;
content: ' ';
}
.timeline .node2 {
width: 24px;
height: 24px;
margin-left: -12px;
margin-top: 0px;
z-index: 2;
background: #eee;
border: 4px solid #cc7264;
position: absolute;
left: 50%;
top: -1px;
border-radius: 100%;
}
.timeline .node2:hover {
width: 48px;
height: 48px;
margin-left: -24px;
margin-top: -12px;
border-width: 6px;
}
.timeline .node2:active {
width: 36px;
height: 36px;
margin-left: -18px;
margin-top: -6px;
background: #eebaa9;
border-width: 6px;
}
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet"/>
<div class="container" id="main-content">
<div class="row clearfix">
<div class="col-xs-0"></div>
<div class="col-xs-2 timeline">
<ul class="year y2014">
<li> <div class="node2"></div></li>
<li> </li>
<li> </li>
<li> <div class="node2"></div></li>
<li> <div class="node2"></div></li>
<li> </li>
<li> <div class="node2"></div></li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> <div class="node2"></div></li>
</ul>
<ul class="year y2015">
<li> <div class="node2"></div></li>
<li> </li>
<li> </li>
<li> <div class="node2"></div></li>
<li> <div class="node2"></div></li>
<li> </li>
<li> <div class="node2"></div></li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> <div class="node2"></div></li>
</ul>
</div>
<div class="col-xs-2 timeline">
<ul>
<li> </li>
<li> <div class="node2"></div></li>
<li> <div class="node2"></div></li>
<li> </li>
<li> <div class="node2"></div></li>
<li> </li>
<li> </li>
<li> </li>
<li> <div class="node2"></div></li>
<li> <div class="node2"></div></li>
<li> </li>
<li> </li>
</ul>
</div>
<div class="col-xs-2 timeline">
<ul>
<li> </li>
<li class="disabled"> <div class="node2"></div></li>
<li class="disabled"> </li>
<li class="disabled"> </li>
<li class="disabled"> </li>
<li class="disabled"> </li>
<li class="disabled"> </li>
<li class="disabled"> </li>
<li class="disabled"> </li>
<li class="disabled"> </li>
<li class="disabled"> <div class="node2"></div></li>
<li class="disabled"> </li>
</ul>
</div>
<div class="col-xs-2 timeline">
<ul>
<li class="disabled"> <div class="node2"></div></li>
<li class="disabled"> </li>
<li class="disabled"> </li>
<li class="disabled"> <div class="node2"></div></li>
<li class="disabled"> <div class="node2"></div></li>
<li class="disabled"> <div class="node2"></div></li>
<li class="disabled"> <div class="node2"></div></li>
<li class="disabled"> </li>
<li class="disabled"> </li>
<li class="disabled"> <div class="node2"></div></li>
<li class="disabled"> </li>
<li class="disabled"> <div class="node2"></div></li>
</ul>
</div>
</div>
</div>
Thank you!
My solution ended up being to create helper functions to pad the data, and simplify making new models.
I start by making a master collection of the node model, so each node gets the backbone cID and a single handler, then I use the collection's "where" functionality (from underscore.js) to grab an array of nodes that match the group. nodeCollection.where({listID:1})
With this list-specific array, I can then create an empty array of size N, var emptyArray = Array(N)
, and a simple foreach loop over the list-specific nodes to replace the position in the array with the node object. (Note: this array is filled with "undefined" objects)
for(node in nodeSublist){
i=node.get("position")-1;//subtract 1 for 0-based array position
emptyArray[i]=node;
}
With this padded array of Nodes, I can and then throw that array into a new collection object. new Entities.NodesCollection(paddedArray)
. Backbone will not create new Node objects, because they are already instantiated.
I set this new padded nodeCollection as an attribute in a new "list" Model, for each list. Then in the list's CollectionView, I simply set the view's collection to the node list in the model, on the initialize:
pass.
initialize: function(){
this.collection = this.model.get("nodes");
}
I can create a collection of these list models, and now I have every object I need. A simple CollectionView for the array of Lists, will output a container and render each NodeList view. Each Nodelist will output UL, and switch between a Node ItemView or an Empty ItemView.
var NodeList = Marionette.CollectionView.extend({
tagName: "ul",
getChildView: function(node){
//this is a real node
if(node.get("name")){
return List.NodeView;
}
//otherwise this node is empty, dont render anything
else{
return List.EmptyNodeView;
}
},
initialize: function(){
this.collection = this.model.get("nodes");
//you cannot set the class on initialize,
//because the DOM object is created before the initialize function,
//so use jquery.
this.$el.addClass("list"+this.model.get("listID"));
}
}
And we're good!
Thank you @Seebiscuit for getting me started!