Class

Collection

kiss.data.Collection(config)

Represents a Collection of records.

A Collection is an interface to manipulate and cache a collection of records. To see how a Collection relates to models, fields and records, please refer to the Model documentation.

Each Model has an associated default Collection to hold its records:

  • this default Collection is instantiated (but not loaded) at the same time as the Model.
  • it means that you can always access the records of a Model, even if you didn't explicity created a Collection for it.
  • this default collection is accessible with: kiss.app.collections[modelId], or simply app.collections.modelId
let myCarCollection = kiss.app.collections["car"]

Below is a table that shows the global flow between KissJS client and KissJS server, and the chain of methods used.

Here is the flow:

  • kiss.data.Collection or kiss.data.Record calls kiss.db
  • kiss.db points to the right db api: kiss.db.memory or kiss.db.offline or kiss.db.online
  • if online, kiss.db perform an HTTP request using kiss.ajax.request
  • the KissJS server receives the request
  • the request is processed by the server Controller
  • the Controller calls MongoDb api with the native MongoDb driver
  • the Controller pass the response back to kiss.ajax.request
  • if a database mutation has occured (CUD operations), the server Controller sends a WebSocket message to the connected clients
  • each Collection intercepts the WebSocket message and update its records accordingly
  • each data Component (field, datatable...) intercepts the message and updates its UI accordingly

Currently, database mutations must be performed at the Record level.

kiss.data.Collection kiss.db HTTP KissJS server - Node.controller MongoDb database
find() find GET /modelId find find({})
find(query) find(modelId, query) POST /modelId :body=query findAndSort(query) find(query.filter).sort(query.sort)
findOne(recordId) findOne(modelId, recordId) GET /modelId/recordId findOne collection.findOne
insertOne(modelId, record) POST /modelId :body=record insertOne insertOne
insertMany(modelId, records) POST /modelId :body=records insertMany insertMany
updateOne(modelId, recordId, update) PATCH /modelId/recordId :body=update updateOne updateOne
updateMany(modelId, query, updates) PATCH /modelId/ :body=query+updates updateMany updateMany
updateBulk(modelId, updates) PATCH /modelId/ :body=updates updateBulk updateBulk
deleteOne(modelId, recordId) DELETE /modelId/recordId delete delete (performs a soft delete)

Technical notes about performances:

  • KissJS collections don't contain raw data, but actual record's instances
  • this allows to use virtual (computed) fields as normal fields, and even perform aggregations on those fields
  • the process of instanciating a record takes a linear time which is about 0.0004ms per field for an Intel core i7-4790K
  • for example: it take 24ms to load 5000 records with 12 fields, or 480ms to load 50000 records with 24 fields
Constructor

# new Collection(config)

Parameters:
Name Type Attributes Description
config object

Collection configuration

mode string <optional>

"memory" | "offline" | "online"

id string <optional>
model object

The Model used to build the collection

records Array.<object> <optional>

Records to init the collection: [{...}, {...}, ...]

sort object <optional>

default sort

filter object <optional>

default filter

projection object <optional>

default projection

group object <optional>

default grouping

groupUnwind boolean <optional>

Unwind allow records belonging to multiple groups to appear as multiple entries

showLoadingSpinner boolean <optional>

if false, doesn't show the loading spinner while retrieving data (default = true)

View Source common/dataCollection.js, line 91

Example
// Register a new collection
let userCollection = new kiss.data.Collection({model: modelUser})

// Get collection records
let users = await userCollection.find()

// Create a new model and use its auto-generated collection
let taskModel = new kiss.data.Model({
 id: "YOUR_MODEL_ID",
 name: "task",
 namePlural: "tasks",
 items: [
     {id: "name", label: "Name", type: "text"},
     {id: "duedate", label: "Due date", type: "date"},
     {id: "done", label: "Done?", type: "checkbox"} // = Checkbox implicit type is "boolean"
 ]
})

// Create a new Record
let newTask = taskModel.create({name: "Task 1", duedate: "2021-03-30", done: false})
await newTask.save()

// Get the default collection for this model, then get the records
let tasksCollection = kiss.app.collections["YOUR_MODEL_ID"]
let tasks = await tasksCollection.find()
console.log(tasks)

Methods

# addHook(hookType, callback)

Add a hook to perform an action before or after a mutation occurs (insert, update, delete)

Parameters:
Name Type Description
hookType string

"beforeInsert" | "beforeUpdate" | "beforeDelete" | "afterInsert" | "afterUpdate" | "afterDelete"

callback function

Function to execute. It receives the following parameters: *insert(record), *update(recordId, update), *delete(recordId)

View Source common/dataCollection.js, line 249

this

