# View Module in Smile

# Objective

We will build a Library application. The Library application has the following requirements:

  • A user can add a book and should be able to add a category to it

  • A user can search for a book and should be able to see details page of a particular book.

  • A user can view recently added books

So, we need five pages - to add a book, add category to a book, search a book, details of book and view recent books which we will design as shown:

  • The add page

  • Add category page

  • The search page

  • The details page

  • The recentBooks page

# Setup

We need to setup our VScode by installing few plugins.

  1. Tslint Vue
  2. Vetur
  3. http://csetutorials.com/format-vue-files-visual-studio-code.html

# Create a new project

Create a Library Project and add component which already has api to integrate with Vuejs

  1. Create a Library project
$ smile new Library
$ cd library
/library $ sbt
[Library] $ smile
  1. Add component
[Library] $ add-component libra-api_release-0.1
[info] downloading libra-api_release-0.1-smile-3.4.0.zip from https://bitbucket.org/sanjibkg/smile-repo/downloads ...
######################################################################## 100.0%
[info] libra-api_release-0.1 is installed in local repository
[info] libra-api_release-0.1 is added to the project
[info] refresh eclipse project
[info] add the import  'import-component libra-api_release-0.1(LibraApi,LibraData,LibraDomain,LibraMongo)' to /Users/sagarmahapatra/i3/library/k/Library.ksmile and smile.
  1. Import the project and also the component to Eclipse and add the import-component to library.ksmile
import-component libra-api_release-0.1(LibraApi)
  1. smile
$ smile
  1. Import the downloaded Libra component to eclipse.

Video reference:

# Add a view module

  1. Add the following to .ksmile file
section library {
    view-module libraryview at libraryview
}
  1. Run the command smile and refresh Eclipse to see libraryview.kview
$ smile

you will get the below warning

$ Do the following two steps to set up the module
[warn]  1. go the module in sbt and run generate
[warn]  2. from the command prompt, cd to modules/library/libraryview and 'npm install'
  1. Open a new terminal and go to the libraryview directory i.e cd to modules/library/libraryview, and run the npm install command.
$ cd library/modules/library/libraryview/
$ npm install
  1. Open libraryview.kview, update the route
route "library"
  1. In sbt execute the compile, run command.
$ compile
$ run
  1. Open libraryview terminal and run npm
$ ~/library/modules/library/libraryview $ npm run serve
  1. Open browser http://localhost:8080/library

Video reference:

# Add Remote Api Point

  1. open libraryview.kview in eclipse and add the remote api point.
remote-api-point LibraApi => "LibraApi"

NOTE

It has to be placed before route and after module.

  1. Open remote-api-point.ts in VScode and add the line "export const LibraApi = axios;"
import axios from 'axios';

export default class RemoteApiPoint {

   public static setup() {
     axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
   }
   
}
export const LibraApi = axios;

# Create Layout

  1. Open libraryview.kview in Eclipse and add layout,slot.

  2. Add a section and name it library, inside which we design the layout.

section library {

	layout Root ('/')  as "" slot(default, toolbar = Toolbar, navbar = Navbar)

}

NOTE

In this project there are 3 slots - toolbar, navbar, default (where the content is displayed).

NOTE

Section are created to make the code more readable and organised.

  1. Add view for toolbar and navbar inside library section.
view Toolbar,Navbar under "bar"

NOTE

under "bar" - puts the Toolbar & Navbar file inside a folder called "bar".

  1. Create the required pages i.e RecentBooks, Search, Add.
layout Root ('/')  as "" slot(default, toolbar = Toolbar, navbar = Navbar)  {
		page RecentBooks('recent-books') view(^default=Recent)
		page Search('search') view(^default=Search)
		page Add('add') view(^default=Add)
		redirect ("") to RecentBooks
	}
  1. Add another layout for details page which will display book details, under which create two pages i.e Summary, AddCategory.
