How to add the functinality of opening/closing widgets to the blog demo

It seems to be departing from Yii, because this script is completely written in javascript, however I think it is a kind of a tip for making use of Yii in a sense. Anyway I would like to share this tip with you guys.

I have made our widgets for the blog demo open/close and keep their status between the pages as seen below. Closing a widget is to click the header (title line) of the widget when it is opened. Opening a widget is also to click the header of the widget when it is closed.

Posted Image

[howto begins]

We need to hold the status of the widgets anywhere, because we will see all the widgets are opened when we move to another page without the persistent status of the widgets.

PersistJS

I have designed to make use of persistJS in order to hold the client status without cookies. There are several backend storages which are not unified, for browsers. Thanks to the persistJS however, we are able to hold the client-side informations in the browser in a unified way.

Installation

  • Comment a following line out (for major users who are using IE :P).


                'urlManager'=>array(


-                      'urlFormat'=>'path',


+                    //  'urlFormat'=>'path',


                        'rules'=>array(


  • Insert following lines before the title tag of protected/views/layouts/main.php.


+ <script type="text/javascript" src="<?php echo Yii::app()->request->baseUrl; ?>/js/jquery-1.3.2.min.js"></script>


+ <script type="text/javascript" src="<?php echo Yii::app()->request->baseUrl; ?>/js/persist-min.js"></script>


<title><?php echo $this->pageTitle; ?></title>


[EDIT]Check following google code out because there must be updates.

Source code

As usual, the source code is available from google code http://code.google.c…demo-enhanced/.

Limitation

In terms of IE, persistJS uses ie backend storage which seems to be dependent to the absolute path of the page. This is why I have commented this line ('urlFormat'=>'path') out.

On the contrary, it is OK for you to use path format in terms of firefox and chrome, though I have not tested all browsers on all platforms.

Terms & Conditions

This script is fully dependent to JQuery and PersistJS. Please consult the original terms and conditions.

FYI, jQuery: both MIT and GPL licenses, PersistJS: No license?

Have fun ;)

Good job!

Thanks Qiang.

BTW, if you are being annoyed by flashing the page of the blog demo, you can remove the clock widget, because it seems to be slow.

I have fixed a bug that the different web application use a common storage key. New source can be found in r8 of google codehttp://code.google.c…urce/detail?r=8. Please use this rather than attaced files on the previous posts.

Hi,

it`s fine feature. I wonder on implementing this feature in database. That is to say that the status was saved in the user table.

I change status by jquery.toggle but I don`t know how to write the current status in database by widget? Is it possible to do by widget?

Thanx for help.

Thanks for your good question.

Actually, I started to develop this feature using ajax in order to store the client-side data to the host. You know there are trade offs between the two approaches.

Ajax:

  + No need to have a cross browser storage

PersistJS:

  + Fast

  + Convenient when frequent interactions between the master and the storage exists

I gave up ajax approach mainly because of the last reason, though my investigation was not enough. I will be able to finish the ajax (or CStatePersister, may be) version after some investigations.

Quote

I change status by jquery.toggle but I don`t know how to write the current status in database by widget? Is it possible to do by widget?

As you may know jQuery has several functions on ajax, I think it is better to use it to transmit the client-side informations to the host. And there are many widgets on a page, it is efficient to transmit those data once, not per widget bases.

Quote

As you may know jQuery has several functions on ajax, I think it is better to use it to transmit the client-side informations to the host. And there are many widgets on a page, it is efficient to transmit those data once, not per widget bases.

That I would like to use ajax functions form jQuery to transmit client-side inforamtion to the host. But I have to create an additional controller to the appropriate action or can use only widget?

Quote

That I would like to use ajax functions form jQuery to transmit client-side inforamtion to the host. But I have to create an additional controller to the appropriate action or can use only widget?

My javascript structure in a pseudo code looks as follows.



 Always: {


    Load the status from storage; // e.g. '1,1,1'  (0:closing, 1:opening)


    Open/close widgets according to the status;


 }


 On click: {


    Find the widget clicked and invert the status; // e.g. '0,1,1'


    Store the status to the storage;


 }


You can simply change this local storage to the ajax call such as,



 $.get(


   ajaxLoad,


   { widget_key: key },


   function(retval) {


           :


   });


and



  $.get(


    ajaxStore,


      { widget_key: key,


        widget_status: title_vals.join()


      }


   );


Other thing you should make is the counterpart methods in the PostController such as,



  public function actionAjaxLoad() {


     $appName = 'blog_enhanced';


     $widgetKey = 'widget_key';


     if (isset($_GET[$widgetKey])) {


        $key = $_GET[$widgetKey];


        $status = $_SESSION[$appName][$key];


        echo $status;


     }


  }


and



   public function actionAjaxStore() {


      $appName = 'blog_enhanced';


      $widgetKey = 'widget_key';


      $widgteStatus = 'widget_status';


      if (isset($_GET[$widgetKey])) {


         $key = $_GET[$widgetKey];


         $status = $_GET[$widgteStatus];


         $_SESSION[$appName][$key] = $status;


       }


   }


.

In this case, I am using $_SESSION variable as a host-side storage. Hope this helps. ;)

Hi,

thanx for help. My question is that PostController is controller responsible for the transmission of data post method (not get) other responsible for post in blog?

Quote

My question is that PostController is controller responsible for the transmission of data post method (not get) other responsible for post in blog?

Though widgets opening/closing is not related post action at all, you can use counterpart ajax action in the PostController (or other, of course) using ajax function such as 'index.php?r=post/ajax'.

Or create your own extended class CController with action ajax.

I have the right?

Yeah, either is OK. If you create actionAjaxLoad method in the PostController, then you should call it as 'index.php?r=post/ajaxLoad&parameter=value…' from jQuery ajax function. Or, if you extend CController and named it AjaxController, you should call it as 'index.php?r=ajax/ajaxLoad&…'.

I do not think this is important because these URL is not exposed to the users, though.

Having tried some modification from client-side storage to host-side one, I found pages sometimes flickering. This comes from the latency(*) of the page rendering.

(1) A widget is rendered as opened

(2) Ajax method retrieves the status from the host (*)

(3) A widget is rendered as closed according to the status

Obviously, it takes longer for the distant host, than the local storage. In order to cope with it, how about following idea?

(A) Modify the code of Portlet as follows.



<?php


class Portlet extends CWidget


{


        public $title;


        public $cssClass='portlet';


        public $headerCssClass='header';


        public $contentCssClass='content';


        public $visible=true;


        public $opened=true;





        public function init()


        {


                if(!$this->visible)


                        return;


                echo "<div class="{$this->cssClass}">n";


                if($this->title!==null)


                        echo "<div class="{$this->headerCssClass}">{$this->title}</div>n";


                if(!$this->opened)


                        return;


                echo "<div class="{$this->contentCssClass}">n";


        }





        public function run()


        {


                if(!$this->visible)


                        return;


                if($this->opened) {


                  $this->renderContent();


                  echo "</div><!-- {$this->contentCssClass} -->n";


                }


                echo "</div><!-- {$this->cssClass} -->";


        }





        protected function renderContent()


        {


        }


}


(B) Create a widget controller having a ajax functionality to tell which widget is opened and closed.

There may not be flickering in this case, I think.

:) thanx, i just trying something similar in my module.

Quote

:) thanx, i just trying something similar in my module.

Nice to hear that :). BTW, I have corrected above code as below.

-        public $opened=false;

+        public $opened=true;

Of course the default value should be ture. This is because I just tested modified code ($opened=false) to see all widgets are closed at the first rendering, and it worked fine.

Finally, I have changed this to use CClientScript as well as ExtendedClientScript that can merge several JS and CSS files. You can get the latest version from the google code.