eLabBlog

Aangepaste oplossingen ontwikkelen met eLabNext Add-ons

Door Tigran Sargsyan 7 min lezen 02 apr 2024

AOByte is onlangs een nieuwe samenwerking gestart met eLabNext, een alles-in-één lab management software. eLabNext oplossingen helpen de kwaliteit van het onderzoek te verbeteren door all-round tools te bieden voor elk lab. Vanwege de snelle uitbreiding heeft eLabNext besloten om een SDK uit te brengen, waarmee ontwikkelaars nieuwe add-ons kunnen maken die andere gebruikers kunnen installeren op het eLabNext dashboard. Met add-ons kunnen gebruikers software van derden integreren in dashboards, software zoals Dropbox, Google Drive, enz. Add-ons stellen gebruikers ook in staat om functionaliteit aan hun dashboard toe te voegen zonder te hoeven wachten tot de gewenste functionaliteit door eLabNext wordt vrijgegeven.

Ons bedrijf is er trots op eLabNext te begeleiden op zijn reis. Omdat we een groeiende vraag zien, hebben we besloten om een deel van onze reis te delen. Dit artikel is een goede plek om te beginnen als je geïnteresseerd bent in de ontwikkeling van eLabNext add-on's.

Beginnen met add-onontwikkeling

Om add-onontwikkeling te starten, moet u eerst de ontwikkelaarsmodus inschakelen via instellingen. Navigeer naar Accountinstellingen > Ontwikkelaar. De ontwikkelaarsmodus wordt ingeschakeld door eenvoudigweg de schakelaar om te zetten. In de ingeschakelde ontwikkelaarsmodus zal de SDK proberen een JavaScript-bestand voor een add-on te injecteren vanuit de "Add-on script URL" bij het laden van de pagina. Een enkel JavaScript-bestand wordt tijdens runtime geladen bij het laden van de pagina telkens wanneer u door het eLabNext dashboard bladert.

Laten we nu proberen een eenvoudige add-on te maken. Voordat u begint met coderen, zijn hier twee waardevolle bronnen: eLabNext SDK-documentatie en eLabNext REST API documentatie.

Gebruik het Download-sjabloon op de pagina Ontwikkelaarsinstellingen om een lege add-on te maken. Dit is een werkende voorbeeldtoevoeging, die via een HTTP-server naar keuze kan worden gevoed naar de SDK. Ons team gebruikt een op NodeJS gebaseerde http-server voor ontwikkelingsdoeleinden. De add-on hieronder zorgt voor de eenvoudige taak om de tabel met taken weer te geven in het dashboard. Het stelt gebruikers ook in staat om taken aan te maken en te verwijderen.


/*
@rootVar: SAMPLE_ADDON
@name: Sample
@description: Sample addon
@author: Stepan Smbatyan
@version: 1.0.0
*/
var SAMPLE_ADDON = {};

((context) => {
  context.init = (config) => {
    $(() => {
      context.SampleAddon = new context.SampleAddon(config);
    });
  };

  context.SampleAddon = new Class({
    Implements: [Options, Events],
    Extends: eLabSDK.Base,
    options: {},
    initialize: function (config) {
      // Store a reference to the function's context
      var self = this;
      // Set the options for the application using the provided configuration
      self.setOptions(config);

      $(document).ready(() => {
        const currentPage = Helper.History.get('pageID');

        const pageID = currentPage || new URLSearchParams(window.location.search).get('pageID');

        renderTaskPage();

        if (pageID === 'tasks') {
          getTasks().then(({ data }) => {
            renderTaskTable(data);

            addDeleteBtnListener();
          });
        }
      });
    },
  });

  // #TODO: remove context.init() when upload as add-on to marketplace
  context.init();
})(SAMPLE_ADDON);

// ======================================= DOM =======================================

/**
 * Renders the task list UI by updating the browser history, creating a button and table,
 * filling the table with task data, and updating the main content section with the table container.
 * @param {Event} e - Optional event object. If provided, prevents the default action.
 */
const renderTaskTable = (data) => {
  const button = createAddTaskButton();
  $('#main-content')
  .html('<section id="tableContainer"></section>')
  .prepend(button.render());

  const table = createTaskTable();
  table.data = data;
  table._renderHTML();
};

/**
 * Creates a custom page for tasks using eLabSDK.
 * This function initializes a new CustomPage object with specified configurations.
 * @returns {CustomPage} A CustomPage object representing the task page.
 */
const renderTaskPage = () => {
  return new eLabSDK.CustomPage({
    rootVar: '.nav-main-level',
    pageID: 'tasks',
    mainMenu: 'Tasks',
    subMenu: 'Task list',
  });
};

