Chandra

 view release on metacpan or  search on metacpan

xs/form.xs  view on Meta::CPAN


SV *
fields(self)
    SV *self
CODE:
{
    HV *hv = (HV *)SvRV(self);
    SV **fields_svp = hv_fetchs(hv, "_fields", 0);
    AV *fields = (AV *)SvRV(*fields_svp);
    AV *names = newAV();
    I32 i, len = av_len(fields);

    for (i = 0; i <= len; i++) {
        SV **fref = av_fetch(fields, i, 0);
        if (fref && SvROK(*fref)) {
            HV *fhv = (HV *)SvRV(*fref);
            SV **n = hv_fetchs(fhv, "name", 0);
            if (n && SvOK(*n))
                av_push(names, newSVsv(*n));
        }
    }

    RETVAL = newRV_noinc((SV *)names);
}
OUTPUT:
    RETVAL

 # ---- action(coderef) - get/set action handler ----

SV *
action(self, ...)
    SV *self
CODE:
{
    HV *hv = (HV *)SvRV(self);

    if (items > 1) {
        if (!SvROK(ST(1)) || SvTYPE(SvRV(ST(1))) != SVt_PVCV)
            croak("action() requires a coderef");
        (void)hv_stores(hv, "_action", newSVsv(ST(1)));
        RETVAL = SvREFCNT_inc(self);
    } else {
        SV **act = hv_fetchs(hv, "_action", 0);
        RETVAL = (act && SvOK(*act)) ? newSVsv(*act) : &PL_sv_undef;
    }
}
OUTPUT:
    RETVAL

 # ---- dispatch(event_type, json_string) - internal: called from bridge ----

void
dispatch(self, event_type, json_str)
    SV *self
    SV *event_type
    SV *json_str
CODE:
{
    HV *hv = (HV *)SvRV(self);
    const char *evt = SvPV_nolen(event_type);
    SV *decoded;

    /* Decode JSON */
    {
        dSP;
        int count;
        ENTER;
        SAVETMPS;
        PUSHMARK(SP);
        XPUSHs(json_str);
        PUTBACK;
        count = call_pv("Cpanel::JSON::XS::decode_json", G_SCALAR | G_EVAL);
        SPAGAIN;
        if (SvTRUE(ERRSV) || count < 1) {
            PUTBACK;
            FREETMPS;
            LEAVE;
            return;
        }
        decoded = (count == 1) ? SvREFCNT_inc(POPs) : NULL;
        PUTBACK;
        FREETMPS;
        LEAVE;
    }

    if (!decoded || !SvROK(decoded) || SvTYPE(SvRV(decoded)) != SVt_PVHV) {
        if (decoded) SvREFCNT_dec(decoded);
        return;
    }

    if (strEQ(evt, "_form_submit")) {
        SV **act = hv_fetchs(hv, "_action", 0);
        if (act && SvROK(*act) && SvTYPE(SvRV(*act)) == SVt_PVCV) {
            HV *dhv = (HV *)SvRV(decoded);
            SV **data_svp = hv_fetchs(dhv, "data", 0);
            dSP;
            ENTER;
            SAVETMPS;
            PUSHMARK(SP);
            XPUSHs(data_svp ? *data_svp : &PL_sv_undef);
            PUTBACK;
            call_sv(*act, G_DISCARD);
            FREETMPS;
            LEAVE;
        }
    } else if (strEQ(evt, "_form_change") || strEQ(evt, "_form_input")) {
        HV *dhv = (HV *)SvRV(decoded);
        SV **field_svp = hv_fetchs(dhv, "field", 0);
        SV **value_svp = hv_fetchs(dhv, "value", 0);

        if (field_svp && SvOK(*field_svp)) {
            STRLEN flen;
            const char *fname = SvPV(*field_svp, flen);

            /* Field-specific handler */
            {
                SV **oc_svp = hv_fetchs(hv, "_on_change", 0);
                if (oc_svp && SvROK(*oc_svp)) {
                    HV *oc_hv = (HV *)SvRV(*oc_svp);
                    SV **cb = hv_fetch(oc_hv, fname, flen, 0);
                    if (cb && SvROK(*cb) && SvTYPE(SvRV(*cb)) == SVt_PVCV) {
                        dSP;
                        ENTER;
                        SAVETMPS;
                        PUSHMARK(SP);
                        XPUSHs(value_svp ? *value_svp : &PL_sv_undef);
                        PUTBACK;
                        call_sv(*cb, G_DISCARD);
                        FREETMPS;
                        LEAVE;
                    }
                }
            }

            /* Global on_change handler */
            {
                SV **gcb = hv_fetchs(hv, "_on_change_global", 0);
                if (gcb && SvROK(*gcb) && SvTYPE(SvRV(*gcb)) == SVt_PVCV) {
                    dSP;
                    ENTER;
                    SAVETMPS;
                    PUSHMARK(SP);
                    XPUSHs(*field_svp);
                    XPUSHs(value_svp ? *value_svp : &PL_sv_undef);
                    PUTBACK;
                    call_sv(*gcb, G_DISCARD);
                    FREETMPS;
                    LEAVE;
                }
            }
        }
    } else if (strEQ(evt, "_form_values")) {
        SV **cb = hv_fetchs(hv, "_get_values_cb", 0);
        if (cb && SvROK(*cb) && SvTYPE(SvRV(*cb)) == SVt_PVCV) {
            HV *dhv = (HV *)SvRV(decoded);
            SV **data_svp = hv_fetchs(dhv, "data", 0);
            dSP;
            ENTER;
            SAVETMPS;
            PUSHMARK(SP);
            XPUSHs(data_svp ? *data_svp : &PL_sv_undef);
            PUTBACK;
            call_sv(*cb, G_DISCARD);
            FREETMPS;
            LEAVE;
            (void)hv_stores(hv, "_get_values_cb", &PL_sv_undef);
        }
    }

    SvREFCNT_dec(decoded);
}

 # ---- _route_event(event_type, json_str) - class method: routes to correct form ----

