A Light Universal Modal Component for Vue 3

vue-universal-modal

Universal modal plugin for Vue@3
Demo

⚠️ This plugin does not support Vue@2

Table of Contents

  • Introduction
  • Features
  • Install plugin
    • Options
  • Usage modal
    • v1.0.x -> v1.1.x change point
    • props
      • props.options
    • emit events
  • Handle global CSS

Introduction

vue-universal-modal plugin is based on the teleport.
It is very light and simple, but it provides essential features for modal use in applications.
(Such as Add & Remove, Visible & Hidden, Transition, Auto bind keyboard and mouse to close, Support SSR, A11Y...)
Here is the Demo

Features

  • Based on the teleport
  • Provides essential features for modal
  • A11Y
  • Support SSR (Insert rendering source into SSR context, Mount from Client-side)

Install plugin

npm install vue-universal-modal

Insert teleport element in your html

...
  <div id="app"></div>
  <!-- teleport target -->
  <div id="modals"></div>
...

Because SSR cannot be implemented by dynamically creating and ref referencing teleport elements, teleport targets must be inserted into html first.

And install plugin in vue application

import 'vue-universal-modal/dist/index.css'

import VueUniversalModal from 'vue-universal-modal'

app.use(VueUniversalModal, {
  teleportTarget: '#modals'
})

Options

app.use(VueUniversalModal, {
  teleportTarget: '#my-modals',
  modalComponent: 'MyModal',
})
nametypedetaultdescription
teleportTarget (required)string Teleport target
modalComponentstring'Modal'Global modal component name

Usage modal

Insert the component wrapped with the modal component. (Slot based)

<template>
  <p>
    <button @click="showModal">
      Show modal
    </button>
  </p>
  <!-- If the option changed modal component the name
  <MyModal>
  -->
  <Modal
    v-model="isShow"
    :close="closeModal"
  >
    <div class="modal">
      <p>
        Hello
      </p>
      <button @click="closeModal">
        close
      </button>
    </div>
  </Modal>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup () {
    const isShow = ref(false)

    function showModal () {
      isShow.value = true
    }

    function closeModal () {
      isShow.value = false
    }

    return {
      isShow,
      showModal,
      closeModal
    }
  }
})
</script>

<style scoped lang="scss">
.modal {
  width: 300px;
  padding: 30px;
  box-sizing: border-box;
  background-color: #fff;
  font-size: 20px;
  text-align: center;
}
</style>

v1.0.x -> v1.1.x change point

  • Use v-model instead of v-if for modal component insertion
  • If you control the insertion of components with v-if, the close animation will not work.
  • emitClose slot argument was deprecated.

props

nametypedetaultdescription
closefunction() => {}Function to close a modal (apply when click dimmed)
disabledbooleanfalseHandle just visibility (as in v-show)
optionsobject{} 

props.options

nametypedetaultdescription
transitionnumber | false300transition duration
closeClickDimmedbooleantrueCloses the modal when dimmed is clicked
closeKeyCodenumber | false27 (esc)Closes the modal when press key
styleModalContentobject{}Inject modal content style (.vue-universal-modal-content)

emit events

Supports emit properties for all transition events.

<template>
  <p>
    <button @click="showModal">
      Show modal
    </button>
  </p>
  <Modal
    v-model="isShow"
    :close="closeModal"
    @before-enter="beforeEnter"
    @after-enter="afterEnter"
    @before-leave="beforeLeave"
    @after-leave="afterLeave"
  >
    <div class="modal">
      <p>
        Hello
      </p>
      <button @click="closeModal">
        close
      </button>
    </div>
  </Modal>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup () {
    const isShow = ref(false)

    function showModal () {
      isShow.value = true
    }

    function closeModal () {
      isShow.value = false
    }

    function beforeEnter () {
      console.log('before enter')
    }

    function afterEnter () {
      console.log('after enter')
    }

    function beforeLeave () {
      console.log('before leave')
    }

    function afterLeave () {
      console.log('after leave')
    }

    return {
      isShow,
      showModal,
      closeModal,
      beforeEnter,
      afterEnter,
      beforeLeave,
      afterLeave
    }
  }
})
</script>

Handle global CSS

You can change it directly to your own style by referring to the source

.vue-universal-modal {
  /* Change dimmed color */
  background-color: rgba(255, 255, 0, 0.3);
}
.vue-universal-modal-content {
  /* Align to top (flex-direction property value is set to column) */
  justify-content: flex-start;
}

Download Details:

Author: hoiheart
The Demo/Documentation: View The Demo/Documentation
Download Link: Download The Source Code
Official Website: https://github.com/hoiheart/vue-universal-modal 
License: MIT

What is GEEK

Buddha Community

A Light Universal Modal Component for Vue 3
Luna  Mosciski

Luna Mosciski

1600583123

8 Popular Websites That Use The Vue.JS Framework

In this article, we are going to list out the most popular websites using Vue JS as their frontend framework.

Vue JS is one of those elite progressive JavaScript frameworks that has huge demand in the web development industry. Many popular websites are developed using Vue in their frontend development because of its imperative features.

