'Dashboard not a constructor' error with Google Charts - reactjs

I am a naive React Developer and facing some difficulty with getting gooogle chart work with react. I am using Google Charts in a ReactJs component with ControlWrapper as shown below.
componentDidMount: function(){
google.charts.load('current', {'packages':['corechart', 'controls']});
this.drawCharts();
},
componentDidUpdate: function(){
google.charts.load('current', {'packages':['corechart', 'controls']});
this.drawCharts();
},
drawCharts: function(){
var cmpt = this;
//Removed detailed code from here due to copyright issues
//adding controls----------------
let dashboard = new google.visualization.Dashboard( document.getElementById(cmpt.props.widgetId) );
let controlId = '${this.props.widgetId}_control';
var controlWrapper = new google.visualization.ControlWrapper({
'controlType' : 'NumberRangeFilter',
'containerId' : controlId,
'options' : {
'filterColumnLabel' : xDataSysName
}
});
var barChart = new google.visualization.ChartWrapper({
'chartType': 'BarChart',
'containerId': this.props.widgetId,
'options': options
});
dashboard.bind(controlWrapper, barChart);
dashboard.draw(data);
if(linkColumn){
let selectionEventHandler = function() {
window.location = data.getValue(barChart.getSelection()[0]['row'], 1 );
};
google.visualization.events.addListener(barChart, 'select', selectionEventHandler);
}
}
},
This is not the whole piece of code but should be enough for the issue I'm facing.
First time I load the page, I get the error in the console saying
google.visualization.Dashboard is not a constructor
I reload the page hitting SHIFT+F5, the error goes away and components load just fine except ones that are dependent on controlWrapper throwing the error as follows
google.visualization.controlWrapper is not a constructor
which never goes away even after reloading the page. I referred to this discussion and tried their solution but I am still getting these error in the manner mentioned above. Please help me figure out how to fix it. Also, I am not able to comprehend why dashboard error goes away on a reload.

need to wait until google charts has fully loaded before trying to use any constructors,
this is done by providing a callback function.
try changing the load statement as follows...
componentDidMount: function(){
google.charts.load('current', {packages:['corechart', 'controls'], callback: this.drawCharts});
},
componentDidUpdate: function(){
google.charts.load('current', {packages:['corechart', 'controls'], callback: this.drawCharts});
},

Related

Use protractor on an non angular page

