Chandra

 view release on metacpan or  search on metacpan

lib/Chandra/Component.pm  view on Meta::CPAN


# ── Lifecycle ──────────────────────────────────────────────

sub mount {
    my ($self, $app, $selector) = @_;
    comp__app $self, $app;
    comp__selector $self, $selector;

    # Bind the action dispatcher for this component
    my $cid = $self->_ensure_cid;
    $app->bind("_comp_action_$cid", sub {
        my ($action, @args) = @_;
        my $method = "on_$action";
        if ($self->can($method)) {
            return $self->$method(@args);
        }
        warn "Chandra::Component: no handler '$method' on " . ref($self) . "\n";
        return undef;
    });

    # Render and inject
    my $html = $self->_wrap_render;
    $app->update($selector, $html);

    comp__mounted $self, 1;
    $self->on_mount if $self->can('on_mount');

    # Mount children
    for my $child (@{comp__children $self}) {
        my $child_cid = $child->_cid;
        $child->mount($app, "#$child_cid");
    }

    return $self;
}

sub unmount {
    my ($self) = @_;
    my $app = comp__app $self;

    # Unmount children first
    for my $child (@{comp__children $self}) {
        $child->unmount;
    }

    $self->on_unmount if $self->can('on_unmount');

    # Remove from DOM
    my $cid = $self->_ensure_cid;
    if ($app) {
        $app->eval("var _e=document.getElementById('$cid');if(_e)_e.remove();");
    }

    comp__mounted $self, 0;
    comp__app $self, undef;
    delete $_comp_registry{$cid};

    return $self;
}

sub update {
    my ($self) = @_;
    my $app = comp__app $self;
    return $self unless $app && comp__mounted $self;

    my $cid = $self->_ensure_cid;
    my $inner = $self->_render_inner;
    $app->update("#$cid", $inner);

    $self->on_update if $self->can('on_update');

    return $self;
}

# Update only a sub-section of the component by a stable CSS selector.
# The selector is relative to the component's root element.
# The $html should already have actions rewritten.
sub update_part {
    my ($self, $sub_id, $html) = @_;
    my $app = comp__app $self;
    return $self unless $app && comp__mounted $self;

    $html = $self->_rewrite_actions($html);
    $app->update("#$sub_id", $html);

    return $self;
}

# ── Child composition ──────────────────────────────────────

sub child {
    my ($self, $component) = @_;
    comp__parent $component, $self;
    my $children = comp__children $self;
    push @$children, $component;
    comp__children $self, $children;
    return $self;
}

sub render_children {
    my ($self) = @_;
    my $html = '';
    for my $child (@{comp__children $self}) {
        $html .= $child->_wrap_render;
    }
    return $html;
}

# ── Internal ───────────────────────────────────────────────

my %_INPUT_TAGS = map { $_ => 1 } qw(input select textarea);

sub _ensure_cid {
    my ($self) = @_;
    my $cid = comp__cid $self;
    unless ($cid) {
        $cid = '_comp_' . ++$_comp_id_counter;
        comp__cid $self, $cid;
        $_comp_registry{$cid} = $self;
    }
    return $cid;
}

sub _action_to_js {
    my ($self, $tag, $action) = @_;
    my $cid = $self->_ensure_cid;
    my ($method, @params) = split /:/, $action;
    my $args = Cpanel::JSON::XS::encode_json([$method, @params]);
    $args =~ s/"/"/g;

    my $invoke = "window.chandra.invoke("_comp_action_$cid"";

    if ($_INPUT_TAGS{$tag}) {
        if ($tag eq 'select') {
            # Selects fire immediately on change
            return ('onchange',
                "var _a=${args};_a.push(this.value);${invoke},_a)");
        }



( run in 1.917 second using v1.01-cache-2.11-cpan-13bb782fe5a )