4

I have a foreach loop in a view page of my module which retrieves a grid of images (the number is determined by the "items_top") from a controller. The problem is that all items are loaded at the same time because there is no pagination set at the moment.

This is the code of my view:

<div class="row row-sm padder-lg ">

        <?php
        foreach ($top->tracks->track as $key => $value) 
        {
            if($key >= $this->config->item("items_top"))
                return false;
            $image = $value->image[3]->text;
                if($image == '')
            $image = $value->image[2]->text;
                if($image == '')
            $image = base_url()."assets/images/no-cover.png";
        ?>       
        <div class="col-xs-6 col-sm-4 col-md-3 col-lg-2">
            <div class="item">
                <div class="pos-rlt">
                <a href="#">

                    <div class="item-overlay opacity r r-2x bg-black">
                        <div class="center text-center m-t-n">
                        <i class="icon-control-play i-2x"></i>                    
                        </div>

                    </div>
                    <a href="#">
                        <div class="r r-2x img-full" style="background:url('<?php echo $image; ?>') no-repeat top center; -webkit-background-size: cover; -moz-background-size: cover; -o-background-size: cover;  background-size: cover;">
                            <div style="height:180px;overflow:hidden;"></div>
                        </div>
                    </a>
                </div>
                <div class="padder-v">
                    <a href="#" class="text-ellipsis"><?php echo $value->name; ?></a>
                    <a href="#" class="text-ellipsis text-xs text-muted"><?php echo $value->artist->name; ?></a>
                </div>
            </div>
        </div>
        <?php
        }
        ?>

    <script>
    $(".nav-sidebar li").removeClass("active");
    $("#topTrack").addClass('active');
    </script>
</div> 

this is my function helper:

function _curl($url) {  
    $CI     =& get_instance();  
    $ch = curl_init(); 
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
    curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,15);
    curl_setopt($ch,CURLOPT_FOLLOWLOCATION,1);    
    if(strtolower(parse_url($url, PHP_URL_SCHEME)) == 'https')
    {
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,1);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,1);
    }
    if($CI->config->item("proxy") != '')
    {       
        curl_setopt($ch, CURLOPT_PROXY, $CI->config->item("proxy"));
    }
    curl_setopt($ch, CURLOPT_URL, $url); 
    $data = curl_exec($ch);
    curl_close($ch); 
    return $data;
}

function getTopTracks($artist = false)
{
$CI     =& get_instance();  
$artist = econde($artist);  

$url    = "http://ws.audioscrobbler.com/2.0/?method=chart.gettoptracks&api_key=".$CI->config->item("lastfm")."&format=json&limit=60&page=3";    
$file_cache = 'cache/tracks_'.sha1("toptracks").".cache";

if($cache)
    {   
        $json = $cache;
        if (time()-filemtime($file_cache) > 24 * 3600) {
          // file older than 24 hours
            @unlink($file_cache);
        }
    }
    else
    {       
        $json   = _curl($url);  
        if($CI->config->item("use_cache") == "1")
            write_file($file_cache, $json);
    }   

if(!$artist)
    $json   = str_ireplace("toptracks","tracks",$json);
else
    $json   = remove_banned($json,true) ;
$json   = str_ireplace("#text","text",$json);
$json   = str_ireplace(":totalResults","",$json);
return remove_banned($json) ;
}

and this is my controller:

public function getTopTracks($return = false,$page = false)
{
    $data['page']   = $page;
    $data['top'] = json_decode(getTopTracks());
    if(count($data['top']->tracks->track) <=1)
            {                   
                $this->config->set_item("auto_country", '0');       
                $data['top'] = json_decode(getTopTracks());
            }
            return $this->load->view(getTemplate('topTracks'),$data,$return);   
 }

I need to slice the loop to make a pagination of these items, I wanna be able to add a button "load more" at the bottom, but the problem is Ajax, I cannot use $page = (int)getFromExternalInput(); because the loop call a public function not a url. What's the correct way to slice this loop?

