@tulipnpm/timekit_project_selector
v2.0.6
Published
TimeKit Project Selector
Downloads
15
Readme
TimeKit Project Selector
The TimeKit Project Selector is a library that extends the functionality of TimeKit's Booking Widget. This library allows users to set up selectors based on project metadata, which will filter down a list of TimeKit projects for a customer to book with. The library has a default UI that embeds on an e-commerce site that can either be customized or overwritten.
Installation
<link rel="stylesheet" href="https://cdn.timekit.io/booking-js/v3/booking.min.css" />
<script type="text/javascript" src="//cdn.timekit.io/booking-js/v3/booking.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/@tulipnpm/timekit_project_selector@latest/dist/timekit_project_selector.min.js"></script>
Initialization
To initialize the TimeKit Project Selector on your site, you first need to import all required libraries onto your webpage (see above). After the library is installed, set up is as easy as running a single function:
timekit_project_selector.init({
app_key: <timekit_app_key>,
api_base_url: <timekit_api_url>
}).then(() => {
// Your code here...
});
This will connect to TimeKit's API and load in all the required data. Once the initialization is completed, the exposed methods will be able to be used. If enabled, it will also render the default UI.
Configurations
TimeKit Project Selector has many configuration options:
| Option | Optional? | Default value | Description | |--------|-----------|---------------|-------------| | app_key | No | | Token required to connect to TimeKit API. Can be found at https://admin.timekit.io/a/apps/<app_slug>/apisettings/keys | | api_base_url | Yes | https://api.timekit.io | Timekit API url pointing to production environment. For staging env change it to staging url. | defaultUI | Yes | true | When true, will create the default user interface for the project selector. More details below | | embed | Yes | false | When true, the user interface will not be shown in the widget, it will be placed inside of a specified div. More details below | | includePrivateAppointments | Yes | false | When true, private appointment types will be fetched from the TimeKit API | | region | Yes | | Initial filter applied when getting default list of projects. Any project that does not have the same t_region metadata value will not be shown on initialization | | selectorOptions | No | | See below for details | | widgetImageUrl | Yes | Tulip Appointments Icon | When using the default widget UI, change this value to your desired image URL to replace widget image | | duplicateCustomerCheck | Yes | false | When a new booking is created, a call is made to check for potential duplicate Timekit customers. Requires the following webhook to be configured via Timekit: /api/customers/timekit_webhook_connect_client|
Selector Options
The selector options are how you can set the metadata fields that you want the user to select from. The order that the keys are placed in the object is the order the customer will see the selector options. If you are using the default UI, you will also need to specify the selector option copyright fields for each option. If you are not using the default UI, simply assign the keys to have value true
.
// Example without using default UI
timekit_project_selector.init({
...,
selectorOptions: {
store_project: true,
service_project: true,
}
});
You must use project type as key in the selectorOptions. Ex. service_project for render Global Appointment Type in step, store_appointment_type_project for render Store Appointment Type in step, store_project for render Stores in step
Selector Option Copyright
When using the default UI, you must set the selector display values for each selector option. The copyright is very flexible to allow you to display the data you want on each option. The title and description fields are what we show above the card selectors. For inside the cards, you can customize the card_title, card_body, card_footer. You are also able to supply an optional card_image which will display an image inside the card.
Please note that the description field can only be up to 300 characters long before it gets cut off from the default UI. This is to prevent the header from becoming too large.
Selector Option Strategy (Optional)
This parameter is used for defining the logic of rendering projects. The strategy field is used for rendering either Stores having Appointment Types belonging to them, or Appointment Types having Stores belonging to them. By default, strategy is set to store_project
(Appointment Type > Store)
strategy: 'store_project',
Selector Option Search Bar
In addition to allowing for customization of the text on the widget, the Timekit Project Selector also allows for the addition of a search bar to allow a user to search for certain text inside of a card.
To enable this search bar, you can include the following inside of a Selector Option.
search_bar: {
enabled: true,
placeholder: 'Placeholder text inside of the search bar'
}
By default, the search bar feature is not enabled if this configuration is missing from a selector option. If this configuration is present, then the enabled field is required. The enabled field must either be true
or false
, where true
indicates that the search bar will show up in the default UI, and false
indicates that it will not be present.
The placeholder field is optional and is not required for the search bar. If this field is missing, the placeholder text inside the search bar will default to Please enter your search term
. Otherwise, the text can be replaced following the specifications below.
Note that the search will only happen after at least 3 or more characters are entered.
There are many different ways to display values to the UI using the selector options.
Selector Option Filters (Optional)
For each projects you can apply default filters by meta data. You can add many filters by key value and only projects with default filters will be displayed
filters: {
't_disabled': 0,
't_private': 0
}
Geo Search Bar
Geo search bar is an alternative to the selector option bar and they shouldn't be used together. When this option is specified, the user will be asked for their geolocation in the browser. If the user shares it, the list of the 5 closest stores will be displayed.
geo_search_bar: {
placeholder: 'Search for a city or postal code'
}
Standard Strings
Adding a string such as 'Select a Store' will display that hardcoded string in the place of the placeholder. This can also be used to display custom HTML elements like icons in the UI.
Metadata Strings
Adding a string with the [meta] prefix will display the metadata for the selected option's project. For example, if your project has metadata of meta.address = '123 Main Street'
, when you use [meta]address
, the UI will deploy '123 Main Street'.
Project Strings
Adding a string with the [project] prefix will have the same effect as the meta prefix, but allows you to get any value from the TimeKit Project. Refer to TimeKit Project Docs for available fields.
Replacement Terms
To combine strings with replacement values, wrap the replacement values with curly braces and write the string how you want it to be displayed. For example to add the address and city of a project, you can add the following: 'The address of the store is {{[meta]address}} in {{meta]city}}'.
Here is a sample of combined selectorOptions.
timekit_project_selector.init({
...,
selectorOptions: {
service_project: {
strategy: 'service_project',
card_title: `{{[project]name}}`,
title: 'Select an Appointment Type',
card_image: `{{[project]image_url}}`,
card_body: `{{[project]description}}`,
description: 'Which appointment type would you like?',
card_footer: '<i class="far fa-clock"></i> {{[project]availability.length}}',
filters: {
't_disabled': 0,
't_private': 0
}
},
store_project: {
title: 'Select a Store',
strategy: 'store_project',
card_title: '[meta]t_store_name',
card_footer: '[meta]t_store_phone',
card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
description: 'Choose a store from the following that you will be visiting for your appointment.',
search_bar: {
enabled: true,
placeholder: 'Search for a city or postal code'
},
},
}
});
Default UI
When enabled on initialization of the library the default user interface will be built on the web page as a widget. To disable the default UI from appearing, simply set the defaultUI configuration to false
.
NOTE: If you are using the widget view, you cannot have a div with ID timekit-project-selector-container on your DOM.
timekit_project_selector.init({
app_key: <timekit_app_key>,
defaultUI: true,
});
Embedding UI
With the default UI there are two options: Widget Mode and Embedded Mode. By default, the UI will build a widget. To use the embedded mode, change the embed configuration to true
. This will not build the widget, but will build in interface into a container div with the id timekit-project-selector-container. This allows you control of the positioning, size and style of the interface.
// Requires a div with ID 'timekit-project-selector-container'. This is where the UI will render.
timekit_project_selector.init({
app_key: <timekit_app_key>,
defaultUI: true,
embed: true,
});
Custom UI
If you do not want to use the default UI and want to create your own UI, change the defaultUI configuration to false
. This will not show the widget or embedded UI.
timekit_project_selector.init({
app_key: <timekit_app_key>,
defaultUI: false,
});
Including Private Appointment Types (Optional)
By default widget fetches only public appointment types, in order to change that behavior and fetch the private appointment types, includePrivateAppointments
configuration option can be specified to true
.
timekit_project_selector.init({
app_key: <timekit_app_key>,
...,
includePrivateAppointments: true,
});
In case if you use custom UI and you need to pull both private and public appointment types from TimeKit API, but want to display the private appointment types only when needed, filters can be used:
timekit_project_selector.init({
app_key: <timekit_app_key>,
defaultUI: false,
includePrivateAppointments: true,
selectorOptions: {
service_project: true,
store_project: true,
}
}).then(() => {
// Add this filter whenever you want to surface only public appointment types
globalProjectFilters['t_private'] = 0;
});
Search For Potential Duplicate Timekit Customers (Optional)
By default, the widget does not check for potential duplicate customers when creating a new booking. To check for potential duplicate customers, the duplicateCustomerCheck
configuration option can be set to true
. A webhook must also be configured via the Admin Dashboard in Timekit under API Settings with the following url: https://your_server_url/api/customers/timekit_webhook_connect_client.
timekit_project_selector.init({
app_key: <timekit_app_key>,
...,
duplicateCustomerCheck: true,
});
More information about Timekit's webhooks can be found here
See below for how to use the exposed methods.
Exposed Methods
The TimeKit Project Selector library exposes the methods that are used to create our default user interface. This gives you the tools to build custom interfaces that are branded for your company to have customers select the correct TimeKit project to book with.
init
Asynchronous method that initiate the TimeKit Project Selector.
timekit_project_selector.init({
app_key: <timekit_app_key>,
}).then(() => {
// Enter code here ...
});
getStrategy
Returns a strategy for searching TimeKit projects. Optionally takes a strategy type argument for use custom strategy instead default.
timekit_project_selector.getStrategy();
timekit_project_selector.getStrategy('store_project');
getProjects()
Returns a list of TimeKit projects Optionally takes project type for return projects by project type only.(Filters not using if null) Optionally takes filters for return filtered projects
await timekit_project_selector.getStrategy('store_project').getProjects({ id: 'ef0cee8a-6096-453f-a634-1597b359cdfa'});
getFilters
Returns an object of metadata key to selected value that represents the filter to value applied to the project selector. Optionally takes a key argument.
timekit_project_selector.getFilters();
timekit_project_selector.getFilters('appointment_type');
addFilter
Adds a key-value pair to the filter manager.
timekit_project_selector.addFilter('appointment-type', 'fitting');
removeFilter
Removes a key-value pair from the filter manager. Takes the key of the filter as an argument.
timekit_project_selector.removeFilter('appointment-type');
selectProject
Opens the BookingJS widget for the selected project. Takes a TimeKit Project as an argument. See BookingJS Documentation for additional details.
timekit_project_selector.selectProject(timekitProject);
Supported Use Cases (Default UI)
Global Appointment Type -> Store -> Booking.js
selectorOptions: {
service_project: {
strategy: 'service_project',
card_title: `{{[project]name}}`,
title: 'Select an Appointment Type',
card_image: `{{[project]image_url}}`,
card_body: `{{[project]description}}`,
description: 'Which appointment type would you like?',
card_footer: '<i class="far fa-clock"></i> {{[project]availability.length}}',
filters: {
't_disabled': 0,
't_private': 0
}
},
store_project: {
title: 'Select a Store',
strategy: 'store_project',
card_title: '[meta]t_store_name',
card_footer: '[meta]t_store_phone',
card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
description: 'Choose a store from the following that you will be visiting for your appointment.',
search_bar: {
enabled: true,
placeholder: 'Search for a city or postal code'
},
}
}
Store -> Store Appointment Type -> Booking.js
selectorOptions: {
store_project: {
title: 'Select a Store',
strategy: 'store_project',
card_title: '[meta]t_store_name',
card_footer: '[meta]t_store_phone',
card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
description: 'Choose a store from the following that you will be visiting for your appointment.',
search_bar: {
enabled: true,
placeholder: 'Search for a city or postal code'
},
//strategy: 'store_project'
},
store_appointment_type_project: {
strategy: 'service_project',
card_title: `{{[project]name}}`,
title: 'Select an Appointment Type',
card_image: `{{[project]image_url}}`,
card_body: `{{[project]description}}`,
description: 'Which appointment type would you like?',
card_footer: '<i class="far fa-clock"></i> {{[project]availability.length}}',
filters: {
't_disabled': 0,
't_private': 0
}
}
}
Example Custom UI
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Custom UI - TimeKit Project Selector Example</title>
<!-- Booking.js minified dependency -->
<link rel="stylesheet" href="https://cdn.timekit.io/booking-js/v3/booking.min.css" />
<script type="text/javascript" src="//cdn.timekit.io/booking-js/v3/booking.min.js" defer></script>
<!-- Tulip Project selector -->
<script src="./../dist/timekit_project_selector.min.js"></script>
</head>
<body>
<h1>Custom UI - TimeKit Project Selector Example</h1>
<!-- Placeholder div for TK Projects of t_project_type = service_project -->
<ul class="global_projects"></ul>
<!-- Placeholder div for TK Projects of t_project_type = store_project -->
<ul class="stores"></ul>
<!-- Placeholder div for bookingjs to be injected into -->
<div id="bookingjs"></div>
<script>
$(document).ready(function () {
const tps = timekit_project_selector;
tps.init({
// Insert widget key provided via TK admin panel
app_key: '',
// Set defaultUI to false as we are using a custom UI
defaultUI: false,
// Selector options still prvided but values are set to true
selectorOptions: {
// Step 1: Is to select the service_project
service_project: true,
// Step 2: Is to select the store project
store_project: true,
}
}).then(async () => {
// You could use simple array instead of using the steps factor.
// This factory makes it simple to track which TK project uuids were selected
let stepsFactory = tps.getStepsFactory();
// Fetch the service_project
let globalProjects = await tps.getStrategy('service_project').getProjects({
t_private: 0,
t_disabled: 0
});
// Render out links for each service_project
globalProjects.forEach((globalProject) => {
let link = "<a href=\"#\" class=\"global_appointment\" id=\"" + globalProject.id + "\"> <li>'" + globalProject.name + "'</li></a>";
$('.global_projects').append(link);
})
// Register an event listener on the newly generated link
$("a.global_appointment").click(function (event) {
stepsFactory.nextStep();
const service_project_id = $(this).attr('id');
let storeProjects = await tps.getStrategy('store_project').getProjects({
service_project_id,
t_disabled: 0,
t_private: 0
});
// Render out links for each store_project
storeProjects.forEach((project) => {
let link = "<a href=\"#\" class=\"store_project\" id=\"" + project.id + "\"> <li>'" + project.name + "'</li></a>";
$('.stores').append(link);
})
// Register an event listener on the newly generated store link
$("a.store_project").click(function (event) {
stepsFactory.nextStep();
const store_project_id = $(this).attr('id');
const project = await tps.getStrategy().getProjects({
store_project_id,
service_project_id
});
// Render Booking.js
tps.selectProject(project);
});
});
}).catch((error) => {
console.log(error.message);
});
});
</script>
</body>
</html>