"GAPI is not defined" message - javascript

I'm trying to use the Google Sheets API for inclusion in my web app, but I keep receiving a error specifying that the gapi library is not defined. I've tried delay the request to the server by using the ComponentDidMount life cycle method, and even using a timeout in that method, but I keep receiving the same error. How can I have the gapi library defined for use then in my app ?
import React from 'react';
var CLIENT_ID = '';
var SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"];
export default class MyNavbar extends React.Component {
constructor(props) {
super(props);
}
componentDidMount(){
this.checkAuth();
}
/**
* Check if current user has authorized this application.
*/
checkAuth(){
gapi.auth.authorize(
{
'client_id': CLIENT_ID,
'scope': SCOPES.join(' '),
'immediate': true
}, this.handleAuthResult());
}
/**
* Handle response from authorization server.
*
* #param {Object} authResult Authorization result.
*/
handleAuthResult(authResult) {
var authorizeDiv = document.getElementById('authorize-div');
if (authResult && !authResult.error) {
// Hide auth UI, then load client library.
authorizeDiv.style.display = 'none';
loadSheetsApi();
} else {
// Show auth UI, allowing the user to initiate authorization by
// clicking authorize button.
authorizeDiv.style.display = 'inline';
}
}
/**
* Initiate auth flow in response to user clicking authorize button.
*
* #param {Event} event Button click event.
*/
handleAuthClick(event) {
gapi.auth.authorize(
{client_id: CLIENT_ID, scope: SCOPES, immediate: false},
handleAuthResult);
return false;
}
/**
* Load Sheets API client library.
*/
loadSheetsApi() {
var discoveryUrl =
'https://sheets.googleapis.com/$discovery/rest?version=v4';
gapi.client.load(discoveryUrl).then(listMajors);
}
/**
* Print the names and majors of students in a sample spreadsheet:
* https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
*/
listMajors() {
gapi.client.sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}).then(function(response) {
var range = response.result;
if (range.values.length > 0) {
appendPre('Name, Major:');
for (i = 0; i < range.values.length; i++) {
var row = range.values[i];
// Print columns A and E, which correspond to indices 0 and 4.
appendPre(row[0] + ', ' + row[4]);
}
} else {
appendPre('No data found.');
}
}, function(response) {
appendPre('Error: ' + response.result.error.message);
});
}
/**
* Append a pre element to the body containing the given message
* as its text node.
*
* #param {string} message Text to be placed in pre element.
*/
appendPre(message) {
var pre = document.getElementById('output');
var textContent = document.createTextNode(message + '\n');
pre.appendChild(textContent);
}
render(){
return (
<div>
<h1>Hello World My Name Is Justin 2</h1>
<div id="authorize-div"></div>
<pre id="output"></pre>
</div>
);
}
}

