Creating a class file, working with code
First of all, as mentioned above, after creating a class in tables.json and synchronizing it in Class_profile, the class becomes fully operational. That is, it already has all the basic methods, a table in the database, its own menu item that will display the table in the interface (for ds_ it is created in Dictionary_system, for d_ in Dictionary or Basic_data, for others in Temporary), and its methods can be called via internal (and external) api, both from methods on the backend and from the client.
Also, during synchronization, a file with a description of typescript structures associated with this class is created, and an interface for the fields of this class is automatically created/updated in it, for example, for the Crm_user class: ICrm_userDataRow, which may be useful for methods working with records of this class. It is located in classes/_structures/<class_name>.ts
You do not need to create a class file for it to work unless you want to add new methods or override base ones. However, in the current version, calling methods of any class from other methods of other (or the same) classes is carried out through a construction of the form:
res = await r.api(Deal, 'getById', {id: 1})
where “Deal” is the class itself imported from the class file. This is necessary so that typescript can check the parameters.
Thus, we need to create a class file.
To create a class file, go to the system interface in the System->Classes menu, find the desired class and in the context menu for this line select “Create class file.”
The file will be created in classes/<Class_name>.ts. It must be manually added to git.
You can import the created class for use in the internal API syntax res = await r.api(<Class_name>, ‘<methodName>’, {id : 1}) But do not create its instance manually to call its methods, as the internal api is used for this. see basic concepts.
In it, you can write your own methods or override the basic ones.
Writing your own methods
When a class file is created, it is copied from a template which, in addition to the necessary code, contains examples of methods. They can be taken as a basis.
Here is an example from there:
/**
* Description of the "example" method
* Описание метода "example".
*/
// @OpenApi()
async example(r: IAPIRequest<{checkSomething:boolean}>): Promise<IAPIResponse<{name:string, age?:number}>> {
const rParams: IAPIParams = r.params
const params: IAPIQueryParams = r.data.params
const checkSomething = params.checkSomething;
const usr = r.client._session.usr
let res: IAPIResponse // Can be used multiple times
// res = await r.api(Crm_user, 'add', {
// firstname: 'John',
// lastname: 'Doe',
// email: 'john.d@example.com'
// })
// if (res.code) return res
// console.log('Added successfully. ID:', res.data.id)
// return new UserOk('Everything is ok', {name: usr.userData.fio})
return new UserOk('Everything is ok', {name: usr.userData.fio, age: usr.userData.age})
// return await new Promise(cb=>{})
return new MyError('notImplemented')
}
Key aspects
- Documentation comment
- @OpenApi() decorator, if you want the method to be automatically included in the documentation
- Description of input parameters in the generic for IAPIRequest.
- Description of output parameters in the generic IAPIResponse
- Ability to get passed parameters from params. (rParams contains system parameters of the method)
- Ability to get information from the request about the session, user, their sockets, etc.
- Calling other methods of this or another class via internal api.
- Be sure to check for errors
if (res.code) return res
- Be sure to check for errors
- Returning an error (MyError or UserError) or success UserOk (specifying the object according to the output parameters)
For reference, here is an example of a method involved in generating this documentation page:
async getContentByUrl(r: IAPIRequest<{
url: string,
query?: {
v?: string
lang?: string
}
}>): Promise<IAPIResponse<{content: string}>> {
const params = r.data.params
const lang = (params.query?.lang || defaultSystemLang).toUpperCase()
const version = params.query?.v
const url = params.url
let res: IAPIResponse
const languages: {
sysname: string,
name: string,
selected: string
}[] = []
// Get documentation item by path
res = await r.api(Documentation_item, 'getByUrl', {url, lang})
const getByUrlData = res.data as IGetByUrlRes
// Get documentationId and list of documentations with such alias to form the language list
const documentationAlias: string = getByUrlData.row?.documentation_alias
|| getByUrlData.suggestions?.[0]?.documentation_alias || url.split('/eso/')[0] || null
let currentDocumentationId: number = getByUrlData.row?.documentation_id || null
if (documentationAlias) {
res = await r.api(Documentation, 'get', {
where: [
whereEq('alias', documentationAlias)
],
columns: ['id', 'language_sysname', 'language'],
})
if (res.code) return
res.data.rows.forEach(one=>{
languages.push({
sysname: one.language_sysname,
name: one.language,
selected: one.language_sysname === lang ? 'selected' : ''
})
})
if (!currentDocumentationId) {
// Select from results by language
currentDocumentationId = res.data.rows
.find(one => one.language_sysname === lang)?.id || null
}
}
// Get menu HTML
let menuHtml = ''
if (currentDocumentationId) {
res = await r.api(Documentation, 'getById', {
id: currentDocumentationId,
columns: ['menu_html'],
})
if (res.code) return
menuHtml = res.data.rows[0].menu_html
}
if (!getByUrlData.row) {
let suggestions = getByUrlData.suggestions.map(suggestion => ({
url: `/docs/${suggestion.url}?lang=${suggestion.documentation_lang_sysname.toLowerCase()}`,
name: `${suggestion.name} (${suggestion.documentation_lang})`,
}))
let title: string = ''
let suggestionsTitle: string = ''
if (!suggestions.length) {
// Form a list of suggestions from first-level documentation items
res = await r.api(Documentation_item, 'get', {
where: [
whereEq('documentation_id', currentDocumentationId),
whereEq('is_active', true),
whereIsNull('parent_id')
],
columns: ['id', 'name', 'url', 'position'],
sort: {
columns: ['position', 'name'],
directions: ['ASC', 'ASC']
},
})
if (res.code) return
suggestions = res.data.rows.map(suggestion => ({
url: `/docs/${suggestion.url}`,
name: `${suggestion.name}`,
}))
title = ''
suggestionsTitle = await langFn('Select section', lang)
} else {
title = await langFn('Document not found', lang)
suggestionsTitle = await langFn('Perhaps you were looking for', lang)
}
// Send "Document not found" page and suggestions
const templatePathNF = path.join(process.cwd(), 'templates', 'documentation', 'tpl_document_not_found.html');
let templateNF = '';
try {
templateNF = fs.readFileSync(templatePathNF, 'utf-8');
} catch (e) {
return new MyError('Error loading template', {tpl: 'tpl_document_not_found'});
}
const viewNF = {
title,
suggestionsTitle,
toMainText: await langFn('Back to documentation home page', lang),
docsListText: await langFn('To documentation list', lang),
docsListUrl: '/docs',
currentLang: lang.toLowerCase(),
languages,
sidebarHtml: menuHtml,
currentUrl: `/docs/${url}`,
suggestions
};
const finalHtmlNF = Mustache.render(templateNF, viewNF);
return new UserOk('ok', {content: finalHtmlNF})
}
// Get current document, if this version is not available, request other versions of the same document,
// and format them as a list of suggestions
const p = {
where: [
// whereEq('id', getByUrlData.row.latest_document_id),
whereEq('documentation_item_id', getByUrlData.row.id),
whereEq('is_active', true)
],
columns: ['id', 'content', 'is_active', 'type_sysname', 'version'],
}
if (version) {
p.where.push(whereEq('version', version))
} else {
// If version is not specified, return current version
p.where.push(whereEq('id', getByUrlData.row.latest_document_id))
}
res = await r.api(Document, 'get', p)
if (res.code) return
const currentDocument = res.data.rows[0]
if (!currentDocument) {
// Get other versions if any
const otherVersions: {
id: number,
version: string,
url: string,
is_active: boolean,
type_sysname: string,
}[] = []
res = await r.api(Document, 'get', {
where: [
whereEq('documentation_item_id', getByUrlData.row.id),
whereEq('is_active', true)
]
})
if (res.code) return
res.data.rows.forEach(one=>{
otherVersions.push({
id: one.id,
version: one.version,
url: one.url,
is_active: one.is_active,
type_sysname: one.type_sysname,
})
})
// Send "Document version not found" page and suggestions (other versions)
const templatePathNF = path.join(process.cwd(), 'templates', 'documentation', 'tpl_document_not_found.html');
let templateNF = '';
try {
templateNF = fs.readFileSync(templatePathNF, 'utf-8');
} catch (e) {
return new MyError('Error loading template', {tpl: 'tpl_document_not_found'});
}
const viewNF = {
title: await langFn('Document version not found', lang),
suggestionsTitle: await langFn('Other available versions', lang),
toMainText: await langFn('Back to documentation home page', lang),
docsListText: await langFn('To documentation list', lang),
docsListUrl: '/docs',
currentLang: lang.toLowerCase(),
languages,
sidebarHtml: menuHtml,
currentUrl: `/docs/${url}`,
suggestions: otherVersions.map(v => ({
url: `/docs/${url}?lang=${lang.toLowerCase()}&v=${v.version}`,
name: `Version ${v.version}`,
}))
};
const finalHtmlNF = Mustache.render(templateNF, viewNF);
return new UserOk('ok', {content: finalHtmlNF})
}
// If found, return it, and also request a list of other versions to form the dropdown list
const allVersionsRes = await r.api(Document, 'get', {
where: [
whereEq('documentation_item_id', getByUrlData.row.id),
whereEq('is_active', true)
],
columns: ['id', 'version', 'priority'],
sort: {
columns: ['priority', 'version'],
directions: ['DESC', 'ASC']
}
})
const versions = (allVersionsRes.data?.rows || []).map((v: any) => ({
v: v.version,
selected: v.version === (currentDocument.version) ? 'selected' : ''
}))
const childLinks = []
// Get child elements
{
const res = await r.api(Documentation_item, 'get', {
where: [
whereEq('documentation_id', currentDocumentationId),
whereEq('is_active', true),
whereEq('parent_id', getByUrlData.row.id),
],
columns: ['id', 'name', 'url'],
sort: {
columns: ['position', 'name'],
directions: ['ASC', 'ASC']
}
})
if (!res.code) {
res.data.rows.forEach(one=>{
childLinks.push({
url: `/docs/${one.url}`,
name: one.name,
})
})
}
}
const md = new MarkdownIt();
const contentHtml = md.render(currentDocument.content || '');
// Template path
const templatePath = path.join(process.cwd(), 'templates', 'documentation', 'tpl_documentation_item.html');
let template = '';
try {
template = fs.readFileSync(templatePath, 'utf-8');
} catch (e) {
return new MyError('Error loading template');
}
const view = {
// title: 'Documentation: ' + params.url,
title: '',
docsListText: await langFn('To documentation list', lang),
docsListUrl: '/docs',
sidebarHtml: menuHtml,
currentUrl: `/docs/${url}`,
content: contentHtml,
currentLang: lang.toLowerCase(),
languages,
versions,
childLinksTitle: childLinks?.length ? `<h5>${await langFn('More information in sections', lang)}:</h5>` : '',
childLinks
};
const finalHtml = Mustache.render(template, view);
return new UserOk('ok', {content: finalHtml})
}