This is the documentation for concrete5 version 5.6 and earlier. View Current Documentation

update: This now loads javascript on view() instead of on_start() in the controller by overriding the block's controller. The code for this how-to is available in the marketplace for free.

Intuitively, if you wanted to change something about a block, you would copy that whole block out of core and change view.php to do what you want. The problem with this approach is that you are essentially "forking" from core. When that block gets updated, your modifications will need to be reapplied to stay current. Your block is then left without new functionality or bug fixes.

The goal of this tutorial is to show that oftentimes, that approach is unnecessary. Building a custom template is a better choice. If you have been thinking about modifying a block, or writing your first add-on, this is a good beginners' exercise. The technique itself demonstrates concrete5's agility and extensibility. Or, perhaps you just like the word "fancy." You're in the right place.

As an example, we will create a "Site Feedback" form similar to the one used in the relaunch of the c5 site.

This how-to works as a c5 package. If you aren't familiar with the c5 package structure, refer to this. If you don't feel like adding it as a package, all relevant code would also work as a standard template. If you don't know what a template is, learn that here.

Create a folder called form_fancybox in your c5 install's packages folder. Now paste or type the following into a file called controller.php inside that folder.

<?
defined('C5_EXECUTE') or die(_("Access Denied."));


class FormFancyboxPackage extends Package {

    protected $pkgHandle = 'form_fancybox';
    protected $appVersionRequired = '5.4.1.1';
    protected $pkgVersion = '1.0';

    public function getPackageDescription() {
        return t("Wraps your forms in a fancybox!");
    }
   /*
public function on_start() {
    View::getInstance()->addHeaderItem(Loader::helper('html')->javascript('jquery.form.js')); //yes, this is forcing an additional js file to every page.  Including the js in the js/ folder will cause strange behavior.  This will also keeps us on the same 'jquery.form.js'
    //yeah this is no good, but you can override view and call parent::view() and it will work.
}
    */



    public function getPackageName() {
        return t("Fancy Forms FOR FREE");
    }

    public function install() {
        $pkg = parent::install();

        BlockType::installBlockTypeFromPackage('form_fancybox', $pkg);
    }
}

Now, go into your dashboard and verify that the package is showing up. If it is, you pasted or typed correctly. Install the package.

Inside your form_fancybox folder, add folders to create this path: blocks/form/templates/fancy/ Within this new fancy/ folder is where a fresh view.php will be created. To understand the value of this technique, read Andrew's article about wrapper templates before proceeding.

For now, put the following code into your view.php. If you read Andrew's article you know what it does.

<?php
defined('C5_EXECUTE') or die("Access Denied.");

$bvt = new BlockViewTemplate($b);
$bvt->setBlockCustomTemplate(false);
include($bvt->getTemplate());

Make a form on some page and set its custom template to 'Fancy.' Note that it is not fancy and make sure everything still works.

By now you should have a sense of where we're headed with this. You should download the Fancybox lightbox from fancybox.net. Go ahead and unpack that to your desktop or a scratch folder in your LAMP server and play around with the included demo. It's a good idea to look at their how-to.

The first thing we are going to do is to get to put the form in the fancy box.
So, move all the .png files, the blank.gif and the jquery.fancybox.1.3.4.css files from the fancybox/ folder into a new folder within your fancy/ folder called "css." Create another folder called "js" and copy jquery.fancybox-1.3.4.pack.js. You may notice there are no includes to any of these files anywhere. That is because c5 is smart enough to take care of this itself.

Go into view.php and modify it to look like this:

<?php
defined('C5_EXECUTE') or die("Access Denied.");


$th = Loader::helper('concrete/urls');

$bt = $b->getBlockTypeObject();
?>
    <script> var CCM_FORM_FANCYBOX_TOOLS_URL = "<?=$th->getToolsURL('fancy_ajax_response','form_fancybox');?>";</script>
    <a href="#fancyFormContainer" class="fancyform">Fancy Form</a>
    <div style="display:none">
        <div id="fancyFormContainer" class="fancybox" >
        <span id="fancyMessages"></span>
        <span id="fancyErrors"></span>
        <?php
        $bvt = new BlockViewTemplate($b);
        $bvt->setBlockCustomTemplate(false);
        include($bvt->getTemplate());
        ?>
        </div>
    </div>

Go into fancy/js/ and put the following into fancy_forms.js

$(document).ready(function() {
    $("a.fancyform").fancybox();
});

So now the default form is ready to display as a fancy box from the link. Everything should still work. If you don't nknow what fancybox does, basically what's happening here is that the form is inside a div that is itself inside a hidden div. Fancybox watches for links to stuff it is in charge of and displays it in a pleasing manner.

