Installation
For releases, we use Semantic Versioning and tags. You can find all available versions and tags on the npm page.
In the sections below, VERSION is a placeholder for the version number or tag:
- latest tag is used by default and contains the latest stable version.
- beta tag can contain pre-released features.
- 2.148.0 is an example of an exact version.
Since the vviinn widgets are developed as Web Components, the installation steps differ depending on your environment:
Frameworkless (JavaScript)
Section titled “Frameworkless (JavaScript)”Install using CDN
Section titled “Install using CDN”Paste the scripts into the head section of your page. The script with the nomodule attribute is only needed to support browsers without the JavaScript modules feature.
<script type="module" src="https://cdn.jsdelivr.net/npm/vviinn-widgets@VERSION/www/build/vviinn-widgets.esm.js"></script><script nomodule src="https://cdn.jsdelivr.net/npm/vviinn-widgets@VERSION/www/build/vviinn-widgets.js"></script>Then you will be able to use the VPR or VPS widgets.
Recommendation widget
Section titled “Recommendation widget”To use the recommendation widget, insert the snippet below where you want to render the widget. Only the token and product-id attributes are required, but you can tweak the widget with other attributes.
<vviinn-vpr-widget token="YOUR_READ_ONLY_TOKEN" product-id="YOUR_PRODUCT_ID"></vviinn-vpr-widget>It’s also possible to create the component instance with the DOM API:
const component = document.createElement("vviinn-vpr-widget");component.token = "YOUR_READ_ONLY_TOKEN";component.productId = "YOUR_PRODUCT_ID";
document.body.append(component);Search widget
Section titled “Search widget”To use the search widget, first insert the snippet below anywhere on the page. The widget is implemented as a modal window, so you need to trigger it.
<vviinn-vps-widget token="YOUR_READ_ONLY_TOKEN"></vviinn-vps-widget>You can also create a component instance with the DOM API:
const component = document.createElement("vviinn-vps-widget");component.token = "YOUR_READ_ONLY_TOKEN";
document.body.append(component);Install using NPM
Section titled “Install using NPM”To use the widgets as ES6 modules, install them from npm:
npm install vviinn-widgets@VERSIONThen add them to your JavaScript code:
import { defineCustomElements } from "vviinn-widgets/dist/loader";defineCustomElements();After that, you can use both the Recommendation and the Search widgets in an HTML template or with JavaScript.
The easiest way to include the component library is to call defineCustomElements() once, from your app’s entry point or a module it imports.
JavaScript example
Section titled “JavaScript example”In this example, we create an importVviinn.js file, import it in App.jsx, and then use the vviinn widgets as shown below:
importVviinn.js
import { defineCustomElements } from "vviinn-widgets/dist/loader";
defineCustomElements();App.jsx
import "./importVviinn";
function App() { return ( <div className="App"> <vviinn-vps-button token="YOUR_READ_ONLY_TOKEN"></vviinn-vps-button> </div> );}
export default App;TypeScript example
Section titled “TypeScript example”In this example, we create an importVviinn.ts file, import it in App.tsx, and then use the vviinn widgets as shown below:
importVviinn.ts
import { defineCustomElements } from "vviinn-widgets/dist/loader";import type { JSX as LocalJSX } from "vviinn-widgets/dist/loader";import type { HTMLAttributes } from "react";
type StencilToReact<T> = { [P in keyof T]?: T[P] & Omit<HTMLAttributes<Element>, "className"> & { class?: string; };};
declare module "react" { namespace JSX { interface IntrinsicElements extends StencilToReact<LocalJSX.IntrinsicElements> {} }}
defineCustomElements(window);App.tsx
import "./importVviinn";
function App() { return ( <div className="App"> <vviinn-vps-button token="YOUR_READ_ONLY_TOKEN"></vviinn-vps-button> </div> );}
export default App;Troubleshooting
Section titled “Troubleshooting”Sometimes other libraries define custom elements, and there can be a conflict. In this case, you can integrate the vviinn widgets as shown below.
useScript.ts
Section titled “useScript.ts”With this custom hook, we add the related scripts to the page:
import { useEffect } from "react";
export const useScript = ( url: string, type: string = "", noModule: boolean = false) => { useEffect(() => { const script = document.createElement("script");
script.src = url; script.async = true; script.defer = true; script.type = type; script.noModule = noModule;
document.body.appendChild(script);
return () => { document.body.removeChild(script); }; }, [url]);};VviinnWidgets.tsx
Section titled “VviinnWidgets.tsx”In this file, we define a wrapper for the vviinn widgets and then render the widgets. CDN_URL (the CDN build folder, e.g. https://cdn.jsdelivr.net/npm/vviinn-widgets@VERSION/www/build/) and isProduction come from your own project configuration:
import type { FC } from "react";import { useScript } from "./useScript";
type Tag = React.DetailedHTMLProps< React.HTMLAttributes<HTMLElement>, HTMLElement>;
type ElementType = "vviinn-vps-button" | "vviinn-vpr-button";
declare module "react" { namespace JSX { interface IntrinsicElements { "vviinn-vpr-button": VviinnTag & { "product-id": string }; "vviinn-vps-button": VviinnTag; } }}
type VviinnWidgetProps = { type: ElementType; token: string; productId?: string;};
type ApiPath = | "https://api.vviinn.com" | "https://api.stage.vviinn.com/";
type VviinnTag = Tag & { token: string; "api-path": ApiPath;};
export const VviinnWidget: FC<VviinnWidgetProps> = ({ type, token, productId,}) => { useScript(`${CDN_URL}vviinn-widgets.esm.js`, "module"); useScript(`${CDN_URL}vviinn-widgets.js`, "", true);
const apiPath: ApiPath = isProduction ? "https://api.vviinn.com" : "https://api.stage.vviinn.com/";
const generalVviinnTagProps: VviinnTag = { token, "api-path": apiPath };
return ( <> {type === "vviinn-vpr-button" && productId && ( <vviinn-vpr-button {...generalVviinnTagProps} product-id={productId} add-style="false" ></vviinn-vpr-button> )}
{type === "vviinn-vps-button" && ( <vviinn-vps-button {...generalVviinnTagProps}></vviinn-vps-button> )} </> );};Following the steps above enables your web components to be used in React. However, there are some additional complexities that must also be considered. Custom Elements Everywhere contains a synopsis of the current issues.
For more information, check the Stencil documentation.
Angular
Section titled “Angular”Using a Stencil-built web component collection within an Angular CLI project is a two-step process. You need to:
- Include the
CUSTOM_ELEMENTS_SCHEMAin the modules that use the components. - Call
defineCustomElements()frommain.ts(or some other appropriate place).
Including the custom elements schema
Section titled “Including the custom elements schema”Including the CUSTOM_ELEMENTS_SCHEMA in the module allows the use of the web components in the HTML markup without the compiler producing errors. This code should be added to the AppModule and every other module that uses your custom elements. Here is an example of adding it to AppModule:
import { BrowserModule } from "@angular/platform-browser";import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core";import { FormsModule } from "@angular/forms";
import { AppComponent } from "./app.component";
@NgModule({ declarations: [AppComponent], imports: [BrowserModule, FormsModule], bootstrap: [AppComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA],})export class AppModule {}The CUSTOM_ELEMENTS_SCHEMA needs to be included in any module that uses custom elements.
Calling defineCustomElements
Section titled “Calling defineCustomElements”A component collection built with Stencil includes a main function that is used to load the components in the collection. That function is called defineCustomElements(), and it needs to be called once during the bootstrapping of your application. One convenient place to do this is in main.ts:
import { enableProdMode } from "@angular/core";import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule } from "./app/app.module";import { environment } from "./environments/environment";
import { defineCustomElements } from "vviinn-widgets/dist/loader";
if (environment.production) { enableProdMode();}
platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.log(err));defineCustomElements();Standalone components
Section titled “Standalone components”Projects created with a recent Angular CLI use standalone components and have no AppModule. In that case, add CUSTOM_ELEMENTS_SCHEMA to the schemas of each component that uses the vviinn widgets, and call defineCustomElements() in main.ts after bootstrapApplication():
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
@Component({ selector: "app-root", templateUrl: "./app.html", schemas: [CUSTOM_ELEMENTS_SCHEMA],})export class App {}main.ts
import { bootstrapApplication } from "@angular/platform-browser";import { appConfig } from "./app/app.config";import { App } from "./app/app";
import { defineCustomElements } from "vviinn-widgets/dist/loader";
bootstrapApplication(App, appConfig) .catch((err) => console.error(err));defineCustomElements();For more information, check the Stencil documentation.
To use the custom element library within a Vue app, the application must be modified to define the custom elements and to tell the Vue compiler which elements to ignore during compilation. This can all be done within the main.js/main.ts file.
In the following example, we import defineCustomElements from vviinn-widgets/dist/loader and call it to register the vviinn widgets. The rest is related to Vue itself.
main.js/main.ts
import "./assets/main.css";
import { createApp } from "vue";import App from "./App.vue";import router from "./router";
import { defineCustomElements } from "vviinn-widgets/dist/loader";
defineCustomElements();
const app = createApp(App);
app.use(router);
app.mount("#app");In a Vue component:
<script setup lang="ts"></script>
<template> <vviinn-vps-button token="YOUR_READ_ONLY_TOKEN"></vviinn-vps-button></template>
<style scoped></style>[Vue warn]: Failed to resolve component: …
Section titled “[Vue warn]: Failed to resolve component: …”If you see the following Vue warning in the browser console:
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
You can fix it by adding the following code to the vite.config.js/vite.config.ts file:
export default defineConfig({ plugins: [ ... vue({ template: { compilerOptions: { isCustomElement: (tag) => { return tag.startsWith("vviinn-"); }, }, }, }), ... ], ...});Troubleshooting
Section titled “Troubleshooting”Sometimes other libraries define custom elements, and there can be a conflict. In this case, you can integrate the vviinn widgets as explained in the Frameworkless section.
For more information, check the Stencil documentation.
In this section, you can learn how to use the vviinn widgets in your Nuxt app.
Install using CDN
Section titled “Install using CDN”Using this way, you need to add the vviinn scripts to the head section with useHead to be able to use the vviinn widgets, as shown below:
<script setup>useHead({ script: [ { src: "https://cdn.jsdelivr.net/npm/vviinn-widgets/www/build/vviinn-widgets.esm.js", type: "module", }, ],});</script>
<template> <div> <vviinn-vps-button token="YOUR_READ_ONLY_TOKEN" ></vviinn-vps-button> </div></template>Install using NPM
Section titled “Install using NPM”To use the widgets as a module, install the vviinn widgets from npm.
Then create a client component in Nuxt and import the vviinn widgets as shown below:
<script>import { defineCustomElements } from "vviinn-widgets/dist/loader";
defineCustomElements();</script>
<template> <div> <vviinn-vps-button token="YOUR_READ_ONLY_TOKEN" ></vviinn-vps-button> </div></template>The final step is to use this client component.
Fix [Vue warn]: Failed to resolve component: vviinn-…
Section titled “Fix [Vue warn]: Failed to resolve component: vviinn-…”To fix the “Failed to resolve component” warning, add the following code to the nuxt.config.js file:
export default defineNuxtConfig({ ..., vue: { compilerOptions: { isCustomElement: (tag) => { return tag.startsWith("vviinn-"); }, }, }, ...});Note: This section has been prepared based on Nuxt v3.13.
Mobile app (WebView)
Section titled “Mobile app (WebView)”In mobile app development, a WebView is an essential component that allows you to display web content within your native Android or iOS app. This section shows you how to integrate the vviinn widgets as a web component in a mobile app.
To learn more about the WebView component, check the official documentation for Android and iOS. If you develop mobile apps with cross-platform technologies, there are popular libraries like React Native WebView, Flutter WebView, Ionic WebView and others that you can use based on your technology stack.
Integration
Section titled “Integration”The first step of integrating the vviinn widgets is to create some HTML files and host them on an HTTPS server. To learn how to integrate the vviinn widgets as a web component in a web page, see the Frameworkless section.
Now you have a URL which, when opened in a browser, shows the vviinn widgets. Recommendation widgets can be passed (as HTML) to WebViews on the fly. The search widget requires HTTPS to be able to show the camera, so in this case we highly recommend hosting the HTML file on a server.
After that, integrate the WebView component into your mobile app. Here are some requirements to consider:
- Enable JavaScript in the
WebViewcomponent. - Set JavaScript mode to unrestricted.
- Add listeners for the vviinn widget events in the
WebViewcomponent. You can find the list of events in the Events section. - Load the
WebViewwith the URL or pass the HTML to it. - Give camera permission to the app.
Permissions
Section titled “Permissions”Android
Section titled “Android”On Android, add these permissions to your AndroidManifest.xml file to be able to use the camera for taking images and videos:
<uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.CAMERA" /><uses-permission android:name="android.permission.RECORD_AUDIO" /><uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /><uses-permission android:name="android.permission.VIDEO_CAPTURE" /><uses-permission android:name="android.permission.AUDIO_CAPTURE" />On iOS, add the following properties to your Info.plist file to be able to use the camera for taking images and videos:
<key>NSMicrophoneUsageDescription</key><string>Flutter requires access to microphone.</string><key>NSCameraUsageDescription</key><string>Flutter requires access to camera.</string>Flutter integration example
Section titled “Flutter integration example”Here is an example of how you can do this in Flutter:
import 'package:flutter/material.dart';import 'package:webview_flutter/webview_flutter.dart';import 'package:permission_handler/permission_handler.dart';
Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Permission.camera.request(); await Permission.microphone.request();
runApp(const MyApp());}
class MyApp extends StatelessWidget { const MyApp({super.key});
@override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); }}
class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title});
final String title;
@override State<MyHomePage> createState() => _MyHomePageState();}
class _MyHomePageState extends State<MyHomePage> { late WebViewController controller;
@override void initState() { super.initState(); controller = WebViewController(onPermissionRequest: (request) => request.grant()) ..enableZoom(true) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..addJavaScriptChannel( 'vviinnProductClick', onMessageReceived: (JavaScriptMessage message) { debugPrint("vviinnProductClick \"${message.message}\""); }, ) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (String url) { controller.runJavaScript('document.addEventListener("vviinnProductClick", ({ detail }) => {vviinnProductClick.postMessage(detail.productId);});'); }, )) ..loadRequest( Uri.parse('URL_TO_YOUR_HTML_FILE'), ); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, title: const Text("test"), ), body: WebViewWidget( controller: controller, ), ); }}In this example, we also ask for microphone permission, but only the camera permission is required.
We also use two packages, webview_flutter and permission_handler, to be able to use WebView and ask for camera permission. However, you can use any package that suits your needs.
To redirect to the product details page in your mobile app, listen to the vviinnProductClick event and redirect to the product details page based on the product ID. To prevent the default redirection, set the product-details-redirect property to false in your widget component.