/
githubmirror
/
pocketbase
Обзор
Документация
Войти
/
githubmirror
/
pocketbase
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
ui/src/fields/relation/input.js
215 строк
7 KB
Gani Georgiev
updated local selected after updating the record value
14 июн 2026, 18:14
14 июн 2026, 18:14
07471c9
Код
Авторство
О чём код?
// { // collection: undefined, // originalRecord: undefined, // record: undefined, // field: undefined, // } export function input(props) { const uniqueId = "rel_" + app.utils.randomString(); // trigger custom change event for clearing field errors function triggerChangeEvent() { fieldContentEl?.dispatchEvent( new CustomEvent("change", { detail: { data: props }, bubbles: true, }), ); } const local = store({ selected: [], isLoading: true, get maxReached() { const maxSelect = props.field.maxSelect || 1; return app.utils.toArray(props.record[props.field.name]).length >= maxSelect; }, }); async function loadSelected() { local.isLoading = true; const ids = app.utils.toArray(props.record[props.field.name]); if (!ids.length) { local.selected = []; local.isLoading = false; return; } const resultRecords = []; const idsToLoad = []; // check for preloaded expand const expanded = app.utils.toArray(props.record.expand?.[props.field.name]); for (const id of ids) { const found = expanded.find((r) => r.id == id); if (found) { resultRecords.push(found); } else { idsToLoad.push(id); } } try { if (idsToLoad.length) { const fieldCollection = app.store.collections.find((c) => c.id == props.field.collectionId); // eagerly expand first level presentable relations (if any and the collections are loaded) const relExpands = []; const presentableRelationFields = fieldCollection?.fields?.filter( (f) => !f.hidden && f.presentable && f.type == "relation", ) || []; for (const field of presentableRelationFields) { relExpands.push(field.name); } const records = await app.pb.collection(props.field.collectionId).getFullList({ requestKey: null, filter: idsToLoad.map((id) => app.pb.filter("id={:id}", { id })).join("||"), expand: relExpands.join(",") || undefined, }); // preserve the original order for (const id of idsToLoad) { const found = records.find((r) => r.id == id); if (found) { resultRecords.push(found); } else { console.warn("missing relation id:", id); } } } local.selected = resultRecords; local.isLoading = false; } catch (err) { if (!err.isAbort) { app.checkApiError(err); local.isLoading = false; } } } function remove(id) { const ids = app.utils.toArray(props.record[props.field.name]); const propIndex = ids.indexOf(id); if (propIndex >= 0) { ids.splice(propIndex, 1); updateRecordValue(ids); } const selectedIndex = local.selected.findIndex((r) => r.id == id); local.selected.splice(selectedIndex, 1); } function updateRecordValue(ids = []) { props.record[props.field.name] = props.field.maxSelect > 1 ? ids : ids?.[0] || ""; triggerChangeEvent(); } const watchers = [ watch( () => props.record[props.field.name], () => loadSelected(), ), ]; const fieldContentEl = t.output( { className: "field-content", name: () => props.field.name, }, // loader t.div( { hidden: () => !local.isLoading, className: "list", }, () => { const ids = app.utils.toArray(props.record[props.field.name]); return ids.map(() => { return t.div({ className: "list-item" }, t.span({ className: "skeleton-loader" })); }); }, ), // list app.components.sortable({ className: "list", hidden: () => local.isLoading, data: () => local.selected, onchange: (sortedList) => { updateRecordValue(sortedList.map((r) => r.id)); local.selected = sortedList; }, dataItem: (record, relIndex) => { return t.div( { rid: record, className: "list-item highlight", }, t.div({ className: "content" }, () => app.components.recordSummary(record)), t.div( { className: "actions" }, t.button( { type: "button", className: "btn sm secondary transparent circle", ariaLabel: app.attrs.tooltip("Remove"), onclick: () => remove(record.id), }, t.i({ className: "ri-close-line", ariaHidden: true }), ), ), ); }, }), // picker btn t.hr({ hidden: () => !app.utils.isEmpty(props.record[props.field.name]), className: "m-t-5 m-b-0", }), t.button( { type: "button", className: "btn sm secondary block", disabled: () => local.isLoading, onclick: (e) => { app.modals.openRecordsPicker({ collection: props.field.collectionId, selectedIds: app.utils.toArray(props.record[props.field.name]), maxSelect: props.field.maxSelect, onselect: (records) => { updateRecordValue(records.map((r) => r.id)); local.selected = records; }, }); }, }, t.i({ className: "ri-magic-line", ariaHidden: true }), t.span({ className: "txt" }, "Open records picker"), ), ); return t.div( { className: "record-field-input field-type-relation", onunmount: () => { watchers.forEach((w) => w?.unwatch()); }, }, t.div( { className: () => `field-list ${props.field.required ? "required" : ""}` }, t.label( { htmlFor: uniqueId }, t.i({ className: app.fieldTypes.relation.icon, ariaHidden: true }), t.span({ className: "txt" }, () => props.field.name), ), fieldContentEl, ), () => { if (props.field.help) { return t.div({ className: "field-help" }, props.field.help); } }, ); }