Tagplugins

VueJS Plugins: developing with HRM

I’ve never being a fan of keeping backend and frontend codebase together, though some of the well known project achieve it quiet well like Discourse.

Keeping JavaScript aside for backend solutions, as part of standalone UI elements seemed as decent solution. But when you have to develop Vue components as plugins and keep the project running – testing becomes messy.

I spent half a day trying to figure out the ways how you can use symlinks to pick local packages to work in dummy VueJS app, but no luck. Both for yarn and npm. There’ve been a long story of resolving these issues for numerous packages like here, or here.

Simple solution with mocking an App behaviour with a sprinkle of chokidar package solves this problem. Well, at least to some extend:

  • You can independently develop Vue Components as plugins
  • From the box testing
  • Once e2e and unit tests are done, you can publish and integrate these plugins in to your application.

Hot Reload for Development environment

First things first – tools being used:

  • vue: 2.9.6
  • node: v10.16.0 (LTS)
  • yarn|npm: whatever make you happy!
  • webpack-dev-server: ^3.7.2

Build instructions are split in 3 parts (base, dev, prod). I’m using webpack-merge to overwrite base with required instructions, depending on the yarn command:

//package.json part with live reload instructions
"scripts": {
     "serve": "webpack-dev-server --config ./build/webpack.dev.config.js --hot --progress -d",
     "test": "jest"
}

Going further – HRM specs, to make things run:

const merge = require('webpack-merge')
const chokidar = require('chokidar')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const base = require('./webpack.base.config.js')

module.exports = merge(base, {
  entry: './dev/dev.js',
  plugins: [
      new HtmlWebpackPlugin({
        template: './dev/index.html',
        inject: true,
      }),
  ],
  optimization: {
   noEmitOnErrors: true,
  },
  mode: 'development',
  devtool: '#eval-source-map',
  devServer: {
    hot: true,
    hotOnly: true,
    open: true,
    inline: true,
    stats: {
      children: false,
      modules: false,
      chunks: false,
    },
    port: 8080,
    before (app, server) {
      chokidar.watch([
        './**/*.html',
      ]).on('all', function () {
        server.sockWrite(server.sockets, 'content-changed');
      })
    },
  }
})

Whenever we run yarn serve script, it will load a node server, and open a ./dev/index.html file template which solely has a <div id="app"></div> container.

Example of Dev structure

Now we simply add a wrapper component Dev.vue that will contain whatever we want to test:

//dev.js
import Vue from 'vue'
import Dev from './Dev.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(Dev)
}).$mount("#app")

Let’s say we have a simple Calendar.vue component we need to load when running serve script:

<template>
  <div id="app">
    <Calendar/>
  </div>
</template>
<script>
import Calendar from '@/components/Calendar.vue'

export default {
  components: {
    Calendar
  }
}
</script>

CakePHP CsvMigrations: prototype in 3 2 .. Done!

Every programmer is tired of coding yet another login form. Yet another CRUD view module. So today I’ll show you an example where our laziness can get you building prototype systems without a single line of extra code.

One of the tasks, that we had to face when developing Qobrix CRM, was fast prototyping of the system. The result of ultimate laziness and DRY concept resulted in cakephp-csv-migrations plugin that we try to use pretty much everywhere, while delivering the system.

Your application is not unique

Whatever you request for your Prototype is mostly based on same functionality:

  • CRUD views
  • Basic CRUD actions
  • Event/Trigger system that allows you mutating the data

If we dive deeper into these points, whatever you work with is form based – your input fields are the minimum atomic unit of interaction: strings, longtexts, datetime. Looks familiar? Exactly, database data types.

In certain cases, you store dates in strings, names in varchars, or even longtext. At this moment we come to the point of having a binding mechanism of your application logic with your storage engine. For simplicity reasons – I’d base this example on RDBMS like MariaDB, MySQL, etc.

Changing those binding might be difficult for the user. As the supplier of the system, you don’t know who’s going to deal with the system: some companies don’t have IT departments, but need to modify things rapidly. The same applies to developers level of expertise for the system. We wanted to make it as simple as possible.

Preparing the App

Note: If you already have a CakePHP application running, just composer require qobo/cakephp-csv-migrations, and you can skip this part.

Theory is boring without examples, so I’ll try to show you a basic thing on how to expand the system with extra modules. For simplicity reasons, I’ll base it on project-template-cakephp template that we frequently use. It already has some dependencies, as well as cakephp-csv-migrations plugin as part of cakephp-utils.

composer create-project qobo/project-template-cakephp baking_app
cd baking_app
./bin/build app:install DB_NAME="baking_app",CHOWN_USER=$USER,CHGRP_GROUP=$USER,PROJECT_NAME="My Baking App"
./bin/phpserv

That’s enough to check that your app is up and running. For basic creadentials and stuff, you can check .env file that was generated by the Robo build scripts.

Baking Recipes Module

Here comes the baking part. We’re going to make a simple recipes module to store our favourite recipes.

./bin/cake bake csv_module Recipes
./bin/cake bake csv_migration Recipes

First command will create dummy MVC instances for CakePHP: Model/Entity, Controller and ApiController files, based on which the second script will verify that you can bake a migration script (based on Phinx migrations).

If you’ll look into migration file created in config/Migrations/<timestamp>_Receipts<timestamp>.php, you see something like that:

<?php
use CsvMigrations\CsvMigration;

class Recipes20180126154309 extends CsvMigration
{
        public function change()
    {
        $table = $this->table('recipes');
        $table = $this->csv($table);

        if (!$this->hasTable('recipes')) {
            $table->create();
        } else {
            $table->update();
        }

        $joinedTables = $this->joins('recipes');
        if (!empty($joinedTables)) {
            foreach ($joinedTables as $joinedTable) {
                $joinedTable->create();
            }
        }
    }
}

Where are the fields? That’s the point where all the magic happens.

CsvMigrations plugin provides you with vast number of input types that can bind to basic data types of your database. They’re stored in config/Modules/Recipes/db/migration.csv file. We’ll expand it a bit:

FIELD NAME,FIELD TYPE,REQUIRED,NOT SEARCHABLE,UNIQUE
id,uuid
name,string
meal_type,list(meal_types)
recipe,text
created,datetime
modified,datetime
created_by,related(Users)
modified_by,related(Users)

I’ve added name, type and recipe fields that can be handled by varchar and longtext data types in the database. Let’s cook it:

./bin/cake migrations migrate

And we are done! You noticed list(<list_name>) type used within migration.csv. This FieldHandler type is used for defined option lists for rendering Select boxes in you form. The lists are stored in config/Modules/Common/lists/meal_types.csv:

VALUE,LABEL,INACTIVE
breakfast,Breakfast,
dinner,Dinner,
supper,Supper
Where are my views?

If you start up the application, and navigate to http://localhost:8000/recipes/add you’ll see something like that:

add recipes form

Now we need to add fields to CRUD form. CsvMigrations can help you with that. All your form fields are located in config/Modules/Recipes/views:

PANEL NAME,FIRST COLUMN FIELD NAME,SECOND COLUMN FIELD NAME
Details,name, meal_type
Recipe,recipe

The example above is for add/edit.csv files being modified. Reloading the add page:

add form complete
add form complete Complete add form
Conclusion

Now you’re ready to work with basic CRUD. All the common CRUD logic is already located in cakephp-csv-migrations plugin that will handle API requests for the index page for DataTables grid loading. If you want to change its behavior, you can always override action methods in `RecipesController`.