App Data Setup

December 11, 2024

Files

js/
  components/
    app-data-component.js
  libs/
    data-server.js
  utils/
    storage-util.js
  servers.js
index.js

Source Code

index.js

{
  urls: [
    "js/libs/data-server.js",
    "js/utils/storage-util.js",
  ],
},
{
  urls: [
    "js/components/app-data-component.js",
  ],
  callback: async function() {
    // setup db
    {
      let dbName = "app-db1";
      let dbVersion = 1;

      await storageUtil.Init(dbName, dbVersion);
    }
  },
},

js/components/app-data-component.js

let compoAppData = (function() {
  
  // # self  
  let SELF = {
    GetActiveWorkspaceId,
    Init_,
    Configure,
    ChangeWorkspace_,
    Add,
    Delete_,
    Rename,
    SetActiveWorkspaceId,
    ClearData_,
    Save_,
    server: {
      RemoveItem: (id) => server.RemoveItem(id),
      // AddItem: (item) => server.AddItem(item),
      GetAll: () => server.GetAll(),
      GetById: (id) => server.GetItem(id),
      GetIndex: (id) => server.GetIndex(id),
      Clear: () => server.Clear()
    },
    GetStoreData_,
  };
  
  let data = {
    activeId: -1,
    items: [],
  };
  
  // # local
  let local = {
    storageName: 'app-profiles',
    serversConfig: { },
    storeKeyPrefixes: { }
  };
  
  let server = new DataServer({
    dataItems: data.items,
    adaptor: {
      lookupKey: 'id',
      GetItem: (item, value) => item.id == value,
    }
  });
  
  // # function
  
  function Configure(serversConfig) {
    for (let key in serversConfig) {
      local.storeKeyPrefixes[key] = '';
    }

    local.serversConfig = serversConfig;
  }

  async function GetStoreData_() {
    let workspaceMap = await storageUtil.getItem_(local.storageName);
    let entries = [];
    
    for (let workspace of workspaceMap.items) {
      let {id} = workspace;
      
      for (let key in local.storeKeyPrefixes) {
        let storeKey = `${key}-${id}`;
        let value = await storageUtil.getItem_(storeKey);
        
        if (value === null) continue;
        entries.push({
          key: storeKey,
          value,
        });
      }
    }
    
    
    entries.push({
      key: local.storageName,
      value: workspaceMap,
    })
    
    return {
      entries,
    };
  }
  
  // # delete
  async function Delete_(id) {
    let item = server.GetItem(id);
    
    server.RemoveItem(id);
    let storeKeys = getStoreKeys(id);
    
    for (let key in storeKeys) {
      let storeKey = storeKeys[key];
      await storageUtil.removeItem_(storeKey);
    }
  }
  
  // # change, # restore
  async function ChangeWorkspace_(id) {
    SetActiveWorkspaceId(id);
    
    let item = server.GetItem(id);
    let storeKeys = getStoreKeys(item.id);
    
    for (let key in local.serversConfig) {
      let serverObj = local.serversConfig[key];
      await serverObj.ChangeWorkspace_(storeKeys[key]);
    }

  }
  
  function getStoreKeys(id) {
    let storekeys = {};
    
    for (let key in local.storeKeyPrefixes) {
      let value = `${key}-${id}`;
      storekeys[key] = value;
    }
    
    return storekeys;
  }
  
  function SetActiveWorkspaceId(id) {
    data.activeId = id;
  } 
  
  function Rename(id, name) {
    let item = server.GetItem(id);
    item.name = name;
  }
  
  function GetActiveWorkspaceId() {
    return data.activeId;
  }
      
  // # add, # create
  function Add({name}) {
    let id = Date.now();
    let item = {
      name,
      id,
    };
    
    data.items.push(item);
    
    return id;
  }
  
  async function ClearData_() {
    await storageUtil.removeItem_(local.storageName);
  }
  
  // # init
  async function Init_() {
    await restoreData_();
    
    if (data.items.length == 0) {
      let id = await Add({
        name: 'Default',
      });
      SetActiveWorkspaceId(id);
      await Save_();
    }
    
    await ChangeWorkspace_(data.activeId);
  }
  
  async function Save_() {
    await storageUtil.setItem_(local.storageName, data);
  }
  
  // # restore
  async function restoreData_() {
    let storeData = await storageUtil.getItem_(local.storageName);
    
    if (!storeData) return;
    
    data = storeData;
    server.SetDataItems(data.items);
  }
  
  
  return SELF;
  
})();

js/libs/data-server.js