Example
// It's possible to add a hook to observe a mutation
tasksCollection.addHook("beforeInsert", function(record) {
 console.log("The following record will be inserted:")
 console.log(record)
})

// It's possible to add multiple hooks to the same mutation
tasksCollection.addHook("beforeInsert", function(record) {
 console.log("Another function executed for the same mutation!")
})

// Input parameters of the callback depend on the mutation type
tasksCollection.addHook("afterUpdate", function(recordId, update) {
 console.log("The record has been udpated: " + recordId)
 console.log(update)
})

tasksCollection.addHook("afterDelete", function(recordId) {
 console.log("The following record has been udpated: " + recordId)
})

# async deleteFakeRecords()

Delete the all fake records created with the method createFakeRecords

View Source common/dataCollection.js, line 509

Example
await myCollection.deleteFakeRecords()

# async deleteMany(query, sendToTrashopt)

Delete many records from a collection

Parameters:
Name Type Attributes Description
query object
sendToTrash boolean <optional>

If true, keeps the original record in a "trash" collection

View Source common/dataCollection.js, line 481

The request's result

# async deleteOne(recordId, sendToTrashopt)

Delete a record from the collection

Parameters:
Name Type Attributes Description
recordId string
sendToTrash boolean <optional>

If true, keeps the original record in a "trash" collection

View Source common/dataCollection.js, line 457

The request's result

# destroy(deleteInMemoryDb)

Destroy the collection.

It deletes the collection and also unsubscribes all its events from kiss.pubsub

Parameters:
Name Type Description
deleteInMemoryDb boolean

If true, force to destroy the in-memory database

View Source common/dataCollection.js, line 192

# async filterBy(filterConfig) → {Array.<object>}

Filter the collection

Parameters:
Name Type Description
filterConfig object

Use MongoDb syntax

View Source common/dataCollection.js, line 813

Array of records

Array.<object>
Example
await myCollection.filterBy({
 $and: [
     {yearOfBirth: 1980},
     {country: "USA"}
 ]
})

# async find(queryopt, nocacheopt, nospinneropt) → {array}

Get the records matching a query.

Remember:

  • without a query parameter, it returns all the records of the collection.
  • the filter can be given as a normalized object which is easy to serialize / deserialize, or as a MongoDb query
  • the sort can be given as a normalized object, or as a MongoDb sort
  • in future releases of kissjs, the query syntax could be extended to "sql"

For more details about the query object, check the example in the db.find() api.

Tech note: This method is the one generating the most http traffic, because it returns a collection of records. Due to the extremely loose coupling system of KissJS components, it can happen that many components are requesting the same collection at the same time, without knowing it.

To solve this, the method is optimized to request the database only once using a combination of pubsub and Promise:

  • the 1st call is changing the collection state "isLoading" to true
  • because isLoading is now true, subsequent calls wait for the response of the 1st call inside a promise, which is waiting for the pubsub event "EVT_COLLECTION_LOADED"
  • when the 1st call has a result, it broadcasts the result in the "EVT_COLLECTION_LOADED" channel, then turns the collection state "isLoading" to false
  • when the subsequent calls receive the result in the pubsub, the promise resolves
Parameters:
Name Type Attributes Description
query object <optional>

Query object

filter * <optional>

The query

filterSyntax string <optional>

The query syntax. By default, passed as a normalized object

sort * <optional>

Sort fields

sortSyntax string <optional>

The sort syntax. By default, passed as a normalized array

group Array.<string> <optional>

Array of fields to group by: ["country", "city"]

groupUnwind boolean <optional>

true to unwind the fields for records that belongs to multiple groups

projection object <optional>

{firstName: 1, lastName: 1, password: 0}

skip object <optional>

Number of records to skip

limit object <optional>

Number of records to return

nocache boolean <optional>

Force the collection to request the database instead of returning the cache

nospinner boolean <optional>

Hide the loading spinner if true

View Source common/dataCollection.js, line 604

Array of records

array
Example
// Retrieves the records using the default or last used query parameters
let myRecords = await myCollection.find()

