Assign click events to D3 chart elements using Backbone.js - backbone.js

I want to bind click events to points created by a chart instantiated by a Backbone View, but events and delegateEvents aren't working (ie, no event is fired on clicking '.point' elements):
// View
var ScatterView = Backbone.View.extend({
defaults:{
// height, width, etc.
},
events:{
'click .point':'clickFunction'
},
clickFunction:function() {
console.log('clicked')
},
initialize:function(options) {
this.options = _.extend({}, this.defaults, options)
this.render()
},
render:function() {
var that = this
this.chart = new Scatter({
container:that.el.id,
// pass parameters: width, height, etc.
})
this.delegateEvents()
}
})
// Scatter function
var Scatter = function(settings) {
this.settings = settings
this.build()
}
Scatter.prototype.build = function() {
// Bind points
this.div = d3.select('#' + settings.container).append('div').attr('id', settings.id).attr('class', 'chart')
this.g = this.div.append('g')
this.points = this.g.selectAll('.point')
.data(this.data, function(d) {return d.id})
.enter().append('circle')
.attr('cx', function(d) {return that.xScale(d.x)})
.attr('cy', function(d) {return that.yScale(d.y)})
.attr('r', that.settings.radius)
.attr('class', 'point')
}
// instantiate view
var sv = new ScatterView({model:sm, el:'#main', chartid:'scatter-' + d, textid:'text-' + d})

You need to set an el property, for example:
var ScatterView = Backbone.View.extend({
el: '#chartContainer', ...
});

Try this:
events:{
"click [class~=point]" : "clickFunction"
}

The problem is that Backbone uses jQuery event binding under the covers, but you're trying to set click events on SVG elements. Without additional plugins, jQuery won't reliably select SVG elements, so there's no way it can bind events to them.

Related

Why does my angular directed work on one table cell but none others?