/**
 * Creates a button element using the eLabSDK.GUI.Button constructor.
 * The button is configured with a label, CSS class,
 * and an action to show a dialog for updating tasks.
 * @returns {eLabSDK.GUI.Button} - A button element configured to add a new task when clicked.
 */
const createAddTaskButton = () => {
  return new eLabSDK.GUI.Button({
    label: 'Add New Task',
    class: 'addNewTaskBtn',
    action: () => showDialog(DIALOG_CONFIGS.CREATE, createTaskAction),
  });
};

const addDeleteBtnListener = () => {
  $('.deleteBtn').on('click', (e) => {
    const id = e.currentTarget.getAttribute('_dataId');

    showDialog(DIALOG_CONFIGS.DELETE, () => deleteTaskAction(id));
  });
};

/**
 * Creates a table element using the Helper.Table.create method.
 * The table is configured with specified target container, data
 * and columns for displaying task information.
 * @returns {HTMLElement} - A table element configured to display task information.
 */
const createTaskTable = () => {
  return Helper.Table.create({
    target: 'tableContainer',
    caption: null,
    data: {},
    columns: [
      {
        name: 'Full Name',
        key: 'fullName',
        width: '20%',
        cellRender: ({ creator }) => `<b>${creator.fullName}</b>`,
      },
      {
        name: 'Title',
        key: 'title',
        width: '20%',
        cellRender: ({ title }) => `<span>${title || '-'}</span>`,
      },
      {
        name: 'Description',
        key: 'contents',
        width: '45%',
        cellRender: ({ contents }) => `<span>${contents || '-'}</span>`,
      },
      {
        name: 'Created',
        key: 'created',
        width: '10%',
        cellRender: ({ created }) => `<span>${created.split('T')[0]}</span>`,
      },
      {
        name: 'Action',
        key: 'actions',
        width: '5%',
        cellRender: ({ taskID }) => `
          <p class='deleteTranslationIcon deleteBtn' _dataId="${taskID}">
            <i class='fa fa-trash-alt _actionIcon' title='Delete translation'></i>
          </p>
        `,
      },
    ],
  });
};

// ======================================= MODAL =======================================

/**
 * Initiates the deletion of a task identified by its taskId asynchronously.
 * Upon successful deletion, closes any open dialogs, reloads the page to reflect the changes.
 * @param {string} taskId - The ID of the task to be deleted.
 * @returns {Promise<void>} - A Promise that resolves after the task deletion and page reload.
 */
const deleteTaskAction = async (taskId) => {
  await deleteTask(taskId);
  Dialog.closeWait();
  window.location.reload();
};

/**
 * Adding a new task with the provided title and description,
 * closing the dialog window, and reloading the current page.
 * @returns {Promise<void>} A promise that resolves once the actions are updated.
 */
const createTaskAction = async () => {
  const title = $('#title').val();
  const description = $('#description').val();

  await addTask({ title, description });
  Dialog.closeWait();
  window.location.reload();
};

/**
 * Displays a dialog window with specified configuration options and a custom button, 
 * calling the provided callback function when the custom button is clicked.
 *
 * @param {Object} config - The configuration object for the dialog window.
 * @param {string} config.title - The title of the dialog window.
 * @param {number} config.width - The width of the dialog window.
 * @param {string} config.btnOk - The label for the OK button.
 * @param {string} config.btnCancelLabel - The label for the Cancel button.
 * @param {string} config.content - The content to be displayed in the dialog window.
 * @param {string} config.customButtonLabel - The label for the custom button.
 * @param {string} config.customButtonStyle - The style for the custom button.
 * @param {Function} callback - The callback function to be called when the custom button is clicked.
 * @returns {void}
 */
const showDialog = (config, callback) => {
  const {
    title,
    width,
    btnOk,
    btnCancelLabel,
    content,
    customButtonLabel,
    customButtonStyle,
  } = config;

  Dialog.show({
    title,
    width,
    btnOk,
    btnCancelLabel,
    content,
    customButtons: [
      {
        label: customButtonLabel,
        style: customButtonStyle,
        fn: callback,
      },
    ],
  });
};

// ======================================= CONSTANTS =======================================

const DIALOG_CONFIGS = {
  DELETE: {
    title: 'Delete Task',
    width: '550',
    btnOk: false,
    btnCancelLabel: 'Close',
    content: '<p>Are you sure you want to delete this task?</p>',
    customButtonLabel: 'Delete Task',
    customButtonStyle: 'background:#fe810',
  },
  CREATE: {
    title: 'Add New Task',
    width: '550',
    btnOk: false,
    btnCancelLabel: 'Close',
    content: `
      <section>
        <input id="title"  type="text" placeholder="Title" />
        <textarea id="description" placeholder="Description" style="padding-top: 8px;"/>
      </section>
    `,
    customButtonLabel: 'Add Task',
    customButtonStyle: 'background:#fe810',
  },
};


// ======================================= API =======================================

