or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

behaviors.mdcollection-views.mdindex.mdobjects-and-application.mdregions.mdutilities.mdviews.md
tile.json

behaviors.mddocs/

Behaviors

Reusable functionality system for views, allowing shared behavior patterns across different view types through composition rather than inheritance.

Capabilities

Behavior Class

Behaviors provide a way to share common functionality across views without using inheritance. They can handle events, manage UI elements, and provide additional methods to views.

/**
 * Reusable functionality for views through composition
 * @param options - Configuration options for the behavior
 * @param view - The view this behavior is attached to
 */
const Behavior: {
  new (options?: BehaviorOptions, view?: View): Behavior;
  extend(properties: object, classProperties?: object): typeof Behavior;
};

interface Behavior {
  /** Initialize the behavior (noop method for overriding) */
  initialize(): void;
  
  /** jQuery-like DOM query within the associated view */
  $(selector: string): JQuery;
  
  /** Destroy the behavior and clean up resources */
  destroy(): void;
  
  /** Proxy view properties to the behavior */
  proxyViewProperties(): void;
  
  /** Bind UI elements defined in the behavior's ui hash */
  bindUIElements(): void;
  
  /** Unbind UI elements */
  unbindUIElements(): void;
  
  /** Get a UI element by name */
  getUI(name: string): JQuery;
  
  /** Delegate model and collection events */
  delegateEntityEvents(): void;
  
  /** Undelegate model and collection events */
  undelegateEntityEvents(): void;
  
  /** Reference to the parent view */
  view: View;
  
  /** Merged UI hash from behavior and view */
  ui: UIHash;
  
  /** Unique identifier for the behavior */
  cid: string;
}

interface BehaviorOptions {
  /** Hash of UI element selectors */
  ui?: UIHash;
  
  /** Hash of DOM event handlers */
  events?: EventsHash;
  
  /** Hash of event triggers */
  triggers?: TriggersHash;
  
  /** Hash of model event handlers */
  modelEvents?: EventsHash;
  
  /** Hash of collection event handlers */
  collectionEvents?: EventsHash;
}

Usage Examples:

import { Behavior, View } from "backbone.marionette";

// Confirmation behavior for dangerous actions
class ConfirmationBehavior extends Behavior {
  ui() {
    return {
      dangerousButtons: '.js-dangerous'
    };
  }
  
  events() {
    return {
      'click @ui.dangerousButtons': 'onDangerousClick'
    };
  }
  
  onDangerousClick(event) {
    event.preventDefault();
    
    const action = event.currentTarget.dataset.action;
    const message = `Are you sure you want to ${action}?`;
    
    if (confirm(message)) {
      // Trigger the original action
      this.view.trigger('confirmed:action', action, event.currentTarget);
    }
  }
}

// Tooltip behavior
class TooltipBehavior extends Behavior {
  ui() {
    return {
      tooltipTriggers: '[data-tooltip]'
    };
  }
  
  events() {
    return {
      'mouseenter @ui.tooltipTriggers': 'showTooltip',
      'mouseleave @ui.tooltipTriggers': 'hideTooltip'
    };
  }
  
  showTooltip(event) {
    const text = event.currentTarget.dataset.tooltip;
    // Show tooltip logic
    console.log('Showing tooltip:', text);
  }
  
  hideTooltip() {
    // Hide tooltip logic
    console.log('Hiding tooltip');
  }
}

// View using multiple behaviors
class ProductView extends View {
  template(data) {
    return `
      <h3>${data.name}</h3>
      <p>${data.description}</p>
      <button class="js-dangerous" data-action="delete" data-tooltip="This will permanently delete the product">
        Delete Product
      </button>
      <button data-tooltip="Edit product details">Edit</button>
    `;
  }
  
  behaviors() {
    return {
      confirmation: {
        behaviorClass: ConfirmationBehavior
      },
      tooltip: {
        behaviorClass: TooltipBehavior
      }
    };
  }
  
  onConfirmedAction(action, element) {
    if (action === 'delete') {
      this.model.destroy();
    }
  }
}

// Usage
const product = new Backbone.Model({
  name: 'Laptop',
  description: 'High-performance laptop'
});

const productView = new ProductView({ model: product });
document.body.appendChild(productView.render().el);