layout Root ('/')  as "" slot(default, toolbar = Toolbar, navbar = Navbar)  {
		page RecentBooks('recent-books') view(^default=Recent)
		page Search('search') view(^default=Search)
		page Add('add') view(^default=Add)
		layout Details (":bookId") slot(holder) {
			page Summary("details") view(holder = Details)
			page AddCategory("add-category") view (holder = AddCategory)
		}
		redirect ("") to RecentBooks
	}

NOTE

":bookId" tells that there should be a parameter bookId which should be passed to the URL to redirected to the pages under that layout. As every detail, add category page should be specific to a bookId.

NOTE

When there are no more layouts to be created give the slot name as "holder" (can be anything except default).

  1. Add view for all the pages
view Recent,Search,Add under "book"
view Details,AddCategory under "book"/"details"
  1. Open RootLayout.Vue file in VScode and add Layout code inside template.
  <v-app id="theVapp">  
      <router-view name="toolbar"/>
      <router-view name="navbar" :key="$route.fullPath"/>
      <v-content class="fill-height" >
       <v-container  fill-height grid-list-xl>
        <v-layout justify-center id="root-layout-default">
          <v-flex fill-height><router-view/></v-flex>
        </v-layout>
      </v-container>
    </v-content>
  </v-app>
  1. Add Layout code in DetailsLayout.Vue
  <v-container pa-0>
    <router-view name="holder"/>
  </v-container>
  1. Open browser http://localhost:8080/library

You should see 3 counter page (navbar, toolbar, recentbooks).

  1. Goto https://metastay.gitlab.io/smile-docs/cookbook/setup_smileform.html and setup SmileGrid and SmileForm.

  2. Open libraryview.kview and create a section called core. Copy the commented decorator code from SmileGrid.vue file and paste it inside the section.

  3. Copy the commented decorator code from SmileForm.vue and paste it in core section.

# Implement ToolBar And NavBar

  1. Open Toolbar.vue file in VSCode and Add tool bar code

NOTE

For predefined layouts refer - https://vuetifyjs.com/en/framework/pre-defined

<template>
  <v-toolbar
      color="success darken-3"
      dark
      app
      :clipped-left="$vuetify.breakpoint.mdAndUp"
      fixed
    >
      <v-toolbar-title style="width: 300px" class="ml-0 pl-3">
        <v-toolbar-side-icon @click.stop="toggleDrawer"></v-toolbar-side-icon>
        <span class="hidden-sm-and-down text-uppercase">Library</span>
      </v-toolbar-title>
  </v-toolbar>
</template>

<script lang="ts">
import { Vue, Component, Prop, Emit, Watch } from 'vue-property-decorator';
// import store, * as Store from '@/../src-gen/store';
// import * as Data from '@/../src-gen/data';
// import * as ServerData from '@/../src-gen/server-data';
// import * as Action from '@/../src-gen/action';

@Component
export default class Toolbar extends Vue {
  private drawer = true;
}
</script>
  1. Open Navbar.vue file in VSCode and Add nav bar code
<template>
  <v-navigation-drawer
      fixed
      :clipped="$vuetify.breakpoint.mdAndUp"
      app
      :value="drawer"
    >
      <v-list dense>
        <template v-for="item in items">
          <v-layout
            row
            v-if="item.heading"
            align-center
            :key="item.heading"
          >
            <v-flex xs6 class="text-xs-center">
              <a href="#!" class="body-2 black--text">EDIT</a>
            </v-flex>
          </v-layout>
          <v-list-tile v-else @click="$router.push({name: item.routerName})" :key="item.text">
            <v-list-tile-action>
              <v-icon>{{ item.icon }}</v-icon>
            </v-list-tile-action>
            <v-list-tile-content>
              <v-list-tile-title>
                {{ item.text }}
              </v-list-tile-title>
            </v-list-tile-content>
          </v-list-tile>
        </template>
      </v-list>
    </v-navigation-drawer>
</template>

<script lang="ts">
import { Vue, Component, Prop, Emit, Watch } from 'vue-property-decorator';
//  import store, * as Store from '@/../src-gen/store';
// import * as Data from '@/../src-gen/data';
// import * as ServerData from '@/../src-gen/server-data';
// import * as Action from '@/../src-gen/action';