// Retrieves the records matching a MongoDb query
let myRecords = await myCollection.find({
 filterSyntax: "mongo", // Means we use a standard MongoDb query syntax
 filter: {
     $and: [
         {yearOfBirth: 1980},
         {country: "USA"}
     ]
 },
 sortSyntax: "mongo",
 sort: {
     birthDate: 1,
     lastName: -1
 },
 group: ["state", "city"]
 skip: 200,
 limit: 100,
)

// Retrieves the records using a normalized query
let myRecords = await myCollection.find({
 filterSyntax: "normalized",
 filter: {
     type: "group",
     operator: "and",
     filters: [
         {
             type: "filter",
             fieldId: "firstName",
             operator: "contains",
             value: "wilson"
         },
         {
             type: "filter",
             fieldId: "birthDate",
             operator: ">",
             value: "2020-01-01"
          }
     ]
 },
 sortSyntax: "normalized",
 sort: [
     {birthDate: "desc"},
     {lastName: "asc"}
 ],
 group: ["state", "city"],
 skip: 200,
 limit: 100,
})

# async findById(recordIds, sortopt, sortSyntaxopt, nocacheopt) → {Array.<object>}

Get multiple records of the collection, found by id

Parameters:
Name Type Attributes Default Description
recordIds string

ids of the records to retrieve

sort Array.<object> | object <optional>

Sort options, as a normalized array or a Mongo object. Normalized example: [{fieldA: "asc"}, {fieldB: "desc"}]. Mongo example: {fieldA: 1, fieldB: -1}

sortSyntax string <optional>
normalized

Sort syntax: "nomalized" | "mongo". Default is normalized

nocache boolean <optional>

If true, doesn't use cache. Default is false

View Source common/dataCollection.js, line 749

The list of records, or false is not found

Array.<object>
Example
const myRecord = await myCollection.findOne("Xzyww90sqxnllM38")

# async findOne(recordId, nocache) → {object}

Get a single record of the collection ASYNCHRONOUSLY

Parameters:
Name Type Description
recordId string
nocache boolean

If true, doesn't use cache

View Source common/dataCollection.js, line 715

The record, or false is not found

object
Example
const myRecord = await myCollection.findOne("Xzyww90sqxnllM38")

# getRecord(recordId) → {object}

Get a single record of the collection SYNCHRONOUSLY

Important: the collection must be loaded before using this method, or it will return undefined

Parameters:
Name Type Description
recordId string

View Source common/dataCollection.js, line 794

The record

object
Example
const myRecord = myCollection.getRecord("Xzyww90sqxnllM38")

# async groupBy(groupFields)

Set a groupBy field and reload the records

Parameters:
Name Type Description
groupFields Array.<string>

Array of fields to group by.

View Source common/dataCollection.js, line 849

Example
await myCollection.groupBy(["country", "city", "age"])

# groupCollapse(groupId)

Collapse a group.

Internally, it does 2 things:

  • builds a new dataset without the collapsed records
  • stores in cache the records that are excluded from the dataset, to use them when expanding the group again
Parameters:
Name Type Description
groupId string

Id of the group to expand/collapse. Example: 3.10.7

View Source common/dataCollection.js, line 1355

# groupCollapseAll()

Collapse all groups

View Source common/dataCollection.js, line 867

# groupExpand(groupId, rowIndex)

Expand a group.

Internally, it retrieves all the hidden records which are hold in cache, and reinjects them in the list of records.

Parameters:
Name Type Description
groupId string

Id of the group to expand/collapse. Example: 3.10.7

rowIndex number

Index of the group row into the datatable

View Source common/dataCollection.js, line 1330

# groupExpandAll()

Expand all groups

View Source common/dataCollection.js, line 858

# async insertFakeRecords(numberOfRecords) → {Array.<object>}

Insert some fake records in the collection, for testing purpose.

It automatically uses the model's fields to generate fake data.

Parameters:
Name Type Description
numberOfRecords integer

Number of fake records to insert

View Source common/dataCollection.js, line 497

The array of inserted records data

Array.<object>
Example
await myCollection.insertFakeRecords(100)

# async insertMany(records) → {Array.<object>}

Insert many records in the collection

Parameters:
Name Type Description
records Array.<object>

An array of records [{...}, {...}] for bulk insert

View Source common/dataCollection.js, line 422

The array of inserted records data

Array.<object>

# async insertOne(record) → {object}

Insert one record in the collection

Parameters:
Name Type Description
record object

A single record

View Source common/dataCollection.js, line 433

The inserted record data

object

# setMode(mode)

Set the database mode

Parameters:
Name Type Description
mode string

memory | offline | online

View Source common/dataCollection.js, line 214

# async sortBy(sortConfig)

Sort the collection

Parameters:
Name Type Description
sortConfig Array.<object>

Array of fields to sort by.

View Source common/dataCollection.js, line 834

Example
await myCollection.sortBy(
 [
     {firstName: "asc"},
     {birthDate: "desc"}
 ]
)

# async updateMany(query, update)

Update many records in a single collection

Parameters:
Name Type Description
query object
update object

View Source common/dataCollection.js, line 469

The request's result

# async updateOne(recordId, update) → {object}

Update a single record in the collection

Parameters:
Name Type Description
recordId string
update object

View Source common/dataCollection.js, line 445

The request's result

object