What is Vue.js &

why its important for the future of PHP 

note : these are the slides from a live presentation,

i've put in some screen shots where the live demo was....

  • Easily add real time "reactive" features to your existing PHP application or website 
  • Reduce server calls by working in the client browser
  • Drastically reduce db calls for common tasks 
  • Elegant syntax - easy to write and understand 
  • Very fast - almost as fast as raw js
  • Very stable - Vue library is mature 
  • Tiny footprint - Vue is approximately 86k 

Why Vue.js ?

who is your presenter ...?

 Andre Marigold

twitter: @ecommercemeetup

dev@cartalot.net

Organizer on Meetup for PHP, E-commerce & Instagram

 

Learned a php framework, built an ecommerce

shopping cart & platform, integrated it fully with Amazon,

built shipping, fulfillment, admin, etc features

 

The php based e-commerce platform reliably processed millions of $ worth of orders from the website and amazon over many years.

 

Mid 2017 - "its been fun" - moved main client to Shopify.

Currently building e-commerce tools with Vue and PHP.

 

 

 

Why Vue in a PHP Application / Stack ?

       Routing     State    Session    Cache      

Models       Views     Controller    

Libraries    Run Time Tricks  APIs        

Templates 

HTML & DOM 

just here please?

In a typical PHP MVC framework there are thousands of functions/methods that have to fire just to display the page. Therefore any page refresh for small tasks is very expensive. When we can save a refresh we save server resources.

 

Its also a greater experience for the user/customer. And this is not a new concept. We have had Jquery and other javascript libraries to allow us to do "Ajax" 

 

So why should we look at Vue....? 

 

 

 

( hey we are PHP developers... )

 Sarah Drasner - Replacing jQuery With Vue.js: No Build Step Necessary

https://www.smashingmagazine.com/2018/02/jquery-vue-javascript/

...in the jQuery version, the DOM is in control- we’re fetching things out of the DOM, listening to it, and responding to it. This ties us to the way that the DOM is currently set up, and forces us to think about how to traverse it. If the structure of the HTML elements were to change, we’d have to adapt our code to correspond to those changes.

 

In the Vue version, we're storing the state- we keep track of one property we want to update and change, and track the element we want to change by a thing called a directive. This means it’s attached directly to the HTML element we need to target. The structure of the DOM can change, the HTML can move around, and none of this would impact our performance or capturing these events.

<div id="app-6">

  <p>{{ message }}</p>

  <input v-model="message">

</div>



<script>

var app6 = new Vue({

  el: '#app-6',

  data: {

    message: 'Hello Vue!'

  }
})

</script>

Lets get Reactive

example is from the Vue docs

the id is "app-6" - Vue will only work with the contents inside this div.

{{ message }} is the Vue template syntax

v-model - binds this data to be reactive

 

in script - we declare a new Vue, and declare the element its responsible for.

 

data will contain many things, right now its establishing message with a default string.  

This was a live demo of 'message' being reactive as we type into the input field

default value

as we type

Using Vue and a few lines of  code - we have a fully reactive element. That works completely in the client (browser). 

<!doctype html>

<html lang="en">
<head>
    <meta charset="utf-8">

    <title>First </title>
    <script src="vue.js"></script>
</head>

<body>

<div id="app-6">
    <h2>{{ message }}</h2>
    <br>
    <br>
    <input v-model="message">
    <br>
    <br>
</div>

<br><br>
<div id="app-5">
    Vue will ignore this because its not inside the declared ID <br><br> 
    <h2>{{ message }}</h2>
</div>
<br><br>


<script>
    var app6 = new Vue({
        el: '#app-6',
        data: {
            message: 'Hello Vue!'
        }
    })

</script>

</body>
</html>

Text

Text

Vue will completely ignore any code or markup that is

outside its designated ID. 

 

So all your other template, php, javascript, etc code

won't have any conflicts. Or put another way....

Vue started out with a very simple premise. Instead of continually refreshing the entire DOM for every and any change....