function DataServer(config = {
    lookupKey: 'id',
}) {

  let SELF = {
      GetAll,
      GetItem,
      AddItem,
      UpdateItem,
      GetIndex,
      Clear,
      RemoveItem,
      SetDataItems,
  };
  
  function SetDataItems(dataItems) {
    config.dataItems = dataItems;
  }

  function AddItem(itemObj) {
      let index = GetIndex(itemObj[config.lookupKey]);
      if (index >= 0) return false;
      
      config.dataItems.push(itemObj);
      return true;
  }
  
  function UpdateItem(value, itemObj) {
      let itemIndex = GetIndex(value);
      if (itemIndex < 0) return false;
      
      config.dataItems[itemIndex] = itemObj;
      return true;
  }

  function Clear() {
      config.dataItems.length = 0;
  }
  
  function GetIndex(value) {
      let items = GetAll();
      let lookupCallback = config.adaptor?.GetItem ?? lookup;
      return items.findIndex(item => lookupCallback(item, value));
  }
  
  function GetAll() {
      return config.dataItems;
  }
  
  function GetItem(value) {
      let lookupCallback = config.adaptor?.GetItem ?? lookup;
      let itemObj = config.dataItems.find(item => lookupCallback(item, value));
      if (itemObj !== undefined) return itemObj;
  
      return null;
  }

  function lookup(item, value) {
      return item[config.lookupKey] == value;
  }
  
  function RemoveItem(value) {
      let itemIndex = GetIndex(value);
      if (itemIndex < 0) return null;
      
      let item = config.dataItems.splice(itemIndex, 1);
      return item;
  }

  return SELF;

}

js/utils/storage-util.js

let storageUtil = (function() {

  let db = null;
  let dbVersion = 1;
  let storeName = 'public';
  let dbName = '';

  let SELF = {
    Init,
    openDb_,
    setItem_,
    getItem_,
    removeItem_,
    clearStore_,
  };

  // # init
  function Init(_dbName, _dbVersion) {
    dbName = _dbName;
    dbVersion = _dbVersion;
  }
  
  // # Clear all items from a specific object store
  async function clearStore_() {
    return new Promise(async (resolve, reject) => {
      await openDb_(); // Ensure the database is open
      const transaction = db.transaction([storeName], 'readwrite');
      const store = transaction.objectStore(storeName);
      const request = store.clear();
  
      request.onsuccess = () => resolve(true);
      request.onerror = (event) => reject('Error clearing store: ' + event.target.errorCode);
    });
  }

  // # Open IndexedDB
  async function openDb_() {
    return new Promise((resolve, reject) => {
      if (db) resolve(db);

      const request = indexedDB.open(dbName, dbVersion);
      request.onerror = (event) => reject('Error opening database: ' + event.target.errorCode);
      request.onsuccess = (event) => {
        db = event.target.result;
        resolve(db);
      };
      request.onupgradeneeded = (event) => {
        db = event.target.result;
        // Create object stores if they don't exist
        if (!db.objectStoreNames.contains(storeName)) {
          db.createObjectStore(storeName, { keyPath: 'key' });
        }
      };
    });
  }

  // # Set Item in IndexedDB
  async function setItem_(key, value) {
    return new Promise(async (resolve, reject) => {
      await openDb_();
      const transaction = db.transaction([storeName], 'readwrite');
      const store = transaction.objectStore(storeName);
      const request = store.put({ key, value });

      request.onsuccess = () => {
        resolve(true);
      };
      request.onerror = (event) => reject('Error storing item: ' + event.target.errorCode);
    });
  }

  // # Get Item from IndexedDB or fallback to localStorage
  async function getItem_(key) {
    return new Promise(async (resolve, reject) => {
      await openDb_();
      const transaction = db.transaction([storeName], 'readonly');
      const store = transaction.objectStore(storeName);
      const request = store.get(key);

      request.onsuccess = (event) => {
        const result = event.target.result;
        if (result) {
          resolve(result.value);
        } else {
          resolve(null); // return null if not found
        }
      };
      request.onerror = (event) => reject('Error retrieving item: ' + event.target.errorCode);
    });
  }

  // # Remove Item from IndexedDB and localStorage
  async function removeItem_(key) {
    return new Promise(async (resolve, reject) => {
      await openDb_();
      const transaction = db.transaction([storeName], 'readwrite');
      const store = transaction.objectStore(storeName);
      const request = store.delete(key);

      request.onsuccess = () => {
        resolve(true);
      };
      request.onerror = (event) => reject('Error removing item: ' + event.target.errorCode);
    });
  }

  return SELF;

})();

Comments

Thank You

for your visit