NineCattoRules
  • 1,626
  • 5
  • 30
  • 65
  • Does the controller return images from the database? If so, you can use the raw limit and offset approach rather than paginate the images. – junkystu Feb 08 '15 at 17:39
  • @junkystu indeed this is my main problem, I get data from an external url – NineCattoRules Feb 09 '15 at 00:04
  • Have you tried building an array with your images? You can then use [PHP's slice](http://php.net/manual/en/function.array-slice.php) to get back chunks of images similar to how you would limit and offset the database. – junkystu Feb 09 '15 at 08:27
  • @junkystu I'm a newbie in codeigniter so it's really hard to proceed that way. – NineCattoRules Feb 09 '15 at 08:46
  • You will have to update your answer with code from your controller or the image data you are getting back for others to help. The more details, the better – junkystu Feb 10 '15 at 22:27
  • 1
    About 'blank page', read this: http://stackoverflow.com/questions/9587413/codeigniter-displays-a-blank-page-instead-of-error-messages – Federico J. Mar 17 '15 at 09:41

2 Answers2

3

I edited the function helper in my question with the code asked for...hope this could be helpful for an answer. Thanks

Ok to update my answer according to your additional information, I created a working example, based on the new information. I did howver have to create a sandbox for myself which is not going to reflect your code 100%. I feel you should be able to take something from this answer however, and you should be able to implement it in your own code.

The code does need a bit of optimising

I am saving the cached files in /application/cache/lastfm

The Cache can be a bit of an issue for pagination here, but I did my best to come up with a working example

application/config/lastfm.php

$config['url'] = 'http://ws.audioscrobbler.com/2.0/';
/**
 * 
 */
$config['API_KEY'] = '';
/**
 * 
 */
$config['limit'] = 12;
/**
 * 
 */
$config['cache_time'] = 24 * 3600;
/**
 * 
 */
$config['proxy'] = "";

application/libraries/curl.php

class Curl
{
    protected $handle;

    public function __construct()
    {
        $this->_init();
    }
    /**
     * 
     * @return type
     */
    protected function _init()
    {
        if(!in_array('curl', get_loaded_extensions())) 
            return log_message('error', 'curl extension is not loaded!');

        $this->handle = curl_init();

        if(!$this->handle) {
            return log_message('error', 'could not create curl handle!');
        } 

    }
    /**
     * 
     * @param type $url
     * @return type
     */
    public function fetch($url)
    {
        $this->setOptions($url);

        $response = curl_exec($this->handle);

        if($errno = curl_errno($this->handle)){
            log_message('error', 'Curl Response Error: ' .  curl_strerror($errno));
        }

        curl_close($this->handle);

        return ($errno === 0) ? $response : false;
    }
    /**
     * 
     * @param type $url
     * @return type
     */
    protected function setOptions($url)
    {
        if(strtolower(parse_url($url, PHP_URL_SCHEME)) == "https"){
            curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 1);
            curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 1);
        }

        $proxy = $this->config->item('proxy', 'lastfm');

        if(!empty($proxy)){
            curl_setopt($this->handle, CURLOPT_PROXY, $proxy);
        }

        curl_setopt($this->handle,CURLOPT_FAILONERROR,true);
        curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($this->handle, CURLOPT_URL, $url);

        return;
    }
    /**
     * 
     * @param type $name
     * @return type
     */
    public function __get($name)
    {
        $instance =&get_instance();
        return $instance->$name;
    }
}

application/controllers/tracks.php

class Tracks extends App_Controller 
{
    public function __construct()
    {
        parent::__construct();

        $this->load->config('lastfm', true);

        $this->load->model('lastfm_model');
    }

    public function index()
    {
        $tracks = $this->lastfm_model->getTracks(1);

        $this->load->view($this->template, array(
            'content'  =>  'lastfm/tracks',
            'tracks_view'  =>  $this->load->view('lastfm/partials/tracks', array(
                'tracks'   =>   $tracks,
                'statistics'    => $tracks['data']->tracks->attr
            ), true)         
        ));
    }

    public function get($pageId=0)
    {
        $tracks = $this->lastfm_model->getTracks($pageId, true);

        if($pageId > $tracks['data']->tracks->attr->totalPages){
            return;
        }

        $this->load->view('lastfm/partials/tracks', array(
            'tracks'   =>   $tracks,
            'statistics'    => $tracks['data']->tracks->attr 
        ));
    }
}

application/models/lastfm_model.php