Define Vue's area of responsibility by simple ID declaration. Thus Vue 

 

     The Progressive Framework

 

was created.

 

Its a very elegant solution to a very complex problem.

Its also Very Fast! 

This also means that Vue - Plays well with others.

Its not trying to control or monitor the entire DOM.

 

Which also means that with one JS call - similar to however you are calling Jquery - you can easily drop it into your App and it just works.  

 

Vue does not demand any changes to your existing stack. 

Vue works with any version of javascript. 

Vue is not a back end. Its not going to replace your framework.

 

However....

 2014 - Vue released

 

 2015 - 2016 Taylot Ottwell tweets about Vue. PHP developers help with Vue Patreon campaign. Jeffrey Way does many tutorials about using Vue in a PHP context. PHP and Vue are like BFF.

 Vue starts to get a lot of attention, developers start to build tools, conferences start happening

 

2017 - Vue actively engages with other projects to create an approved ecosystem . Conferences increase.  Every Vue tutorial (almost) starts out showing you the new CLI tools and assumes you will host on some version Node / Express / etc.

 

2018 - Vue is no longer just about the 86k library. Its about improving and promoting the Vue ecosystem . And lots of conferences around the world. And little to no attention paid to the first community - PHP - that supported Vue financially .

 

so where does that leave PHP developers...?

 

 

So if the Vue ecosystem is all based on Node - why should a PHP dev care or trust that there is a future with Vue? 

Because the core 86k Vue library is so great - that is why there is such an active ecosystem around it. 

But for PHP websites and applications - we already have an ecosystem! We already have many different frameworks to choose from. All of them take care of sessions, routing, security, etc etc etc. 

And we have zillions of existing applications written in PHP. 

And we have many options for hosting and deployment. 

And all of these solutions are production ready. 

 

Vue elegantly solves the one big feature we don't have - getting and working with live data for common tasks. 

 

 

Why should you care again? 

 

* Building with Vue is Fun

* Building with Vue components is super fast

* You can build application features that were difficult or impossible to do with standard PHP with very little code. 

* It turns out that writing javascript is actually very similar to PHP so NBD.

* The Vue 2 library - as it is now - will last for years. Its already in use by thousands of apps. Its stable.    

 

Biggest reason: 

*** Your clients / customers / boss will love the work! ***

 this was a live demo of product inventory/export app

Screenshots of the App ( live demo)  

After normal php framework / page load - Vue gets an array of parent products from a server api using Axios

Select a parent product, Vue fetches the child products from the server. Client browser has not refreshed.

Screenshots of the App live demo 2

Select products to update their export status. Vue methods add each product to an array as they are selected. Vue "Computed" values add visual cues like the X  next to the selected items.

 Update button clicked -> the array of products to update are sent to the server. The results back from the api are checked, and the working window of child products is removed. All without refreshing the client browser creating an optimum user experience.

now a look through the code....

* Vue 2.5

https://vuejs.org/v2/guide/

 

<!-- development version, includes helpful console warnings -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
or:

<!-- production version, optimized for size and speed -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

 

* Axios (for api)

https://github.com/axios/axios

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

 

* Boostrap Vue (optional to help with layout, cards, buttons, etc)

https://bootstrap-vue.js.org/

<!-- Add this to <head> -->  

<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css"/>

<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css"/>

<!-- Add this after vue.js -->

<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>

<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>

 

 

<style>


    html {
        font-size: 14px;
    }

    ,

    active {

        background-color: #0c5460;
    }

    notactive {

        background-color: #0c5460;
    }

    .mb-3:hover {
        background-color: rgba(255, 255, 104, 0.58);
    }

    .light-grey {
        background-color: rgba(107, 107, 99, 0.14);
    }

    .light-white {
        background-color: rgba(107, 107, 99, 0.02);
    }

    .light-red {
        background-color: rgba(255, 14, 15, 0.15);
    }

    .post_name {
        background-color: lightblue;
    }


</style>