void
_route_event(class, event_type, json_str)
    const char *class
    SV *event_type
    SV *json_str
CODE:
{
    SV *decoded;
    PERL_UNUSED_VAR(class);

    /* Decode JSON to extract the form id */
    {
        dSP;
        int count;
        ENTER;
        SAVETMPS;
        PUSHMARK(SP);
        XPUSHs(json_str);
        PUTBACK;
        count = call_pv("Cpanel::JSON::XS::decode_json", G_SCALAR | G_EVAL);
        SPAGAIN;
        if (SvTRUE(ERRSV) || count < 1) {
            PUTBACK;
            FREETMPS;
            LEAVE;
            return;
        }
        decoded = (count == 1) ? SvREFCNT_inc(POPs) : NULL;
        PUTBACK;
        FREETMPS;
        LEAVE;
    }

    if (!decoded || !SvROK(decoded) || SvTYPE(SvRV(decoded)) != SVt_PVHV) {
        if (decoded) SvREFCNT_dec(decoded);
        return;
    }

    /* Extract id from the decoded hash */
    {
        HV *dhv = (HV *)SvRV(decoded);
        SV **id_svp = hv_fetchs(dhv, "id", 0);
        if (id_svp && SvOK(*id_svp)) {
            STRLEN id_len;
            const char *id = SvPV(*id_svp, id_len);
            SV *form = _form_lookup(aTHX_ id, id_len);
            if (form) {
                /* Call $form->dispatch($event_type, $json_str) */
                dSP;
                ENTER;
                SAVETMPS;
                PUSHMARK(SP);
                XPUSHs(form);
                XPUSHs(event_type);
                XPUSHs(json_str);
                PUTBACK;
                call_method("dispatch", G_DISCARD);
                FREETMPS;
                LEAVE;
            }
        }
    }

    SvREFCNT_dec(decoded);
}

 # ---- attach(app) - register form and bind events on the app ----

SV *
attach(self, app)
    SV *self
    SV *app
CODE:
{
    HV *hv = (HV *)SvRV(self);
    SV **id_svp = hv_fetchs(hv, "id", 0);
    STRLEN id_len;
    const char *id;

    if (!id_svp || !SvOK(*id_svp))
        croak("attach(): form has no id");
    id = SvPV(*id_svp, id_len);

    /* Register this form in the global registry */
    _form_register(aTHX_ id, id_len, self);

    /* Bind the four form events on the app (once globally) */
    if (!_form_events_bound) {
        static const char *events[] = {
            "_form_submit", "_form_change", "_form_input", "_form_values"
        };
        int i;
        for (i = 0; i < 4; i++) {
            dSP;
            SV *evt_name = sv_2mortal(newSVpv(events[i], 0));
            SV *evt_copy = newSVpv(events[i], 0);

            /* Build a closure: sub { Chandra::Form->_route_event('evt', $_[0]); 1 } */
            SV *cb;
            {
                SV *code_str = sv_2mortal(newSVpvf(
                    "sub { Chandra::Form->_route_event('%s', $_[0]); 1 }",
                    events[i]));
                cb = eval_pv(SvPV_nolen(code_str), TRUE);
                SvREFCNT_inc(cb);
            }

            ENTER;
            SAVETMPS;
            PUSHMARK(SP);
            XPUSHs(app);
            XPUSHs(evt_name);
            XPUSHs(sv_2mortal(newRV_noinc(cb)));
            PUTBACK;
            call_method("bind", G_DISCARD);
            FREETMPS;
            LEAVE;

            SvREFCNT_dec(evt_copy);
        }
        _form_events_bound = 1;
    }

    /* Inject the bind_js for this form */



( run in 0.839 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )