SERVERLESS FUNCTIONS AND VUE.JS

SUCCESS!

NOT FAIL…

SERVERLESS AN ACTUALLY INTERESTING THING WITH A CLICKBAITY TITLE FaaS FUNCTIONS AS A SERVICE

SARAH DRASNER @SARAH_EDO

BENEFITS • You pay only for what you use (usually a lot less) • Less time babysitting a server • Same idea as functional programming, breaking things into small bits • Principle of least power

LINK TO CODEPEN

LINK TO CODEPEN

SERVERLESS ALL THE THINGS? NO, NOT REALLY.

SO WHAT IS IT GOOD FOR? • Clean data on a cron job ⏰

• Take data and use it to create data visualizations "

• Crop images uploaded by the user and create a user profile

Consumption Plan Free account signup

VUE.JS, STRIPE, AND SERVERLESS FIRST USE CASE

SAMPLE VUE SHOP VISIT THE SITE

THE APPLICATION VISIT THE REPO

UNDER API

app.get( '/' , (req, res) => res.render( 'index.pug' , { keyPublishable })); app.post( '/charge' , (req, res) => {

let amount = 500 ; stripe.customers .create({ email: req.body.stripeEmail, source: req.body.stripeToken }) .then(customer => stripe.charges.create({ amount, description: 'Sample Charge' , currency: 'usd' , customer: customer.id }) ) .then(charge => res.render( 'charge.pug' )); }); app.listen( 4567 ); What Stripe expects: Express

Where we’re going we don’t need Express!