This framework was created by Evan You and still it is maintained by his private team members. Vue is of course an open-source framework which is based on MVVM concept (Model-view view-Model) and used extensively in building sublime user-interfaces and also considered a prime choice for developing single-page heavy applications.

Released in February 2014, Vue JS has gained 64,828 stars on Github, making it very popular in recent times.

Evan used Angular JS on many operations while working for Google and integrated many features in Vue to cover the flaws of Angular.

“I figured, what if I could just extract the part that I really liked about Angular and build something really lightweight." - Evan You

#vuejs #vue #vue-with-laravel #vue-top-story #vue-3 #build-vue-frontend #vue-in-laravel #vue.js

A Wrapper for Sembast and SQFlite to Enable Easy

FHIR_DB

This is really just a wrapper around Sembast_SQFLite - so all of the heavy lifting was done by Alex Tekartik. I highly recommend that if you have any questions about working with this package that you take a look at Sembast. He's also just a super nice guy, and even answered a question for me when I was deciding which sembast version to use. As usual, ResoCoder also has a good tutorial.

I have an interest in low-resource settings and thus a specific reason to be able to store data offline. To encourage this use, there are a number of other packages I have created based around the data format FHIR. FHIR® is the registered trademark of HL7 and is used with the permission of HL7. Use of the FHIR trademark does not constitute endorsement of this product by HL7.

Using the Db

So, while not absolutely necessary, I highly recommend that you use some sort of interface class. This adds the benefit of more easily handling errors, plus if you change to a different database in the future, you don't have to change the rest of your app, just the interface.

I've used something like this in my projects:

class IFhirDb {
  IFhirDb();
  final ResourceDao resourceDao = ResourceDao();

  Future<Either<DbFailure, Resource>> save(Resource resource) async {
    Resource resultResource;
    try {
      resultResource = await resourceDao.save(resource);
    } catch (error) {
      return left(DbFailure.unableToSave(error: error.toString()));
    }
    return right(resultResource);
  }

  Future<Either<DbFailure, List<Resource>>> returnListOfSingleResourceType(
      String resourceType) async {
    List<Resource> resultList;
    try {
      resultList =
          await resourceDao.getAllSortedById(resourceType: resourceType);
    } catch (error) {
      return left(DbFailure.unableToObtainList(error: error.toString()));
    }
    return right(resultList);
  }

  Future<Either<DbFailure, List<Resource>>> searchFunction(
      String resourceType, String searchString, String reference) async {
    List<Resource> resultList;
    try {
      resultList =
          await resourceDao.searchFor(resourceType, searchString, reference);
    } catch (error) {
      return left(DbFailure.unableToObtainList(error: error.toString()));
    }
    return right(resultList);
  }
}

I like this because in case there's an i/o error or something, it won't crash your app. Then, you can call this interface in your app like the following:

final patient = Patient(
    resourceType: 'Patient',
    name: [HumanName(text: 'New Patient Name')],
    birthDate: Date(DateTime.now()),
);

final saveResult = await IFhirDb().save(patient);

This will save your newly created patient to the locally embedded database.

