CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-knockout

JavaScript MVVM library that makes it easier to create rich, responsive UIs with automatic UI synchronization through observable data binding

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

components.mddocs/

Component System

Reusable UI component system with view model and template composition, registration, and loading capabilities for modular application architecture. Components provide encapsulation and reusability by combining templates and view models into self-contained widgets.

Capabilities

Component Registration

Functions for registering, unregistering, and managing component definitions.

/**
 * Register a component with a name and configuration
 * @param name - Component name (must be unique)
 * @param config - Component configuration object
 */
function register(name: string, config: ComponentConfig): void;

/**
 * Unregister a component and clear its cached definition
 * @param name - Component name to unregister
 */
function unregister(name: string): void;

/**
 * Check if a component is registered
 * @param name - Component name to check
 * @returns True if component is registered
 */
function isRegistered(name: string): boolean;

/**
 * Clear cached definition for a component (forces reload)
 * @param name - Component name
 */
function clearCachedDefinition(name: string): void;

Usage Examples:

import ko from "knockout";

// Register a simple component
ko.components.register("hello-world", {
    template: "<h1>Hello, <span data-bind='text: name'></span>!</h1>",
    viewModel: function(params) {
        this.name = params.name || "World";
    }
});

// Register component with external template
ko.components.register("user-profile", {
    template: { element: "user-profile-template" },
    viewModel: UserProfileViewModel
});

// Check if component exists
if (ko.components.isRegistered("hello-world")) {
    console.log("Component is available");
}

// Unregister component
ko.components.unregister("hello-world");

Component Configuration

Configuration options for defining component templates and view models.

interface ComponentConfig {
    /** Template configuration */
    template: TemplateConfig;
    /** View model configuration (optional) */
    viewModel?: ViewModelConfig;
    /** Whether to load synchronously */
    synchronous?: boolean;
    /** AMD require path for dynamic loading */
    require?: string;
}

type TemplateConfig = 
    | string                    // Inline HTML string
    | Node[]                    // Array of DOM nodes
    | DocumentFragment          // Document fragment
    | TemplateElement          // Element reference
    | RequireConfig;           // AMD module

interface TemplateElement {
    element: string | Node;     // Element ID or DOM node
}

type ViewModelConfig = 
    | ViewModelConstructor      // Constructor function
    | ViewModelFactory         // Factory with createViewModel method
    | ViewModelStatic          // Static instance
    | RequireConfig;           // AMD module

interface ViewModelConstructor {
    new(params?: any): ViewModel;
}

interface ViewModelFactory {
    createViewModel: CreateViewModel;
}

interface ViewModelStatic {
    instance: any;
}

interface RequireConfig {
    require: string;           // AMD module path
}

type CreateViewModel = (params: any, componentInfo: ComponentInfo) => ViewModel;

interface ViewModel {
    dispose?: () => void;
    koDescendantsComplete?: (node: Node) => void;
}

Usage Examples:

import ko from "knockout";

// String template with constructor view model
ko.components.register("simple-counter", {
    template: `
        <div>
            <p>Count: <span data-bind="text: count"></span></p>
            <button data-bind="click: increment">+</button>
            <button data-bind="click: decrement">-</button>
        </div>
    `,
    viewModel: function(params) {
        this.count = ko.observable(params.initialCount || 0);
        this.increment = () => this.count(this.count() + 1);
        this.decrement = () => this.count(this.count() - 1);
    }
});

// Element template with factory view model  
ko.components.register("user-editor", {
    template: { element: "user-editor-template" },
    viewModel: {
        createViewModel: function(params, componentInfo) {
            return new UserEditorViewModel(params.user, componentInfo.element);
        }
    }
});

// AMD module loading
ko.components.register("external-widget", {
    template: { require: "text!templates/widget.html" },
    viewModel: { require: "viewmodels/widget" }
});

// Static instance view model
ko.components.register("singleton-service", {
    template: "<div>Service Status: <span data-bind='text: status'></span></div>",
    viewModel: { 
        instance: globalServiceInstance 
    }
});

Component Loading

Functions for retrieving and loading component definitions.

/**
 * Get component definition asynchronously
 * @param name - Component name
 * @param callback - Callback receiving component definition
 * @returns Request identifier
 */
function get(name: string, callback: (definition: Component, config: ComponentConfig) => void): string;
interface Component {
    /** Compiled template nodes */
    template: Node[];
    /** View model factory function (optional) */
    createViewModel?: CreateViewModel;
}

interface ComponentInfo {
    /** Component's root DOM element */
    element: Node;
    /** Original template nodes before component replaced them */
    templateNodes: Node[];
}

Usage Examples:

import ko from "knockout";

// Get component definition
ko.components.get("user-profile", function(definition, config) {
    console.log("Component loaded:", definition);
    console.log("Template nodes:", definition.template);
    
    if (definition.createViewModel) {
        const viewModel = definition.createViewModel({
            userId: 123
        }, {
            element: document.getElementById("target"),
            templateNodes: []
        });
    }
});

Component Loaders

Extensible loader system for customizing component loading behavior.

/**
 * Array of component loaders (processed in order)
 */
const loaders: Loader[];

/**
 * Default component loader implementation
 */
const defaultLoader: DefaultLoader;
interface Loader {
    /** Get component configuration */
    getConfig?(name: string, callback: (config: ComponentConfig | null) => void): void;
    
    /** Load complete component */
    loadComponent?(name: string, config: ComponentConfig, callback: (component: Component | null) => void): void;
    
    /** Load template only */
    loadTemplate?(name: string, templateConfig: TemplateConfig, callback: (template: Node[] | null) => void): void;
    