<!-- ******************************************************** App  ********************  -->

<div id="app">

    <b-container>
        <b-row>
            <b-col>
              <h4 align="center"> Wayfair Product Export Admin - Pear work </h4>

            </b-col>


        </b-row>


        <b-row>
            <b-col cols="4">


                <!-- Message -->
                <div v-if="!showparentproducts">
                    Getting The Parent Products from Server....
                </div>


                <!-- List out the Products  -->
                <div v-if="showparentproducts">

                    <list-parent-products
                            :parentproducts="parentproducts"

                            v-on:return-working-parent-please="showtheworkingparent"
                    ></list-parent-products>

                    <!--   5) Our Emit Journey - we are now in the main Vue app, so all methods available -->


                </div>
            </b-col>


            <b-col cols="8">

                <?php
                //<div class="sticky-top">
                ?>

                <div class="sticky-top">


                    <b-row>
                        <b-col>
                            <strong> {{ childstatustext }} </strong> <br>
                        </b-col>
                    </b-row>

                    <!-- Show the Current (Working) Parent Product -->
                    <div v-if="showworkingproductpanel">


                        <current_working_parent_product
                                :workingparent="workingparent"


                        ></current_working_parent_product>

                    </div><!-- /Current (Working) Parent Product -->

                    <!-- Show the working child products  -->
                    <div v-if="showchildproducts">


                        <list-child-products
                                :childproducts="childproducts"

                                v-on:switchtheexportvalueplease="switchtheexportvalueplease"

                                v-on:updateallchildproductsplease="updateallchildproductsplease"

                                v-on:updateallchildproductstono="updateallchildproductstono"

                                v-on:updateallchildproductstoyes="updateallchildproductstoyes"
                        ></list-child-products>

                    </div><!-- /child products -->

                </div>
            </b-col>

        </b-row>


    </b-container>


</div>









<?php

// We are splitting up the Vue components into their own view file
$this->load->view('meetup/pear_components/parent_products');

$this->load->view('meetup/pear_components/working_parent');

$this->load->view('meetup/pear_components/child_products');


?>