Behavior Lifecycle and Integration

Methods for integrating behaviors with views and managing their lifecycle.

/**
 * Initialize the behavior (called automatically during view construction)
 * Override this method to add custom initialization logic
 */
initialize(): void;

/**
 * Destroy the behavior and clean up resources
 * Called automatically when the parent view is destroyed
 */
destroy(): void;

/**
 * Proxy specific view properties to the behavior
 * Makes view properties accessible directly on the behavior
 */
proxyViewProperties(): void;

Usage Examples:

// Behavior with custom initialization and cleanup
class ModelSyncBehavior extends Behavior {
  initialize() {
    this.syncInterval = setInterval(() => {
      if (this.view.model) {
        this.syncModel();
      }
    }, 30000); // Sync every 30 seconds
  }
  
  syncModel() {
    console.log('Syncing model...');
    this.view.model.fetch();
  }
  
  onDestroy() {
    if (this.syncInterval) {
      clearInterval(this.syncInterval);
    }
  }
}

// Behavior that proxies view properties
class AdvancedBehavior extends Behavior {
  initialize() {
    // Access view properties directly
    console.log('View model:', this.model);
    console.log('View collection:', this.collection);
  }
  
  someMethod() {
    // Can use view properties as if they were behavior properties
    if (this.model) {
      return this.model.get('someAttribute');
    }
  }
}

UI Element Management

Methods for managing UI elements within behaviors, similar to views.

/**
 * Bind UI elements defined in the behavior's ui hash
 * Merges with view's UI elements
 */
bindUIElements(): void;

/**
 * Unbind UI elements from the behavior
 */
unbindUIElements(): void;

/**
 * Get a UI element by name from the merged ui hash
 * @param name - UI element name
 * @returns jQuery object for the UI element
 */
getUI(name: string): JQuery;

/**
 * jQuery-like DOM query within the associated view's element
 * @param selector - CSS selector
 * @returns jQuery object
 */
$(selector: string): JQuery;

Usage Examples:

// Behavior with complex UI interactions
class FormValidationBehavior extends Behavior {
  ui() {
    return {
      inputs: 'input, textarea, select',
      submitButton: '.js-submit',
      errorContainer: '.js-errors'
    };
  }
  
  events() {
    return {
      'blur @ui.inputs': 'validateField',
      'click @ui.submitButton': 'validateForm'
    };
  }
  
  validateField(event) {
    const $field = this.$(event.currentTarget);
    const value = $field.val();
    const fieldName = $field.attr('name');
    
    // Validation logic
    const isValid = this.isFieldValid(fieldName, value);
    
    if (isValid) {
      $field.removeClass('error');
    } else {
      $field.addClass('error');
    }
  }
  
  validateForm(event) {
    event.preventDefault();
    
    let isFormValid = true;
    const errors = [];
    
    this.getUI('inputs').each((index, input) => {
      const $input = this.$(input);
      const value = $input.val();
      const fieldName = $input.attr('name');
      
      if (!this.isFieldValid(fieldName, value)) {
        isFormValid = false;
        errors.push(`${fieldName} is invalid`);
      }
    });
    
    if (isFormValid) {
      this.view.trigger('form:valid');
    } else {
      this.showErrors(errors);
    }
  }
  
  showErrors(errors) {
    const $errorContainer = this.getUI('errorContainer');
    $errorContainer.html(errors.map(error => `<div class="error">${error}</div>`).join(''));
  }
  
  isFieldValid(fieldName, value) {
    // Custom validation logic
    return value && value.length > 0;
  }
}

Event Delegation

Methods for handling model, collection, and custom events within behaviors.

/**
 * Delegate model and collection events defined in behavior options
 * Called automatically during behavior initialization
 */
delegateEntityEvents(): void;

/**
 * Undelegate model and collection events
 * Called automatically during behavior cleanup
 */
undelegateEntityEvents(): void;

Usage Examples:

// Behavior that responds to model changes
class ModelWatchBehavior extends Behavior {
  modelEvents() {
    return {
      'change': 'onModelChange',
      'change:status': 'onStatusChange',
      'sync': 'onModelSync',
      'error': 'onModelError'
    };
  }
  