in my recent test I need to first login and take some actions on an non angular page (https://www.qa.dealertrack.com/default1.aspx)
then switch to an angular page and finish the test.
In conf I have
global.driver = browser.driver;
My page object looks like:
var LogInPage = function() {
this.loginUrl = 'https://www.qa.dealertrack.com/default1.aspx';
this.id = browser.driver.findElement(by.name('username'));
this.password = browser.driver.findElement(by.name('password'));
this.loginButton = browser.driver.findElement(by.name('login'));
this.logIn = function(id, password) {
// maximize window
driver.manage().window().maximize();
// log in
driver.get('https://www.qa.dealertrack.com/default1.aspx');
this.id.sendKeys(id);
this.password.sendKeys(password);
this.loginButton.click();
}
};
My test looks like:
describe('Sample Test - Log In', function() {
var loginPage = require('../pages/LogInPage.js');
/**
* Disable waiting for AngularJS for none Angular page
*/
beforeEach(function() {
isAngularSite(false);
});
it('logging in', function() {
loginPage.logIn('xxx', 'xxx');
})
})
However, even before getting to the site, protractor throws error NoSuchElementError: no such element: Unable to locate element:{'method':'name','selector':'username'}
But when I commented out all the element variables and related lines, only left
driver.get(this/loginUrl);
It worked. Why would browser.driver.get works but browser.driver.findElement does not?
This is my first question on Stackoverflow. Thank everyone!!
Before login you need to set
browser.driver.ignoreSynchronization=true;
So it shold be like
browser.driver.ignoreSynchronization=true;
login();
browser.driver.ignoreSynchronization=false;
I tried to removed all the elements I declared outside my functions in page object. Instead of that, I just hard code the elements in the function. And it worked.

React update state if image not found.

I can't seem to wrap my head around this problem. What I'm trying to do is have React attempt to verify an image is not returning a 404 to display an alternative image instead. Something like the below does not work as React does not wait for the image to attempt to load for returning the component.
getInitialState: function () {
var img = new Image();
img.onerror = function() {
img.src = "alternativeImage.jpg"
},
img.src = this.props.image;
return {image: <img src={img.src} />};
},
The above code will display the images just fine but the 404 alternative image does not show up.
I've tried to place this in another method outside of getInitialState. I've also tried to have it call an external method outside of the React component, but nothing works.
There is a short hand method of adding onerror attribute on the image tag itself, but it seems React has the same issue of not executing that code and or updating itself based on the outcome.
I think the most frustrating part is that I cannot seem to have any function called that React would be able to work with from the JavaScript image object.
Thanks for any help you can provide.
Here's a solution that I came up with. Pretty new to React and programming in general so take this with a grain of salt....
const DEFAULT_IMAGE="http://i.imgur.com/lL3LtFD.jpg"
<img src={this.props.SRC} onError={(e)=>{e.target.src=DEFAULT_IMG}}/>
WARNING: If the default constant is invalid, the browser will be caught in an infinite loop (at least when I tested this in Chrome).
I haven't found a solution to this yet, but in vanilla javascript, you set the onerror attribute to null which tells the browser to make only a single request for the asset.
jQuery/JavaScript to replace broken images
It's a bad habit to put components into state. What you probably want is something like the following:
var Img = React.createClass({
componentDidMount: function () {
var self = this;
var img = new Image();
img.onerror = function () {
self.setState({ src: 'http://i.imgur.com/lL3LtFD.jpg' });
};
img.src = this.state.src;
},
getInitialState: function () {
return { src: '/404.jpg' };
},
render: function () {
return <img src={this.state.src} />;
}
});
jsfiddle link: http://jsfiddle.net/e2e00wgu

Backbone Router & Deep Linking

My single page web application consists of 4-5 views stacked vertically, when a user chooses a menu item, the page will scroll to the appropriate view. When you come into the application for the first time this is not a problem, however if you deep link to a menu item my page throws a fit because it's trying to access properties of an element that does not yet exists.
The problem I am having is understanding why the elements do not exist at the time the router is trying to scroll the page.
If you load / and then select home no problems, but if you directly hit #home via browser that when I get jQuery undefined errors.
Uncaught TypeError: Cannot read property 'top' of undefined
Inside router I am instantiating and rendering all of my views within the initialize function. The idea is the initialize will always happen before any of my routes, clearly not the case.
Again I've read a few threads that show how to have a before and after function for either all routes of individual routes but even using that approach scrollToById fails because it doesn't know what $(id) is at the time of being called.
define(function (require, exports, module) {
var Backbone = require('backbone');
return Backbone.Router.extend({
initialize: function(){
require(['ui/menu/menu','ui/home/home', 'ui/samples/samples', 'ui/resume/resume', 'ui/contact/contact'],
function(Menu, Home, Samples, Resume, Contact){
var menu = new Menu();
menu.render();
var home = new Home();
home.render();
var samples = new Samples();
samples.render();
var resume = new Resume();
resume.render();
var contact = new Contact();
contact.render();
});
},
routes: {
'' : 'init',
'home' : 'home',
'samples' : 'samples',
'resume' : 'resume',
'contact' : 'contact'
},
init: function(){
},
home: function (){
this.scrollToById($(".home-container"));
},
samples: function(){
this.scrollToById($(".samples-container"));
},
resume: function(){
this.scrollToById($(".resume-container"));
},
contact: function(){
this.scrollToById($(".contact-container"));
},
scrollToById: function(id) {
var val = $(id).offset().top - 127;
$('html, body').animate({
scrollTop: val
}, 2000);
}
});
});
Appreciate any tips or advice.
I think the routes event handlers in the router are getting initialized at the same time as the initialize function. Because of this, route events are getting triggered before the DOM elements are rendered.
I would try making a new function outside of Router that contains everything currently inside the initialize function. Then the final thing in that function can be to create an instance of the router. This will ensure that no routes events are called until your scripts and DOM are loaded.

How do I simply refresh a view in Backbone?

I'm brand new to backbone and just learning the basics. I am building an image gallery with backbone. I am displaying a large version of an image. The routing is working properly. When a url is passed with an id the appropriate JSON is loaded into the model and the html is injected into the dom. Everything displays as expected.
However, I tried entering a url for the JSON for an image that didn't exist and noticed that the view still rendered but with the previously rendered view's properties (image url) still present. How do I ensure that the view is refreshed - all empty properties? Or is it the model that needs to be refreshed?
Note: I am re-using the view to avoid the overhead of creating and dystroying the view itself.
Here is the view in question:
var ImageView = Backbone.View.extend({
template: Handlebars.compile(
'<div class="galleryImageSingle">'+
'<h2>{{title}}</h2>' +
'<img id="image" src="{{imageUrl}}" class="img-polaroid" />' +
'<div class="fb-share share-btn small"><img src="img/fb-share-btn- small.png"/></div>'+
'</div>' +
'<div class="black-overlay"></div>'
),
initialize: function () {
this.listenTo(this.model, "change", this.render);
//this.model.on('change',this.render,this);
},
fbSharePhoto: function () {
console.log('share to fb ' + this.model.attributes.shareUrl)
},
close: function () {
//this.undelegateEvents();
this.remove();
},
render: function () {
this.$el.html(this.template(this.model.attributes));
this.delegateEvents({
'click .fb-share' : 'fbSharePhoto',
'click .black-overlay' : 'close'
});
return this;
}
})
Here is the router:
var AppRouter = Backbone.Router.extend({
routes: {
"" : "dashboard",
"image/:iId" : "showImage",
},
initialize: function () {
// this.galleriesCollection = new GalleriesCollection(); //A collection of galleries
// this.galleriesCollection.fetch();
this.imageModel = new Image();
this.imageView = new ImageView ({ model: this.imageModel });
},
dashboard: function () {
console.log('#AppRouter show dashboard - hide everything else');
//$('#app').html(this.menuView.render().el);
},
showImage: function (iId) {
console.log('#AppRouter showPhoto() ' + iId);
this.imageModel.set('id', iId);
this.imageModel.fetch();
$('#imageViewer').html(this.imageView.render().el);
}
});
Is is it that the model still has the old info or the view, or both?
For extra credit, how could I detect a failure to fetch and respond to it by not triggering the corresponding view? Or I am I coming at it wrongly?
Thanks in advance for any advice.
////////////////////////////////////////////////////////////////////////////
Looks like I found something that works. I think just the process of framing the question properly helps to answer it. (I'm not allowed to answer the question so I'll post what I found here)
It appears that its the model that needs refreshing in this case. In the app router when I call the showImage function I clear the model and reset its values to default before calling fetch and this did the trick. Ironically the trick here is showing a broken image tag.
showImage: function (iId) {
console.log('#AppRouter showPhoto() ' + iId);
this.imageModel.clear().set(this.imageModel.defaults);
this.imageModel.set('id', iId);
this.imageModel.fetch();
$('#imageViewer').html(this.imageView.render().el);
}
For my own extra credit offer: In the event of an error (if needed fetch() accepts success and error callbacks in the options hash). Still definitely open to hearing about a way of doing this thats baked in to the framework.
You can just update the model like this:
ImageView.model.set(attributes)

Where is this event calling to in MarionetteJS

I am exploring the BBCloneMail demo application for MarionetteJS, but I am not seeing how the events are triggering the rendering actions. I saw some global 'show' event here:
https://github.com/marionettejs/bbclonemail/blob/master/public/javascripts/bbclonemail/components/appController.js#L25
show: function(){
this._showAppSelector("mail");
Marionette.triggerMethod.call(this, "show");
},
But I don't see, where/how the Marionette.triggerMethod results into rendering the Mail component. I was trying to call the triggerMethod for my case, but I get a 'cannot call apply for undefined'. Why is the call above working for the BBcloneMail application.
The Application controller for my case:
MA.AppController = Marionette.Controller.extend({
initialize: function(){
_.bindAll(this, "_showGenres");
},
show: function() {
if (MA.currentUser) {
MA.navbar.show(new MA.Views.Items.LogoutNavbar({model: MA.currentUser}));
}
else
{
MA.navbar.show(new MA.Views.Items.LoginNavbar());
}
this._showGenres();
},
_showGenres: function() {
var categoryNav = new MA.Navigation.Filter({
region: MA.filter
});
this.listenTo(categoryNav, "genre:selected", this._categorySelected);
categoryNav.show();
MA.main.show(MA.composites.movies);
},
showMovieByGenre: function(genre){
var movies = new MA.Controllers.MoviesLib();
that = this;
$.when(movies.getByCategory(genre)).then(that._showMovieList);
Backbone.history.navigate("#movies/genres/" + genre);
},
_showMovieList: function(movieList){
var moviesLib = new MA.Controllers.MoviesLib({
region: MA.main,
movies: movieList
});
Marionette.triggerMethod.call(this, "show");
}
});
I init the application controller in a init.js with:
app = new MA.AppController();
Looking at the source for triggerMethod, this is a way of both triggering an event (the string being passed in), and additionally (if it exists) running a method on the object that has an 'on' prefix.
In your case the error relates to line 560, specifically that there is no method apply on undefined. Based on the code its (in your case) trying to call the equivilent of this.trigger('show') - but AppController doesn't have a method called trigger.
In which case I'm guessing that in the BBCloneMail example this (being bassed into triggerMethod.call) is not actually the controller, but instead the view that is to be shown.

Resources