/**
 * Retrieves tasks by making a GET request to eLabSDK.
 *
 * @returns {Promise<Array>} A promise that resolves with an array of tasks upon successful retrieval, or rejects with an error response.
 */
const getTasks = () => new Promise((resolve, reject) => {
  new eLabSDK.API.Call({
    method: 'GET',
    path: 'tasks',
    onSuccess: (xhr, status, response) => {
      resolve(response);
    },
    onError: (xhr, status, response) => {
      reject(response);
    },
  }).execute();
});

/**
 * Adds a new task with the provided title and description by making a POST request to eLabSDK.
 *
 * @param {Object} task - An object containing the title and description of the task.
 * @param {string} task.title - The title of the task.
 * @param {string} task.description - The description of the task.
 * @returns {Promise<Object>} A promise that resolves with an array of tasks upon successful retrieval, or rejects with an error response.
 */
const addTask = ({ title, description }) => new Promise((resolve, reject) => {
  const data = {
    assigneeID: 0,
    title,
    contents: description,
  };

  new eLabSDK.API.Call({
    method: 'POST',
    path: 'tasks',
    pathParams: {},
    onSuccess: (xhr, status, response) => {
      resolve(response);
    },
    onError: (xhr, status, response) => {
      reject(response);
    },
  }).execute(data);
});

/**
 * Deletes a task with the specified ID by making a DELETE request to eLabSDK.
 *
 * @param {string} id - The ID of the task to be deleted.
 * @returns {Promise<Object>} A promise that resolves with an array of tasks upon successful retrieval, or rejects with an error response.
 */
const deleteTask = (id) => new Promise((resolve, reject) => {
  new eLabSDK.API.Call({
    method: 'DELETE',
    path: `tasks/${id}`,
    onSuccess: (xhr, status, response) => {
      resolve(response);
    },
    onError: (xhr, status, response) => {
      reject(response);
    },
  }).execute();
});

Een van de cruciale dingen om te onthouden tijdens het maken van een add-on is voorrang geven aan het gebruik van SDK- en API-methoden boven aangepaste code. Een goed voorbeeld hiervan is het renderen van knoppen of het maken van HTTP-verzoeken. Door de SDK-methodes te gebruiken, kunt u er zeker van zijn dat bijvoorbeeld knoppen de juiste styling hebben of dat alle benodigde headers worden toegevoegd aan uw HTTP-verzoek.

Complexere add-onontwikkeling

Uiteraard zullen de meeste add-ons die gemaakt worden ingewikkelder zijn dan dit voorbeeld. Natuurlijk willen ontwikkelaars bij het leveren van complexere functies gebruik maken van de kracht van het opdelen van code in modules, het minimaliseren van code voor productie, het schrijven van testcases voor hun code en het gebruiken van alle andere voordelen van moderne webontwikkeling. Terwijl we aan de add-ons werkten, hebben we een boilerplate add-on gemaakt, waarmee gebruikers een projectstructuur, packaging, testen, enz. kunnen realiseren. Het project kan worden gevonden op GitHub.

Vergeet niet dat de eLabNext SDK in een stroomversnelling zit; de documentatie moet dus worden aangevuld. Neem contact op met ons team als u zich in een situatie bevindt waarbij hulp nodig zou kunnen zijn. Ons team zal verder schrijven over het eLabNext add-on ontwikkelingsproces. We zullen onderwerpen behandelen zoals het indienen van add-ons bij eLab Marketplace, tips en trucs voor de ontwikkeling van eLabNext add-on's, praten over de ontwikkeling van meer ingewikkelde functies, enzovoort.

Aanbevolen Voor jou

4 min lezen 16 mei 2024
Door Ahmed Khalil

Een revolutie in wetenschappelijk onderzoek: De kracht van spraakassistenten in laboratoriumdigitalisering

Ontdek hoe de integratie van spraaktechnologie met elektronische labnotebooks (ELN's) workflows stroomlijnt, fouten minimaliseert en R&D versnelt.

Lees meer
4 min lezen 09 mei 2024
Door Zareh Zurabyan

Uw lab digitaliseren, één monster per keer

Van inventarisatie tot kwaliteitscontrolemaatregelen, leer hoe u uw lab effectief kunt digitaliseren, één monster per keer.

Lees meer
8 min lezen 03 mei 2024
Door eLabNext

Hoe haalt u het meeste uit uw laboratoriumprocedures?

In deze blog verkennen we de essentie van laboratoriumprotocollen en SOP's. Ontdek hoe digitale laboplossingen je kunnen helpen om effectievere labprocedures te schrijven.

Lees meer

Zet vandaag de eerste stap
naar een All Digital Lab!

Plan een persoonlijke demo voor deskundige begeleiding en een gratis evaluatie van uw labworkflow.

nl_NLNL