IMPORTANT: this database will expect that all previously created resources have an id. When you save a resource, it will check to see if that resource type has already been stored. (Each resource type is saved in it's own store in the database). It will then check if there is an ID. If there's no ID, it will create a new one for that resource (along with metadata on version number and creation time). It will save it, and return the resource. If it already has an ID, it will copy the the old version of the resource into a _history store. It will then update the metadata of the new resource and save that version into the appropriate store for that resource. If, for instance, we have a previously created patient:

{
    "resourceType": "Patient",
    "id": "fhirfli-294057507-6811107",
    "meta": {
        "versionId": "1",
        "lastUpdated": "2020-10-16T19:41:28.054369Z"
    },
    "name": [
        {
            "given": ["New"],
            "family": "Patient"
        }
    ],
    "birthDate": "2020-10-16"
}

And we update the last name to 'Provider'. The above version of the patient will be kept in _history, while in the 'Patient' store in the db, we will have the updated version:

{
    "resourceType": "Patient",
    "id": "fhirfli-294057507-6811107",
    "meta": {
        "versionId": "2",
        "lastUpdated": "2020-10-16T19:45:07.316698Z"
    },
    "name": [
        {
            "given": ["New"],
            "family": "Provider"
        }
    ],
    "birthDate": "2020-10-16"
}

This way we can keep track of all previous version of all resources (which is obviously important in medicine).

For most of the interactions (saving, deleting, etc), they work the way you'd expect. The only difference is search. Because Sembast is NoSQL, we can search on any of the fields in a resource. If in our interface class, we have the following function:

  Future<Either<DbFailure, List<Resource>>> searchFunction(
      String resourceType, String searchString, String reference) async {
    List<Resource> resultList;
    try {
      resultList =
          await resourceDao.searchFor(resourceType, searchString, reference);
    } catch (error) {
      return left(DbFailure.unableToObtainList(error: error.toString()));
    }
    return right(resultList);
  }

You can search for all immunizations of a certain patient:

searchFunction(
        'Immunization', 'patient.reference', 'Patient/$patientId');

This function will search through all entries in the 'Immunization' store. It will look at all 'patient.reference' fields, and return any that match 'Patient/$patientId'.

The last thing I'll mention is that this is a password protected db, using AES-256 encryption (although it can also use Salsa20). Anytime you use the db, you have the option of using a password for encryption/decryption. Remember, if you setup the database using encryption, you will only be able to access it using that same password. When you're ready to change the password, you will need to call the update password function. If we again assume we created a change password method in our interface, it might look something like this:

class IFhirDb {
  IFhirDb();
  final ResourceDao resourceDao = ResourceDao();
  ...
    Future<Either<DbFailure, Unit>> updatePassword(String oldPassword, String newPassword) async {
    try {
      await resourceDao.updatePw(oldPassword, newPassword);
    } catch (error) {
      return left(DbFailure.unableToUpdatePassword(error: error.toString()));
    }
    return right(Unit);
  }

You don't have to use a password, and in that case, it will save the db file as plain text. If you want to add a password later, it will encrypt it at that time.

General Store

After using this for a while in an app, I've realized that it needs to be able to store data apart from just FHIR resources, at least on occasion. For this, I've added a second class for all versions of the database called GeneralDao. This is similar to the ResourceDao, but fewer options. So, in order to save something, it would look like this:

await GeneralDao().save('password', {'new':'map'});
await GeneralDao().save('password', {'new':'map'}, 'key');

The difference between these two options is that the first one will generate a key for the map being stored, while the second will store the map using the key provided. Both will return the key after successfully storing the map.

Other functions available include:

// deletes everything in the general store
await GeneralDao().deleteAllGeneral('password'); 

// delete specific entry
await GeneralDao().delete('password','key'); 

// returns map with that key
await GeneralDao().find('password', 'key'); 

FHIR® is a registered trademark of Health Level Seven International (HL7) and its use does not constitute an endorsement of products by HL7®

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add fhir_db

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  fhir_db: ^0.4.3

Alternatively, your editor might support or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:fhir_db/dstu2.dart';
import 'package:fhir_db/dstu2/fhir_db.dart';
import 'package:fhir_db/dstu2/general_dao.dart';
import 'package:fhir_db/dstu2/resource_dao.dart';
import 'package:fhir_db/encrypt/aes.dart';
import 'package:fhir_db/encrypt/salsa.dart';
import 'package:fhir_db/r4.dart';
import 'package:fhir_db/r4/fhir_db.dart';
import 'package:fhir_db/r4/general_dao.dart';
import 'package:fhir_db/r4/resource_dao.dart';
import 'package:fhir_db/r5.dart';
import 'package:fhir_db/r5/fhir_db.dart';
import 'package:fhir_db/r5/general_dao.dart';
import 'package:fhir_db/r5/resource_dao.dart';
import 'package:fhir_db/stu3.dart';
import 'package:fhir_db/stu3/fhir_db.dart';
import 'package:fhir_db/stu3/general_dao.dart';
import 'package:fhir_db/stu3/resource_dao.dart'; 

example/lib/main.dart

import 'package:fhir/r4.dart';
import 'package:fhir_db/r4.dart';
import 'package:flutter/material.dart';
import 'package:test/test.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final resourceDao = ResourceDao();

  // await resourceDao.updatePw('newPw', null);
  await resourceDao.deleteAllResources(null);

  group('Playing with passwords', () {
    test('Playing with Passwords', () async {
      final patient = Patient(id: Id('1'));

      final saved = await resourceDao.save(null, patient);

      await resourceDao.updatePw(null, 'newPw');
      final search1 = await resourceDao.find('newPw',
          resourceType: R4ResourceType.Patient, id: Id('1'));
      expect(saved, search1[0]);

      await resourceDao.updatePw('newPw', 'newerPw');
      final search2 = await resourceDao.find('newerPw',
          resourceType: R4ResourceType.Patient, id: Id('1'));
      expect(saved, search2[0]);

      await resourceDao.updatePw('newerPw', null);
      final search3 = await resourceDao.find(null,
          resourceType: R4ResourceType.Patient, id: Id('1'));
      expect(saved, search3[0]);

      await resourceDao.deleteAllResources(null);
    });
  });

  final id = Id('12345');
  group('Saving Things:', () {
    test('Save Patient', () async {
      final humanName = HumanName(family: 'Atreides', given: ['Duke']);
      final patient = Patient(id: id, name: [humanName]);
      final saved = await resourceDao.save(null, patient);

      expect(saved.id, id);

      expect((saved as Patient).name?[0], humanName);
    });

    test('Save Organization', () async {
      final organization = Organization(id: id, name: 'FhirFli');
      final saved = await resourceDao.save(null, organization);

      expect(saved.id, id);

      expect((saved as Organization).name, 'FhirFli');
    });

    test('Save Observation1', () async {
      final observation1 = Observation(
        id: Id('obs1'),
        code: CodeableConcept(text: 'Observation #1'),
        effectiveDateTime: FhirDateTime(DateTime(1981, 09, 18)),
      );
      final saved = await resourceDao.save(null, observation1);

      expect(saved.id, Id('obs1'));

      expect((saved as Observation).code.text, 'Observation #1');
    });

    test('Save Observation1 Again', () async {
      final observation1 = Observation(
          id: Id('obs1'),
          code: CodeableConcept(text: 'Observation #1 - Updated'));
      final saved = await resourceDao.save(null, observation1);

      expect(saved.id, Id('obs1'));

      expect((saved as Observation).code.text, 'Observation #1 - Updated');

      expect(saved.meta?.versionId, Id('2'));
    });

    test('Save Observation2', () async {
      final observation2 = Observation(
        id: Id('obs2'),
        code: CodeableConcept(text: 'Observation #2'),
        effectiveDateTime: FhirDateTime(DateTime(1981, 09, 18)),
      );
      final saved = await resourceDao.save(null, observation2);

      expect(saved.id, Id('obs2'));

      expect((saved as Observation).code.text, 'Observation #2');
    });

    test('Save Observation3', () async {
      final observation3 = Observation(
        id: Id('obs3'),
        code: CodeableConcept(text: 'Observation #3'),
        effectiveDateTime: FhirDateTime(DateTime(1981, 09, 18)),
      );
      final saved = await resourceDao.save(null, observation3);

      expect(saved.id, Id('obs3'));

      expect((saved as Observation).code.text, 'Observation #3');
    });
  });

  group('Finding Things:', () {
    test('Find 1st Patient', () async {
      final search = await resourceDao.find(null,
          resourceType: R4ResourceType.Patient, id: id);
      final humanName = HumanName(family: 'Atreides', given: ['Duke']);

      expect(search.length, 1);

      expect((search[0] as Patient).name?[0], humanName);
    });

    test('Find 3rd Observation', () async {
      final search = await resourceDao.find(null,
          resourceType: R4ResourceType.Observation, id: Id('obs3'));

      expect(search.length, 1);

      expect(search[0].id, Id('obs3'));

      expect((search[0] as Observation).code.text, 'Observation #3');
    });

    test('Find All Observations', () async {
      final search = await resourceDao.getResourceType(
        null,
        resourceTypes: [R4ResourceType.Observation],
      );

      expect(search.length, 3);

      final idList = [];
      for (final obs in search) {
        idList.add(obs.id.toString());
      }

      expect(idList.contains('obs1'), true);

      expect(idList.contains('obs2'), true);

      expect(idList.contains('obs3'), true);
    });

    test('Find All (non-historical) Resources', () async {
      final search = await resourceDao.getAll(null);

      expect(search.length, 5);
      final patList = search.toList();
      final orgList = search.toList();
      final obsList = search.toList();
      patList.retainWhere(
          (resource) => resource.resourceType == R4ResourceType.Patient);
      orgList.retainWhere(
          (resource) => resource.resourceType == R4ResourceType.Organization);
      obsList.retainWhere(
          (resource) => resource.resourceType == R4ResourceType.Observation);

      expect(patList.length, 1);

      expect(orgList.length, 1);

      expect(obsList.length, 3);
    });
  });

  group('Deleting Things:', () {
    test('Delete 2nd Observation', () async {
      await resourceDao.delete(
          null, null, R4ResourceType.Observation, Id('obs2'), null, null);

      final search = await resourceDao.getResourceType(
        null,
        resourceTypes: [R4ResourceType.Observation],
      );

      expect(search.length, 2);

      final idList = [];
      for (final obs in search) {
        idList.add(obs.id.toString());
      }

      expect(idList.contains('obs1'), true);

      expect(idList.contains('obs2'), false);

      expect(idList.contains('obs3'), true);
    });

    test('Delete All Observations', () async {
      await resourceDao.deleteSingleType(null,
          resourceType: R4ResourceType.Observation);

      final search = await resourceDao.getAll(null);

      expect(search.length, 2);

      final patList = search.toList();
      final orgList = search.toList();
      patList.retainWhere(
          (resource) => resource.resourceType == R4ResourceType.Patient);
      orgList.retainWhere(
          (resource) => resource.resourceType == R4ResourceType.Organization);

      expect(patList.length, 1);

      expect(patList.length, 1);
    });

    test('Delete All Resources', () async {
      await resourceDao.deleteAllResources(null);

      final search = await resourceDao.getAll(null);

      expect(search.length, 0);
    });
  });

  group('Password - Saving Things:', () {
    test('Save Patient', () async {
      await resourceDao.updatePw(null, 'newPw');
      final humanName = HumanName(family: 'Atreides', given: ['Duke']);
      final patient = Patient(id: id, name: [humanName]);
      final saved = await resourceDao.save('newPw', patient);

      expect(saved.id, id);

      expect((saved as Patient).name?[0], humanName);
    });

    test('Save Organization', () async {
      final organization = Organization(id: id, name: 'FhirFli');
      final saved = await resourceDao.save('newPw', organization);

      expect(saved.id, id);

      expect((saved as Organization).name, 'FhirFli');
    });

    test('Save Observation1', () async {
      final observation1 = Observation(
        id: Id('obs1'),
        code: CodeableConcept(text: 'Observation #1'),
        effectiveDateTime: FhirDateTime(DateTime(1981, 09, 18)),
      );
      final saved = await resourceDao.save('newPw', observation1);

      expect(saved.id, Id('obs1'));

      expect((saved as Observation).code.text, 'Observation #1');
    });

    test('Save Observation1 Again', () async {
      final observation1 = Observation(
          id: Id('obs1'),
          code: CodeableConcept(text: 'Observation #1 - Updated'));
      final saved = await resourceDao.save('newPw', observation1);

      expect(saved.id, Id('obs1'));

      expect((saved as Observation).code.text, 'Observation #1 - Updated');

      expect(saved.meta?.versionId, Id('2'));
    });

    test('Save Observation2', () async {
      final observation2 = Observation(
        id: Id('obs2'),
        code: CodeableConcept(text: 'Observation #2'),
        effectiveDateTime: FhirDateTime(DateTime(1981, 09, 18)),
      );
      final saved = await resourceDao.save('newPw', observation2);

      expect(saved.id, Id('obs2'));

      expect((saved as Observation).code.text, 'Observation #2');
    });

    test('Save Observation3', () async {
      final observation3 = Observation(
        id: Id('obs3'),
        code: CodeableConcept(text: 'Observation #3'),
        effectiveDateTime: FhirDateTime(DateTime(1981, 09, 18)),
      );
      final saved = await resourceDao.save('newPw', observation3);

      expect(saved.id, Id('obs3'));

      expect((saved as Observation).code.text, 'Observation #3');
    });
  });

  group('Password - Finding Things:', () {
    test('Find 1st Patient', () async {
      final search = await resourceDao.find('newPw',
          resourceType: R4ResourceType.Patient, id: id);
      final humanName = HumanName(family: 'Atreides', given: ['Duke']);

      expect(search.length, 1);

      expect((search[0] as Patient).name?[0], humanName);
    });

    test('Find 3rd Observation', () async {
      final search = await resourceDao.find('newPw',
          resourceType: R4ResourceType.Observation, id: Id('obs3'));

      expect(search.length, 1);

      expect(search[0].id, Id('obs3'));

      expect((search[0] as Observation).code.text, 'Observation #3');
    });

    test('Find All Observations', () async {
      final search = await resourceDao.getResourceType(
        'newPw',
        resourceTypes: [R4ResourceType.Observation],
      );

      expect(search.length, 3);

      final idList = [];
      for (final obs in search) {
        idList.add(obs.id.toString());
      }

      expect(idList.contains('obs1'), true);

      expect(idList.contains('obs2'), true);

      expect(idList.contains('obs3'), true);
    });

    test('Find All (non-historical) Resources', () async {
      final search = await resourceDao.getAll('newPw');

      expect(search.length, 5);
      final patList = search.toList();
      final orgList = search.toList();
      final obsList = search.toList();
      patList.retainWhere(
          (resource) => resource.resourceType == R4ResourceType.Patient);
      orgList.retainWhere(
          (resource) => resource.resourceType == R4ResourceType.Organization);
      obsList.retainWhere(
          (resource) => resource.resourceType == R4ResourceType.Observation);

      expect(patList.length, 1);

      expect(orgList.length, 1);

      expect(obsList.length, 3);
    });
  });

  group('Password - Deleting Things:', () {
    test('Delete 2nd Observation', () async {
      await resourceDao.delete(
          'newPw', null, R4ResourceType.Observation, Id('obs2'), null, null);

      final search = await resourceDao.getResourceType(
        'newPw',
        resourceTypes: [R4ResourceType.Observation],
      );

      expect(search.length, 2);

      final idList = [];
      for (final obs in search) {
        idList.add(obs.id.toString());
      }

      expect(idList.contains('obs1'), true);

      expect(idList.contains('obs2'), false);

      expect(idList.contains('obs3'), true);
    });

    test('Delete All Observations', () async {
      await resourceDao.deleteSingleType('newPw',
          resourceType: R4ResourceType.Observation);

      final search = await resourceDao.getAll('newPw');

      expect(search.length, 2);

      final patList = search.toList();
      final orgList = search.toList();
      patList.retainWhere(
          (resource) => resource.resourceType == R4ResourceType.Patient);
      orgList.retainWhere(
          (resource) => resource.resourceType == R4ResourceType.Organization);

      expect(patList.length, 1);

      expect(patList.length, 1);
    });

    test('Delete All Resources', () async {
      await resourceDao.deleteAllResources('newPw');

      final search = await resourceDao.getAll('newPw');

      expect(search.length, 0);

      await resourceDao.updatePw('newPw', null);
    });
  });
} 

Download Details:

Author: MayJuun

Source Code: https://github.com/MayJuun/fhir/tree/main/fhir_db

#sqflite  #dart  #flutter 

Vue 3 UX Wins with Async Components & Suspense

Lazy loading components is a easy way to improve the user experience of your app especially if your code bundle is big or if users are on slow connections.

Vue 3 has introduced several new features to help you achieve this easily and with great UX through the improvements to the async component API and the new Suspense component.


Why lazy load components?

Some parts of your UI don’t need to be loaded the instant a user visits your app, for example, dynamic UI features like modals and tooltips, etc. And, if you’re using the single-page app architecture, page content on unseen pages shouldn’t be loaded until needed either.

You can get an easy performance win by “lazy loading” the components that contain such features and content. This means the code for these components is not included in the initial code bundle sent to a user and is instead loaded on demand.

Example scenario

In this example app, our app displays a component ChatWindow which loads if the user is authenticated.

The details aren’t important, but let’s assume authentication can only be determined at runtime, and that this component is big and bulky. For these reasons, we may want to lazy load it.

App.vue

<template>
  <h3>Chat with friends here</h3>
  <chat-window v-if="auth" />
</template>
<script>
import ChatWindow from "@/components/ChatWindow";

export default {
  components: {
    ChatWindow
  },
  ...
}
</script>

Lazy loading with Vue 3 async component API

Vue 3 has introduced the defineAsyncComponent API which makes it very simple to lazy load a component.

All you need to do is pass a function to the constructor that loads your component. Assuming you’re bundling your code with Webpack or something similar, the easiest way to do this is to use the dynamic import feature (import) which will ensure your component is built into a separate file and loaded only when called upon.

<script>
import { defineAsyncComponent } from "vue";

const ChatWindow = defineAsyncComponent(
  () => import("@/components/ChatWindow")
);

export default {
  components: {
    ChatWindow
  },
  ...
}
</script>

When this app is built, you’ll see any dynamically imported component as a separate file in your build.

File                                 Size

dist/js/chunk-vendors.f11402df.js    82.39 KiB
dist/js/app.ada103fb.js              20.59 KiB
dist/js/ChatWindow.3c1708e4.js       5.47 KiB
dist/css/app.8221c481.css            1.76 KiB
dist/css/ChatWindow.f16731cd.css     2.75 KiB

For more info on how this works, see my previous article Code Splitting With Vue.js And Webpack.

#vue.js #components #web performance #vue #vue 3 #programming

Jack Downson

Jack Downson

1579951364

A Vue JS Modal Component Mobile Friendly

Vue.js modal

Simple to use, highly customizable, mobile-friendly Vue.js 2.0+ modal with SSR support.

Install

npm install vue-js-modal --save

How to use

Include plugin in your main.js file.

import VModal from 'vue-js-modal'

Vue.use(VModal)

/*
By default, the plugin will use "modal" name for the component.
If you need to change it, you can do so by providing "componentName" param.

Example:

Vue.use(VModal, { componentName: "foo-modal" })
...
<foo-modal name="bar"></foo-modal>
*/

Create modal:

<modal name="hello-world">
  hello, world!
</modal>

Call it from anywhere in the app:

methods: {
  show () {
    this.$modal.show('hello-world');
  },
  hide () {
    this.$modal.hide('hello-world');
  }
}

You can easily send data into the modal:

this.$modal.show('hello-world', { foo: 'bar' })

And receive it in beforeOpen event handler:

<modal name="hello-world" @before-open="beforeOpen"/>
methods: {
  beforeOpen (event) {
    console.log(event.params.foo);
  }
}

If you use Bower package manager - you will have to initialize library differently:

Vue.use(window["vue-js-modal"].default);

Dialog

It is a simplified version of the modal, which has most parameters set by default and is pretty useful for quick prototyping, showing alerts or creating mobile-like modals.

To start using <v-dialog/> you must set dialog: true in plugin configuration:

Vue.use(VModal, { dialog: true })

And include it in your project:

<v-dialog/>

Call it (all params except of “text” are optional):

this.$modal.show('dialog', {
  title: 'Alert!',
  text: 'You are too awesome',
  buttons: [
    {
      title: 'Deal with it',
      handler: () => { alert('Woot!') }
    },
    {
      title: '',       // Button title
      default: true,    // Will be triggered by default if 'Enter' pressed.
      handler: () => {} // Button click handler
    },
    {
      title: 'Close'
    }
 ]
})

Vue.js modal

Dynamic Modals

In order to instantiate modals at runtime (for lazy-loading or decluttering templates), it is possible to create modals dynamically.

To start using this feature you must set dynamic: true in plugin configuration:

Vue.use(VModal, { dynamic: true, dynamicDefaults: { clickToClose: false } })

And include the <modals-container/> component it in your project:

<modals-container/>

Alternatively, the modals container can be automatically appended to the document body once the plugin is loaded using injectModalsContainer: true:

Vue.use(VModal, { dynamic: true, injectModalsContainer: true })

Call it (the first argument is the component definition, the second are component properties, the third modal parameters, and the fourth the modal event listeners):

this.$modal.show({
  template: `
    <div>
      <h1>This is created inline</h1>
      <p>{{ text }}</p>
    </div>
  `,
  props: ['text']
}, {
  text: 'This text is passed as a property'
}, {
  height: 'auto'
}, {
  'before-close': (event) => { console.log('this will be called before the modal closes'); }
})

It can also be used with .vue files:

import MyComponent from './MyComponent.vue'

this.$modal.show(MyComponent, {
  text: 'This text is passed as a property'
}, {
  draggable: true
})

Other than defining the name modal parameter, it’s also possible to close dynamic modals emitting a 'close' event:

this.$modal.show({
  template: `
    <div>
      <p>Close using this button:</p>
      <button @click="$emit('close')">Close</button>
    </div>
  `
})

If using the injectModalsContainer flag, the first mounted Vue instance without parents will be treated as the application root. This is only important to keep in mind if more than one root Vue instance is being used, which is unlikely. But if that’s the case, the root to use can be indicated with the root parameter when invoking dynamic modals or modifying this plugin’s rootInstance attribute:

import App from './App.vue'
import VModal from 'vue-js-modal'

const app1 = new Vue({
  el: '#app-1',
  render: h => h(App)
})

const app2 = new Vue({
  el: '#app-2',
  render: h => h(App)
})

VModal.rootInstance = app2

It is possible to set default property values for dynamic modals.

Example:

import VueJsModal from 'plugin'

Vue.use(VueJsModal, {
  dynamic: true,
  dynamicDefaults: {
    foo: 'foo'
  }
})
{
    showDynamicRuntimeModal () {
      this.$modal.show({
        template: `
          <div class="example-modal-content">
            <p>{{ text }}</p>
            <p>Default Property: {{ foo }} - value is "foo"</p>
          </div>
        `,
        props: ['text', 'foo']
      }, {
        text: 'This text is passed as a property'
      })
    },
}

Note: keep in mind that there are some limitations in using dynamic modals. If you need full functionality then use ordinary modal instead.

SSR

Include plugin in your nuxt.config.js file:

module.exports = {
  plugins: ['~plugins/vue-js-modal']
}

And your plugins/vue-js-modal.js will look like:

import Vue from 'vue'
import VModal from 'vue-js-modal/dist/ssr.index'

Vue.use(VModal)

Extracted CSS

There is also an SSR build with CSS file extracted. Take a look in /dist folder.

Properties

Name Required Type Default Description
name true [String, Number] Name of the modal
delay false Number 0 Delay between showing overlay and actual modal box
resizable false Boolean false If true allows resizing the modal window, keeping it in the center of the screen.
adaptive false Boolean false If true, modal box will try to adapt to the window size
draggable false [Boolean, String] false If true, modal box will be draggable.
scrollable false Boolean false If height property is auto and the modal height exceeds window height - you will be able to scroll modal
reset false Boolean false Resets position and size before showing modal
clickToClose false Boolean true If set to false, it will not be possible to close modal by clicking on the background
transition false String Transition name
overlayTransition false String ‘overlay-fade’ Transition name for the background overlay
classes false [String, Array] ‘v–modal’ Classes that will be applied to the actual modal box, if not specified, the default v--modal class will be applied
width false [String, Number] 600 Width in pixels or percents (e.g. 50 or “50px”, “50%”)
height false [String, Number] 300 Height in pixels or percents (e.g. 50 or “50px”, “50%”) or "auto"
minWidth false Number (px) 0 The minimum width to which modal can be resized
minHeight false Number (px) 0 The minimum height to which modal can be resized
maxWidth false Number (px) Infinity The maximum width of the modal (if the value is greater than window width, window width will be used instead
maxHeight false Number (px) Infinity The maximum height of the modal (if the value is greater than window height, window height will be used instead
pivotX false Number (0 - 1.0) 0.5 Horizontal position in %, default is 0.5 (meaning that modal box will be in the middle (50% from left) of the window
pivotY false Number (0 - 1.0) 0.5 Vertical position in %, default is 0.5 (meaning that modal box will be in the middle (50% from top) of the window
root false Vue instance null Root instance to obtain modal container from. This property is only necessary when using dynamic modals with more than one root instance, which is uncommon

Events

Name Description
before-open Emits while modal is still invisible, but was added to the DOM
opened Emits after modal became visible or started transition
before-close Emits before modal is going to be closed. Can be stopped from the event listener calling event.stop() (example: you are creating a text editor, and want to stop closing and ask the user to correct mistakes if the text is not valid)
closed Emits right before modal is destroyed

Example:

<template>
  <modal name="example"
         :width="300"
         :height="300"
         @before-open="beforeOpen"
         @before-close="beforeClose">
    <b>{{time}}</b>
  </modal>
</template>
<script>
export default {
  name: 'ExampleModal',
  data () {
    return {
      time: 0,
      duration: 5000
    }
  },
  methods: {
    beforeOpen (event) {
      console.log(event)
      // Set the opening time of the modal
      this.time = Date.now()
    },
    beforeClose (event) {
      console.log(event)
      // If modal was open less then 5000 ms - prevent closing it
      if (this.time + this.duration < Date.now()) {
        event.stop()
      }
    }
  }
}
</script>

Example with a dynamic modal:

<script>
export default {
  name: 'ExampleModal',
  data () {
    return {
      time: 0,
      duration: 5000
    }
  },
  methods: {
    openModal () {
      this.$modal.show({
        template: `<b>{{time}}</b>`,
        props: ['time']
      }, {
        time: this.time
      }, {
        width: 300,
        height: 300
      }, {
        'before-open': this.beforeOpen,
        'before-close': this.beforeClose
      })
    },
    beforeOpen (event) {
      console.log(event)
      // Set the opening time of the modal
      this.time = Date.now()
    },
    beforeClose (event) {
      console.log(event)
      // If modal was open less then 5000 ms - prevent closing it
      if (this.time + this.duration < Date.now()) {
        event.stop()
      }
    }
  }
}
</script>

This example initializes time variable every time the modal is being opened. And then forbids closing it for the next 5000 ms

Other

Height: “auto”

From v1.2.6 height can be set to “auto”. If you want to be able to scroll modal in case it’s height exceeds window height - you can set flag scrollable="true".

p.s. scrollable will only work with height="auto".

Example:

<modal name="foo" height="auto" :scrollable="true">...</modal>

Auto height:

Scrollable content & auto height:

Close button

If you want to have a Close (x) button in the top-right corner, you can use “top-right” slot for it. There is deliberately no predefined Close button style - you will have to implement/use your own button.

Example:

<template>
  <modal name="foo">

    <div slot="top-right">
      <button @click="$modal.hide('foo')">
        ❌
      </button>
    </div>

    Hello, ☀️!

  </modal>
</template>

Draggable handler

Draggable property can accept not only Boolean but also String parameters. With String value, you can specify a CSS selector to the element which will be a “handler” for dragging.

Example:

<modal name="bar" draggable=".window-header">
  <div class="window-header">DRAG ME HERE</div>
  <div>
     Hello, 🌎!
  </div>
</modal>

Overlay background color

If you want to change overlay background color, you can easily do it using CSS.

For all modals:

.v--modal-overlay {
  background: red;
}

For specific modal:

.v--modal-overlay[data-modal="my_modal_name"] {
  background: transparent;
}

Fullscreen

<modal name="fs" :adaptive="true" width="100%" height="100%">
Dont forget about close button :)
</modal>

Developers

To run an example:

# Clone repo
git clone https://github.com/euvl/vue-js-modal.git

# Run unit tests
npm run unit

# Run linter
npm run lint

# Build main library for client & SSR
cd vue-js-modal
npm install
npm run build

# Build and run demo
cd demo/client_side_rendering
npm install
npm run dev

Unit Testing in Vue 2.0 using Jest

Include the plugin to your <Component>.spec.js.

For example: If you’re using the plugin in your Main component, then you should include the plugin to your Main.spec.js file.

import VModal from 'vue-js-modal'

Vue.use(VModal)

#vue-modal #vue-js #vue-modal-component

Vue 3 Tutorial (for Vue 2 Users)

Vue 3 has not been officially released yet, but the maintainers have released beta versions for us punters to try and provide feedback on.

If you’re wondering what the key features and main changes of Vue 3 are, I’ll highlight them in this article by walking you through the creation of a simple app using Vue 3 beta 9.

I’m going to cover as much new stuff as I can including fragments, teleport, the Composition API, and several more obscure changes. I’ll do my best to explain the rationale for the feature or change as well.


What we’ll build

We’re going to build a simple app with a modal window feature. I chose this because it conveniently allows me to showcase a number of Vue 3 changes.

Here’s what the app looks like in it’s opened and closed states so you can picture in your mind what we’re working on:

Vue 3 app modal

Vue 3 installation and setup

Rather than installing Vue 3 directly, let’s clone the project vue-next-webpack-preview which will give us a minimal Webpack setup including Vue 3.

$ git clone https://github.com/vuejs/vue-next-webpack-preview.git vue3-experiment
$ cd vue3-experiment
$ npm i

Once that’s cloned and the NPM modules are installed, all we need to do is remove the boilerplate files and create a fresh main.js file so we can create our Vue 3 app from scratch.

$ rm -rf src/*
$ touch src/main.js

Now we’ll run the dev server:

$ npm run dev

Creating a new Vue 3 app

Straight off the bat, the way we bootstrap a new Vue app has changed. Rather than using new Vue(), we now need to import the new createApp method.

We then call this method, passing our Vue instance definition object, and assign the return object to a variable app.

Next, we’ll call the mount method on app and pass a CSS selector indicating our mount element, just like we did with the $mount instance method in Vue 2.

src/main.js

import { createApp } from "vue";

const app = createApp({
  // root instance definition
});

app.mount("#app");

Reason for change

With the old API, any global configuration we added (plugins, mixins, prototype properties etc) would permanently mutate global state. For example:

src/main.js

// Affects both instances
Vue.mixin({ ... })

const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })

This really shows up as an issue in unit testing, as it makes it tricky to ensure that each test is isolated from the last.

Under the new API, calling createApp returns a fresh app instance that will not be polluted by any global configuration applied to other instances.

Learn more: Global API change RFC.

#vue.js #components #composition api #design patterns #vue 3 #vue