class LastFM_Model extends CI_Model
{
    /**
     *
     * @var type 
     */
    protected $options;
    /**
     *
     * @var type 
     */
    protected $cache;
    /**
     *
     * @var type 
     */
    protected $method;
    /**
     *
     * @var type 
     */
    protected $byPassCache = false;

    public function __construct()
    {
        parent::__construct();

        $this->options = $this->config->config['lastfm'];
    }


    /**
     * 
     * @param type $page
     */
    public function getTracks($page=0, $bypass_cache=false, $method='chart.gettoptracks')
    {
        $this->method = $method;

        $this->cache = $this->_getCachePath();

        $this->byPassCache = $bypass_cache;

        $url= $this->_buildUrl($page);

        if($bypass_cache){
            return array(
                'src'  =>  'curl',
                'data'  =>  $this->_parseContent($this->_fetch($url), false)
            );
        }

        if($cache = $this->_getCache()){
            return array(
                'src'  =>  'cache',
                'data'  =>  $this->_parseContent($cache)
            );
        }

        return array(
            'src'  =>  'curl',
            'data'  =>  $this->_parseContent($this->_fetch($url), false)
        );
    }
    /**
     * 
     * @param type $page
     * @return type
     */
    protected function _buildUrl($page=0)
    {
        $query_data = array(
            'method'    =>  $this->method,
            'format'    =>  'json',
            'page'      =>  $page,                     
            'limit'     =>  $this->options['limit'],
            'api_key'   =>  $this->options['API_KEY']
        );

        $query = http_build_query($query_data);

        return $this->options['url'] . '?' . $query;
    }
    /**
     * 
     * @param type $url
     * @return type
     */
    protected function _fetch($url)
    {
        $this->load->library('curl');

        $data = $this->curl->fetch($url);

        $data = str_ireplace(array('#text', '@attr'), array('text', 'attr'), $data);

        if(!$this->byPassCache){
            $this->_setCache($data);
        }


        return ($data) ? $data : false;
    }
    /**
     * 
     * @return type
     */
    protected function _getCachePath()
    {
        return APPPATH . "cache/lastfm/{$this->method}.cache";
    }
    /**
     * 
     * @param type $data
     * @return type
     */
    protected function _setCache($data)
    {
        if(file_exists($this->cache)){
            unlink($this->cache);
        }

        if(!empty($data)){
            return file_put_contents($this->cache, $data);
        }
    }
    /**
     * 
     * @return type
     */
    protected function _getCache()
    {
        return ( file_exists($this->cache) && ( time() - filemtime($this->cache) ) < $this->options['cache_time']  )
            ? $this->cache
            : false;
    }
    /**
     * 
     * @param type $content
     * @param type $cache
     * @return type
     */
    protected function _parseContent($content, $cache=true)
    {
        if($cache)
            $content = file_get_contents($content);

        return json_decode($content);
    }
}

application/views/lastfm/tracks.php

<div id="track_list">
    <?php echo $tracks_view; ?>
</div>

<script>
var SITE_URL = "<?php echo site_url();?>"; 
</script>
<script>
/** LastFm **/
(function($){  

    var lastFm = {
        /**
         * 
         * @type type
         */
        options : {
            cache : false,
            url   : undefined,
            type  : "GET",
            dataType : "html",
            data : {}
        },
        /**
         * 
         * @returns {undefined}
         */
        init : function(){
            this.More = $("a#more");

            this.trackList = $("#track_list");

            this.trackList.on('click', this.More, $.proxy(this.handleEventClick, this));
        },
        /**
         * 
         * @param {type} event
         * @returns {undefined}
         */
        handleEventClick : function(event){

            event.preventDefault();

            var target, pageId;

            target = event.target;

            pageId = parseInt($(target).attr('data-pageId'));

            $(target).remove();

            this.pageID = pageId+1;

            this.fetch(this.pageID);

        },
        /**
         * 
         * @returns {undefined}
         */
        fetch : function(pageId){

            this.options.url = SITE_URL + "/tracks/get/"+pageId;

            this.dojax().then(
                $.proxy(this.handleResponse, this),
                $.proxy(this.handleErrors, this)
            );
        },
        /**
         * 
         * @returns {unresolved}
         */
        dojax : function(){
            return $.ajax(this.options).promise();
        },
        /**
         * 
         * @param {type} response
         * @returns {undefined}
         */
        handleResponse : function(response){

            this.More.remove();

            this.trackList.append(response);

            $("html, body").animate({
                scrollTop : $("[data-pageSlideId="+this.pageID+"]").filter(':last').offset().top
            });
        },
        /**
         * 
         * @param {type} error
         * @returns {undefined}
         */
        handleErrors : function(error){

        }
    };

    lastFm.init();

}(jQuery));
</script>