This is still not fancy enough. We need to make it submit using AJAX.

If you're not familiar with jquery and javascript, read this other how-to by Andrew about how javascript / jquery work with c5 and also this tutorial about jquery's AJAX functionality.

The package controller loads the jquery forms plugin to all page views. This is a little overhead, but since the block has already rendered there is no obvious "clean" way to include it. You might want to read about its options.

So now we are ready to do some AJAX with our form. Initially, I tried to just do a plain old jquery $.post of the form's data to the form submission URL. It worked, but not quite the way you would want. Not at all fancy. After some mentoring from the c5 team, I created a tool for dealing with form submission which handles the AJAX request for us.

In form_fancybox/ put a folder called tools/ next to blocks/, and put the following code into a new file called fancy_ajax_response.php.

<?php
/*
Having the class right here is very hacky.  If this were much more than five lines, it would be in its own file. */


class FancyFormBlockController extends FormBlockController { //by extending stuff from core, you can add functionality. Hopefully, in such a way that it won't break in future updates.
    public function setNoSubmitFormRedirect($doIt){
        $this->noSubmitFormRedirect = $doIt; //this is a protected variable with no 'set' method in core.  
    }
    public function view() {
         $this->addHeaderItem(Loader::helper('html')->javascript('jquery.form.js'));
         parent::view();
    }
}

if( $_GET['bID'] ) { 

    $b = Block::getByID( intval($_GET['bID']) );
    $c = $b->getBlockCollectionObject();
    if(!$b) throw new Exception(t('File not found.'));

    $formController = new FancyFormBlockController($b);//To see why this is cool, delete 'Fancy' and note the error.  We're fixing a (admittedly simple) problem without having to modify the original source. OOP!
    $formController->setNoSubmitFormRedirect(true);

    $formController->action_submit_form(); //as long as what results from this is relatively the same, this will always work.

    $resp = $formController->get('formResponse');
    $errors = $formController->get('errors');

    if(isset($errors)){ //these errors come in from the controller as an object.  It would be more clever to fix this client side. 
        $errorstring = '';
        foreach ($errors as $error) {
            $errorstring .= $error."\n";
        }
    }

    $json = Loader::helper('json'); //if you have not messed around in c5 much, visit concrete/helpers/ within your concrete5 installation.

    $response = array(
        'resp' => $resp,
        'errors' => $errorstring,
        'thankyou' => $formController->thankyouMsg
    );

    echo $json->encode($response);

}
?>

There. So now you have something for form.jquery.js to talk to. Add this above the <a href="#fancy... line in view.php:

<script> var CCM_FORM_FANCYBOX_TOOLS_URL = "<?=$th->getToolsURL('fancy_ajax_response','form_fancybox');?>";</script>

Back in your templates/fancy/ folder add this to js/fancy_forms.js:

//this all goes right after the .fancybox() line.

    $.ajaxSetup({
        cache: false
    });

    $('div .fancybox > form').bind('submit', function() { //capture form submission
        var action = $(this).attr('action');
        s = action.split("?");
        action = s[1];
        $(this).ajaxSubmit({
            url : CCM_FORM_FANCYBOX_TOOLS_URL +"?"+ action,
            //target : '#fancyFormContainer', //uncomment this if you want to see the server's response in your fancy box.
            dataType : 'json',
            success : function(response){
                var thankyou = true; //usually we want to send a thank you message, but not if there are errors.
                console.log(response);
                if (response.resp) {
                    $('#fancyMessages').html('<p>'+response.resp+'</p>');
                    thankyou = false;
                }
                if (response.errors) {
                    $('#fancyErrors').html('<p>'+response.errors+'</p>');
                    thankyou = false;
                }
                if (thankyou)
                    $('#fancyFormContainer').html(response.thankyou);
            }
        });
        return false; // <-- important! (prevents default behavior)
    });

Ok, so now go look at the form. It should be fully fancy and AJAXified. But what about the cool "bump in" style button?

Change the <a href... line to

 <div id="fancybump"><a href="#fancyFormContainer" class="fancyform"></a></div>

and in fancy/css/ create fancy_form.css:

#fancybump a{
    display: block;
    background: url('site_feedback_tab.png');
    height: 127px;
    width: 31px;
    position: absolute;
    top: 256px;
    left: -4px;
}

#fancybump a:hover {
    left: 0px;
}

Just make sure you have the .png in your css folder and check out your forms again.

Fancy.

But yeah, this is a good exercise for anybody looking into extending concrete5 in a way that shouldn't plant the seed for headaches. In a perfect world, you can extract all the information you need from any block, whether it lives in core or as an addon, pass it off to client, make your addon, etc and never have to touch anything you aren't responsible for ever again.

Loading Conversation