<script>



    // base url
    var base_url = '<?php echo base_url();?>';

    new Vue({
        el: '#app',
        data: {
            showparentproducts: false, // flag whether to show parent products panel
            parentsstatustext: '', // status msg on getting parents from server
            parentproducts: [], // parent products that come back from the server

            workingparent: [],

            showworkingproductpanel: false,

            showchildproducts: false,
            childstatustext: '',
            childproducts: [],

            childproduct: [],

            productstoupdate: [],
        },
        created: function () {


            this.returnParentProductsFromServer();

            console.log('we are created');

        },
        computed: {},
        methods: {

            // =============================================== Step 1 Get the parent products
            returnParentProductsFromServer: function () {

                this.parentsstatustext = 'Requesting products from server...';

                // REQUIRED for inside the Axios function
                var vm = this;

                axios.get(base_url + 'productadmin/returnparentproducts')
                    .then(function (response) {


                        if (response.status == '204') {

                            vm.parentsstatustext = 'No parent products returned';

                            vm.showparentproducts = false;
                        }
                        else {

                            vm.showNewParentProducts(response.data);

                        }

                    })
                    .catch(function (error) {

                        vm.parentsstatustext = 'There was an error returning parent products';

                    });

            },

            showNewParentProducts: function (products) {

                // clear message
                this.parentsstatustext = '';

                // assign
                this.parentproducts = products;

                //  console.log('check the products  ') ;

                //  console.log(this.parentproducts) ;

                // show panel
                this.showparentproducts = true;

                return this.parentproducts;


            },

            // =============================================== Step 2 Select a working parent , get child produccts

            //  6) Our Emit Journey - Only took six steps to get here!!!
            // talk about encapsulation :-)

            showtheworkingparent: function (parent) {

                this.showworkingproductpanel = true;

                this.workingparent = parent;

                this.returnChildProductsFromServer(this.workingparent.parent_product_id);

            },

            returnChildProductsFromServer: function (parent_id) {


                this.childstatustext = 'Requesting child products from server...';

                // REQUIRED for inside the Axios function
                var vm = this;

                axios.post(base_url + 'productadmin/returnchildproducts', {

                    parent_product_id: parent_id

                })
                    .then(function (response) {

                        if (response.status == '204') {

                            vm.childstatustext = 'No child products returned for ';

                            console.log('No child products back from server ');

                            vm.showchildproducts = false;
                        }
                        else {

                            //  console.log('YES child products returned');

                            vm.showNewChildProducts(response.data);

                        }

                    })
                    .catch(function (error) {
                        vm.childstatustext = 'There was a server error returning the child products ';
                    });

            },


            showNewChildProducts: function (childproducts) {


                // clear message
                this.childstatustext = '';

                // assign
                this.childproducts = childproducts;

                // show panel
                this.showchildproducts = true;

                return this.childproducts;


            },


            // ==================== swith export val

            switchtheexportvalueplease: function (child) {


                let newvalue = '';

                if (child.wayfair_export == 'yes') {
                    newvalue = 'no';
                }

                else {
                    newvalue = 'yes';
                }


                let indx = this.productstoupdate.length;

                mynewitem = {
                    id: child.id,
                    wayfair_export: newvalue,

                };

                // Vue.set
                //  Vue.set(vm.items, indexOfItem, newValue)

                // Add the item to the array of products to update
                this.$set(this.productstoupdate, indx, mynewitem);


            },

            updateallchildproductsplease: function () {


                this.updateChildProductsOnServer(this.productstoupdate);

            },

            updateallchildproductstono: function () {

                for (let i = 0; i < this.childproducts.length; i++) {

                    this.productstoupdate[i] = {

                        id: this.childproducts[i].id ,
                        wayfair_export:  'no'
                    }

                }

                this.updateChildProductsOnServer(this.productstoupdate);

            },

            updateallchildproductstoyes: function () {

                for (let i = 0; i < this.childproducts.length; i++) {

                    this.productstoupdate[i] = {

                        id: this.childproducts[i].id,
                        wayfair_export: 'yes'
                    }

                }

                this.updateChildProductsOnServer(this.productstoupdate);

            },

            updateChildProductsOnServer: function (childproducts) {


                // REQUIRED for inside the Axios function
                var vm = this;

                axios.post(base_url + 'productadmin/updatechildproducts', {

                    productstoupdate: childproducts

                })
                    .then(function (response) {

                        if (response.status == '204') {

                            vm.childstatustext = 'There was a server error trying to update the child products ';


                        }
                        else {

                            vm.childstatustext = 'Child Products Are Updated  ';

                            vm.clearworkingvalues();

                        }

                    })
                    .catch(function (error) {
                        vm.childstatustext = 'There was an error returning child products';
                    });

            },

            clearworkingvalues: function () {


                this.showworkingproductpanel = false;

                this.showchildproducts = false;

                // Clear the previous values !
                this.productstoupdate = [];

                this.childproducts = [];


            }

        }
    })

</script>

caution - this is very messy code, was playing around with the event/emit names

Now the Vue Components.

In this example they are in three separate files.  

 

The parent components are called in the main html like:  

<list-parent-products
          :parentproducts="parentproducts"

            v-on:return-working-parent-please="showtheworkingparent" >
</list-parent-products>

in this case we are passing the 'parentproducts' array to the component - it is a 'prop' (property) of the component

v-on is 'listening' for any  events that Vue has 'emitted' -

(which is usually calling a method in the parent or main Vue app)   

Parent Products Components (included view file)

