d3 data items as backbone views - backbone.js

I'm creating a barchart with d3 where the data is in a backbone collection.
I want the user to be able to interact with the bars in the chart, selecting them, editing data, etc. etc.
I figured the best way to do this was to create a view for the chart, and a separate view for the bars.
in my chart view, I have
create_bar: function(){
var chart = d3.select("div#chart");
timeline.selectAll("div")
.data(Myapp.chart.models)
.enter()
.append(function(d){console.log(d);
var bar = new Myapp.Views.ChartBar({model:d});
return bar.el;
});
}
but unfortunately, it looks like append fails with a function.
I'm looking at putting some moderately complex html within the bar div, as well as a few data-points.
Any suggestions on how to do this?

Backbone views with d3 make kind of an odd mix, but here's one way it might work:
timeline.selectAll("div")
.data(Myapp.chart.models)
.enter()
.append('div')// or 'span' or even Myapp.Views.ChartBar.prototype.el
.each(function(d, i) {
var bar = new Myapp.Views.ChartBar({model:d});
bar.setElement(this);// Here "this" is the dom element
}
EDIT:
The last 2 lines can be combined into a single one, skipping the call to setElement():
var bar = new Myapp.Views.ChartBar({model:d, el:this});

Related

Reloading Nested Angular directives from database

I have a super specific issue with an app I'm building.
Angular Code:
$scope.step1 = function ()
{
console.log("step1");
var columnDirective = '<ee-col object-ref1="Column1Clicked" object-ref2="Column2Clicked"></ee-col>';
var container = document.getElementById("container");
var angElement = angular.element(container);
angElement.html("");
angElement.append($compile(columnDirective)($scope));
};
$scope.step2 = function ()
{
console.log("step2");
var imageDirective = '<ee-img object-ref1="ImageRef1" ></ee-img>';
var container = document.getElementById("col2");
var angElement = angular.element(container);
angElement.html("");
angElement.append($compile(imageDirective)($scope));
};
$scope.step3 = function (){
console.log("step3");
var myImg ='<img src="https://www.dropbox.com/s/4ztbvr93gb3kllo/testcard.jpg?dl=1"/>'
var container = document.getElementById("ImagePlaceholder");
var angElement = angular.element(container);
angElement.html(myImg);
};
$scope.step4 = function (){
console.log("step4");
var container = document.getElementById("container");
var angElementContainer = angular.element(container);
var copied = document.getElementById("copied");
var angElementCopied = angular.element(copied);
angElementCopied.html(angElementContainer.html());
};
Basically I have an editor where I can drag/drop directives into a container and some of these directives themselves are containers
I have created a fiddle to illustrate this issue (does not use drag/drop)
https://jsfiddle.net/starkx/jehLd5og/
Step1: I add my column directive to a container div. My column directive has two cells and you can see the click events for each of these cells being raised in the console as you click around them
Step2: simulates dragging an image directive (this has an imageContainer) into the right hand cell. now when you click in this cell you get the click event from the image directive, whereas the left hand cell still shows the column click event
Step3:simulates dragging an image into the imageContainer. the click events all still function
In my application I am now at a position where I would save this information and a subsequent reload of this page would pull this data from the database.
I am simulating this in Step4 by copying the contents of the container div to the copied div.
In this copied code the ng-clicks do not work and you get no console output. I have tried compiling this 'copied' code but I have not been able to get the original stuff in steps (1-3) to work at the same time as the code in the 'copied' div.
Such is the nature of this issue that my google-fu has let me down somewhat and I'm suffering from being a relative beginner at angular coding.
(note: the fiddle is not designed so that you can click the steps willy-nilly. they must be done in order and once only. This is also a very simplified example as it is also possible to nest column directives within each other)
any help gratefully received
Edit: I have added a 5th step to $compile the copied code, but whilst the ng-click methods get called, their parameter is passed as 'undefined' in the copied section.

Angularjs - Charts.js: Same chart element doesn't redraw on other view

I am new to angularjs, trying to create my first directive. I am creating a directive to load Charts.js2.0(beta) into my application.
I have 2 views managed by angular-route, both html view has ng-included a html page that contains only charts-element.
The problem is the first page properly draws the chart, when i go to other view the charts div is loaded but charts is not re-drawn. And now if i go back to first view its blank.
Link to Plunker
What i am doing wrong? Is there any issue with my directive?
Thanks in advance.
There appears to be an issue with the Charts library modifying the existing object on the root scope, and thereby ignoring it forever afterward. I can't really trace down what is doing it, but here's a fix for you: http://plnkr.co/edit/jDQFV62FSeXAQJ6o7jE8
Here is what you had
scope.$watch('config', function(newVal) {
if(angular.isDefined(newVal)) {
if(charts) {
charts.destroy();
}
var ctx = element[0].getContext("2d");
charts = new Chart(ctx, scope.config);
//scope.$emit('create', charts);
}
});
Above, you can see that you're passing scope.config directly into the charts method. That appears to be modifying the data somehow, and since that's passed by reference, you're actually modifying $rootScope.sales.charts. If you copy that object and use it locally like below, you don't have that problem.
Here's how I fixed it.
scope.$watch('config', function(newVal) {
var config = angular.copy(scope.config);
if(angular.isDefined(newVal)) {
if(charts) {
charts.destroy();
}
var ctx = element[0].getContext("2d");
charts = new Chart(ctx, config);
//scope.$emit('create', charts);
}
});
You can see that instead of passing that object directly in, we use angular to make a copy (angular.copy()), and that's the object we pass in.
I think it has relation with the id of the canvas where you are drawing. I've had this problem too amd it was because i was using the same id for the canvas of two graphs in different views. Be sure that those ids are different and that the javasrcipt of each graph is in the controller of each view or in each view itself.
Taking a look at your pluker I see that you are using the same html for the graph and I guess that when angular moves from one of your views to the other thinks that the graph is already drawn. Differentiating two graphs will solve the problem. I don't know of there is any other approach that allows using the same html for the canvas of the graph.
Hope it helps you solve it

How to determine if I should use a layout view Marionette?

I have skimmed through all the marionette articles on the layout view and I am not sure if there are advantages to using that versus how I have my app setup now, let me show you.
After building the components to my app I then created an app level view that handles the initialization and rending of all those views.
var Backbone = require('backbone'),
TeamsView = require('./teams'),
DataView = require('./teamData'),
LeaderView = require('./leader');
module.exports = appView = Backbone.View.extend({
el: '#wrap',
template: require('../../templates/app.hbs'),
initialize: function() {
window.App.views.teamsView = new TeamsView({ collection: window.App.data.teams });
window.App.views.dataView = new DataView({ collection: window.App.data.teams });
window.App.views.leaderView = new LeaderView({ collection: window.App.data.teams });
},
render: function() {
var teamsView = window.App.views.teamsView;
var dataView = window.App.views.dataView;
var leaderView = window.App.views.leaderView;
this.$el.find('#basketball .app').prepend(teamsView.render().el);
this.$el.find('#basketball .app').prepend(dataView.render().el);
this.$el.prepend(leaderView.render().el);
}
});
Then inside a controller I render the app view above.
This feels comfortable to me, but somewhere deep inside says it's wrong and I should be looking at layout views?
So my question more specifically is when putting the pieces together in an backbone application should I be looking into layout views or is creating a single app level view (like above) sufficient?
What you're doing is fine at the simplest level, though perhaps more manual than it needs to be. However, you may want a Layout View as things get more complex.
The value of a Layout View comes when you have a region in your App that you'd like to have contain sub-regions, but only during certain views. For example, you might have an index/list inside of your App's #mainRegion that just has a bunch of teams, in which case you could use a CollectionView (or CompositeView if you want to add some styling) along with the ItemView for each team.
However, say you click to edit one of the teams, and now you want the #mainRegion to show the edit page, which itself has some information about the team in #infoRegion and then an edit form in a #formRegion. These are regions that are specific to the edit page, and so you'd want to use a Layout View to manage them rather than delegating all the way up to the App level.
I hope this makes sense, and I'm happy to clarify if needed. You can also check out the documentation for a breakdown of when to use each view type: https://github.com/marionettejs/backbone.marionette/wiki/Use-cases-for-the-different-views

Backbone.marionnette - Rebinding events vs creating new view

I have a Layout that has several tabs. Clicking one of these tabs will show the appropriate composite view in the page's content region. After navigating back and forth between different tabs I noticed that the composite views have lost their native bindings to render on collection reset and model changes.
Is there a way I should be rebinding the events being used in _initialEvents of a composite view when showing a view for a second time, or should I be creating a new composite view every I show a tab?
Currently I am creating all my views in initialize of my Layout and then using show with the view when a tab is clicked.
initialize: function(){
_.bindAll(this);
// Tabs
this.places_page = new Places_Layout();
},
show_places_page: function(){
this.content.show(this.places_page);
this.places_page.delegateEvents();
},
You don not have to create a Layout/Item/Composite/Collection view each time you switch from tab to tab, on the contrary you can save the content in a variable just the way you are doing, the problem you have is that the variable is being re-declared each time you want to render the content.
The solution is that you have to verify if that variable (this.places_page) is declared if not append it to the view so when you call it more times it will be holding the same layout view without any problem, just note that when you render the main view (the one holding the regions) the nested child views(in regions) will be lost until new navegation through them.
initialize: function(){
_.bindAll(this);
// You can asign a diferent variable for each view so when you call show_places_page it will render with the same view.
if (!this.places_page){
this.places_page = new Places_Layout();
}
// other tab
if (!this.other_page){
this.other_page = new OtherPage_Layout();
}
},
show_places_page: function(){
this.content.show(this.places_page);
this.places_page.delegateEvents();
},
This does not sound like the best approach to me.
You should use the layout's region managers to show views without needing functions like you have defined.
I would go for this approach
var view = new CustomView();
layout.content.show(view);`
then later on:
var newView = new SecondCustomView();
layout.content.show(newView);
If you want to continue down the road that you are on then you would probably be best to use this approach:
initialize: function () {
_.bindAll(this);
},
show_places_page: function () {
var placesLayout = new Places_Layout();
this.content.show(placesLayout);
}
Does that make sense?
Its hard to suggest the best course of action without seeing more structure around this.
Is there a reason that you are creating the views in initialize?
Marionette(v.1) onwords uses Backbone.BabySitter to manage child views .
In your case you do the same.
Just create a containter to store all tab view. Later query the container to return the view you need to display.
this.tabViewsContainer = new Backbone.ChildViewContainer();
this.tabViewContainer.add(new CustomView(),'tab1');
this.tabViewContainer.add(new SecondCustomView(),'tab2');
To Later Show the view just do this
var custv = container.findByCustom("tab1");
this.content.show(custv);
In close method your layout view successfully close all view in container
this.tabViewsContainer.each(function(view){view.close()});
You should not create all the views inside the initialize as this will cause you memory leaks that's why you should do dynamic creation of the views. Also I would suggest create a common function for showing a view in your content region to increase the code re-usability. I would suggest you something like following solution:
//define the regions of your layout view
regions: {
content: '#content'
},
//Maintain a config for the tab content view classes.
contentViews: {
tab1: Tab1View,
tab2: Tab2View,
tab3: Tab3View
},
//keeps all the view instances
viewInstances: {},
/*
* show tab function is called when you click a tab item.
* Consider each tab has a attribute for tab name.
* For example HTML of your one tab is like:
* <div data-tab-name="tab_name">Tab <tab_name></div>
*/
showTab: function (e) {
var tabName = $(e.currentTarget).attr("data-tab-name");
/*
* code for showing selected tab goes here...
*/
//check and create the instance for the content view
if (!this.viewInstances[tabName]) {
this.viewInstances[tabName] = new this.contentViews[tabName]();
}
//Then here you are actually showing the content view
this.content.show(this.viewInstances[tabName]);
this.viewInstances[tabName].delegateEvents(); //this is to rebind events to the view.
}

how to handle multiple views within a single view with backbonejs

I'm new to Backbone and I don't fully understand it yet, and I've come across a situation I can't find any documentation on. What I if I have a view that contains multiple views? For example, I have a view called StackView. The purpose of this view is to neatly lay out a set of cards. It manages the animation of adding, removing, and adjusting cards in the stack. Each card is a CardView. How would I handle this? I've seen people talk about views within views by simple creating a variable in the view and assigning the View instance to that variable. Should I just be adding an array of CardViews in a variable of a StackView?
That's what I do, and it works well. Here's a snippet of a View I use in an application. I've re-written it back into regular javascript from my coffeescript, so I apologize for any typos:
render: function() {
var _this = this;
this.$el.html(this.template());
this.listItemViews = [];
// for each model in the collection, create a new sub-view and add
// it to the parent view
this.collection.each(function(model){
var view = new App.Views.Projects.ListItem({model:model}); // create the new sub-view
_this.listItemViews.push(view); // add it to the array
_this.$('#project-table tbody').append(view.render().$el); // append its rendered element to the parent view's DOM
});
return this;
}
This lets my Table view maintain a reference to all the listItemView views.
Of course, if you do this, you should make sure to properly remove these child views and unbind any events when you remove the parent view.

Resources