11 KiB
Vendored
Advanced Attributes
Master complex attribute features including inheritance, templates, computed values, and system attributes.
Prerequisites
- Understanding of basic labels and relations
- Familiarity with note hierarchy
- Knowledge of promoted attributes
Attribute Inheritance
Inheritance Mechanism
Attributes marked as inheritable propagate down the note tree to all descendants.
Basic Inheritance
// Parent note
#workspace #inheritable
// All child notes automatically receive
#workspace (inherited)
Inheritance Chain
Root Note
├── #theme=dark #inheritable
│ └── Child A (inherits #theme=dark)
│ └── Child B (inherits #theme=dark)
│ └── #theme=light (overrides inherited value)
Controlling Inheritance
Enable Inheritance
Mark attribute as inheritable:
note.setLabel('category', 'project', true); // true = inheritable
Block Inheritance
Stop inheritance at specific level:
// Parent has #inheritable attribute
#workspace #inheritable
// Child blocks inheritance
#workspace #noInherit
Inheritance Templates
Define inheritable attribute sets:
// Template note
#template
#label:status=text #inheritable
#label:priority=number #inheritable
#relation:assignee #inheritable
Advanced Inheritance Patterns
Conditional Inheritance
// Only inherit if condition met
if (note.hasLabel('acceptInheritance')) {
note.inheritAttributesFrom(parent);
}
Selective Inheritance
// Inherit only specific attributes
const inheritableAttrs = parent.getAttributes()
.filter(attr => attr.isInheritable && attr.name.startsWith('project'));
inheritableAttrs.forEach(attr => {
note.setAttribute(attr.type, attr.name, attr.value);
});
Computed Attributes
Dynamic Attribute Values
Attributes whose values are calculated based on other data.
Formula Attributes
// Define computed attribute
#formula=noteCount
#formulaCode=`
const children = note.getChildNotes();
return children.length;
`
Aggregation Attributes
// Sum values from children
#aggregate=totalHours
#aggregateCode=`
const children = note.getChildNotes();
return children
.map(child => parseInt(child.getLabelValue('hours') || 0))
.reduce((sum, hours) => sum + hours, 0);
`
Reactive Attributes
Update automatically when dependencies change:
// Watch for changes
api.onAttributeChange((note, attr) => {
if (attr.name === 'status') {
// Update computed attributes
updateComputedProgress(note);
}
});
function updateComputedProgress(note) {
const children = note.getChildNotes();
const completed = children.filter(c => c.getLabelValue('status') === 'done').length;
const total = children.length;
note.setLabel('progress', `${completed}/${total}`);
note.setLabel('progressPercent', Math.round((completed/total) * 100));
}
System Attributes
Core System Attributes
Note Configuration
#disableVersioning- Disable revision tracking#readOnly- Make note read-only#hideFromTree- Hide note in tree view#excludeFromSearch- Exclude from search results#excludeFromExport- Skip during export
Display Attributes
#iconClass- Custom icon (e.g.,bx bx-star)#cssClass- CSS class for styling#color- Note color in tree#sorted- Keep children sorted
Behavior Attributes
#runOnNoteCreation- Execute script on creation#runOnNoteTitleChange- Execute on title change#runOnNoteChange- Execute on any change#runOnChildNoteCreation- Execute when child created
Widget Attributes
Control widget behavior and display:
// Configure widget visibility
#widget=attributeList
#widgetMode=view // view|edit|both
#widgetPosition=right // left|right|center
// Custom widget configuration
#customWidget=myWidget
#widgetOptions={"columns": 3, "sortBy": "date"}
Script Attributes
Define script execution contexts:
// Backend script
#run=backend
#runAtHour=2 // Run at 2 AM daily
// Frontend script
#run=frontend
#runOnEvent=activeNoteChanged
// API endpoint
#customRequestHandler=/api/custom
Attribute Templates
Defining Templates
Create reusable attribute definitions:
// Attribute definition note
#label:status=text
#label:priority=single,low,medium,high,critical
#label:dueDate=date
#relation:assignee=person
#relation:project
Template Application
Manual Application
const template = api.getNoteWithLabel('attributeTemplate');
const definitions = template.getAttributeDefinitions();
targetNote.applyTemplate(definitions);
Automatic Application
// On note creation
#runOnChildNoteCreation=`
const template = api.getNoteWithLabel('taskTemplate');
child.applyTemplate(template);
`
Complex Templates
Hierarchical Templates
// Project template
{
attributes: [
{name: 'projectStatus', type: 'label', value: 'active'},
{name: 'projectManager', type: 'relation'}
],
childTemplates: {
'tasks': 'taskTemplate',
'docs': 'documentTemplate',
'meetings': 'meetingTemplate'
}
}
Conditional Templates
function applyConditionalTemplate(note) {
const noteType = note.type;
const templates = {
'text': 'textNoteTemplate',
'code': 'codeNoteTemplate',
'task': 'taskNoteTemplate'
};
const templateId = templates[noteType];
if (templateId) {
note.applyTemplate(api.getNote(templateId));
}
}
Bulk Attribute Operations
Mass Attribute Updates
Add Attributes to Multiple Notes
const notes = api.searchForNotes('#needsUpdate');
notes.forEach(note => {
note.setLabel('lastUpdated', new Date().toISOString());
note.setLabel('updatedBy', 'bulkOperation');
});
Remove Attributes in Bulk
const notes = api.searchForNotes('#deprecated');
notes.forEach(note => {
note.removeLabel('deprecated');
note.removeLabel('oldVersion');
});
Attribute Migration
Rename Attributes
function migrateAttributes(oldName, newName) {
const notes = api.searchForNotes(`#${oldName}`);
notes.forEach(note => {
const value = note.getLabelValue(oldName);
note.removeLabel(oldName);
note.setLabel(newName, value);
});
}
Transform Attribute Values
function transformAttributeValues(attrName, transformer) {
const notes = api.searchForNotes(`#${attrName}`);
notes.forEach(note => {
const oldValue = note.getLabelValue(attrName);
const newValue = transformer(oldValue);
note.setLabel(attrName, newValue);
});
}
// Example: Convert dates to ISO format
transformAttributeValues('date', (value) => {
return new Date(value).toISOString();
});
Advanced Attribute Patterns
Attribute Chains
Link attributes across notes:
// Follow relation chain
function getRelationChain(startNote, relationName, maxDepth = 10) {
const chain = [];
let current = startNote;
let depth = 0;
while (current && depth < maxDepth) {
const target = current.getRelationTarget(relationName);
if (!target) break;
chain.push(target);
current = target;
depth++;
}
return chain;
}
Attribute Validation
Ensure attribute integrity:
class AttributeValidator {
constructor(rules) {
this.rules = rules;
}
validate(note, attrName, value) {
const rule = this.rules[attrName];
if (!rule) return true;
if (rule.type === 'number') {
return !isNaN(value);
}
if (rule.pattern) {
return new RegExp(rule.pattern).test(value);
}
if (rule.enum) {
return rule.enum.includes(value);
}
return true;
}
}
// Usage
const validator = new AttributeValidator({
email: {pattern: '^[^@]+@[^@]+\\.[^@]+$'},
priority: {enum: ['low', 'medium', 'high']},
score: {type: 'number'}
});
Attribute Indexing
Optimize attribute queries:
// Build attribute index
class AttributeIndex {
constructor() {
this.index = new Map();
}
buildIndex(attributeName) {
const notes = api.searchForNotes(`#${attributeName}`);
const valueMap = new Map();
notes.forEach(note => {
const value = note.getLabelValue(attributeName);
if (!valueMap.has(value)) {
valueMap.set(value, []);
}
valueMap.get(value).push(note.noteId);
});
this.index.set(attributeName, valueMap);
}
findByAttributeValue(attributeName, value) {
const valueMap = this.index.get(attributeName);
return valueMap ? valueMap.get(value) || [] : [];
}
}
Performance Optimization
Efficient Attribute Queries
// Batch attribute operations
api.runAsTransaction(() => {
notes.forEach(note => {
// Multiple attribute changes in single transaction
note.setLabel('status', 'processed');
note.setLabel('processedDate', Date.now());
note.save();
});
});
Attribute Caching
// Cache frequently accessed attributes
const attributeCache = new Map();
function getCachedAttribute(noteId, attrName) {
const key = `${noteId}:${attrName}`;
if (!attributeCache.has(key)) {
const note = api.getNote(noteId);
const value = note.getLabelValue(attrName);
attributeCache.set(key, value);
}
return attributeCache.get(key);
}
Troubleshooting
Inheritance Not Working
Symptom: Child notes not receiving inheritable attributes.
Solutions:
- Verify attribute is marked as inheritable
- Check for #noInherit blockers
- Ensure note hierarchy is correct
- Clear attribute cache
Computed Attributes Not Updating
Symptom: Formula attributes showing stale values.
Solutions:
- Check formula syntax for errors
- Verify dependencies are tracked
- Manually trigger recalculation
- Review event handlers
Attribute Conflicts
Symptom: Multiple inheritance sources causing conflicts.
Solutions:
- Define clear inheritance priorities
- Use explicit overrides
- Implement conflict resolution logic
- Document inheritance hierarchy
Best Practices
-
Design Inheritance Hierarchy
- Plan attribute inheritance paths
- Use templates for consistency
- Document inheritance rules
-
Optimize Attribute Access
- Cache frequently used attributes
- Batch attribute operations
- Use indexes for large datasets
-
Validate Attribute Data
- Implement validation rules
- Use type checking
- Handle edge cases
-
Monitor Performance
- Profile attribute queries
- Track inheritance depth
- Optimize complex formulas
-
Maintain Documentation
- Document custom attributes
- Record validation rules
- Track attribute dependencies