@Component
export default class Navbar extends Vue {
 private drawer: true;
  private items = [
        { icon: 'library_books', text: 'Recently Added', routerName : 'RecentBooks' },
        { icon: 'search', text: 'Search', routerName : 'Search' },
        { icon: 'add', text: 'Add', routerName : 'Add' },
      ];
}

</script>

<style>
</style>
  1. Open browser http://localhost:8080/library

You should see the toolbar and navbar and counterpage.

NOTE

The drawer will not work as there is no communication of the variable "drawer" between toolbar.vue and navbar.vue. To resolve this we design a store by which both navbar and toolbar can access one global variable "drawer".

# Adding Store

  1. Open eclipse, in libraryview.kview create ads (action, data, store) under section library.
section library {
	ads Library
}

NOTE

Before Creating a store make sure you have stopped SBT and npm serve.*

  1. Open Library.kviewads file and define a store.
data UI (drawer: boolean = `true`)
store [UI] UI{
	getter drawer => boolean
	mutation toggleDrawer()
}
  1. Open UIGettersCode.ts under store folder and implement drawer method to return the current drawer state.
export class UIGettersCode implements UIGetters {

   public drawer(state: Data.Library.UI): boolean {
    return state.drawer;
   }
  1. Open UIMutationsCode.ts file implement toggleDrawer method.
 public toggleDrawer(state: Data.Library.UI  ): void  {
    state.drawer = !state.drawer;
   }

NOTE

If your getting any errors in getter or setter .ts code - delete both the files and run prepare-force , generate-force commands on sbt.

  1. In Toolbar.vue file import store and write code to toggle the drawer inside script tag. Remove the "private drawer = true;" statement.
<script lang="ts">
import { Vue, Component, Prop, Emit, Watch } from 'vue-property-decorator';
import store, * as Store from '@/../src-gen/store';
// import * as Data from '@/../src-gen/data';
// import * as ServerData from '@/../src-gen/server-data';
// import * as Action from '@/../src-gen/action';

@Component
export default class Toolbar extends Vue {
  public toggleDrawer() {
    Store.Mutation.Library.UI.TOGGLE_DRAWER();
  }
}
</script>
  1. In Navbar.vue file import store and access the drawer inside script tag. Remove the "private drawer: true;" statement.
<script lang="ts">
import { Vue, Component, Prop, Emit, Watch } from 'vue-property-decorator';
import store, * as Store from '@/../src-gen/store';
// import * as Data from '@/../src-gen/data';
// import * as ServerData from '@/../src-gen/server-data';
// import * as Action from '@/../src-gen/action';

@Component
export default class Navbar extends Vue {
  private  dialog = false;
  @Store.Getter.Library.UI.drawer private drawer: boolean;
  private items = [
       { icon: 'library_books', text: 'Recently Added', routerName : 'RecentBooks' },
       { icon: 'search', text: 'Search', routerName : 'Search' },
      { icon: 'add', text: 'Add', routerName : 'Add' },
      ];
  }
</script>
  1. Now you should be able to use drawer to open/close the Navbar.

# Add Book Page

  1. Open libraryview.kviewads file and define action and input data for addBook Api.
data AddBookForm(isbn: string, title: string, author: string, publisher: string, publishedDate: string?, status: boolean = `true`)
action [LibraApi:W:Book.addBook] AddBook {
	input(AddBookForm)
}
  1. Add SmileForm decorator for the input data as it should be displayed as a form to the user.
data AddBookForm(core.SmileForm)(isbn: string(), title: string(), author: string(), publisher: string(), publishedDate: string?(datepicker), status: boolean = `true`())

NOTE

The form will not be displayed unless you give atleast one "()" to a field.