I am using a ContextMenu directive within a kendo grid. I have made one change to it so I can include icons in the text (changed $a.text(text) to $a.html(text).
I have one in the first cell (I highjacked the hierarchical cell) that has row operations (add, clone& delete) and one on a span within each cell that changes the cell values operation (addition, subtraction, equals, etc...)
Both of these were working. I am unsure what I changed that stopped it from working because I last checked it several changes ago (I'm still locked out of TFS so I can't revert).
One change I made was to include a disabled/enabled check to the working contextMenu. I tried adding the same to the broken one and no dice.
I do perform a $compile on the working menu and the broken one is only included in the kendo field template.
If I must compile the field template (and I didn't need to before), how can this be done?
So here is some code.
working menu:
$scope.getRowContextMenu = function (event) {
var options =
[[
"<span class='fa fa-files-o'></span>Clone Rule", function (scope, cmEvent) {/*omitted for brevity*/}),rowContextDisableFunction]]
}
var setHierarchyCell = function (grid) {
var element = grid.element;
var hCells = element.find("td.k-hierarchy-cell");
hCells.empty();
var spanStr = "<span context-menu='getRowContextMenu()' class='fa fa-bars'></span>";
hCells.append($compile(spanStr)($scope));
var span = hCells.find("span.fa");
span.on('click', function (event) {
$(this).trigger('contextmenu', event);
});
}
kendo template:
var mutliFormTemplate = function (fieldName, type) {
var result = "";
result += "<span context-menu='getOperationContextMenuItems()' class='fa #= " + fieldName + "_Obj.OperationSymbol # type-" + type + "'> </span>\n";
/*The rest pertains to the cell value. excluded for brevity*/
return result;
}
$scope.getOperationContextMenuItems = function () {
//I trimmed this all the way down to see if I could get it working. Still no joy
return [
["test", function () { }, true]
];
}
Creating the kendo columns dynamically:
$scope.model = {
id: "RuleId",
fields: {}
};
$scope.fieldsLoaded = function (data, fields) {
var column = {}
$.each(fields, function () {
var field = this;
$scope.columns.push({
field: field.Name,
title: field.Name,
template: mutliFormTemplate(field.Name, "selector")
});
column[field.Name ] = { type: getFieldType(field.Type.BaseTypeId) }
});
$scope.model.fields = column;
}
Thanks for any and all help ^_^

Backbone/Marionette Fetching large collection causes browser to freeze

I have a CompositeView that I am working with in Marionette that when the collection fetches can potentially pull 1000s of records back from the API. The problem I am seeing is that when the UI loads, the browser wigs out and the script freezes the browser up.
I have tried following this blog post (http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/), but can't seem to figure out how to translate that to working in Marionette.
Here is my CompositeView:
var EmailsMenuView = Marionette.CompositeView.extend({
template: _.template(templateHTML),
childView: EmailItemView,
childViewOptions: function(model, index){
return {
parentIndex: index,
parentContainer: this.$el.closest('form')
};
},
childViewContainer: '.emails',
ui: {
emails: '.emails',
options: '.email-options',
subject: "#subject_line",
fromName: "#from_name",
fromEmail: "#from_email",
thumbnail: '.thumbnail img',
emailName: '.thumbnail figcaption'
},
events: {
'change #ui.subject': 'onSubjectChange',
'change #ui.fromName': 'onFromNameChange',
'change #ui.fromEmail': 'onFromEmailChange'
},
childEvents: {
'menu:selected:email': 'onMenuEmailSelect'
},
collectionEvents: {
'change:checked': 'onEmailSelected'
},
behaviors: {
EventerListener: {
behaviorClass: EventerListener,
eventerEvents: {
'menu:next-click': 'onNext',
'menu:previous-click': 'onPrev'
}
},
LoadingIndicator: {
behaviorClass: LoadingIndicator,
parentClass: '.emails'
}
},
renderItem: function(model) {
console.log(model);
},
/**
* Runs automatically during a render
* #method EmailsMenuView.onRender
*/
onRender: function () {
var colCount, showGridList, selected,
threshold = 300;
if(this.model.get('id')) {
selected = this.model;
}
// refresh its list of emails from the server
this.collection.fetch({
selected: selected,
success: function (collection) {
colCount = collection.length;
console.log(colCount);
},
getThumbnail: colCount <= threshold
});
this.collection.each(this.renderItem);
var hasEmail = !!this.model.get('id');
this.ui.emails.toggle(!hasEmail);
this.ui.options.toggle(hasEmail);
eventer.trigger(hasEmail ? 'menu:last' : 'menu:first');
}
});
My ItemView looks like this:
var EmailItemView = Marionette.ItemView.extend({
tagName: 'article',
template: _.template(templateHTML),
ui: {
radio: 'label input',
img: 'figure img',
figure: 'figure'
},
events: {
'click': 'onSelect',
'click #ui.radio': 'onSelect'
},
modelEvents: {
'change': 'render'
},
behaviors: {
Filter: {
behaviorClass: Filter,
field: "email_name"
}
},
/**
* initializes this instance with passed options from the constructor
* #method EmailItemView.initialize
*/
initialize: function(options){
this.parentIndex = options.parentIndex;
this.parentContainer = options.parentContainer;
this.listenTo(eventer, 'menu:scroll', this.onScroll, this);
},
/**
* Runs automatically when a render happens. Sets classes on root element.
* #method EmailItemView.onRender
*/
onRender: function () {
var checked = this.model.get("checked");
this.$el.toggleClass('selected', checked);
this.ui.radio.prop('checked', checked);
},
/**
* Runs after the first render, only when a dom refrsh is required
* #method EmailItemView.onDomRefresh
*/
onDomRefresh: function(){
this.onScroll();
},
/**
* Marks this item as checked or unchecked
* #method EmailItemView.onSelect
*/
onSelect: function () {
this.model.collection.checkSingle(this.model.get('id'));
this.trigger('menu:selected:email');
},
templateHelpers: {
formattedDate: function() {
var date = this.updated_ts.replace('#D:', '');
if (date !== "[N/A]") {
return new Moment(date).format('dddd, MMMM DD, YYYY [at] hh:mm a');
}
}
},
/**
* Delays the load of this view's thumbnail image until it is close to
* being scrolled into view.
* #method EmailItemView.onScroll
*/
onScroll: function () {
if (this.parentIndex < 10) {
// if it's one of the first items, just load its thumbnail
this.ui.img.attr('src', this.model.get('thumbnail') + '?w=110&h=110');
} else if (this.parentContainer.length && !this.ui.img.attr('src')) {
var rect = this.el.getBoundingClientRect(),
containerRect = this.parentContainer[0].getBoundingClientRect();
// determine if element is (or is about to be) visible, then
// load thumbnail image
if (rect.top - 300 <= containerRect.bottom) {
this.ui.img.attr('src', this.model.get('thumbnail') + '?w=110&h=110');
}
}
}
});
I am trying to adjust my CompositeView to work where I can build the HTML and then render the cached HTML versus appending 1000s of elements
Render all 1000 elements isn't good idea - DOM will become too big. Also on fetch there are parse for all 1000 models, and it works synchroniously. So there are two ways - load less data, or splist data to parse/render by chunks

BackboneJS - How to hide a <button> when all models has been loaded

I want to hide my "show more"-button as soon all my models has been loaded into the collection. How can I achieve that? Lets say, I have a collection which contain 20 models. I display 4 to start with and when I have clicked myself through to all 20 models, the "showMore"- button should disappear.
So far I have in my View:
events: {
'click .showMore': 'showMore'
},
showMore: function(){
this.collection.fetch({remove: false});
},
afterRender: function(){
var collection = this.collection;
if(collection.length > 3) {
$('<button class="showMore">Show more</button>').insertAfter('div.news');
}
}
And my Collection:
myCollection = Backbone.Collection.extend({
step: 0,
parse: function(response){
var slice = response.data.news.slice(this.step*4,(this.step+1)*4)
this.step++;
return slice;
}
});
Thanks in advance...
You could try counting the # of models you have on the page. For example, if your models are in <div class="myModel">, you could add
if(document.getElementByClassName('myModel').length == collection.length) {
$('.showMore').hide()
}
to your afterRender function.
I solved myself in a different way!
If I console.log the length, the collection has the amount of models given by the var slice-function I have in my Collection (see code).
So I made it like this:
afterRender: function(){
$("article.news:gt(3)").hide();
var count = $("article.news").length;
if(count === 1){
$(".showmore").hide();
}
var i = 4;
$(".showmore").on("click" , function() {
i = i + 4;
$("article.news:lt(" + i + ")").show();
if(i > count){
$(".showmore").hide();
}
});
}
and removed the .slice()-method completely.
article.news is the model selector in this case.
Its not the prettiest solution, but it works for me.

How to get `scroll at bottom` and `scroll at top` of a list view of a Sencha Touch list view?

I want to code lazy loading for the list view so that I need to add the action for my controller that fired when user scrolls down to bottom of the list and when scrolls up to the top of the list.
How can I do that using Sencha Architect?
I do not use Architect, but what you need is get a ref of your list in controller, and attach an handler for the scroll event of the scroller object in the initialize event of the list:
Controller
config: {
refs: {
list: '...',
...
},
control: {
list: {
initialize: 'onListInit'
},
...
},
...
},
onListInit: function() {
var scroller = this.getScrollable().getScroller();
scroller.on('scrollstart', this.onNnListScrollStart, this);
scroller.on('scrollend', this.onNnListScrollEnd, this);
},
onNnListScrollStart: function(scroller, x, y) {
console.log('START SCROLL');
},
onNnListScrollEnd: function(scroller, x, y) {
console.log('scroll x:'+x);
console.log('scroll y:'+y);
var bottom = scroller.maxPosition.y;
var top = scroller.minPosition.y;
var isScrollUp = scroller.dragDirection.y === -1;
var isScrollDown = scroller.dragDirection.y === 1;
if (bottom === y && isScrollDown) {
console.log('BOTTOM');
}
if (top === y && isScrollUp) {
console.log('TOP');
}
console.log('END SCROLL');
},
...
A Sencha Architect sample project implementing this guide can be downloaded here

Extjs 3.4 checkchange listener not working on Checkcolumn

The checkchange listener for my checkColumn is not working. Any ideas why not?
var checked = new Ext.grid.CheckColumn({
header: 'Test',
dataIndex: 'condition',
renderer: function(v,p,record){
var content = record.data['info'];
if(content == 'True'){
p.css += ' x-grid3-check-col-td';
return '<div class="x-grid3-check-col'+(v?'-on':'')+' x-grid3-cc-'+this.id+'"> </div>';
}
},
listeners:{
checkchange: function(column, recordIndex, checked){
alert("checked");
}
}
});
In Ext.ux.grid.CheckColumn, add this initialize method that register a checkchange event:
initComponent: function(){
Ext.ux.grid.CheckColumn.superclass.initComponent.call(this);
this.addEvents(
'checkchange'
);
},
Then in processEvent fire the event:
processEvent : function(name, e, grid, rowIndex, colIndex){
if (name == 'mousedown') {
var record = grid.store.getAt(rowIndex);
record.set(this.dataIndex, !record.data[this.dataIndex]);
// Fire checkchange event
this.fireEvent('checkchange', this, record.data[this.dataIndex]);
return false; // Cancel row selection.
} else {
return Ext.grid.ActionColumn.superclass.processEvent.apply(this, arguments);
}
},
The resulting CheckColumn component should look like this:
Ext.ns('Ext.ux.grid');
Ext.ux.grid.CheckColumn = Ext.extend(Ext.grid.Column, {
// private
initComponent: function(){
Ext.ux.grid.CheckColumn.superclass.initComponent.call(this);
this.addEvents(
'checkchange'
);
},
processEvent : function(name, e, grid, rowIndex, colIndex){
if (name == 'mousedown') {
var record = grid.store.getAt(rowIndex);
record.set(this.dataIndex, !record.data[this.dataIndex]);
this.fireEvent('checkchange', this, record.data[this.dataIndex]);
return false; // Cancel row selection.
} else {
return Ext.grid.ActionColumn.superclass.processEvent.apply(this, arguments);
}
},
renderer : function(v, p, record){
p.css += ' x-grid3-check-col-td';
return String.format('<div class="x-grid3-check-col{0}"> </div>', v ? '-on' : '');
},
// Deprecate use as a plugin. Remove in 4.0
init: Ext.emptyFn
});
// register ptype. Deprecate. Remove in 4.0
Ext.preg('checkcolumn', Ext.ux.grid.CheckColumn);
// backwards compat. Remove in 4.0
Ext.grid.CheckColumn = Ext.ux.grid.CheckColumn;
// register Column xtype
Ext.grid.Column.types.checkcolumn = Ext.ux.grid.CheckColumn;
In ExtJS 3, the checkcolumn plugin does not actually use ExtJS's checkbox component, so checkbox events are not available. The checkcolumn is simply an extended grid column that has added a custom renderer to style the cell like a checkbox.
By default, the only events you can listen to are Ext.grid.Column's events (click, contextmenu, dblclick, and mousedown).
This answer to a similar question shows how to override the CheckColumn and add the beforecheckchange & checkchange events.
Simple Answer
Check box check or uncheck when user click on check box in extjs 3 grid.
use this property in grid: => columnPlugins: [1, 2],
I belive this property use in your code is wornig perfectly.
xtype:grid,
columnPlugins: [1, 2],

Resources