    /** Load view model only */
    loadViewModel?(name: string, viewModelConfig: ViewModelConfig, callback: (createViewModel: CreateViewModel | null) => void): void;
}

interface DefaultLoader extends Loader {
    getConfig(name: string, callback: (config: ComponentConfig | null) => void): void;
    loadComponent(name: string, config: ComponentConfig, callback: (component: Component) => void): void;
    loadTemplate(name: string, templateConfig: TemplateConfig, callback: (template: Node[]) => void): void;
    loadViewModel(name: string, viewModelConfig: ViewModelConfig, callback: (createViewModel: CreateViewModel) => void): void;
}

Usage Examples:

import ko from "knockout";

// Custom loader for database-backed components
const databaseLoader = {
    getConfig: function(name, callback) {
        // Load component config from database
        fetch(`/api/components/${name}`)
            .then(response => response.json()) 
            .then(config => callback(config))
            .catch(() => callback(null));
    }
};

// Add custom loader (processed before default loader)
ko.components.loaders.unshift(databaseLoader);

// Custom loader for CSS-in-JS templates
const cssInJsLoader = {
    loadTemplate: function(name, templateConfig, callback) {
        if (templateConfig.cssInJs) {
            // Custom loading logic for CSS-in-JS templates
            loadCssInJsTemplate(templateConfig.cssInJs)
                .then(nodes => callback(nodes))
                .catch(() => callback(null));
        } else {
            callback(null); // Let other loaders handle
        }
    }
};

ko.components.loaders.unshift(cssInJsLoader);

Component Binding

Using components in templates via the component binding.

Usage in HTML:

<!-- Basic component usage -->
<div data-bind="component: 'hello-world'"></div>

<!-- Component with parameters -->
<div data-bind="component: { name: 'user-profile', params: { userId: currentUserId } }"></div>

<!-- Dynamic component name -->
<div data-bind="component: { name: selectedComponentName, params: componentParams }"></div>

<!-- Component with observable parameters -->
<div data-bind="component: { 
    name: 'data-grid', 
    params: { 
        data: gridData, 
        pageSize: pageSize,
        onRowClick: handleRowClick 
    } 
}"></div>

Custom Elements

Using components as custom HTML elements.

Usage in HTML:

<!-- Component as custom element -->
<hello-world params="name: userName"></hello-world>

<!-- Complex parameters -->
<user-profile params="
    user: selectedUser, 
    editable: isEditable,
    onSave: saveUser,
    onCancel: cancelEdit
"></user-profile>

<!-- Observable parameters -->
<data-table params="
    items: tableData,
    pageSize: itemsPerPage,
    sortColumn: currentSortColumn,
    sortDirection: sortDirection
"></data-table>

Component Lifecycle

Managing component lifecycle with disposal and completion events.

Usage Examples:

import ko from "knockout";

// View model with lifecycle methods
function MyComponentViewModel(params, componentInfo) {
    const self = this;
    
    // Initialize component
    this.data = ko.observable(params.initialData);
    this.isLoading = ko.observable(false);
    
    // Called when component descendants are complete
    this.koDescendantsComplete = function(node) {
        console.log("Component descendants complete", node);
        // Initialize plugins, setup event handlers, etc.
    };
    
    // Called when component is disposed
    this.dispose = function() {
        console.log("Component disposed");
        // Cleanup subscriptions, timers, event handlers, etc.
        if (self.subscription) {
            self.subscription.dispose();
        }
    };
    
    // Setup subscriptions
    this.subscription = this.data.subscribe(function(newValue) {
        // Handle data changes
    });
}

ko.components.register("lifecycle-component", {
    template: "<div>Component content</div>",
    viewModel: MyComponentViewModel
});

Component Communication

Patterns for communication between components and their parents.

Usage Examples:

import ko from "knockout";

// Parent-to-child communication via parameters
function ParentViewModel() {
    this.childData = ko.observable("Hello Child");
    this.childConfig = ko.observable({ theme: "dark" });
}

// Child-to-parent communication via callbacks
function ChildViewModel(params) {
    this.data = params.data;
    this.config = params.config;
    
    this.handleClick = function() {
        // Notify parent of events
        if (params.onChildClick) {
            params.onChildClick("button clicked", this);
        }
    };
    
    this.updateParent = function() {
        // Update parent data
        if (params.onDataUpdate) {
            params.onDataUpdate({ newValue: "Updated from child" });
        }
    };
}

ko.components.register("parent-component", {
    template: `
        <div>
            <child-component params="
                data: childData,
                config: childConfig,
                onChildClick: handleChildClick,
                onDataUpdate: handleDataUpdate
            "></child-component>
        </div>
    `,
    viewModel: function() {
        const self = this;
        this.childData = ko.observable("Parent data");
        this.childConfig = ko.observable({ setting: "value" });
        
        this.handleChildClick = function(message, childViewModel) {
            console.log("Child clicked:", message);
        };
        
        this.handleDataUpdate = function(updateData) {
            console.log("Child updated:", updateData);
        };
    }
});

Component Name Detection

Utility function for detecting component names from DOM nodes.

/**
 * Get component name for a DOM node (if it represents a component)
 * @param node - DOM node to check
 * @returns Component name or null
 */
function getComponentNameForNode(node: Node): string | null;

Usage Examples:

import ko from "knockout";

// Check if element is a component
const element = document.getElementById("my-element");
const componentName = ko.components.getComponentNameForNode(element);

if (componentName) {
    console.log(`Element is component: ${componentName}`);
} else {
    console.log("Element is not a component");
}

docs

binding.md

components.md

index.md

observables.md

performance.md

templates.md

utils.md

tile.json