application/views/lastfm/partials/tracks.php

    <div id="page_<?php echo $statistics->page;?>">
    <?php 
    $columnsPerRow = 4;

    foreach(array_chunk($tracks['data']->tracks->track, $columnsPerRow) as $track): ?>

    <div class="row" data-pageSlideId="<?php echo $statistics->page;?>">

        <?php foreach($track as $trac):?>

            <?php $image = (!empty($trac->image[2]->text)) ? $trac->image[2]->text : false;?>

            <div class="large-3 medium-6 columns text-center" >
                <div class="panel" style="background: url('<?php echo $image;?>') no-repeat;background-size: cover;">
                    <h6><?php echo $trac->name;?></h6>
                    <h5><?php echo $trac->artist->name;?></h5>
                </div>
            </div>

        <?php endforeach;?>

    </div>

    <?php endforeach; ?>

    </div>


<div class="row">
    <div class="large-12 columns text-center">
        <a href="" class="button" id="more" data-pageId="<?php echo $statistics->page;?>">Load More...</a>
    </div>
</div>
Philip
  • 4,596
  • 2
  • 17
  • 28
  • 1
    Hi Philip, the code above is one of my views, there is no pagination (that's the reason of my question), and since now the foreach loop retrieve all items at once, instead I wish to load only some of them and I like to have a "load more" button to retrieve others – NineCattoRules Mar 16 '15 at 14:52
  • 1
    Then you would need to read the API of the request server to see if they offer such a service. ie: $offsets & $limits for pagination – Philip Mar 16 '15 at 15:28
  • It's the API of last.fm and I'm not able to find those infos...is this the only solution? Instead is it possible to store items on my database and than make the pagination? – NineCattoRules Mar 16 '15 at 15:42
  • 1
    The API does indeed have limits and pages/offsets that you can send as params `&limit=10&page=2` http://www.last.fm/api/show/track.search – Philip Mar 16 '15 at 15:57
  • 2
    fantastic, I did use google to search for those infos and found nothing. So basically it could be done easily than thought, all I need to do is change my function helper...ok I'll try! – NineCattoRules Mar 16 '15 at 16:33
  • I tried without success, I'm sure it's my fault. I updated my question with the code written so far, all I have is an empty screen. – NineCattoRules Mar 16 '15 at 23:14
  • 2
    The code I provided won't work, it was purely for displaying chunks of an array in sections(this is what I thought you were after at the time of posting, it is not a valid answer). What you are after is getting a display of results based on certain pagination rules from the lastFm API. My answer will need to be tweaked a little, however you will need to provide more code you have written for me to do this. – Philip Mar 17 '15 at 00:27
  • Do I need to provide anything else? I'm really not sure if the question is now much understandable – NineCattoRules Mar 18 '15 at 09:43
  • 1
    I'm working on a valid answer for you. You dont need to provide any more data. Check back in about an hour. – Philip Mar 18 '15 at 09:52
  • sorry for the dumb question but since I don't wanna say something wrong, how could I check it? – NineCattoRules Mar 18 '15 at 10:58
  • 1
    Is it not your code then ? Can you point me to where you got the code then. ? I need to find out how your getting the data so I can write a solution – Philip Mar 18 '15 at 11:05
  • 1
    It might be best to contact the author of the script then, as it is a private script. – Philip Mar 18 '15 at 11:24
  • Ok, it's getting data using curl...I have been so stupid, it's at the start of my function helper :( – NineCattoRules Mar 18 '15 at 13:43
  • 1
    I edited the function helper in my question with the code asked for...hope this could be helpful for an answer. Thanks – NineCattoRules Mar 18 '15 at 15:51
  • 1
    OMG :D I really need some time to understand and test it. Thanks – NineCattoRules Mar 19 '15 at 17:09
  • I thought and hope it was much easier than this...I need to study more before being able to accomplished something like this. Thanks in any case for your time – NineCattoRules Mar 20 '15 at 10:16
0

I think what you need to do is to break your code into functions instead of using it direct on the view. The question is a little confusing but let's see if I can help you. I'll use my pagination listing code that I use in codeigniter with datatables.

<?php
#model fuction that take the list data from db
public function listSomething($listStart, $numberOfRows)
{
    $this->db->select('login, nome');
    $this->db->from('list');
    $this->db->limit($numberOfRows, $listStart);
    $queryUsuario = $this->db->get();
    return $queryUsuario->result();
}

#controller fuction
public function listSomething ()
{
    #pego as váriaveis do datatables via post.
    $numeroDeRegistros = intval($this->input->post('length'));
    $listStart = $this->input->post('start');

    #Carega o model de usuario.
    $this->load->model('UsuarioModel');

    #Cria uma váriavel para armazenar o resultado já no formato do DataTables.

    #Pega uma lista de usuário com parâmetros de paginação.
    $list = listSomething($listStart, $numberOfRows);
    #Conta os usuário registrados no banco.
    $listCount = count($list);

    #Pega o tamanho que a lista deve ter, passado pelo Datatables.
    $listLength = intval($this->input->post('length'));
    #Se o tamanho da lista for maior que a propria lista, o total é o tamanho.
    if ( $listLength > $listCount ) {
       $listLength = $listCount;
    }

    #Se o fim da lista é maior que o total da lista, então o fim da lista é o total.
    $listEnd = $listStart + $listLength;
    if ( $listEnd > $listCount ) {
       $listEnd = $listCount;
    }
    #Formata os dados antes de passar para json.
    for($i = 0; $i < $listEnd-$listStart; $i++) {
        foreach($list[$i] as $var => $value) {
            $list["data"][$i][] = $value;
        }
        $list["data"][$i][] =
        '
        <a href="javascript:;" class="btn btn-xs default blue" id="editaUsuarioBnt"><i class="fa fa-pencil"></i> Editar</a>
        <a href="javascript:;" class="btn default btn-xs red" id="excluiUsuario"><i class="fa fa-trash-o"></i> Excluir </a>
        <a href="javascript:;" class="btn btn-xs default yellow"><i class="fa fa-times"></i> Redefinir Senha</a>
        ';                
    }

    #Envia a lista para formatar em json.
    $this->jsonDataTables($list, $listCount);
}

#library function that append some datatables data and output json.
public function jsonDataTables (&$list, $totalDaLista) 
{

    #Gravo na variável o valor de quantas 
    $sEcho = intval($this->input->post('draw'));

    if ( $list != null ) {

        if (isset($_REQUEST["customActionType"]) && $_REQUEST["customActionType"] == "group_action") {
              $list["customActionStatus"] = "OK"; // pass custom message(useful for getting status of group actions)
              $list["customActionMessage"] = "Group action successfully has been completed. Well done!"; // pass custom message(useful for getting status of group actions)
        }

        $list["draw"] = $sEcho;
        $list["recordsTotal"] = $totalDaLista;
        $list["recordsFiltered"] = $totalDaLista;

        echo json_encode($list);
    }
    else {
        echo json_encode("");
    }
}

So you just need to create a datatables or adapt it to your own table. You I'll need to be posting the the vars anytime you update the table, so codeigniter controller can keep track of the changes for the listStart, listLength and etc.

So assuming you know how DataTables work, it won't be difficult to figure what the code does. And as you said about the data, I'm assuming you I'll have an array as a result from your external source like $list[0],...,$list[$i].

so instead of taking it from the db, in you your model/whatever fuction listSomething you will probably have something like:

#model fuction that take the list data from db
public function listSomething($listStart, $numberOfRows)
{
    $list = getMyExternalImages();
    return array_slice($list, $listStart, $numberOfRows);
}

#I'll probably need this to count how many results you have
public function countExternalImages()
{
    $list = getMyExternalImages();
    return count($list);
}

If you can read the code and the explanations I think you can adapt it to your needs. If you have any question, just ask.

Ricardo Silva
  • 349
  • 2
  • 9