/**
*
* KissJS stands for **K**eep **I**t **S**imple **S**tupid **J**ava**S**cript
*
* It's a simple javascript library to build web applications.
*
* It includes:
* - out-of-the-box [UI components](../../index.html#ui=start§ion=components)
* - a [powerful datatable](../../index.html#ui=start§ion=datatable)
* - a [calendar view](../../index.html#ui=start§ion=calendar)
* - a [kanban board](../../index.html#ui=start§ion=kanban)
* - a [gallery view](../../index.html#ui=start§ion=gallery)
* - a [timeline view](../../index.html#ui=start§ion=timeline)
* - a chart view
* - a dashboard view
*
* All these components have high performances when the number of records is heavy (> 10 000)
*
* Bonus stuff:
* - **view manager**, if you want to use KissJS not only for its UI Components, but also to build a complete application with multiple views
* - **client router** which works 100% offline (even with file:/// paths)
* - **pubsub** which is at the heart of the components reactivity
* - **NoSQL database wrapper** which allows to work in memory, offline, or online
* - **NoSQL ORM** to manage Models, Collections, Records, and automate the updates when records have relationships
*
* A few recommandations:
* - don't try to explore the API documentation directly: it's boring and uninteresting
* - you'd rather [jump to the guide](../../index.html#ui=landing-page) which contains a few easy step by step tutorials
*
* Here is an overview of what it provides:
*
* <img src="../../resources/doc/KissJS - Overview.png">
*
* @namespace
*/
const kiss = {
$KissJS: "KissJS - Keep It Simple Stupid Javascript",
// Build number
version: 4540,
// Tell isomorphic code we're on the client side
isClient: true,
/**
* Reserved namespaces for kiss modules
*
* @ignore
*/
db: {},
ux: {},
doc: {},
app: {},
acl: {},
ajax: {},
cache: {},
tools: {},
views: {},
theme: {},
global: {},
fields: {},
screen: {},
router: {},
loader: {},
pubsub: {},
plugins: {},
context: {},
session: {},
formula: {},
undoRedo: {},
webfonts: {},
language: {},
directory: {},
selection: {},
templates: {},
websocket: {},
loadingSpinner: {},
// Utility classes/functions that are defined in their own files
lib: {
formula: {},
},
/**
* Add client/server shared methods to a specific kiss module
*
* @param {string} moduleName
* @param {object} methods
*
* @example
* kiss.addToModule("tools", {
* sayHello: function() {
* console.log("Hello")
* },
* sayGoodbye: () => console.log("Goodbye!")
* })
*
* kiss.tools.sayHello() // "Hello"
* kiss.tools.sayGoodbye() // "Goodbye"
*/
addToModule(moduleName, methods) {
// console.log("kiss - Loading module into kiss: " + moduleName)
Object.assign(kiss[moduleName], methods)
},
/**
* KissJS ui layer is the heart and the initial purpose of the library.
* It provides built-in UI components, which consists of:
*
* ### Containers
* - [kiss.ui.Block](kiss.ui.Block.html): basic container
* - [kiss.ui.Panel](kiss.ui.Panel.html): container with a header and other properties that allow to create standard draggable windows and modal windows
* - [kiss.ui.WizardPanel](kiss.ui.WizardPanel.html): panel where items are displayed one at a time (each wizard page) with helper buttons (next, previous) to navigate through the pages
*
* ### Elements
* - [kiss.ui.Button](kiss.ui.Button.html): standard clickable button with an icon
* - [kiss.ui.Dialog](kiss.ui.Dialog.html): modal dialog box
* - [kiss.ui.Html](kiss.ui.Html.html): component to insert html
* - [kiss.ui.Image](kiss.ui.Image.html): component to insert an image
* - [kiss.ui.Menu](kiss.ui.Menu.html): menu of clickable items with icons
* - [kiss.ui.Notification](kiss.ui.Notification.html): notification which disappears automatically (toast message)
* - [kiss.ui.Spacer](kiss.ui.Spacer.html): simple empty element used as a spacer in the layout
* - [kiss.ui.Tip](kiss.ui.Tip.html): tip that follows the mouse cursor
*
* ### Data components
* - [kiss.ui.Datatable](kiss.ui.Datatable.html): powerful datatable
* - [kiss.ui.Calendar](kiss.ui.Calendar.html): simple calendar
* - [kiss.ui.Kanban](kiss.ui.Kanban.html): nice kanban with standard view setup (sort, filter, group, fields)
* - [kiss.ui.Gallery](kiss.ui.Gallery.html): simple gallery view with standard view setup (sort, filter, group, fields)
* - [kiss.ui.Timeline](kiss.ui.Timeline.html): powerful timeline with standard view setup (sort, filter, group, fields) + options like color, period, and more
* - [kiss.ui.ChartView](kiss.ui.ChartView.html): chart view (with setup) embedding a chart component
* - [kiss.ui.Dashboard](kiss.ui.Dashboard.html): dashboard of multiple charts
*
* ### Fields
* - [kiss.ui.Field](kiss.ui.Field.html): text, textarea, number, date or time fields
* - [kiss.ui.Checkbox](kiss.ui.Checkbox.html): checkbox with multiple design options
* - [kiss.ui.Select](kiss.ui.Select.html): highly flexible field to select one or more options into a dropdown list
* - [kiss.ui.Attachment](kiss.ui.Attachment.html): widget to manage file attachments
* - [kiss.ui.Color](kiss.ui.Color.html): field to select a color
* - [kiss.ui.ColorPicker](kiss.ui.ColorPicker.html): widget to pick a color
* - [kiss.ui.Icon](kiss.ui.Icon.html): field to select an icon
* - [kiss.ui.IconPicker](kiss.ui.IconPicker.html): widget to pick an icon
* - [kiss.ui.Slider](kiss.ui.Slider.html): slider widget to select a number value
* - [kiss.ui.Rating](kiss.ui.Rating.html): widget used for ranking and notation
*
* **The ui layer is a work in progress and might change rapidly according to new project requirements.**
*
* @namespace
*/
ui: {},
/**
* KissJS ux is a set of Ui eXtensions that are too specific to be in the core ui library.
* It constantly evolves with new projects and requirements, and currently consists of:
*
* ### Fields
* - [kiss.ux.AiImage](kiss.ux.AiImage.html): an attachment field connected to OpenAI to generate Dall-E images
* - [kiss.ux.AiTextarea](kiss.ux.AiTextarea.html): a paragraph field connected to OpenAI to generate content
* - [kiss.ux.CodeEditor](kiss.ux.CodeEditor.html): a field to write code, embedding the famous Ace Editor
* - [kiss.ux.MapField](kiss.ux.MapField.html): a map with a text field to enter an address or geo coordinates. Uses OpenLayers internally.
* - [kiss.ux.RichTextField](kiss.ux.RichTextField.html): a rich text editor to manage formatted text. Uses Quill internally.
* - [kiss.ux.Directory](kiss.ux.Directory.html): a field to select people from the address book | Used in pickaform project
* - [kiss.ux.Link](kiss.ux.Link.html): a link to connect records together and build relations in a NoSQL context | Used in pickaform project
* - [kiss.ux.SelectViewColumn](kiss.ux.SelectViewColumn.html): dropdown list that allows to select values extracted from a datatable column | Used in pickaform project
* - [kiss.ux.SelectViewColumns](kiss.ux.SelectViewColumns.html): field the allows to select a record in a view, and assign values to multiple fields at once | Used in pickaform project
*
* ### Elements
* - [kiss.ux.Map](kiss.ux.Map.html): a component to display a Map using OpenLayers
* - [kiss.ux.Chart](kiss.ux.Chart.html): a component to display a Chart using Chart.js
* - [kiss.ux.QrCode](kiss.ux.QrCode.html): a component to display a QRCode
*
* @namespace
*/
ux: {},
/**
* KissJS data layer provides a way to manage data locally and to proxy data from a KissJS server (or a REST server). It consists of:
* - [kiss.data.Model](kiss.data.Model.html): to define your models
* - [kiss.data.Collection](kiss.data.Collection.html): to store data according to your models
* - [kiss.data.Record](kiss.data.RecordFactory-Record.html): to manipulate instances
* - [kiss.data.Transaction](kiss.data.Transaction.html): to perform batch updates over multiple collections and multiple records
*
* **The data layer is a work in progress and might change rapidly according to new project requirements.**
*
* @namespace
*/
data: {
/**
* See [Model documentation](kiss.data.Model.html).
*
* @ignore
*/
Model: {},
/**
* See [Collection documentation](kiss.data.Collection.html).
*
* @ignore
*/
Collection: {},
/**
* See [Record documentation](kiss.data.RecordFactory-Record.html).
*
* @ignore
*/
Record: {},
/**
* See [Transaction documentation](kiss.data.Transaction.html).
*
* @ignore
*/
Transaction: {},
/**
* See [trash documentation](kiss.data.trash.html).
*
* @ignore
*/
trash: {},
/**
* Add a method to all models
*
* This method is useful to plug a method to all records after they are already instanciated.
*
* A typical use case is when a plugin needs to add a global feature to all records.
* We can call this method in the plugin initialization.
*
* @param {string} methodName
* @param {function} method
*
* @example
* kiss.data.addMethodToAllModels("getFields", function() {
* return Object.keys(this).join(", ")
* })
*
* console.log(myRecord.getFields()) // "firstName, lastName, birthDate"
*
*/
addMethodToAllModels(methodName, method) {
Object.values(kiss.data.Record).forEach(recordClass => {
recordClass.prototype[methodName] = method
})
},
/**
* Add a property to all models
*
* This method is useful to plug a property to all records after they are already instanciated.
*
* A typical use case is when a plugin needs to add a global feature to all records.
* We can call this method in the plugin initialization.
*
* @param {string} propertyName
* @param {function} getter - The getter function, which receives the instanciated record as input parameter
*
* @example
* // To add a property
* kiss.data.addPropertyToAllModels("WORKFLOW_STEP", function(record) {
* return kiss.global.workflowSteps[record["workflow-stepId"]]
* })
*
* console.log(myRecord.WORKFLOW_STEP) // "Analysis"
*
* // To add a property which has methods
* kiss.data.addPropertyToAllModels("workflow", function(record) {
* return {
* start: function() {
* console.log("STARTING WORKFLOW...")
* },
* vote: function(recordId, stepId, decisionId, comment, actors) {
* console.log("VOTE FOR..." + recordId)
* }
* }
* })
*
* // Usage
* myRecord.workflow.start()
*/
addPropertyToAllModels(propertyName, getter) {
Object.values(kiss.data.Record).forEach(recordClass => {
Object.defineProperty(recordClass.prototype, propertyName, {
get: function () {
try {
return getter(this)
} catch (err) {
log("kiss.data.Model - addPropertyToAllModels", 4, err)
return "Formula error"
}
},
set: function (value) {
try {
this.value = value
//this[propertyName] = value
} catch (err) {
log("kiss.data.Model - addPropertyToAllModels", 4, err)
}
},
configurable: true
})
})
}
},
/**
* Load some .js and .css into the page header.
* This is mainly used in development mode, when the library is not yet bundled and minified
*
* @namespace
*/
loader: {
// List of KissJS core modules
core: {
scripts: [
"modules/dataTrash",
"modules/ajax",
"modules/context",
"modules/session",
"modules/fields",
"modules/acl",
"modules/undoRedo",
"modules/directory",
"modules/pubsub",
"modules/websocket",
"modules/router",
"modules/views",
"modules/theme",
"modules/language",
"modules/plugins",
"modules/selection",
"modules/screen",
"modules/webfonts"
]
},
// List of KissJS db modules
db: {
scripts: [
"db/offline",
"db/memory",
"db/online",
"db/faker"
]
},
// List of KissJS models
models: {
scripts: [
"account",
"apiClient",
"file",
"group",
"link",
"trash",
"user",
"view"
]
},
// List of KissJS ui components
ui: {
scripts: [
// Containers
"containers/block",
"containers/panel",
// Data
"data/datatable",
"data/calendar",
"data/kanban",
"data/gallery",
"data/timeline",
"data/chartview",
"data/dashboard",
// Elements
"elements/spacer",
"elements/html",
"elements/image",
"elements/button",
"elements/menu",
"elements/tip",
"elements/notification",
"elements/dialog",
// Fields
"fields/field",
"fields/checkbox",
"fields/rating",
"fields/slider",
"fields/select",
"fields/icon",
"fields/iconPicker",
"fields/color",
"fields/colorPicker",
"fields/attachment",
// Form
"form/form",
"form/formTabBar",
"form/formActions",
"form/formSideBar",
"form/formContent",
"form/formFeatureDescription",
// Helpers
"helpers/data/dataFilter",
"helpers/data/dataFilterGroup",
"helpers/data/dataFilterWindow",
"helpers/data/dataSort",
"helpers/data/dataSortWindow",
"helpers/data/dataFieldsWindow",
"helpers/data/recordSelectionWindow",
"helpers/files/fileUploadLocal",
"helpers/files/fileUploadLink",
"helpers/files/fileUploadDropbox",
"helpers/files/fileUploadBox",
"helpers/files/fileUploadGoogleDrive",
"helpers/files/fileUploadOneDrive",
"helpers/files/fileUploadInstagram",
"helpers/files/fileUploadTakePhoto",
"helpers/files/fileUploadinstagramSession",
"helpers/files/fileUploadBoxSession",
"helpers/files/fileUploadWebSearch",
"helpers/files/fileUploadWindow",
"helpers/files/filePreviewWindow",
"helpers/files/fileLibraryWindow",
// Views
"views/common/matrix",
"views/authentication/error",
"views/authentication/invite",
"views/authentication/login",
"views/authentication/register",
"views/authentication/resetPassword",
"views/authentication/templates"
],
styles: [
// Base
"styles/base",
"styles/colors/light",
"styles/geometry/default",
"styles/animations/animate",
// Components
"abstract/component",
"abstract/dataComponent",
"containers/block",
"containers/panel",
"data/datatable",
"data/calendar",
"data/kanban",
"data/gallery",
"data/timeline",
"data/chartview",
"data/dashboard",
"elements/button",
"elements/html",
"elements/image",
"elements/menu",
"elements/tip",
"elements/notification",
"elements/dialog",
"fields/field",
"fields/checkbox",
"fields/rating",
"fields/slider",
"fields/select",
"fields/icon",
"fields/iconPicker",
"fields/color",
"fields/colorPicker",
"fields/attachment",
"form/form",
"helpers/data/dataSort",
"helpers/data/dataFilter",
"helpers/data/dataFilterGroup",
"helpers/data/dataFieldsWindow",
"helpers/files/fileUpload",
"helpers/files/filePreviewWindow",
"views/authentication/styles"
]
},
// List of KissJS ux components
ux: {
scripts: [
"aiImage/aiImage",
"aiTextarea/aiTextarea",
"chart/chart",
"codeEditor/codeEditor",
"directory/directory",
"link/link",
"map/map",
"mapField/mapField",
"qrcode/qrcode",
"richTextField/richTextField",
"selectViewColumn/selectViewColumn",
"selectViewColumns/selectViewColumns",
"wizardPanel/wizardPanel"
],
styles: [
"codeEditor/codeEditor",
"directory/directory",
"link/link",
"map/map",
"mapField/mapField",
"richTextField/richTextField"
]
},
/**
* Load a script file asynchronously into the page.
*
* @param {string} path - Provide the path to the javascript file, without the extension .js
* @param {object} [config] - Optional object configuration
* @param {object} [config.options] - Optional object to pass any custom attributes to the script tag, like id, data-*, defer, async...
* @param {string|boolean} [config.autoAddExtension='.js'] - The extension to auto append to the path url. Default is '.js'. Set to false to disable it.
*
* @example
* kiss.loader
* .loadScript("views/common/topbar")
* .then(() => {
* console.log("Javascript file loaded!")
* })
*
* // With options. This is equivalent to:
* // <script async type="text/javascript" src="https://www.dropbox.com/static/api/2/dropins.js" id="dropboxjs" data-app-key="2tkajpbphy1m8dj"></script>
* kiss.loader.loadScript("https://www.dropbox.com/static/api/2/dropins.js", {
* id: "dropboxjs",
* "data-app-key": "2tkajpbphy1m8dj"
* })
*
*/
loadScript(path, config = {}) {
return new Promise(function (resolve, reject) {
const script = document.createElement("script")
script.type = "text/javascript"
script.async = true
const autoAddExtension = (config.autoAddExtension === false) ? "" : ".js"
script.src = path + autoAddExtension + "?build=" + kiss.version
if (config.options) {
Object.keys(config.options).forEach(key => {
script.setAttribute(key, config.options[key])
})
}
const head = document.getElementsByTagName("head")[0]
head.appendChild(script)
script.onload = resolve
script.onerror = reject
})
},
/**
* Load a CSS file asynchronously into the page
*
* @param {string} path - Provide the path to the CSS files, without the extension .css
*
* @example
* kiss.loader
* .loadStyle("views/common/topbar")
* .then(() => {
* console.log("CSS file loaded!")
* })
*/
loadStyle(path) {
return new Promise(function (resolve, reject) {
const style = document.createElement("link")
style.rel = "stylesheet"
style.type = "text/css"
style.async = true
style.href = path + ".css?build=" + kiss.version
const head = document.getElementsByTagName("head")[0]
head.appendChild(style)
style.onload = resolve
style.onerror = reject
})
},
/**
* Load many scripts asynchronously and resolve Promise when all scripts are loaded
*
* @param {string[]} paths - Array of javascript file paths to load, without .js extension
*
* @example
* kiss.loader
* .loadScripts([
* "views/common/topbar",
* "views/common/logo",
* "views/common/buy"
* ]).then(() => {
* console.log("All javascript files are loaded!")
* })
*/
async loadScripts(paths) {
await Promise.allSettled(paths.map(kiss.loader.loadScript))
},
/**
* Load many styles asynchronously and resolve Promise when all styles are loaded
*
* @param {string[]} paths - Array of CSS file paths to load, without .cess extension
*
* @example
* kiss.loader
* .loadStyles([
* "views/common/topbar",
* "views/common/logo",
* "views/common/buy"
* ]).then(() => {
* console.log("All CSS files are loaded!")
* })
*/
async loadStyles(paths) {
await Promise.allSettled(paths.map(kiss.loader.loadStyle))
},
/**
* Load KissJS library dynamically from all its source files.
*
* - In development, KissJS is a collection of javascript and css files that must be loaded separately
* - UI eXtensions (UX) always need to be loaded manually and separately
* - In production, KissJS is bundled and doesn't require to load modules dynamically
*
* Please note that dynamic loading of libraries is a bit tricky and hacky:
* - Because a librariy can depend on another one, they must be loaded in the right order
* - KissJS uses this technic because the native browser module system doesn't support file:// path (and KissJS does)
* - We couldn't use external dependencies (like requirejs, systemjs...) which don't work with file path (file://)
*
* @param {object} config
* @param {object} config.useDb - If false, load the library without the db/data related scripts (default is true)
* @param {object} config.useUx - If false, load the library without the ui extensions (default is true).
* @async
*/
async loadLibrary(config = {}) {
const useDb = (config.useDb === false) ? false : true
const useUx = (config.useUx === false) ? false : true
// Load the main app module
const libraryPath = kiss.loader.getLibraryPath()
await kiss.loader.loadScript(libraryPath + "/core/modules/app")
// Load the base classes first, because some classes inherit from them
await kiss.loader.loadScript(libraryPath + "/ui/abstract/component")
await kiss.loader.loadScript(libraryPath + "/ui/abstract/container")
await kiss.loader.loadScript(libraryPath + "/ui/abstract/dataComponent")
// Load the database wrapper
if (useDb) {
await kiss.loader.loadScript(libraryPath + "/core/db/nedb")
await kiss.loader.loadScript(libraryPath + "/core/db/api")
}
// Load global tools
await kiss.loader.loadScript(libraryPath + "/core/modules/logger")
await kiss.loader.loadScript(libraryPath + "/core/modules/tools")
await kiss.loader.loadScript(libraryPath + "/core/modules/loadingSpinner")
// Shared modules
await kiss.loader.loadScript(libraryPath + "/../common/global")
await kiss.loader.loadScript(libraryPath + "/../common/prototypes")
await kiss.loader.loadScript(libraryPath + "/../common/formula")
await kiss.loader.loadScript(libraryPath + "/../common/formulaParser")
await kiss.loader.loadScript(libraryPath + "/../common/formulaParserOperators")
await kiss.loader.loadScript(libraryPath + "/../common/tools")
if (useDb) {
await kiss.loader.loadScript(libraryPath + "/../common/dataModel")
await kiss.loader.loadScript(libraryPath + "/../common/dataRecord")
await kiss.loader.loadScript(libraryPath + "/../common/dataCollection")
await kiss.loader.loadScript(libraryPath + "/../common/dataRelations")
await kiss.loader.loadScript(libraryPath + "/../common/dataTransaction")
}
// Load everything else
const dbScripts = (useDb) ? kiss.loader.db.scripts.map(path => libraryPath + "/core/" + path) : []
const coreScripts = kiss.loader.core.scripts.map(path => libraryPath + "/core/" + path)
const uiScripts = kiss.loader.ui.scripts.map(path => libraryPath + "/ui/" + path)
const styles = kiss.loader.ui.styles.map(path => libraryPath + "/ui/" + path)
const models = kiss.loader.models.scripts.map(path => libraryPath + "/../common/models/" + path)
try {
await Promise.all([
kiss.loader.loadScripts(dbScripts),
kiss.loader.loadScripts(coreScripts),
kiss.loader.loadScripts(uiScripts),
kiss.loader.loadScripts(models),
kiss.loader.loadStyles(styles)
])
// Load ux scripts and styles
if (useUx) {
const uxScripts = kiss.loader.ux.scripts.map(path => libraryPath + "/ux/" + path)
const uxStyles = kiss.loader.ux.styles.map(path => libraryPath + "/ux/" + path)
await Promise.all([
kiss.loader.loadScripts(uxScripts),
kiss.loader.loadStyles(uxStyles)
])
}
// Immediately init language because every translations depends on it
await kiss.language.init(true)
}
catch (err) {
console.log("kiss.loader - loadLibrary", 4, err)
}
},
/**
* Get the path to the KissJS library, as defined in the script tag.
*
* @returns {string} The path to the KissJS library
*/
getLibraryPath() {
let libraryPath = ""
const scripts = document.getElementsByTagName("script")
for (let script of scripts) {
if (script.src && (script.src.includes("kissjs.min.js") || script.src.includes("kissjs.js"))) {
libraryPath = script.src.substring(0, script.src.lastIndexOf("/"))
break
}
else if (script.src.includes("kiss.js")) {
libraryPath = script.src.substring(0, script.src.lastIndexOf("/core"))
break
}
}
return libraryPath
}
},
/**
* KissJS service worker.
* Mainly used for PWA at the moment.
*
* @namespace
*/
serviceWorker: {
/**
* Default service worker file
*
* @ignore
*/
serviceWorkerFile: "./serviceWorker.js",
/**
* Set the location of the service worker file.
* Returns the service worker so that this method is chainable with the init method.
*
* @param {string} path
* @returns {object} kiss.serviceWorker
*
* @example
* await kiss.serviceWorker.setFile("./myServiceWorkerFile.js").init()
*/
setFile(path) {
if (!path) return
kiss.serviceWorker.serviceWorkerFile = path
return kiss.serviceWorker
},
/**
* Init the service worker
*
* @async
*/
async init() {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register(kiss.serviceWorker.serviceWorkerFile)
log("kiss.serviceWorker - Registered with scope", 1, registration.scope)
}
catch(err) {
log("kiss.serviceWorker - Registration failed", 3, err)
}
}
}
}
}
;
Source