Monday, September 27, 2010

Re: JS helper

On Mon, Sep 27, 2010 at 4:56 AM, lyba@poczta.fm <lyba@poczta.fm> wrote:
> Hi,
>
> I needed a simple Ajax feature to load box with additional info and
> close it after reading. I am new to Ajax and seldom used JS scripts
> till now. I need your opinion/help on optimizing following piece of
> code. The JS helper documentation is terrible - I'll share my
> experience on this at the end of this post for these interested.
> Please advice on questions that follow the code.
>
> 1. I have 'ajaxInfoBox' element that is called with an ID of the
> object I want to display. I have a need of few on the page hence
> element - to keep unique div ids.
> 2. Element displays icons with arrows down (opens box when clicked)
> and up (closes box when clicked). Additionally they hide/show
> alternatively themselves. Also I have a hidden busy indicator on main
> layout that is copied and displayed within the box while the box data
> is being loaded.
> 3. The content of the box comes (Ajax request) from another
> controller, which prepares set of data and renders it through
> dedicated HTML view. To makes things simple (I am not efficient in JS)
> I prefer using HTML views rather then JSON and JQuery loaded by
> inclusions functions.
>
> Here is the 'ajaxInfoBox' element code (bare functionality, no
> exceptions):
>
>        echo '<div class="AjaxCommands">';
>        echo '<A href="#" id="OpenCommand'.$Id.'" alt="Info">'.$this->Html-
>>image('icons/Colored/PNG/arrow_down.png').'</A>';
>        echo $this->Js->get('#OpenCommand'.$Id)->event('click',
>                $this->Js->request(array('controller' => 'Characters', 'action' =>
> 'displayCharacter', $Id), array (
>                        'update' => '#InfoBox'.$Id,
>                        'before' =>'$("#busy-indicator").clone().appendTo("#InfoBox'.
> $Id.'").show();'
>                        . '$("#InfoBox'.$Id.'").show();',
>                ))
>        );
>        echo $this->Js->get('#OpenCommand'.$Id)->event('click', '$
> ("#OpenCommand'.$Id.'").hide();');
>        echo $this->Js->get('#OpenCommand'.$Id)->event('click', '$
> ("#CloseCommand'.$Id.'").show();');
>        echo '<A href="#" id="CloseCommand'.$Id.'" alt="Close"
> style="display: none">'.$this->Html->image('icons/Colored/PNG/
> arrow_top.png').'</A>';
>        echo $this->Js->get('#CloseCommand'.$Id)->event('click', '$
> ("#InfoBox'.$Id.'").empty();');
>        echo $this->Js->get('#CloseCommand'.$Id)->event('click', '$
> ("#CloseCommand'.$Id.'").hide();');
>        echo $this->Js->get('#CloseCommand'.$Id)->event('click', '$
> ("#InfoBox'.$Id.'").hide();');
>        echo $this->Js->get('#CloseCommand'.$Id)->event('click', '$
> ("#OpenCommand'.$Id.'").show();');
>
>        echo '</div>';
>        echo '<div class="InfoBox" id=InfoBox'.$Id.'></div>';
>
> Few problems I faced with the above:
> - I have not managed to deploy toggle() JQuery function through the JS
> helper hence so many separate lines
> - used many event JS helpers functions as concatenating actions after
> one get() did not work as expected.
> - did not use url attribute for HTML->img as id is tied to image not
> the link therefore hiding id hides image but leaves a pixel of the
> selected link
> - not sure if copying and placing busy-indicator div into box and
> replacing it with target content is a good idea
>

Personally, I think there are bunch more problems than that. Cake's JS
helpers may be useful in some ways but, IMHO, they're more likely than
not to create a maintenance nightmare. Rather than filling the page
with endlessly repeated inline javascript, better to write your
handlers once in a dedicated function. I'll show you a basic way to do
so.

<div class="Commands">
<?php
echo $this->Html->link(
'display',
array(
'controller' => 'characters',
'action' => 'view',
$Id
),
array('class' => 'CmdOpen')
);
// other stuff ...
?>
</div>
<div class="Target"></div>


jquery:

$(function()
{
/* JQuery Loading plugin - explained below
*/
$.loading.element = '#indicator';
$.loading.align = 'center';

/* attach click event to links
*/
$('div.Commands a.CmdOpen').click(function(ev)
{
/* stop browser from following link
*/
ev.preventDefault();

/* get target div for this link
*/
var target = $(this).parent('div').next('div.Target');

/* change link image
*/
$(this).addClass('Active');

/* show loading img
*/
target.loading();

$.ajax({
url: $(this).attr('href');
cache: false,
success:
function(html)
{
/* hide loading img
*/
target.loading(false);

/* display content - could use an effect to make nicer
*/
target.html(html);
},
error:
function (XMLHttpRequest, status, errorThrown)
{
target.loading(false);
alert('error ...');
}
});
});


$('a.CmdOpen.Active').live('click', function(ev)
{
$(this).parent('div').next('.Target').slideUp().html('');
$this->removeClass('Active');
});
});

controller:

public function view($id = null)
{
// check id and fetch record ...

if ($this->RequestAction->isAjax())
{
Configure::write('debug', 0);
this->layout = 'ajax';
$this->viewPath = 'elements/characters';
$this->render('view');
}
}


In app/views/characters/view.ctp put:

$this->element('view');

And put your formatting in elements/characters/view.ctp. That way,
whether the action is requested with AJAX or not, you needn't repeat
any markup.

The Loading plugin handles displaying/hiding an "AJAX loading" image,
message, etc.
http://plugins.jquery.com/project/loading

CSS:

#loading { width: 10px; height: 10px; background-image:
url(path/to/loading/img); display: none; }

You can also style your links:

a.CmdOpen {
padding-right: 10px;
background: transparent url(path/to/closed.png) right 0 no-repeat;
}
a.CmdOpen.Active { background-image: url(path/to/open.png); }


> Above lessons helped me started but are bare working examples that
> will not allow to do anything sensible without learning JQuery. Any
> sensible feature needs to be implemented as a JQuery code embedded
> into the cake-like JS helper code. Any more advanced JQuery code is a
> hell to work with within short strings cake functions provide. Any
> inclusions of additional files is fine if you want to start writing
> extensive JQuery functions, but with somehow dilutes the strength of
> CakePHP framework as a tool for weekend programmers. I am not into
> inclusion of JS files and functions implemented there.

After more than a decade dealing with javascript, I can say that
JQuery is an absolute joy to work with. In any case--and I mean this
respectfully--if you want to do advanced things with a browser, you
should expect to roll your sleeves up. Cake's JS helpers may abstract
a lot of stuff that less advanced programmers may not be comfortable
dealing with, but that comes at a cost. Specifically, a page full of
inline JS. The code CAN all be inline but that certainly doesn't mean
it's the best way to go. However, it's pretty much the only viable way
to do it using Cake's libraries. As I've shown, there's a much better,
more manageable approach that can be taken. But it does require that
one take some time to study the issue.

Check out the new CakePHP Questions site http://cakeqs.org and help others with their CakePHP related questions.

You received this message because you are subscribed to the Google Groups "CakePHP" group.
To post to this group, send email to cake-php@googlegroups.com
To unsubscribe from this group, send email to
cake-php+unsubscribe@googlegroups.com For more options, visit this group at http://groups.google.com/group/cake-php?hl=en

No comments: