Master the art of opening and managing forms with elegant chains
The FormBuilderExtensions class provides a powerful, fluent API for opening and managing forms in Fractal Platform. It uses a pattern called "method chaining" that makes your code readable and expressive.
The simplest way to open a form is directly from a FractalPlatformClient:
// Open form for the first document Client.OpenForm(result => { if (result.Result) { // User clicked OK/Save Console.WriteLine("Form saved successfully!"); } else { // User clicked Cancel Console.WriteLine("Form was cancelled"); } });
// Open form for document with ID 5 Client.OpenForm(docID: 5, handleResult: result => { Console.WriteLine($"Document ID: {result.DocID}"); });
// Open form with specific data shape Client.OpenForm("{'Name':$,'Age':$}", result => { if (result.Result) { // Access the collection with changes var collection = result.Collection; } });
// Open form for query results DocsWhere("Users", "{'Name':'Bob'}") .OpenForm(result => { Console.WriteLine("Form opened for Bob"); });
// Get collection first, then open form var collection = Client.GetCollection(); collection.OpenForm(result => { Console.WriteLine("Collection form opened"); });
The "Want" pattern is the core of FormBuilder. It lets you declare your intentions about what should happen when the form is saved.
| Want Type | Purpose | When to Use |
|---|---|---|
| WantNotModifyExistingDocuments | View-only or no save | Showing data without changing it |
| WantModifyExistingDocuments | Update existing data | Editing existing records |
| WantCreateNewDocumentFor | Create new document | Adding new records |
| WantCreateNewDocumentForArray | Add to array field | Adding items to lists |
| WantMergeDocumentFor | Merge data into existing | Combining documents |
| WantUnmergeDocumentFor | Remove merged data | Separating documents |
// Open form in read-only mode Client.WantNotModifyExistingDocument() .OpenForm(result => { // Changes won't be saved to database });
// Open form and save changes to existing document Client.WantModifyExistingDocument() .OpenForm(result => { if (result.Result) { // Changes are automatically saved! Console.WriteLine("Document updated in database"); } });
// Create new user Client.WantCreateNewDocumentFor("Users") .OpenForm(result => { if (result.Result) { // New document created in Users collection Console.WriteLine($"New user created with ID: {result.TargetDocID}"); } });
// Find specific documents and modify them DocsWhere("Users", "{'Name':'Bob'}") .WantModifyExistingDocuments() .OpenForm(result => { // Bob's document is updated });
Dimensions are layers of data that control different aspects of your form: UI layout, theme, and the actual document data.
| Dimension | Purpose | Example Use |
|---|---|---|
| UI | Form layout and structure | Hide fields, change labels, add sections |
| Theme | Visual appearance | Colors, fonts, light/dark mode |
| Document | The actual data | User records, product info, etc. |
| Patch | Track changes | Undo/redo, change history |
| Filter | Data filtering | Show subset of data |
Use SetDimension to replace dimension data completely:
// Set UI dimension to hide Age field DocsWhere("Users", "{'Name':'Bob'}") .SetUIDimension("{'Age':{'Visible':false}}") .WantModifyExistingDocuments() .OpenForm(result => { // Form opens without Age field visible });
Use ExtendDimension to merge new data with existing:
// Add UI rules while keeping existing ones Client.WantModifyExistingDocument() .ExtendUIDimension("{'Name':{'ReadOnly':true}}") .OpenForm(result => { // Name field is read-only in the form });
Use ResetDimension to remove dimension data:
// Remove custom UI settings Client.WantNotModifyExistingDocument() .ResetDimension(DimensionType.UI) .OpenForm(result => { // Form uses default UI layout });
// Set dark theme Client.WantNotModifyExistingDocument() .SetThemeDimension(ThemeType.Dark) .OpenForm(result => { // Form appears in dark theme }); // Or with JSON for more control Client.WantNotModifyExistingDocument() .SetThemeDimension("{'DefaultTheme':'Dark','ChooseThemeOnLoginPage':true}") .OpenForm(result => { });
// Configure both UI and Theme Client.WantModifyExistingDocument() .SetUIDimension("{'Age':{'Visible':false}}") .SetThemeDimension(ThemeType.Light) .OpenForm(result => { // Light theme + Age hidden });
// Extend document with additional data DocsWhere("Users", "{'Name':'Bob'}") .ExtendDocument("{'TempField':'Some value'}") .WantNotModifyExistingDocuments() .OpenForm(result => { // Form shows additional TempField (not saved to DB) }); // Set entire document shape Client.WantNotModifyExistingDocument() .SetDocument("{'Name':'','Age':0,'Email':''}") .OpenForm(result => { // Form uses this exact document structure });
When you use WantModifyExistingDocuments, the system tracks all changes in a "patch". When the user saves, the patch is automatically applied to the database.
// Edit Bob's data DocsWhere("Users", "{'Name':'Bob'}") .WantModifyExistingDocuments() .OpenForm(result => { if (result.Result) { // All changes user made are saved to database // The patch was automatically applied } });
// Edit with custom UI DocsWhere("Users", "{'Name':'Bob'}") .SetUIDimension("{'Weight':{'Label':'Weight (kg)'}}") .WantModifyExistingDocuments() .OpenForm(result => { if (result.Result) { Console.WriteLine("User updated!"); } });
// Edit all managers DocsWhere("Users", "{'IsManager':true}") .WantModifyExistingDocuments() .OpenForm(result => { if (result.Result) { // All matching documents are updated var count = result.Collection.DocumentStorage .GetDocIDs(context).Count; Console.WriteLine($"Updated {count} managers"); } });
DocsWhere("Users", "{'Name':'Bob'}") .WantModifyExistingDocuments() .OpenForm(result => { if (result.Result && result.NeedReloadData) { // Backend data changed, refresh your UI RefreshUserList(); } });
// Create new user Client.WantCreateNewDocumentFor("Users") .OpenForm(result => { if (result.Result) { // New document created in Users collection Console.WriteLine($"Created user with ID: {result.TargetDocID}"); } });
// Create user with pre-filled data Client.WantCreateNewDocumentFor("Users") .SetDocument("{'Name':'New User','Age':0,'Email':''}") .OpenForm(result => { if (result.Result) { Console.WriteLine("User created with template"); } });
// Use existing data as template for new document DocsWhere("Users", "{'Name':'Bob'}") .WantCreateNewDocumentFor("Users") .OpenForm(result => { if (result.Result) { // Bob's data used as template, new user created } });
Use WantCreateNewDocumentForArray to add items to arrays:
// Add new job to Bob's Jobs array Client.WantCreateNewDocumentForArray( collName: "Users", json: "{'Jobs':[@0,$]}", // @0 means insert at position 0 targetDocID: 1) // Bob's document ID .SetDocument("{'Company':'','Position':''}") .OpenForm(result => { if (result.Result) { // New job added to Bob's Jobs array } });
// Add new child to Bob Client.WantCreateNewDocumentForArray( "Users", "{'Childs':[@0,$]}", 1) .SetDocument("{'Name':'','Age':0}") .SetUIDimension("{'Name':{'Label':'Child Name'}}") .OpenForm(result => { if (result.Result) { Console.WriteLine("Child added to Bob's record"); } });
[@0,$] means "insert at position 0 and use the form data ($)". You can use any index or "Last" to append to the end.
Merging combines data from one document into another. It's useful for scenarios like:
// Merge form data into target document Client.WantMergeDocumentFor( collName: "Users", targetDocID: 2) // Ted's document .SetDocument("{'Weight':80,'Car':{'Model':'Toyota'}}") .OpenForm(result => { if (result.Result) { // Form data merged into Ted's document // Ted now has Weight: 80 and Car.Model: Toyota } });
// Use Bob's data to merge into Ted DocsWhere("Users", "{'Name':'Bob'}") .WantMergeDocumentFor("Users", 2) .OpenForm(result => { if (result.Result) { // Bob's form data merged into Ted } });
// Show only specific fields to merge DocsWhere("Users", "{'Name':'Bob'}") .WantMergeDocumentFor("Users", 2) .SetUIDimension("{'Name':{'Visible':false},'Age':{'Visible':false}}") .OpenForm(result => { if (result.Result) { // Only visible fields are merged } });
Unmerge removes previously merged data:
// Remove specific fields from document Client.WantUnmergeDocumentFor( collName: "Users", targetDocID: 2) .SetDocument("{'Weight':$,'Car':{'Model':$}}") .OpenForm(result => { if (result.Result) { // Weight and Car.Model removed from Ted } });
The beauty of FormBuilder is how methods chain together. Each method returns a WantInfo object that has more methods available.
// Complex workflow in one chain DocsWhere("Users", "{'IsManager':true}") .AndWhere("{'Weight':GreatOrEqual(70)}") .OrderBy("{'Name':$}") .Take(5) .SetUIDimension("{'Weight':{'Label':'Weight (kg)','ReadOnly':true}}") .SetThemeDimension(ThemeType.Dark) .WantModifyExistingDocuments() .OpenForm(result => { // Managers with weight >= 70, sorted by name, first 5 // Dark theme, weight field read-only // Changes saved to database });
WantInfo has all BaseQuery methods available:
Client.WantNotModifyExistingDocument() .AndWhere("{'Age':GreatOrEqual(18)}") .OrderByDesc("{'Age':$}") .Take(10) .OpenForm(result => { // Top 10 adults, sorted by age descending }); // You can even use Count, Select, etc. var count = Client.WantNotModifyExistingDocument() .AndWhere("{'IsManager':true}") .Count(); // Returns number without opening form
// Chain multiple dimension operations DocsWhere("Users", "{'Name':'Bob'}") .SetUIDimension("{'Age':{'Visible':false}}") .ExtendUIDimension("{'Weight':{'Label':'Weight (kg)'}}") .SetThemeDimension(ThemeType.Light) .ExtendDocument("{'TempNote':'Review this user'}") .WantModifyExistingDocuments() .OpenForm(result => { // All dimension changes applied });
// Get collection without opening form var collection = DocsWhere("Users", "{'Name':'Bob'}") .SetUIDimension("{'Age':{'Visible':false}}") .WantNotModifyExistingDocuments() .ToCollection(); // Now you can work with collection directly var storage = collection.GetStorage();
Use SkipOpenFormIf to conditionally show a form:
// Only open form if user has permission bool hasPermission = CheckUserPermission(); Client.SkipOpenFormIf(!hasPermission) .OpenForm(result => { if (!hasPermission) { // Form was skipped, but callback still runs Console.WriteLine("Permission denied"); } else { // Form was shown and user interacted with it } });
bool isReadOnly = true; DocsWhere("Users", "{'Name':'Bob'}") .SkipOpenFormIf(isReadOnly) .WantModifyExistingDocuments() .OpenForm(result => { // Callback runs even if skipped });
var collection = Client.GetCollection(); bool hasData = collection.DocumentStorage .GetDocIDs(context).Count > 0; collection.SkipOpenFormIf(!hasData) .OpenForm(result => { if (!hasData) { Console.WriteLine("No data to display"); } });
// Check if document exists first var exists = DocsWhere("Users", "{'Name':'Bob'}") .Exists(); DocsWhere("Users", "{'Name':'Bob'}") .SkipOpenFormIf(!exists) .WantModifyExistingDocuments() .OpenForm(result => { if (!exists) { Console.WriteLine("Bob not found"); } else if (result.Result) { Console.WriteLine("Bob updated"); } });
// Complete user profile editing workflow public void EditUserProfile(uint userId) { DocsWhere("Users", userId) .SkipOpenFormIf(!canEdit) .SetUIDimension(@"{ 'Salary': {'Visible': " + hasPermission.ToString().ToLower() + @"}, 'SSN': {'Visible': " + hasPermission.ToString().ToLower() + @"} }") .WantModifyExistingDocuments() .OpenForm(result => { if (!canEdit) { ShowNotification("Access denied"); } else if (result.Result) { ShowNotification("Data updated"); LogActivity($"User {userId} edited by {CurrentUser.ID}"); } }); }
public void EmployeeOnboardingWizard() { // Step 1: Basic Information Client.WantCreateNewDocumentFor("Employees") .SetDocument("{'Name':'','Email':'','Phone':''}") .SetUIDimension("{'Title':'Step 1: Basic Information'}") .OpenForm(result1 => { if (result1.Result) { var employeeId = result1.TargetDocID; // Step 2: Department and Position DocsWhere("Employees", employeeId) .ExtendDocument("{'Department':'','Position':'','Manager':''}") .SetUIDimension("{'Title':'Step 2: Department Assignment'}") .WantModifyExistingDocuments() .OpenForm(result2 => { if (result2.Result) { // Step 3: Benefits and Salary DocsWhere("Employees", employeeId) .ExtendDocument("{'Salary':0,'BenefitsPlan':''}") .SetUIDimension("{'Title':'Step 3: Compensation'}") .WantModifyExistingDocuments() .OpenForm(result3 => { if (result3.Result) { ShowNotification("Employee onboarding complete!"); SendWelcomeEmail(employeeId); } }); } }); } }); }
public void EditUserWithFallback(string username) { // Try to find user, create if not exists var exists = DocsWhere("Users", $"{{'Name':'{username}'}}") .Exists(); if (exists) { // User exists - edit them DocsWhere("Users", $"{{'Name':'{username}'}}") .WantModifyExistingDocuments() .OpenForm(result => { if (result.Result) { ShowNotification("User updated"); } }); } else { // User doesn't exist - create them Client.WantCreateNewDocumentFor("Users") .SetDocument($"{{'Name':'{username}'}}") .OpenForm(result => { if (result.Result) { ShowNotification("New user created"); } }); } }
// 1. Start with data source DocsWhere("Collection", filter) // 2. Add query refinements (optional) .AndWhere(additionalFilter) .OrderBy(sortField) .Take(limit) // 3. Configure dimensions (optional) .SetUIDimension(uiConfig) .SetThemeDimension(theme) .ExtendDocument(extraData) // 4. Declare intention .WantModifyExistingDocuments() // 5. Optional: Conditional skip .SkipOpenFormIf(condition) // 6. Open and handle result .OpenForm(result => { if (result.Result) { // Success! } });