<!-- ************  All Parent Products  ********************  -->
<template id="list-parent-products-template">

    <div>
        <!-- if parent product status is not blank...  -->


        <b-row>
            <b-col>
                <strong> {{ parentsstatustext }} </strong>
            </b-col>
        </b-row>


        <!-- v for through each parent product   -->
        <one-parent-product v-for="parent in parentproducts"

                            :parent="parent"

                            :key="parent.id"

                            v-on:createtheworkingparent_please="returntheworkingparent"

        ></one-parent-product>


        <!-- 3) Our Emit Journey - calls the  method in the parent component  -->

    </div> <!-- /wrapping div -->

</template>


<!-- ****************  Single Parent Product  ********************  -->
<template id="one-parent-product-template">

    <div>

        <b-card
                border-variant="success"
        >

            <b-row>


                <b-col cols="8">
                    {{ parent.parent_title }}

                </b-col>

                <b-col cols="2">

                    <!--  1) Our Emit Journey starts here    -->
                    <b-button variant="primary" v-on:click="createworkingparent" size="sm">
                        Show
                    </b-button>

                </b-col>

            </b-row>

        </b-card>


    </div><!-- /wrapping div -->


</template>

<script>


    Vue.component('list-parent-products', {
        template: "#list-parent-products-template",
        props: ['parentproducts', 'parentsstatustext', 'showparentproducts'],
        data: function () {
            return {
                hello: 'world',

            };
        },

        methods: {

            // 4) Our Emit Journey -  Emits again to call  the  method in the actual Vue app
            // look at us we are bubbling up !!

            returntheworkingparent: function (parent) {

                this.$emit('return-working-parent-please', parent);

            }
        },


    });


    Vue.component('one-parent-product', {
        template: "#one-parent-product-template",
        props: ['parent'],

        data: function () {
            return {

                hello: 'you',

            };

        },

        methods: {


            // 2) Our Emit Journey - calls the child method
            // Emits it up to the parent, in this case list-parent-products
            createworkingparent: function () {

                this.$emit('createtheworkingparent_please', this.parent);

            },


        },
        computed: {},

    });
</script>

List Parent Products

is a component that holds the array of products. It can also display status messages or content about the group of products.

We 'foreach' through the product  array -

v-for="parent in parentproducts"

and call the...

One Parent Product

which is a component / template to display each individual Parent Product

Its a little bit more work up front to do it this way. But breaking them up like this allows us to easily surface the individual Product details like Name and ID, and pass them to other methods - such as displaying them in the working area with their child products.

 

After the template code is the Vue / Javascript code for the components.

In cases like this where its expected both Components are working together, it makes sense to include them together in one file.

Working Parent Component

<!-- ****************  Working Parent Product  ********************  -->
<template id="current-working-parent-product-template">

    <div>


        <b-row class="light-white">


            <b-col offset-md="2">
                <h6> {{ workingparent.parent_title }}   </h6>

            </b-col>


        </b-row>


    </div><!-- /wrapping div -->


</template>

<script>
      Vue.component('current_working_parent_product', {
        template: "#current-working-parent-product-template",
        props: ['workingparent'],
        data: function () {
            return {
                john: 'doe',


            };
        },

        methods: {
 
        },

    });

</script>

Child Products Component

<!-- ******************************************************** All Child Products  ********************  -->
<template id="list-child-products-template">

    <div>

        <b-row>
            <b-col><br>

                <b-button variant="outline-primary" v-on:click="updateallchildproductsyes" size="sm">
                    Set All Child Products to Export
                </b-button>


            </b-col>

            <b-col><br>

                <b-button variant="outline-danger" v-on:click="updateallchildproductsno" size="sm">
                    Set All Child Products to NOT Export
                </b-button>


            </b-col>


        </b-row>

        <b-row>

            <b-col><br>
                Or Select export status per child product, and then click the Update button below:

            </b-col>

        </b-row>

        <b-card>

            <b-row>

                <b-col cols="2">
                    <strong>Sku</strong>

                </b-col>


                <b-col cols="3">
                    <strong>Product</strong>

                </b-col>

                <b-col cols="2">
                    <div><strong>Quantity</strong></div>
                </b-col>


                <b-col cols="2">
                    <strong>Export?</strong>
                </b-col>

                <b-col cols="3">


                </b-col>


            </b-row>

            <one-child-product v-for="childproduct in childproducts"

                               :childproduct="childproduct"

                               :key="childproduct.id"

                               v-on:switchtheexportvalue="switchtheexportvaluerook"

            ></one-child-product>

        </b-card>


        <b-row>
            <b-col offset-md="4">

                <b-button variant="outline-primary" v-on:click="updateallchildproducts" size="md">
                    Update The Selected Child Products
                </b-button>

                <br><br><br>
            </b-col>

        </b-row>



    </div> <!-- /wrapping div -->

