how can i display data for every let's say odd entry?
So first record Z, second record do Y, third record do Z, fourth do Y.
or lets say i get from model every nth record, and another that does n-1?
public function getNewItem()//for main page grids
{
$criteria = new CDbCriteria;
$criteria->order = 'posted_date DESC';
$criteria->compare('product_status',"Y");
$criteria->compare('product_approval_status',"Y");
//????
return new CActiveDataProvider( $this, array(
'criteria'=>$criteria,
'pagination'=>false,
));
}
There are two ways to do this; depending on how complex your filtering needs to be;
First method you can modify your criteria compare to filter by only odd or even records like this
$criteria = new CDbCriteria;
$criteria->order = 'posted_date DESC';
$criteria->compare('product_status',"Y");
$criteria->compare('product_approval_status',"Y");
$criteria->addCondition('MOD(id,2)=1','AND');
or any such condition to get the corresponding subset of records
Second method is to filter it in the partial file you use in the CListView, I use this method to usually add additional code not for filtering, for example you want to add a new row every forth element, For this use the $indexand $widget variable made available in the itemView attribute in the CListView call with something like this.
<?php if(($index)%6 == 0 || $index == 0 ){ ?>
<div class="row">
<?php } ?>
// Regular view rendering code
// ...
<?php if(($index+1)%6 == 0 || ($index+1) == $widget->dataProvider->itemCount){ ?>
</div>
<?php }
Here I have used this to insert a new div for every 6 items in the list or if its the first/last item
This is useful for conditional rendering of items/ adding classes etc.
Related
I have a table that contains different versions of a text. I want to display the diffs of each version with the previous version. I also want to paginate through the versions, in case there are more than 20. However, to diff the last text on each page I would need the first text of the next page. I cannot just make the page size one larger (21 in this case), because the second page would skip its first entity, and the third its first two etc.
$config = $this->Paginator->getConfig();
$this->Paginator->setConfig('limit', $config['limit'] + 1);
$inscriptions = $this->paginate($query);
I might instead be able to solve the problem by making a separate ->paginate() call for the single entity, but I would rather not execute a second query if possible.
$inscriptions = $this->paginate($query);
$config = $this->Paginator->getConfig();
$this->Paginator->setConfig([
'limit' => 1,
'page' => ($config['page'] * $config['limit']) + 1
]);
$inscriptions[] = $this->paginate($query)->first();
Is there a way to skip the first n results? In that case I could set the page size to 21 but set the page number to 1, and skip the first ((old page number - 1) * old page size) entities.
It is possible to make a custom paginator that extends the default one, but functions as described:
<?php
namespace App\Datasource;
use Cake\Core\InstanceConfigTrait;
use Cake\Datasource\Paginator;
use Cake\Datasource\QueryInterface;
use Cake\Datasource\RepositoryInterface;
class DiffPaginator extends Paginator
{
use InstanceConfigTrait;
protected function getQuery(RepositoryInterface $object, ?QueryInterface $query = null, array $data): QueryInterface
{
$data['options']['offset'] = ($data['options']['page'] - 1) * $data['options']['limit'];
$data['options']['limit'] += 1;
unset($data['options']['page']);
return parent::getQuery($object, $query, $data);
}
protected function buildParams(array $data): array
{
$paging = parent::buildParams($data);
if ($paging['current'] == $paging['perPage'] + 1) {
$paging['current'] -= 1;
}
return $paging;
}
}
In your controller then use the following:
$this->Paginator->setPaginator(new DiffPaginator);
I try to make a "getThreeWorks" method with Laravel. Each view post to its 'orientation'. It is stored in the same table. For example, "Work 1 has the web orientation, Work 2 has the 2D orientation and Work 3 has the 3D orientation".
At the end of each post, I would like to propose a link to 3 other works (in a random order).
So I would like a link to a work that has the "web" orientation, another that has the "2D" orientation and one that has the "3D" orientation.
I can't get SQL query at all. Can you help me ? Thank you !
public function getThreeWorks()
{
$workFrom3D = Work::where('orientation', '3D')->inRandomOrder->limit(1)->get();
$workFrom2D = Work::where('orientation', '2D')->inRandomOrder->limit(1)->get();
$workFromWeb = Work::where('orientation', 'web')->inRandomOrder->limit(1)->get();
}
Can't you remove the limit(1) and instead use a group by orientation? Then you get one of each.
You can create a new collection with your current code.
$work = collect([$workFrom3D, $workFrom2D, $workFromWeb]);
If i understood, you want show some "posts" on footer of each post, right ? you can try this:
public function getThreeWorks()
{
$workFrom3D = Work::where('orientation', '3D')->get();
$workFrom2D = Work::where('orientation', '2D')->get();
$workFromWeb = Work::where('orientation', 'web')->get();
$randomFrom3d = $workFrom3D->random();
$workFrom2D = $workFrom2D->random();
$workFromWeb = $workFromWeb->random();
}
also, you should include one more condition on your where, to avoid repeat the same post on page where will show this list, like:
$workFromWeb = Work::where('orientation', 'web')
->where('post_id','!=', $actualPostId)->get();
Im trying to search by a foreign table as follows:
2 tables:
people:
id
name
...
url:
id
peopleID
url
People.php model:
public function getUrls()
{
return $this->hasMany(Urls::className(), ['peopleID' => 'id'])->select(['url']);
}
PeopleSearch.php model:
...
$query->joinWith(['urls']);
...
$query
->andFilterWhere(['or',
['like', 'name', $this->name],
...
['like', 'url', $this->name]]
);
This works to search value entered in "name" field in several fields including foreign url one but in my view i enter a manual pagination by using something like:
$dataProvider->prepare();
if ($dataProvider->totalCount > 0)
echo Yii::t('app', 'Showing').": <b> ".($dataProvider->pagination->page*$dataProvider->pagination->pageSize+1)."-".($dataProvider->pagination->page*$dataProvider->pagination->pageSize+$dataProvider->count)."</b> ".Yii::t('app', 'of')." <b>".$dataProvider->totalCount."</b> ".Yii::t('app', 'items');
else echo Yii::t('app', 'No results found.');
echo LinkPager::widget(['pagination' => $dataProvider->pagination])
And $dataProvider->totalCount gives me the total amount of records from joined table but not the total records from people one. For instance if i have 2 records in people table and each one has 20 urls in "url" table index.php view shows "showing 1-2 of 40 items" instead of "showing 1-2 of 2 items"
Also LinkPager::widget shows a wrong number of total pages
Note that $dataProvider is passed from the controller to the view with
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
What could I do for pagination to perform the way i want?
Thank you in advance,
In People.php model my recommendation is to remove the ->select(['url']):
public function getUrls()
{
return $this->hasMany(Urls::className(), ['peopleID' => 'id']);
}
This way you can still manipulate those urls if needed.
In PeopleSearch.php model:
...
// $query->joinWith(['urls']);
// This line is the one that makes it so you get 20 results instead of 2, because you actually get one result for each url related to the people returned by the query.
$query->with(['urls']);
// This last line makes sure the model class populates the relation using only one query.
// Two queries will end up being executed to populate both the people and url models,
// however you will get the right amount for $dataProvider->totalCount.
...
if(strlen($this->url) > 0) {
$urlsPeopleIDs = \app\models\Url::find()
->select('peopleID')
->asArray()
->where(['like', 'url', $this->url])
->all();
$query->andWhere(['id' => $urlsPeopleIDs]);
}
// This way you will only filter by url when you receive a url string with lenght > 0.
// If you haven't already, you will need to create a public property called 'url' in your PeopleSearch.php model and add a 'string' or 'safe' rule so you can actually load it's value from post.
I am trying to create a multidimensional array of my navigation. All my pages are created in a CMS and then stored in my DB. Each page will have the usual fields like title, url etc but also they will have a field which tells me if it has any children 'p_has_children' as well as a field which will tell me its parent page 'p_parent' (if it is a child of a page).
I thought I could write a function which I could call and send it an array of all the top level navigation items. Then as I loop through each item I would check if it had any children, get the children (and assign them to an array) and then send them through the same function like so....
function sideNavArray()
{
//Get the side navigation
$getSidenav = $this->crud_model->fetch_rows_where('pages', array('p_side_nav' => 'y', 'p_active' => 'y'));
//Call the navbuilder function
$sidenav = $this->sidenavBuilder($getSidenav, 'navitem', 'nav');
//return the generated nav
return $sidenav;
}
function sidenavBuilder($navItems, $itemClass, $navLevel, $i = 0, $sidenav = array())
{
//Loop over each nav item
foreach($navItems as $navItem){
//For each item I want to add th nav_title, slug, url and page type
$sidenav[$i]['p_nav_title'] = $navItem->p_nav_title;
$sidenav[$i]['p_slug'] = $navItem->p_slug;
$sidenav[$i]['p_url'] = $navItem->p_title;
$sidenav[$i]['p_type'] = $navItem->p_type;
//See if this page has any children
if($navItem->p_has_children == 'y'){
//If the page has children then I want to fetch them from the DB
$subnav = $this->crud_model->fetch_rows_where('pages', array('p_parent' => $navItem->id, 'p_active' => 'y', 'p_protected !=' => 'y'));
if(!empty($subnav)){
//Change the item class and level to subnavitem and subnav
$itemClass = 'sub' . $itemClass;
$navLevel = 'sub' . $navLevel;
//Assign the children to the same array as its parent in the "subnav" level and send it through the sitenavBuilder function
$sidenav[$i][$navLevel] = $this->sitenavBuilder($subnav, $itemClass, $navLevel);
}
}
$i++;
//End foreach loop
}
return $sidenav;
}
I am sure some of you are looking at this right now and saying well of course that won't work!
I currently have a navigation being generated but it is on the front end and every time I want to add an additional level I have to go into the code and add it manually. I want to avoid having to write each level of the navigation as I want it to be able to grow to as many levels as it can without me having to go in and code that extra level if it is needed every time.
I hope this all makes sense. Basically I want to know if there is a way to get this nav array to be built by running through the same loop over and over until it doesn't need to anymore.
I'm using Yii's Dataprovider to output a bunch of users based on the column "points";
It works fine now but I have to add a feature so if the user is online, he gets an extra 300 points.
Say Jack has 100 points, Richmond has 300 points, However Jack is online, so Jack should rank higher than Richmond.
Here is my solution now:
$user=new Rank('search');
$user->unsetAttributes();
$user->category_id = $cid;
$dataProvider = $user->search();
$iterator = new CDataProviderIterator($dataProvider);
foreach($iterator as $data) {
//check if online ,update points
}
However, this CDataProviderIterator seems change my pagination directly to the last page and I can't even switch page anymore. What should I do?
Thank you very much!
Here is the listview:
$this->widget('zii.widgets.CListView', array(
'id'=>'userslist',
'dataProvider'=>$dataProvider,
'itemView'=>'_find',
'ajaxUpdate'=>false,
'template'=>'{items}<div class="clear"></div><div style="margin-right:10px;"><br /><br />{pager}</div>',
'pagerCssClass'=>'right',
'sortableAttributes'=>array(
// 'create_time'=>'Time',
),
));
Updated codes in Rank.php model
$criteria->with = array('user');
$criteria->select = '*, (IF(user.lastaction > CURRENT_TIMESTAMP() - 1800, points+300, points)) as real_points';
$criteria->order = 'real_points DESC';
However, it throws me error:
Active record "Rank" is trying to select an invalid column "(IF(user.lastaction > CURRENT_TIMESTAMP() - 1800". Note, the column must exist in the table or be an expression with alias.
CDataProviderIterator iterates every dataprovider value, and stops at the end. I don't know all about this classes, but think the reason is in some internal iterator, that stops at the end of dataprovider after your foreach.
Iterators are used when you need not load all data (for large amounts of data) but need to process each row.
To solve your problem, just process data in your view "_find". Add points there if online.
Or if you want place this logic only in the model (following MVC :) ), add method to your model:
public function getRealPoints() {
return ($this->online) ? ($this->points + 300) : $this->points;
}
And you can use $user->realPoints to get points according to user online status
update: To order your list by "realPoints" you need to get it in your SQL.
So use your code:
$user=new Rank('search');
$user->unsetAttributes();
$user->category_id = $cid;
$dataProvider = $user->search();
and modify $user->search() function, by adding:
$criteria->select = '*, (IF(online='1', points+300, points)) as real_points';
$criteria->order = 'real_points DESC';
where online and points - your table columns.