Kick things off! var stripe = require ( ‘stripe' )( 'sk_test_XXXXXXXXXXXXXXXX' ); // ^ this is a stripe testing key module .exports = function (context, req) { context.log( 'starting to get down' );

If we have what we need,

create the customer and then the charge if ( req.body && req.body.stripeEmail && req.body.stripeToken && req.body.stripeAmt ){ stripe.customers .create({ email: req.body.stripeEmail, source: req.body.stripeToken }) .then(customer => { context.log( 'starting the stripe charges' ); stripe.charges.create({ amount: req.body.stripeAmt, description: 'Sample Charge' , currency: 'usd' , customer: customer.id }); }) ...

Then finish, otherwise error logs ... .then(charge => { context.log( 'finished the stripe charges' ); context.res = {

// status: 200 body: 'This has been completed' }; context.done(); }) .catch(err => { context.log(err); context.done(); }); } else { context.log(req.body); context.res = { status: 400 , body: "We're missing something" }; context.done(); } };

SUCCESS! POST ON CSS-TRICKS

OUR CART VUEX HAS A MANIFEST OF ALL OF OUR PRODUCTS, ITEMS, AND TOTAL

< div v-if= "cartTotal > 0"

<!--we'll add our checkout here-->

</ div

< div v-else-if= "cartTotal === 0 && success === false" class= "empty"

<!--we'll add our empty state here-->

</ div

< div v-else>

<!--we'll add success here-->

</ div

Cart.vue Base Logic

OUR CART < div v-if= "cartTotal > 0"

< h1

Cart </ h1

< div class= "cartitems" v-for= "item in cart" key= "item"

< div class= "carttext"

< h4

{{ item.name }} </ h4

< p

{{ item.price | usdollar }} x {{ item.count }} </ p

< p

Total for this item: < strong

{{ item.price * item.count }} </ strong

</ p

</ div

< img class= "cartimg" :src= "/${item.img}" :alt= "Image of ${item.name}"

</ div

< div class= "total"

< h3

Total: {{ total | usdollar }} </ h3

</ div

<!--we're going to add our checkout here-->

</ div

computed: { ... total() {

return

Object .values( this .cart) .reduce((acc, el) => acc + (el.count * el.price), 0 ) .toFixed( 2 ); } }

VUE STRIPE CHECKOUT EASIEST WAY REPO

STRIPE ELEMENTS WE’LL USE:

OR npm i vue-stripe-elements-plus yarn add vue-stripe-elements-plus

DEFAULT STRIPE-ELEMENT < template

< div id= 'app'

< h1

Please give us your payment details: </ h1

< card class= 'stripe-card' :class= '{ complete }' stripe= 'pk_test_XXXXXXXXXXXXXXXXXXXXXXXX' :options= 'stripeOptions' @change= 'complete = $event.complete' />

< button class= 'pay-with-stripe'

  @click=

'pay'

  :disabled=

'!complete' > Pay with credit card

</ button

</ div

</ template

DEFAULT STRIPE-ELEMENT import { stripeKey, stripeOptions } from

'./stripeConfig.json' import { Card, createToken } from

'vue-stripe-elements-plus' export

default { data () {

return { complete: false , stripeOptions: {

// see https://stripe.com/docs/stripe.js#element-options for details } } },

components: { Card }, methods: { pay() {

// createToken returns a Promise which resolves in a result object with

// either a token or an error key. createToken().then(data => console .log(data.token)) } } }

WHAT WE’LL NEED: data() {

return { submitted: false , complete: false , status: '' , response: '' , stripeOptions: {

// you can configure that cc element. }, stripeEmail: '' } },

WHAT WE’LL NEED: props: { total: { type: [ Number , String ], default: '50.00' }, success: { type: Boolean , default: false } },

HERE WE GO! pay() { createToken().then(data => {

// we'll change this to know it was submitted

this .submitted = true ;

// this is a token we would use for the stripeToken below

console .log(data.token); axios .post(

'https://sdras-stripe.azurewebsites.net/api/charge? code=zWwbn6LLqMxuyvwbWpTFXdRxFd7a27KCRCEseL7zEqbM9ijAgj1c1w==' , { stripeEmail: this .stripeEmail, // send the email stripeToken: data.token.id, // testing token stripeAmt: this .total // send the amount }, { headers: {

'Content-Type' : 'application/json' } } ) ...

HERE WE GO! ... .then(response => {

this .status = 'success' ;

this .$emit( 'successSubmit' );

this .$store.commit( 'clearCartCount' );

// console logs for you :)

this .response = JSON .stringify(response, null , 2 );

console .log( this .response); }) .catch(error => {

this .status = 'failure' ;

// console logs for you :)

this .response = 'Error: ' + JSON .stringify(error, null , 2 );

console .log( this .response); }); });

pay() • Uses Axios to post to our function to our function URL • Tracks whether we've submitted the form or not, with this.submitted

• It sends the email, token, and total to our serverless function • If it's successful, commits to our Vuex store, mutation clears our cart, and emits to the parent cart component that the payment was successful.  

• If it errors, it changes the status to failure, and logs the error response for help with debugging

WHILE THE FUNCTION WORKS ITS MAGIC ✨ LINK TO CODEPEN

IN THE CASE OF FAILURE

Template Script < div v-if= "status === 'failure'"

< h3

Oh No! </ h3

< p

Something went wrong! </ p

< button @click= "clearCheckout"

Please try again </ button

</ div

clearCheckout() {

this .submitted = false ;

this .status = '' ;

this .complete = false ;

this .response = '' ; }

IN THE CASE OF SUCCESS

AppSuccess.vue window .setTimeout(() => this .$emit( 'restartCart' ), 3000 ); THIS CODEPEN

< div v-if= "cartTotal > 0"

<!--we'll add our checkout here-->

</ div

< div v-else-if= "cartTotal === 0 && success === false" class= "empty"

<!--we'll add our empty state here-->

</ div

< div v-else>

<!--we'll add success here-->

</ div

< div v-if= "cartTotal > 0"

< h1

Cart </ h1

...

< app-checkout :total= "total" @successSubmit= "success = true"

</ app-checkout

</ div

< div v-else-if= "cartTotal === 0 && success === false" class= "empty"

< h1

Cart </ h1

< h3

Your cart is empty. </ h3

< nuxt-link exact to= "/"

< button

Fill 'er up! </ button

</ nuxt-link

</ div

< div v-else>

< app-success @restartCart= "success = false" />

< h2

Success! </ h2

< p

Your order has been processed, it will be delivered shortly. </ p

</ div

SECOND USE CASE LET’S GET VISUAL GOOGLE MAPS API POWERED DATA VIS

Series:   Exploring data with Serverless and Vue DEMO   AND   REPO W/ OSS CODE

[
{

" Name " : " Simona Cotin " ,

" Conference " : " DLD 2017 Tel Aviv Israel " ,

" From " : " 9/4/2017 " ,

" To " : " 9/7/2017 " ,

" Location " : " Tel Aviv " ,

" Link " : “"

},

...

]

What we start with looks like this

MAKE THE FUNCTION

getGeo(makeIterator(content), (updatedContent, err) => {

if (!err) {

// we need to base64 encode the JSON to embed it into the PUT (dear god, why)

let updatedContentB64 = new Buffer(

JSON .stringify(updatedContent, null , 2 ) ).toString( 'base64' );

let pushData = { path: GH_FILE, message: 'Looked up locations, beep boop.' , content: updatedContentB64, sha: data.sha }; ... }; We're going to retrieve the geo-information for each item in the original data

function

getGeo (itr, cb) {

let curr = itr.next();

if (curr.done) {

// All done processing- pass the (now-populated) entries to the next callback cb(curr.data);

return ; }

let location = curr.value.Location;

[
{

" Name " : " Simona Cotin " ,

" Conference " : " DLD 2017 Tel Aviv Israel " ,

" From " : " 9/4/2017 " ,

" To " : " 9/7/2017 " ,

" Location " : " Tel Aviv " ,

" Link " : “"

},

...

]

The function updates our cda-data.json… …which we pull into our Vuex store

[
{

" Name " : " Simona Cotin " ,

" Conference " : " DLD 2017 Tel Aviv Israel " ,

" From " : " 9/4/2017 " ,

" To " : " 9/7/2017 " ,

" Location " : " Tel Aviv " ,

" Link " : "" ,

" Latitude " : 32.0852999 ,

" Longitude " : 34.78176759999999

},

...

]

The function updates our cda-data.json… …which we pull into our Vuex store

computed: {

speakerData () {

return

this . $store . state . speakerData ; },

filteredData () {

const x

this . selectedFilter , filter

new

RegExp ( this . filteredText , ' i ' )

return

this . speakerData . filter ( el

=> {

if (el[x] !==

undefined ) { return el[x] . match (filter) }

else

return

true ; }) } }

< table

class

" scroll "

< thead

< tr

< th

v-for

" key in columns "

      {{ key }} 
    

</ th

</ tr

</ thead

< tbody

< tr

v-for

" (post , i) in filteredData "

< td

v-for

" entry in columns "

< a

:href

" post . Link "

target

" _blank "

        {{ post[entry] }} 
      

</ a

</ td

</ tr

</ tbody

</ table

this . speakerData . forEach ( function ( index ) {

…

if (val in endUnit) {

// if we already have this location (stored together as key) let's increment it. 
 // this way of writing it is more performant than functional, 
 if you don’t believe me, let’s arm wrestle.

if (key in endUnit[val]) { endUnit[val][key][ 2 ] += magBase; } else { endUnit[val][key]

[lat , long , magBase]; } } else {

let y

{}; y[key]

[lat , long , magBase]; endUnit[val]

y; } })

THREE.JS Single element < div ref= "container"

</ div

Call on mounted mounted() {

//we have to load the texture when it's mounted and pass it in

let earthmap = THREE.ImageUtils.loadTexture( '/world7.jpg' );

this .initGlobe(earthmap); }

//from const geometry = new THREE.SphereGeometry( 200 , 40 , 30 ); //to const geometry = new THREE.IcosahedronGeometry( 200 , 0 );

VUE.JS + SERVERLESS

THANK YOU! @sarah_edo These slides:
http://bit.ly/2DZfxsw