Im using drupal 7
I have a date field.
Which displays the date selected as it is supposed to.
However I would to change the display to say 'this date is almost over' under certain php validation or 'this date is now closed' under different validation.
I cant use string over reides as it doesnt accept php. Would I have to use a custom module and use hook form alter or node api?
thanks for any help
Yes, you would have to use a module to adjust how the image field is displayed. You can either create a new display format using hook_field_formatter_info() along with hook_field_formatter_view(), or you can hook an existing display using hook_field_formatter_view() by checking $display['type']. I would recommend the former.
Here is an example of how the two hooks could work together:
function modulename_field_formatter_info()
{
return array(
'modulename_formatter' => array(
'label' => t('Custom Date Format'),
'field types' => array('date'),
),
);
}
function modulename_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display)
{
if ($display['type'] != 'modulename_formatter')
return;
foreach ($items as $delta => item)
{
if ($item['date'] < time()) //Or 'datetime' or 'datestamp'
$element[$delta]['#markup'] = 'this date is now closed.';
}
return $element;
}
I might have some of the indices wrong, but they should be pretty close.
http://api.drupal.org/api/drupal/modules!field!field.api.php/function/hook_field_formatter_info/7
http://api.drupal.org/api/drupal/modules!field!field.api.php/function/hook_field_formatter_view/7
Related
I would like to create a filter where the search would only give me the entries starting with what is searched.
For exemple, I'm searching for a post code starting with "92". With the current filter, it gives me "92000", but also "29200" when I only want the first one.
In SQl it would be "where postcode like $value%" or something like that.
Edit after the 2 first answers:
I tried the custom callback. But the exemple is a for a linked entity displayed as a checked box, and I just want a "where like something%" on a field in the entity. No leftjoin !
Here is my code so far, but I get "Warning: Illegal string offset 'value'"
$this->datagrid->add('postCode', 'doctrine_orm_callback', array(
'callback' => function($queryBuilder, $field, $value) {
if (!$value['value']) {
return;
}
$queryBuilder->andWhere('s.postCode like :value');
$queryBuilder->setParameter('value', $value);
return true;
}
));
The field is 'postCode' in the entity 'Trainer'
You need a "custom callback filter" to set the rules you need to the queryBuilder.
It is well covered in Sonata documentation on filters.
I had given up on this feature for a while, but then I tried to solve this again, and bam ! it works ! So here is my solution in case anybody needs it:
$this->datagrid->add('postCode', 'doctrine_orm_callback', array(
'callback' => function($queryBuilder, $alias, $field, $value) {
if (empty($value['value'])) {
return;
}
$queryBuilder->where('t.postCode LIKE :postcode');
$queryBuilder->setParameter('postcode', $value['value'].'%');
$queryBuilder->orderBy('t.postCode', 'ASC');
return true;
},
'field_type' => 'text'
));
I made module with form, that use autocomplete field like that:
$form['field_taxonomy_tags'] = [
'#type' => 'entity_autocomplete',
'#target_type' => 'taxonomy_term',
'#selection_settings' => [
'target_bundles' => array('tags'),
],
'#autocreate' => array(
'target_bundles' => array('tags'),
'bundle' => ('tags'),
),
'#title' => ('tags'),
'#tags' => TRUE,
];
Autocomplete works fine, and i can add taxonomy terms from tags vocabulary easily. But there is some problem with #autocreate option i think. Have searched all documentation, and code inside drupal core. Entity is never created ;/
When i try to get value from this field, my browser is dead... there is some entity type variable, but huge.
After some debugging i found way to get it work, but im not happy about it :) Very strange, maybe some of you guys can help me to find better way?
public function submitForm(array &$form, FormStateInterface $form_state) {
$tags = $form_state->getValue('field_taxonomy_tags');
foreach ($tags as $tag)
{
if(is_object($tag['entity']))
{
$tag['entity']->save();
}
}
}
As you can see, I need to save those tags manually, dont know why ;/ Without it, there is no term created.
It is better way. I dont need to save every tag, its enough if we attach them to a node. Its entity object, that can be passed as node value, and after that, all tags will be created:
$node = Node::create(array(
'type' => 'YOUR_content_type',
'title' => $form_state->getValue('title')
));
$fieldNames = array_keys($node->getFieldDefinitions());
$values = $form_state->getValues();
// be aware with that, i use this loop for testing because i have same names
// you can use $node->set('content type field name', $value); directly without any field definitions
foreach ($values as $key=>$value)
{
if(in_array($key, $fieldNames))
{
$node->set($key, $value);
}
}
// here we save all data, taxonomy entities too
$node->save();
I'm writing a Drupal 7 module to display child nodes of a module in the content.
Nodes have a field parent_nodes (node reference) where one ore more nodes are selected as parents.
First, I've created a view projects with a block view display subprojects, displaying nodes of type project with a field_parent_project contextual filter.
This is my module:
<?php
function projects_preprocess_node(&$variables) {
if ($variables['type'] == 'project') {
if (isset($variables['view_mode']) && $variables['view_mode'] == 'full') {
_projects_add_subprojects($variables);
}
}
}
function _projects_add_subprojects(&$variables) {
$nid = $variables['nid'];
$view = views_get_view('projects');
$preview = $view->preview('subprojects', array($nid));
$subprojects = array(
'#title' => t('Subprojects'),
'#label_display' => 'above',
'#weight' => 10,
//'#theme' => 'field',
'#markup' => $preview,
);
if (!isset($variables['content']['subprojects'])) {
$variables['content']['subprojects'] = array();
}
$variables['content']['subprojects'][] = $subprojects;
dpm($variables['content']);
}
This is working, adding the view display output to the node's content.
Only some things aren't working:
title (label)
weight do not change display position when rendered with other contents (it's always the first, above body).
If I uncomment the '#theme' => 'field' line, title is shown as a label, but nothing is rendered. This is because the field theme is used and I guess it needs #items and does not use the #markup element.
I cannot use a children nodes as reference, but only parent nodes.
The solution must be independent to theme, so no not answer "change your theme template" or similar
How can I show children nodes in node? I'm looking for a way to get something interpretable how a it is was a field
If you didn't already know, the Viewfield module allows you to specify a View as a field in your content type. This may save you some coding but you may not want a whole module to do such a specific task so...
If you want to continue with the custom code which you've written, then you need to re-structure your added content to the correct render array structure that Drupal expects. Try something like this:
$subprojects_view_output = array(
'#type' => 'markup',
'#markup' => $preview,
);
$subprojects = array(
'#theme' => 'field',
'#weight' => 10,
'#title' => t('Subprojects'),
'#items' => $subprojects_view_output,
);
With the above, the title/label for your field as well as the content of the field (the view itself) should show up. The code is untested so may not be 100% correct in terms of syntax and all but hopefully gives you a path to a solution.
EDIT: I tested the above and it does not work because in order to use the existing theme_field function it seems that Drupal expects more information required to render a field like the #field_name, #field_type, #entity_type, etc. as you should see in the warning messages.
Essentially, you are faking a field and you will need to provide Drupal with all the info it expects if you want to continue to use the built-in theme_field function, including all the variables as expected in the preprocess functions.
Alternatively, you can continue to use your original code and add a #prefix to get your title/label to render like this:
$subprojects = array(
'#weight' => 10,
'#prefix' => '<div id="subprojects-view">asdf:</div>',
//'#theme' => 'field',
'#markup' => $preview,
);
Then style the title/label with CSS accordingly. I didn't have any problems with the weighting as you described.
Thanks to #nmc, this is my final solution. If no results are found, it does not display the title. The check for results is done by if (count($view->result) == 0). Weight is working.
<?php
function projects_preprocess_node(&$variables) {
$type = $variables['type'];
if ($type == 'project' || $type == 'customer') {
if (isset($variables['view_mode']) && $variables['view_mode'] == 'full') {
_projects_add_subprojects_markup($variables);
}
}
}
function _projects_add_subprojects_markup(&$variables) {
$nid = $variables['nid'];
$view = views_get_view('projects');
$preview = $view->preview('subprojects', array($nid));
if (count($view->result) == 0) {
return;
}
$variables['content']['subprojects'] = array(
'#weight' => 10,
'#prefix' => '<h2>' . t('Subprojects') . '</h2>',
'#markup' => $preview,
);
}
I wanted to specify the output of a field from within my model so I added a date key to my $_schema:
models/Tags.php
<?php
protected $_schema = array(
'id' => array('type' => 'integer', 'key' => 'primary'),
'title' => array('type' => 'string'),
'created' => array('type' => 'integer', 'date' => 'F jS, Y - g:i a'),
'modified' => array('type' => 'integer')
);
?>
I store my time as an unsigned integer in the db (output of time()).
I want my base model to format any field that has the date key for output. I thought the best place to do that would be right after a find:
extensions/data/Model.php
<?php
static::applyFilter('find', function($self, $params, $chain) {
$schema = $self::schema();
$entity = $chain->next($self, $params, $chain);
foreach ($schema as $field => $options) {
if (array_key_exists('date', $options)) {
//format as a date
$params['data'][$field] = $entity->formatDate($field, $options['date']);
}
}
return $entity;
});
public function formatdate($entity, $field, $format, $timezone = 'Asia/Colombo') {
$dt = new \DateTime();
$tz = new \DateTimeZone($timezone);
$dt->setTimestamp($entity->$field);
$dt->setTimezone($tz);
return $dt->format($format);
}
?>
This doesn't seem to be working. When I execute a find all, this filter seems to get hit twice. The first time, $entity contains a count() of the results and only on the second hit does it contain the Records object.
What am I doing wrong? How do I alter this so that simply doing <?= $tag->created; ?> in my view will format the date the way I want? This, essentially, needs to be an 'after filter', of sorts.
EDIT
If I can find a way to access the current model entity object (not the full namespaced path, $self contains that), I can probably solve my problem.
Regardless of a small fix for your after find filter, I would do it differently.
Every time you'll do a find, you'll override your date format, even if you don't want to display it, but only do a business logic like comparing dates etc ...
Since you want to format your output only in your views (we are not talking about formatting on the fly json responses for an API, etc.), why not using a helper method ?
An other way is to add an instance method in your model (or a BaseModel), called created_at(). Then, you will call it from a view with <?= $entity->created_at() ?>
You can still force a format fetched from your $_schema, or pass it as a param, etc ...
A helper seems cleaner as we are talking about presenting data in your views.
I'm reading the OP's problem as the find filter executes twice. If that's right, then why not just check to see if the $entity contains a recordset?
I have 2 dates (create, update) that i want to merge in a new column, selecting the newest date... how can I do it?
Here is the array creation:
$this->Message= array(
'fields' => array('Message.id','Message.type','Message.createdate','Message.updatedate'),
'conditions' => $cond);
$messages = $this->Message->find('all', $conditionsMessage);
Now I need another field (lets call it NewDate) Message.NewDate that gets the newest date from Message.createdate and Message.updatedate, so i can call it after in a view using $messages[NewDate]
Help plz...
Thx!
UPDATE:
It's not hard to loop over the array with something like
foreach($messages as $k => $m){
if(strtotime($m['Message']['updatedate']) > strtotime($m['Message']['createdate'])){
$messages[$k]['Message']['NewDate'] = $m['Message']['updatedate'];
}else{
$messages[$k]['Message']['NewDate'] = $m['Message']['createdate'];
}
}
ORIGINAL ANSWER:
I would think that your Message.updatedate would always be the newest date, so you could just select that. But assuming that's not the case for some reason, you can create a virtual field in your model:
public $virtualFields = array(
'NewDate' => "IF(Message.updatedate > Message.createdate, Message.updatedate, Message.createdate)"
);
This uses the MySQL IF() function. If you're not using MySQL you'd have to figure out how to do something similar with your database.
http://dev.mysql.com/doc/refman/5.0/en/control-flow-functions.html#function_if