  collectionEvents() {
    return {
      'add': 'onModelAdded',
      'remove': 'onModelRemoved',
      'reset': 'onCollectionReset'
    };
  }
  
  onModelChange(model) {
    console.log('Model changed:', model.changedAttributes());
    this.view.render(); // Re-render view on model change
  }
  
  onStatusChange(model, status) {
    console.log('Status changed to:', status);
    this.updateStatusIndicator(status);
  }
  
  onModelSync(model) {
    console.log('Model synced successfully');
    this.showSuccessMessage();
  }
  
  onModelError(model, error) {
    console.log('Model error:', error);
    this.showErrorMessage(error.message);
  }
  
  onModelAdded(model, collection) {
    console.log('Model added to collection:', model);
  }
  
  onModelRemoved(model, collection) {
    console.log('Model removed from collection:', model);
  }
  
  onCollectionReset(collection) {
    console.log('Collection reset with', collection.length, 'models');
  }
  
  updateStatusIndicator(status) {
    const $indicator = this.$('.status-indicator');
    $indicator.removeClass('active inactive').addClass(status);
  }
  
  showSuccessMessage() {
    this.$('.message').text('Changes saved successfully').addClass('success');
  }
  
  showErrorMessage(message) {
    this.$('.message').text(`Error: ${message}`).addClass('error');
  }
}

Properties

/** Unique identifier prefix for behaviors */
cidPrefix: 'mnb';

/** Reference to the parent view */
view: View;

/** Merged UI hash from behavior and view */
ui: UIHash;

/** Unique identifier for the behavior instance */
cid: string;

Advanced Usage Patterns

Configurable Behaviors

// Behavior with configuration options
class DropdownBehavior extends Behavior {
  defaults() {
    return {
      trigger: 'click',
      closeOnOutsideClick: true,
      animation: 'slide'
    };
  }
  
  initialize(options) {
    this.options = _.defaults(options || {}, this.defaults());
    this.setupDropdown();
  }
  
  setupDropdown() {
    // Configure dropdown based on options
    this.bindTriggerEvents();
    
    if (this.options.closeOnOutsideClick) {
      this.bindOutsideClickEvents();
    }
  }
  
  bindTriggerEvents() {
    const eventName = `${this.options.trigger} .js-dropdown-trigger`;
    this.view.delegateEvents({
      [eventName]: this.toggleDropdown.bind(this)
    });
  }
  
  toggleDropdown() {
    // Toggle with configured animation
    const $dropdown = this.$('.js-dropdown-content');
    
    if (this.options.animation === 'slide') {
      $dropdown.slideToggle();
    } else if (this.options.animation === 'fade') {
      $dropdown.fadeToggle();
    } else {
      $dropdown.toggle();
    }
  }
}

// Usage with configuration
class MenuView extends View {
  behaviors() {
    return {
      dropdown: {
        behaviorClass: DropdownBehavior,
        trigger: 'hover',
        animation: 'fade',
        closeOnOutsideClick: false
      }
    };
  }
}

Behavior Communication

// Behaviors can communicate with each other through the view
class TabsBehavior extends Behavior {
  events() {
    return {
      'click .js-tab': 'onTabClick'
    };
  }
  
  onTabClick(event) {
    const tabId = event.currentTarget.dataset.tab;
    this.activateTab(tabId);
    
    // Notify other behaviors
    this.view.trigger('behavior:tab:changed', tabId);
  }
  
  activateTab(tabId) {
    this.$('.js-tab').removeClass('active');
    this.$(`[data-tab="${tabId}"]`).addClass('active');
  }
}

class ContentBehavior extends Behavior {
  initialize() {
    // Listen to tab changes
    this.view.on('behavior:tab:changed', this.showTabContent.bind(this));
  }
  
  showTabContent(tabId) {
    this.$('.js-tab-content').hide();
    this.$(`#${tabId}-content`).show();
  }
}

// View using both behaviors
class TabbedView extends View {
  behaviors() {
    return {
      tabs: TabsBehavior,
      content: ContentBehavior
    };
  }
}

Type Definitions

interface BehaviorHash {
  [name: string]: BehaviorDefinition;
}

interface BehaviorDefinition {
  behaviorClass: typeof Behavior;
  [option: string]: any;
}