Here try this.
import React from 'react';
import asyncLoad from 'react-async-loader'; // for loading script tag asyncly `npm i --save react-async-loader`
const CLIENT_ID = '';
const SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"];
// For making gapi object passed as props to our component
const mapScriptToProps = state => ({
// gapi will be this.props.gapi
gapi: {
globalPath: 'gapi',
url: 'https://your-gapi-url'
}
});
#asyncLoad(mapScriptToProps)
class MyNavbar extends React.Component {
constructor(props) {
super(props);
this.gapi = null;
// You need to bind methods to this class's object context. (i.e this)!
this.checkAuth = this.checkAuth.bind(this);
this.handleAuthResult = this.authResult.bind(this);
this.handleAuthClick = this.handleAuthClick.bind(this);
this.loadSheetsApi = this.loadSheetsApi.bind(this);
this.listMajors = this.listMajors.bind(this);
}
componentDidMount() {
// Check is gapi loaded?
if (this.props.gapi !== null) {
this.checkAuth();
}
}
componentWillReceiveProps({ gapi }) {
if (gapi!== null) {
this.checkAuth();
}
}
/**
* Check if current user has authorized this application.
*/
checkAuth() {
// this will give you an error of gapi is not defined because there is no
// reference of gapi found globally you cannot access global object which are
// not predefined in javascript( in this case its window.gapi ).
// properties added by Programmer cannot be accessed directly( if it's not in the same file as well as the same scope!) in commonjs modules. Because they
// don't' run in a global scope. Any variable in another module which is not
// exported, will not be available to other modules.
this.gapi = window.gapi; // you can do like this. Now you can access gapi in all methods if this class.
this
.gapi
.auth
.authorize({
'client_id': CLIENT_ID,
'scope': SCOPES.join(' '),
'immediate': true
}, this.handleAuthResult());
}
/**
* Handle response from authorization server.
*
* #param {Object} authResult Authorization result.
*/
handleAuthResult(authResult) {
var authorizeDiv = document.getElementById('authorize-div');
if (authResult && !authResult.error) {
// Hide auth UI, then load client library.
authorizeDiv.style.display = 'none';
loadSheetsApi();
} else {
// Show auth UI, allowing the user to initiate authorization by clicking
// authorize button.
authorizeDiv.style.display = 'inline';
}
}
/**
* Initiate auth flow in response to user clicking authorize button.
*
* #param {Event} event Button click event.
*/
handleAuthClick(event) {
// gapi.auth.authorize( here also gapi is not defined
this
.gapi
.auth
.authorize({
client_id: CLIENT_ID,
scope: SCOPES,
immediate: false
}, handleAuthResult);
return false;
}
/**
* Load Sheets API client library.
*/
loadSheetsApi() {
var discoveryUrl = 'https://sheets.googleapis.com/$discovery/rest?version=v4';
// also will give your error
// for gapi being not defined.
// gapi.client.load(discoveryUrl).then(listMajors);
this
.gapi
.client
.load(discoveryUrl)
.then(listMajors);
}
/**
* Print the names and majors of students in a sample spreadsheet:
* https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
*/
listMajors() {
this.gapi
.client
.sheets
.spreadsheets
.values
.get({spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Class Data!A2:E'})
.then(function (response) {
var range = response.result;
if (range.values.length > 0) {
appendPre('Name, Major:');
for (i = 0; i < range.values.length; i++) {
var row = range.values[i];
// Print columns A and E, which correspond to indices 0 and 4.
appendPre(row[0] + ', ' + row[4]);
}
} else {
appendPre('No data found.');
}
}, function (response) {
appendPre('Error: ' + response.result.error.message);
});
}
/**
* Append a pre element to the body containing the given message
* as its text node.
*
* #param {string} message Text to be placed in pre element.
*/
appendPre(message) {
// as you can see. You are accessing window.document as document.
// its fine because its defined in javascript (implicitly),
// not explicitly by programmer(here you!).
var pre = document.getElementById('output');
var textContent = document.createTextNode(message + '\n');
pre.appendChild(textContent);
}
render() {
return (
<div>
<h1>Hello World My Name Is Justin 2</h1>
<div id="authorize-div"></div>
<pre id="output"></pre>
</div>
);
}
}
export default MyNavbar;

You need to load the gapi library before bundle.js in index.html file, or better you can load the gapi js script async with react-async-loader.
Here's how you can do it :
import React, { Component, PropTypes } from 'react';
import asyncLoad from 'react-async-loader'; // for loading script tag asyncly
// For making gapi object passed as props to our component
const mapScriptToProps = state => ({
gapi: {
globalPath: 'gapi',
url: 'https://your-gapi-url'
}
});
// decorate our component
#asyncLoad(mapScriptToProps)
class yourComponent extends Component {
componentDidMount() {
// Check is gapi loaded?
if (this.props.gapi !== null) {
this.checkAuth();
}
}
componentWillReceiveProps({ gapi }) {
if (gapi!== null) {
this.checkAuth();
}
}
checkAuth = () => {
// Better check with window and make it available in component
this.gapi = window.gapi;
this.gapi.auth.authorize({
'client_id': CLIENT_ID,
'scope': SCOPES.join(' '),
'immediate': true
}, this.handleAuthResult);
}
handleAuthResult = (authData) => {
// Your auth loagic...
}
render() {
return ( <div>
{ this.props.gapi &&
<YourSpreadsheetOrWhatever data={ this.getData() } />
}
</div>)
}
}
Try replacing your code with this and check if it works.

I found this project https://github.com/rlancer/gapi-starter it may be helpful.
Google Login & API + ReactJS + Flow + Webpack starter kit
Google API's are great but they were designed before the module partner of Javascript programing became popular. This starter fixes that handing Google login and library loading for you.
Get the Code!
git clone https://github.com/rlancer/gapi-starter.git
cd gapi-starter
npm install

You need to load the gapi library before bundle.js in index.html file as :
<script src="https://apis.google.com/js/api.js"></script>
And then you just need to initialize your gapi variablesas :
let gapi = window.gapi;

Related

There are no accepted cards available for use with this merchant - Google Pay

I am trying to integrate Google Pay web into my website but when i click "pay with googlepay" its shows the below error:
There are no accepted cards available for use with this merchant.
When i read documentation it says you can add example as merchant for testing, I just wanted to use test environment but still its not working.
Here is the code that i am using:
const allowedAuthMethods = ['PAN_ONLY','CRYPTOGRAM_3DS'] ;
const baseCardPaymentMethod = {
type: 'CARD',
parameters: {
allowedCardNetworks: allowedNetworks,
allowedAuthMethods: allowedAuthMethods
}
};
const googlePayBaseConfiguration = {
apiVersion: 2,
apiVersionMinor: 0,
allowedPaymentMethods: [baseCardPaymentMethod]
};
/**
* Holds the Google Pay client used to call the different methods available
* through the API.
* #type {PaymentsClient}
* #private
*/
let googlePayClient;
/**
* Defines and handles the main operations related to the integration of
* Google Pay. This function is executed when the Google Pay library script has
* finished loading.
*/
function onGooglePayLoaded() {
googlePayClient = new google.payments.api.PaymentsClient({
environment: 'TEST'
});
googlePayClient.isReadyToPay(googlePayBaseConfiguration)
.then(function(response) {
if(response.result) {
createAndAddButton();
} else {
alert("Unable to pay using Google Pay");
}
}).catch(function(err) {
console.error("Error determining readiness to use Google Pay: ", err);
});
}
/**
* Handles the creation of the button to pay with Google Pay.
* Once created, this button is appended to the DOM, under the element
* 'buy-now'.
*/
function createAndAddButton() {
const googlePayButton = googlePayClient.createButton({
// currently defaults to black if default or omitted
buttonColor: 'default',
// defaults to long if omitted
buttonType: 'long',
onClick: onGooglePaymentsButtonClicked
});
document.getElementById('buy-now').appendChild(googlePayButton);
}
/**
* Handles the click of the button to pay with Google Pay. Takes
* care of defining the payment data request to be used in order to load
* the payments methods available to the user.
*/
function onGooglePaymentsButtonClicked() {
const tokenizationSpecification = {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: 'example',
gatewayMerchantId: 'exampleGatewayMerchantId'
}
};
const cardPaymentMethod = {
type: 'CARD',
tokenizationSpecification: tokenizationSpecification,
parameters: {
allowedCardNetworks: ['VISA','MASTERCARD'],
allowedAuthMethods: ['PAN_ONLY','CRYPTOGRAM_3DS'],
billingAddressRequired: true,
billingAddressParameters: {
format: 'FULL',
phoneNumberRequired: true
}
}
};
const transactionInfo = {
totalPriceStatus: 'FINAL',
totalPrice: '123.45',
currencyCode: 'USD',
countryCode: 'US'
};
const merchantInfo = {
merchantId: '01234567890123456789', //Only in PRODUCTION
merchantName: 'Example Merchant Name'
};
const paymentDataRequest = Object.assign({}, googlePayBaseConfiguration, {
allowedPaymentMethods: [cardPaymentMethod],
transactionInfo: transactionInfo,
merchantInfo: merchantInfo
});
googlePayClient
.loadPaymentData(paymentDataRequest)
.then(function(paymentData) {
processPayment(paymentData);
}).catch(function(err) {
// Log error: { statusCode: CANCELED || DEVELOPER_ERROR }
});
}
function processPayment(paymentData) {
// TODO: Send a POST request to your processor with the payload
// https://us-central1-devrel-payments.cloudfunctions.net/google-pay-server
// Sorry, this is out-of-scope for this codelab.
return new Promise(function(resolve, reject) {
// #todo pass payment token to your gateway to process payment
const paymentToken = paymentData.paymentMethodData.tokenizationData.token;
console.log('mock send token ' + paymentToken + ' to payment processor');
setTimeout(function() {
console.log('mock response from processor');
alert('done');
resolve({});
}, 800);
});
} ```
There are no accepted cards available for use with this merchant.
This message means that the current Google user doesn't have any cards that are compatible with the payment options that the merchant has provided. Specifically allowedCardNetworks and allowedAuthMethods.
Here is a JSFiddle that I created based on your snippet: https://jsfiddle.net/aumg6ncb/
This is what I get back after clicking on the button:
If You are using Testing mode:-
I think You used testing card on your Chrome browser or Google Wallet
When testing Google Pay you should have a real card saved in your Chrome browser or Google Wallet, and have your test API keys/test Google Pay environment active. The real card does not get charged, and Google passes a test card during the checkout flow instead of a real card. Our normal test cards do not work with Google Pay when the user tries to save them in Chrome

Authenticated and public routes in Backbone Marionette

We have a large Marionette app, with sub apps/modules.
Each of these registers its own router within the App.addInitializer.
What is the best way to flag certain routes as public and others as requiring authentication?
I have a way in the app to check if the user is authenticated or not, but I'm trying to avoid having to implement that check in every route handler.
PrivateModuleRouter.Router = Marionette.AppRouter.extend({
appRoutes: {
"privateRoute(/)" : "handlePrivateRoute",
}
});
var API = {
handlePrivateRoute: function() {
//I don't want to repeat this everywhere..
if(!Auth.isAuthenticated()) {
App.navigate('/login', {trigger:true});
} else {
PrivateRouteController.showForm();
}
};
App.addInitializer(function(){
new PrivateModuleRouter.Router({
controller: API
});
});
Is there way in the route definition to flag it as private, and then a top level route handler performs this check?
If it's on a Router event though, this may not trigger if the route handler was triggered directly (not passing trigger:true, and calling API.handlePrivateRoute() directly.
Disclaimer: as I don't personally use Marionette, this answer is based on Backbone only.
The execute function
Backbone provides the execute function in the router as a way to handle that kind of logic. Even the example has authentication logic in it:
var Router = Backbone.Router.extend({
execute: function(callback, args, name) {
if (!loggedIn) {
goToLogin();
return false;
}
args.push(parseQueryString(args.pop()));
if (callback) callback.apply(this, args);
}
});
The authentication router
One way to avoid repeating the execute in each router would be to make a base router for your app.
var BaseRouter = Backbone.Router.extend({
constructor: function(prefix, opt) {
// get the hash
this.auth = _.result(this, "auth", {});
BaseRouter.__super__.constructor.apply(this, arguments);
},
// requires auth by default?
authDefault: false,
/**
* Check the `auth` hash for a callback. Returns `authDefault` if
* the callback is not specified.
* #param {String} callbackName name of the function.
* #return {Boolean} true if the callback is private.
*/
hasAuth: function(callbackName) {
return _.result(this.auth, callbackName, this.authDefault);
},
// To easily override the auth logic in a specific router
checkAuth: function(){
return Auth.isAuthenticated();
},
execute: function(callback, args, name) {
if (this.hasAuth(name) && !this.checkAuth()) {
this.navigate('/login', { trigger: true });
return false;
}
}
});
Defining the specific routers
Then for each of your router, extend BaseRouter.
var SpecificRouter = BaseRouter.extend({
routes: {
'*otherwise': 'home', // notice the catch all
'public': 'publicRoute',
'private': 'privateRoute',
'unspecified': 'defaultAccessRoute'
},
/**
* The auth hash works like this:
* "functionName": [boolean, true if needs auth]
*
* home and publicRoute could be left out as it's the default here.
*/
auth: {
home: false, // public
publicRoute: false, // public
privateRoute: true, // needs authentication
// defaultAccessRoute will be public because BaseRouter
// defines `authDefault: false`.
},
home: function() {},
publicRoute: function() {},
privateRoute: function() {},
defaultAccessRoute: function() {},
});
And for a router which all routes are private by default:
var PrivateRouter = BaseRouter.extend({
authDefault: true,
routes: {
'*otherwise': 'home', // private
'only-private': 'onlyPrivate', // private
},
// ...snip...
/**
* Optional example on how to override the default auth behavior.
*/
checkAuth: function() {
var defaultAuthResult = PrivateRouter.__super__.checkAuth.call(this);
return this.specificProperty && defaultAuthResult;
}
});
In github you can find many solution for calling some methods before router's execution. For marionette you can use ideas from marionette-lite extension based in filters system.
You should define filter, for example RequresAuthFilter as:
import { Filter } from 'marionette-lite';
const RequresAuthFilter = Filter.extend({
name: 'requresAuth', // name is used in controller for detect filter
async: true, // async mode
execution: Filter.Before,
handler(fragment, args, next) {
// Requesting server to check if user is authorised
$.ajax({
url: '/auth',
success: () => {
this.isSignedIn = true;
next();
},
error: () => {
Backbone.navigate('login', true);
}
});
},
});
or short sync way:
import { Filter } from 'marionette-lite';
const RequresAuthFilter = Filter.extend({
name: 'requresAuth',
handler(fragment, args) {
if (!window.isSignedIn) {
Backbone.navigate('login', true);
}
},
});
And add this filter to Controller as:
const AppController = Marionette.Object.extend({
// Add available filters map
filtersMap: [
new RequresAuthFilter()
],
filters: {
// e.g. Action that need authentication and if user isn't
// authenticated gets redirect to login page
requresAuth: ['logout', 'private'],
},
logout() { /* ... */ },
private() { /* ... */ }
});

Ember.js with Cloudkit JS

I have built a small prototype project using CloudKit JS and am now starting to build the next version of it and am wanting to use Ember as I have some basic experience with it. However, I am not too sure where to place the CloudKit JS code. For example where should I add the configure part and the auth function? I think that once I find the spot for the auth code, I could then add some of my query functions into the individual views and components, right?
Here is my configure code (with the container and id removed):
CloudKit.configure({
containers: [{
containerIdentifier: '###',
// #todo Must generate a production token for app store version
apiToken: '###',
auth: {
persist: true
},
// #todo Must switch to production for app store version
environment: 'development'
}]
});
Here is the auth function:
function setupAuth() {
// Get the container.
var container = CloudKit.getDefaultContainer();
//Function to call when user logs in
function gotoAuthenticatedState( userInfo ) {
// Checks if user allows us to look up name
var userName = '';
if ( userInfo.isDiscoverable ) {
userName = userInfo.firstName + ' ' + userInfo.lastName;
} else {
userName = 'User record name: ' + userInfo.userRecordName;
}
//Calls out initialization function
init();
//Sets up UI for logged in users
setAuthenticatedUI( userName );
//Register logged out function
container
.whenUserSignsOut()
.then( gotoUnauthenticatedState );
}
//Function to call when user logs out
function gotoUnauthenticatedState( error ) {
//Checks if error occurred
if ( error && error.ckErrorCode === 'AUTH_PERSIST_ERROR' ) {
displayError( logOutError, 'Error code: AUTH_PERSIST_ERROR' );
}
// Sets up the UI for logged out users
setUnauthenticatedUI();
//Register logged in function
container
.whenUserSignsIn()
.then( gotoAuthenticatedState )
.catch( gotoUnauthenticatedState );
}
// Check a user is signed in and render the appropriate button.
return container.setUpAuth()
.then( function( userInfo ) {
// userInfo is the signed-in user or null.
if ( userInfo ) {
gotoAuthenticatedState( userInfo );
} else {
gotoUnauthenticatedState();
}
});
}
The init() then calls functions to setup the queries to adds a chart to the page using records. The setAuthenticatedUI() and setUnauthenticatedUI() functions simply apply and remove classes once the user has been authenticated.
The answer pretty much depends on the version of Ember you're using and if how you are planning on using it. With routes? Simple routes? RouteHandlers?
For example, if you are at Ember v2.3.0, you could consider using dependency injection (https://guides.emberjs.com/v2.3.0/applications/dependency-injection/) to provide a configured container instance to the rest of your app, e.g.:
export function initialize(application) {
var container = CloudKit.configure(config).getDefaultContainer();
application.register('ckcontainer:main', container);
application.inject('route', 'ckcontainer', 'ckcontainer:main');
}
export default {
name: 'ckcontainer',
initialize: initialize
};
Then in a route, you can obtain a reference like so:
export default Ember.Route.extend({
activate() {
// The ckcontainer property is injected into all routes
var db = this.get('ckcontainer').privateCloudDatabase;
}
});
-HTH

Google Drive API authentication, JavaScript

I am running on a virtual host (as well as a Python SimpleHTTPServer) and I have been following Google's instructions, but I can't get the Google drive authorization to pop up on page refresh.
https://developers.google.com/drive/web/auth/web-client
Ive placed the first block of code in my index.html file, and Ive placed the following 2 snippets in <script> tags within <body>. I filled in the CLIENT_ID with the ID I created in the Google developer console...what am I missing?
<html>
<head>
<script type="text/javascript">
var CLIENT_ID = '1052173400541-355uhjrflurk7fmlon0r5umnn12i9ag3.apps.googleusercontent.com';
var SCOPES = [
'https://www.googleapis.com/auth/drive.file',
'email',
'profile',
// Add other scopes needed by your application.
];
/**
* Called when the client library is loaded.
*/
function handleClientLoad() {
checkAuth();
}
/**
* Check if the current user has authorized the application.
*/
function checkAuth() {
gapi.auth.authorize(
{'client_id': CLIENT_ID, 'scope': SCOPES, 'immediate': true},
handleAuthResult);
}
/**
* Called when authorization server replies.
*
* #param {Object} authResult Authorization result.
*/
function handleAuthResult(authResult) {
if (authResult) {
// Access token has been successfully retrieved, requests can be sent to the API
} else {
// No access token could be retrieved, force the authorization flow.
gapi.auth.authorize(
{'client_id': CLIENT_ID, 'scope': SCOPES, 'immediate': false},
handleAuthResult);
}
}
</script>
<script type="text/javascript" src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
</head>
<body>
<h1>welcome to google_drive.local</h1>
<script type='text/javascript'>
/**
* Load the Drive API client.
* #param {Function} callback Function to call when the client is loaded.
*/
function loadClient(callback) {
gapi.client.load('drive', 'v2', callback);
}
/**
* Print a file's metadata.
*
* #param {String} fileId ID of the file to print metadata for.
*/
function printFile(fileId) {
var request = gapi.client.drive.files.get({
'fileId': fileId
});
request.execute(function(resp) {
if (!resp.error) {
console.log('Title: ' + resp.title);
console.log('Description: ' + resp.description);
console.log('MIME type: ' + resp.mimeType);
} else if (resp.error.code == 401) {
// Access token might have expired.
checkAuth();
} else {
console.log('An error occured: ' + resp.error.message);
}
});
}
</script>
</body>
</html>
By setting immediate=true you are suppressing the auth popup.
It should be false the first time through and is generally set to true thereafter for each hourly refresh.

How to integrate websocket with emberjs?

I'm learning and building emberjs app with rails.
In this app, I want the data to be pushed rather than polled to the client app.
For.e.g. the following snippet at http://awardwinningfjords.com/2011/12/27/emberjs-collections.html
// Setup a global namespace for our code.
Twitter = Em.Application.create({
// When everything is loaded.
ready: function() {
// Start polling Twitter
setInterval(function() {
Twitter.searchResults.refresh();
}, 2000);
// The default search is empty, let's find some cats.
Twitter.searchResults.set("query", "cats");
// Call the superclass's `ready` method.
this._super();
}
});
It polls twitter API, but my question is how to make an EmberJS app that uses a WebSocket connection to update its state?
You have to implement a DS.Adapter that understands how to handle WebSockets. Here is an simple example:
var SOCKET = 'ws://localhost:9090/some-websocket';
var ID = 'uuid';
var FIND = 'find';
var FIND_MANY = 'findMany';
var FIND_QUERY = 'findQuery';
var FIND_ALL = 'findAll';
/**
* Implementation of WebSocket for DS.Store
*/
App.Store = DS.Store.extend({
revision: 4,
adapter: DS.Adapter.create({
socket: undefined,
requests: undefined,
send: function(action, type, data, result) {
/* Specific to your web socket server side implementation */
var request = {
"uuid": generateUuid(),
"action": action,
"type": type.toString().substr(1),
"data": data
};
this.socket.send(JSON.stringify(request));
/* So I have access to the original request upon a response from the server */
this.get('requests')[request.uuid] = request;
return request;
},
find: function (store, type, id) {
this.send(FIND, type, id);
},
findMany: function (store, type, ids, query) {
this.send(FIND_MANY, type, ids);
},
findQuery: function (store, type, query, modelArray) {
this.send(FIND_QUERY, type, query, modelArray).modelArray = modelArray;
},
findAll: function (store, type) {
this.send(FIND_ALL, type);
},
/* Also implement:
* createRecord & createRecords
* updateRecord & updateRecords
* deleteRecord & deleteRecords
* commit & rollback
*/
init: function () {
var context = this;
this.set('requests', {});
var ws = new WebSocket(SOCKET);
ws.onopen = function () {
};
ws.onmessage = function(event) {
var response = JSON.parse(event.data);
var request = context.get('requests')[response.uuid];
switch (request.action) {
case FIND:
App.store.load(type, response.data[0]);
break;
case FIND_MANY:
App.store.loadMany(type, response.data);
break;
case FIND_QUERY:
request.modelArray.load(response.data);
break;
case FIND_ALL:
App.store.loadMany(type, response.data);
break;
default:
throw('Unknown Request: ' + request.action);
}
/* Cleanup */
context.get('requests')[response.uuid] = undefined;
};
ws.onclose = function () {
};
this.set('socket', ws);
}
});
});
I actually was playing around with the code from that article a few days ago. Keep the handle bar template the same, and use the following code. Obviously, this all depends on what JSON you're passing through the socket. The following code is tried and tested with ntwitter for node.
Twitter = Em.Application.create({
ready: function() {
var socket = io.connect();
socket.on('message', function(json) {
Twitter.searchResults.addTweet(Twitter.Tweet.create(JSON.parse(json)));
});
this._super();
}
});
//Model
Twitter.Tweet = Em.Object.extend();
//Collection
Twitter.searchResults = Em.ArrayController.create({
content: [],
_idCache: {},
addTweet: function(tweet) {
var id = tweet.get("id");
if (typeof this._idCache[id] === "undefined") {
this.pushObject(tweet);
this._idCache[id] = tweet.id;
}
}
});
With websockets you are observing for socket events. When an event is triggered you handle that event (if appropriate) and then set your values.
Looking at your code you would you would observe Socket.onmessage. If the message contains what you are looking for then call refresh.

Categories

Resources