  1. Open Add.vue in VSCode and import the data , smileForm and define the addInputForm Data.
<script lang="ts">
import { Vue, Component, Prop, Emit, Watch } from 'vue-property-decorator';
// import store, * as Store from '@/../src-gen/store';
import * as Data from '@/../src-gen/data';
// import * as ServerData from '@/../src-gen/server-data';
// import * as Action from '@/../src-gen/action';
import SmileForm from '@/section/core/components/smile/SmileForm.vue';

@Component({
  components: { SmileForm }
})
export default class Add extends Vue {
  private addBookForm = new Data.Library.AddBookForm();
  private decorator = Data.Library.AddBookForm.toSmileFormDecorator();

}
</script>

NOTE

Decotator for each SmileForm/SmileGrid should be defined (with different variable names) as to convert the fields to the specified (in eclipse) decorator Format.

  1. In Add.vue add SmileForm Code in the template.
<template>
  <v-layout>
    <v-flex>
      <v-card>
        <SmileForm
          v-model='addBookForm'
          :decorator='decorator'
          title='Add Book'
          submit-text='Add Book'
          @submit='addBook'>
        ></SmileForm>
      </v-card>
    </v-flex>
  </v-layout>
</template>

NOTE

v-model takes the inputForm data.

  1. Import Action and define the addBook method inside script tag to add the book to DB when the "Add Book" button is clicked.
private addBook() {
    Action.Library.AddBook.execute(this.addBookForm, output => this.$router.push({name : 'Details.Summary', params : {bookId : output.bookId}}));
  }

NOTE

this.$router.push - takes us to a different page when the function execution is completed. Here on submitting it takes us to details page when book details will be displayed.

  1. Open browser http://localhost:8080/add You should be able to see AddForm page.
  1. After inputing the field, when you click on "Add Book" you must be directed to details page, with the book id in the url.

# Search Book Page

  1. Open libraryview.kviewads file and define action and input, output data for SearchBook Api with SmileForm and SmileGrid decorators.
data SearchBookForm(core.SmileForm)(title: string?({ "flexClasses" : "md3"}), category: string?({ "flexClasses" : "md3"}), author: string?({ "flexClasses" : "md2"}), publisher: string?({ "flexClasses" : "md2"}))

data SearchBookResultGrid(core.SmileGrid)(bookId: string, isbn: string(), title: string({"link" : ":bookId/details"}), category: string*(), author: string(), publisher: string(), status: boolean = `true`())
action [LibraApi:R:Book.searchBook] SearchBook {
	input(SearchBookForm)
	output(SearchBookResultGrid*)
}
  1. Open Search.vue in VSCode and import the data, SmileForm, SmileGrid and define the searchForm and searchOutput Data and write SmileForm and SmileGrid code in the template and write a action for SearchBook inside searchBook method.
<template>
  <v-layout column>
    <v-flex>
      <v-card>
        <SmileForm
          v-model='searchBookForm'
          :decorator='decorator'
          title='Search Book'
          submit-text='Search'
          @submit='searchBook'
          :cancel="false">
        ></SmileForm>
      </v-card>
    </v-flex>
    <v-flex>
      <v-card>
        <SmileGrid
          grid-title='Search Result'
          :items="searchBookResult"
          :decorator="gridDecorator"
        ></SmileGrid>
      </v-card>
    </v-flex>
  </v-layout>
</template>

<script lang="ts">
import { Vue, Component, Prop, Emit, Watch } from 'vue-property-decorator';
import * as Data from '@/../src-gen/data';
import SmileForm from '@/section/core/components/smile/SmileForm.vue';
import SmileGrid from '@/section/core/components/smile/SmileGrid.vue';
import * as Action from '@/../src-gen/action';


@Component({
  components: { SmileForm, SmileGrid }
})
export default class Search extends Vue {
  private searchBookForm = new Data.Library.SearchBookForm();
  private decorator = Data.Library.SearchBookForm.toSmileFormDecorator();
  private searchBookResult: Data.Library.SearchBookResultGrid[] = [];
  private gridDecorator = Data.Library.SearchBookResultGrid.toSmileGridDecorator();

  private searchBook() {
    Action.Library.SearchBook.execute(this.searchBookForm, output => this.searchBookResult = output);
  }
}
</script>

NOTE

"items" in SmileGrid take gridOutput data as input.

