npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@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>