</template>


<!-- ******************************************************** Single Child Product  ********************  -->
<template id="one-child-product-template">

    <div>


        <b-row>

            <b-col cols="2">
                {{ childproduct.sku }}
                <br><br>

            </b-col>


            <b-col cols="3">
                {{ childproduct.variant_title }}
                <br><br>

            </b-col>

            <b-col cols="2">
                {{ childproduct.inventory_quantity }}
            </b-col>


            <b-col cols="2">
                {{ childproduct.wayfair_export }}
                <br><br>

            </b-col>

            <b-col cols="2">
                <b-button variant="outline-primary" :class="{active: isActive, notactive: !isActive }" size="sm"
                          v-on:click="switchexportvalue">
                    Switch Status
                </b-button>


            </b-col>


            <b-col cols="1"><strong>{{ tracker }}</strong>


            </b-col>


        </b-row>


    </div><!-- /wrapping div -->


</template>

<script>


    Vue.component('list-child-products', {
        template: "#list-child-products-template",
        props: ['childproducts', 'childstatustext', 'showchildproducts'],
        data: function () {
            return {
                hello: 'world',

            };
        },

        methods: {



            switchtheexportvaluerook: function (child) {


                this.$emit('switchtheexportvalueplease', child);

            },

            updateallchildproducts: function () {


                this.$emit('updateallchildproductsplease');

            },

            updateallchildproductsno: function () {


                this.$emit('updateallchildproductstono');

            },

            updateallchildproductsyes: function () {


                this.$emit('updateallchildproductstoyes');

            },
        },


    });


    Vue.component('one-child-product', {
        template: "#one-child-product-template",
        props: ['childproduct'],

        data: function () {
            return {

                hello: 'you',
                onchangeflag: false,
                isActive: false,
                changeflag: '',


            };

        },

        methods: {


            switchexportvalue: function () {

                this.onchangeflag = true;
                this.isActive = true;

                this.$emit('switchtheexportvalue', this.childproduct);

            },


        },
        computed: {


            tracker: function () {

                let msg = '';

                if (this.onchangeflag == true) {
                    msg = 'X';
                }

                return msg;

            },
        },

    });

</script>

lets go back in the code and look at how to Emit an event from your components...  

in the demo we went through the six steps from button click to final method in the Vue app.  number one is:

"our emit journey starts here"

in a comment in the Parent Products components code

 

Points to Ponder....

 

* Using Vue along with APIs gives you a new way of incrementally building out your app with new services, server tech, etc.

The API that you call can be in the same framework, or it can be on a different server with a different stack. This allows you to try different tech without disturbing your critical framework / stack.

* Every small task you can push to the client browser - will make your application feel much faster. And you save the server costs.

* Every small task you can do in the Vue javascript can reduce your database costs.  

* You can pass timed security tokens from your framework to the Vue app to be included in the http headers as part of the API call. So any api call the Vue app makes can be validated if needed.

* Show / Hide Components is a really fast and powerful way to build out working screens for common tasks. You can bring the appropriate window to the User just as needed and remove quickly when the task is done .  

 

 

 

Thanks so much. Hopefully this gives you some quick ideas on how Vue can work in a PHP context. SF PHP should be doing another presentation next month in August where we will get in depth with a more complex application using Vue and PHP.

Why Vue.js ?

By cartalot

Why Vue.js ?

  • 1,417