  1. Open browser http://localhost:8080/libraryview#/search You should be able to see SearchForm and Search Result Grid in the page.
  1. Now you should be able to search a book and find the result displayed in the grid
  1. On clicking the book name in the grid you should be directed to book details page.

# Book Details Page

  1. Open Library.kviewads in eclipse and define action for getDetails.
data BookDetails(bookId: string, isbn: string, title: string, category: string*, author: string, publisher: string, status: boolean = `true`)
action [LibraApi:R:Book.getBookDetails] GetBookDetails {
	output(BookDetails?)
}
  1. Open Details.vue in VSCode and define BookDetails data.
private bookDetails = new Data.Library.BookDetails();
  1. Define getBookDetails method and call it in mounted method.
<script lang="ts">

import { Vue, Component, Prop, Emit, Watch } from 'vue-property-decorator';
// import store, * as Store from '@/../src-gen/store';
import * as Data from '@/../src-gen/data';
// import * as ServerData from '@/../src-gen/server-data';
import * as Action from '@/../src-gen/action';

@Component
export default class Details extends Vue {
private bookDetails = new Data.Library.BookDetails();
private mounted() {
    this.getBookDetails();
  }

  private getBookDetails() {
    Action.Library.GetBookDetails.execute1(this.$route.params.bookId, output => {
      if (output) { 
        this.bookDetails = output;
      }
    });
  }
}

</script>
  1. Write the template code to display details.
<template>
  <v-layout column
        justify-center>
    <v-flex>
        <v-expansion-panel >
          <v-expansion-panel-content
            hide-actions
          >
            <v-layout
              slot="header"
              align-center
              row
              spacer
            >
              <v-flex xs4 sm2 md1>
                <v-avatar
                  slot="activator"
                  size="36px"
                >
                  <v-icon
                    color="red"
                  >book</v-icon>
                </v-avatar>
              </v-flex>
    
              <v-flex sm5 md3 hidden-xs-only>
                <strong v-html="bookDetails.title"></strong>
                <span
                  v-if="bookDetails.author"
                  class="grey--text"
                >
                  &nbsp;({{ bookDetails.author }})
                </span>
              </v-flex>
    
              <v-flex no-wrap xs5 sm3>
                <v-chip
                  v-if="bookDetails.status"
                  color="green lighten-4"
                  class="ml-0"
                  label
                  small
                >
                  AVAILABLE
                </v-chip>
                <v-chip
                  v-if="!bookDetails.status"
                  color="red lighten-4"
                  class="ml-0"
                  label
                  small
                >
                  NOT AVAILABLE
                </v-chip>
                <strong v-html="bookDetails.isbn"></strong>
              </v-flex>
    
              <v-flex
                v-if="bookDetails.publisher"
                class="grey--text"
                ellipsis
                hidden-sm-and-down
              >
                &mdash;
                {{ bookDetails.publisher }}
              </v-flex>
            </v-layout>

            <!-- todo ADD CATEGORY -->
            <!-- todo Make Book Available/Unavailable --> 

     	</v-expansion-panel-content>
      </v-expansion-panel>
    </v-flex>
  </v-layout>
</template>

NOTE

Refer vuetify Avatars to see how the details template is designed. You can use any of the template.

  1. Search for a book and go to details page, you should be able to see the details of the book.

# Add Category

  1. Open Details.vue in VSCode and add a button "Add Category" above "todo ADD CATEGORY" comment in template.
    <v-card>
      <v-divider></v-divider>
      <v-card-text>
        <span class="text-xs-center" v-for="(category, index) in bookDetails.category" :key="index">
          <v-chip close">{{category}}</v-chip>
        </span>
      </v-card-text>
      <v-card-actions>
        <v-btn flat @click="$router.push({name : 'Details.AddCategory', params : $route.params})">Add Category</v-btn>
	  </v-card-actions>
    </v-card>

    <!-- todo Make Book Available/Unavailable --> 

  1. In browser you should be able to see the ADD CATEGORY button on clicking the expansion card in details page. Upon clicking the ADD CATEGORY button you should be directed to Add Category Page.
  1. Open Library.kviewads in eclipse and define a AddCategory action and input form
data AddCategoryForm(core.SmileForm)(bookId: string, category: string())
action [LibraApi:W:Book.addCategory] AddCategory
  1. Open AddCategory.vue in VSCode and define addCategory input data, smile Form and addCategory method which has AddCategory action inside script.
<script lang="ts">

import { Vue, Component, Prop, Emit, Watch } from 'vue-property-decorator';
// import store, * as Store from '@/../src-gen/store';
import * as Data from '@/../src-gen/data';
// import * as ServerData from '@/../src-gen/server-data';
import * as Action from '@/../src-gen/action';
import SmileForm from '@/section/core/components/smile/SmileForm.vue';

@Component({
  components: { SmileForm }
})
export default class AddCategory extends Vue {
  private addCategoryForm = new Data.Library.AddCategoryForm();
  private addCategoryFormDecorator = Data.Library.AddCategoryForm.toSmileFormDecorator();

  public addCategory() {
    this.addCategoryForm.bookId = this.$route.params.bookId;
    Action.Library.AddCategory.execute(
      this.addCategoryForm,
      output => {
        this.$router.push({name : 'Details.Summary', params : {bookId : this.$route.params.bookId}});
      }
    );
  }
}
</script>
  1. Write the SmileForm code inside template
<template>
 <v-layout>
   <v-flex>
     <v-card>
       <SmileForm
       title="Add Category"
       v-model='addCategoryForm'
       :decorator='addCategoryFormDecorator'
       submit-text='Add Category'
       @submit='addCategory'
       >
       </SmileForm>
     </v-card>
   </v-flex>
 </v-layout>
</template>
  1. Now on clicking the ADD CATEGORY in your browser you must see a Add Category form and upon clicking add the category should be added to your DB and you should be directed to details page and the category displayed as a chip.
  1. In Library.kviewads file write action to remove a category.
action [LibraApi:W:Book.removeCategory]RemoveCategory
  1. To remove the category when the cross(x) symbol is clicked implement the "removeCategory(category)" method in Details.vue file. Import Snackbar.
<script lang="ts">
import { Vue, Component, Prop, Emit, Watch } from 'vue-property-decorator';
// import store, * as Store from '@/../src-gen/store';
import * as Data from '@/../src-gen/data';
// import * as ServerData from '@/../src-gen/server-data';
import * as Action from '@/../src-gen/action';
import * as Snackbar from 'node-snackbar';

@Component
export default class Details extends Vue {
  private bookDetails = new Data.Library.BookDetails();
  private mounted() {
    this.getBookDetails();
  }

  private getBookDetails() {
    Action.Library.GetBookDetails.execute1(
      this.$route.params.bookId,
      output => {
        if (output) {
          this.bookDetails = output;
        }
      }
    );
  }
  private removeCategory(category: string) {
    console.log('I am in removeCategory  ' + category);
    Action.Library.RemoveCategory.execute2(this.$route.params.bookId, category, output =>  {
      this.getBookDetails();
      Snackbar.show({ text: category + ' category removed.', pos: 'bottom-center' });
    });
  }
}
</script>
  1. Call the removeCategory method in v-chip of category in template of Details.vue

 <v-chip close @input="removeCategory(category)">{{category}}</v-chip>

Now you should be able to remove a category.

# Assignments

  1. Implement the Recent Book page.

Follow these steps :

  • In Eclipse
    • Define Action.
    • Define Data for input and output (if any).
    • Define SmileForm/SmileGrid Decorators (if any).
  • In VSCode
    • Create input and output data, define decorators for input , output.
    • Design the template.
    • Implement the required action when some action(onclick,onsubmit .etc) is trigerred.
  1. Add a button to change the status of the book from available to unavailable and vice versa. The code can be inserted where the comment "todo Make Book Available/Unavailable" is written in the template.

# Complete Project Reference

https://bitbucket.org/metastay/library