Child-view gets rendered twice - forward

I've noticed that my child-view gets rendered twice for some reasons.
Here is a simple test-case:
Let's assume I've two controllers, ControllerA and ControllerB. I'm forwarding the action forwardTestAction from ControllerA to ControllerB and display the view returned from ControllerB as a child-view.
ControllerA::forwardTestAction()
public function forwardTestAction()
{
$childView = $this->forward()->dispatch(ControllerB::class, [
'action' => 'forward-test',
]);
$mainView = new ViewModel();
$mainView->addChild($childView, 'content');
return $mainView;
}
ControllerB::forwardTestAction()
public function forwardTestAction()
{
$number = new \stdClass();
$number->a = 10.4;
$view = new ViewModel([
'number' => $number,
]);
return $view;
}
Template forward-test.phtml of ControllerA
<?= $content; ?>
Template forward-test.phtml of ControllerB
<?php
$number->a = $this->currencyFormat($number->a);
echo $number->a;
The currencyFormat plugin throws an exception, because number gets formatted twice. The reason is because the child-view itself gets rendered twice.
That's a really weird behaviour and causes not only performance issues but also many other problems e.g. numberFormatting etc.
I'm doing something wrong?
Also I should mention that the html only gets outputted once, but the template gets rendered twice. Maybe it's just a weird constellation of my current setup. I also posted an issue on Github and they were not able to reproduce it.

Related

Updating Posted Form Values on Postback Without Losing Validation Messages

I had an issue similar to that posted in this related question:
mvc 4 textbox not updating on postback
Because I wanted to be a bit more targeted than "nuke it all" I am using ModelState.Remove("propertyname") instead to just affect specific values in my model.
However even this is too broad for what I want, because this removes any validation information, etc. that might have been accrued for this property.
How can I update the specific posted back value without losing all this other useful state information?
The below code displays the current behaviour, (with unwanted behaviour in bold):
submitting "test" returns a model with "replacement", but a form with
"test"
submitting "testlong" returns a model with "replacement", but a form
with "testlong", and displays a validation message
submitting "remove" returns a model with "replacement", and form with
"replacement"
submitting "removelong" returns a model with "replacement", and form
with "replacement" and no validation message
Model:
public class TestViewModel
{
[StringLength(7, ErrorMessage = "{0} must be less than {1} characters")]
public string A { get; set; }
}
Action:
public ActionResult Test(TestViewModel model)
{
if(model.A == "remove" || model.A == "removelong")
{
ModelState.Remove("A");
}
model.A = "replacement";
return View(model);
}
View:
#model TestNamespace.TestViewModel
#{ Layout = null; }
<!DOCTYPE html>
<html>
<head></head>
<body>
<form>
#Html.ValidationMessageFor(m=>m.A)
#Html.TextBoxFor(m => m.A)
</form>
</body>
</html>
Some examples where this would be useful:
Telling the user what they got wrong, fixing it for them, and telling
them we fixed it.
An application that logs validation errors for analysis, and always
does so as the last step before the view so as to capture validation
errors added during the Action.
Manager/Customer mandated logic which is nonsensical, but still required despite advice to that effect.
There is no clearly provided way to do this, due to the fact that in 99% of cases this is an X-Y problem (e.g. the reason for wanting this is flawed).
However if you really want to do this, you can do it with the following instead of Model.Remove("propertyName"):
ModelState propertyState = ModelState["propertyName"];
if (propertyState != null)
{
propertyState.Value = null;
}
or in another form
ModelState propertyState;
if (ModelState.TryGetValue("propertyName"))
{
propertyState.Value = null;
}

Chunk is called with unique data, but renders duplicate content

Guess my problem is closely related to this one : Snippet duplicates content when used multiple times on page
The elements of my problem are the following ...
$modx->loadedResources : an (empty) array registered in the main $modx object via a snippet on page load. The array holds resource id's of the resources fetched from the DB randomly, so the same resource isn't shown twice on the same page.
loadRandomResource : a snippet using XPDO-style querying to load a random resource from the DB. It uses $modx->parseChunk() to fill the placeholders in the chunk with the resource data. With each call, it appends the id of the fetched resource being fetched to the $modx->loadResources array.
I used some debugging to check if the resource id's were properly being stored in my array, each time I fetch a new random resource, which happens to be the case. I then checked if the db returns different results, each time I call the loadRandomResource snippet, and it does. I can also confirm that it doesn't return duplicate results (I exclude the already loaded resource ID's in my XPDO query).
However, when calling the snippet at 3 various locations throughout my page template, all 3 snippet calls render the same resource, which is weird, since my debug shows that unique data is being loaded from the DB, and being sent to the chunk for rendering.
Please find below both the snippet code, as well as the chunk mark-up.
Does anyone have any ideas? Any help is much appreciated!
loadRandomResource snippet
$criteria = $modx->newQuery('modResource');
$criteria->select(array('id','pagetitle'));
$criteria->sortby('RAND()');
$criteria->limit(1);
$whereOptions = array(
'parent' => 2,
'deleted' => false,
'hidemenu' => false,
'published' => true
);
if (!empty($modx->loadedResources)) {
$whereOptions['id:NOT IN'] = $modx->loadedResources;
}
$criteria->where($whereOptions);
$resources = $modx->getCollection('modResource', $criteria);
$output = '';
foreach ($resources as $resource) {
$fields = $resource->toArray();
$fields['tv.tvPersonalPicture'] = $resource->getTVValue('tvPersonalPicture');
$fields['tv.tvJobTitle'] = $resource->getTVValue('tvJobTitle');
$output .= $modx->parseChunk('cnkTeamListItem', $fields);
$modx->loadedResources[] = $fields['id'];
}
return $output;
cnkTeamListItem chunk
<div>
<img src="[[+tv.tvPersonalPicture]]" alt="[[+pagetitle]]" />
<h2>[[+pagetitle]]<br /><span>[[+tv.tvJobTitle]]</span></h2>
</div>
I found the answer myself, solution is a bit odd though ...
I was calling my custom snippet 3 times in my template, uncached. Each call though exactly looked the same ...
[[!loadRandomResource? &type='teammember']]
Even though I had the exclamation mark in place, still ModX was caching the call, within the same page request.
So when I added a random unique value to each of the 3 calls, the issue was solved.
Call 1 : [[!loadRandomResource? &type='teammember' &unique='123465']]
Call 2 : [[!loadRandomResource? &type='teammember' &unique='987654']]
Call 1 : [[!loadRandomResource? &type='teammember' &unique='666666']]
Don't know if this is a bug or a feature, but I thought that the exclamation mark prevented caching, both across different pageviews, as well as within the same page view. Anyhow, thx for helping.
I use this code for rendering chunks in snippents:
<?php
// get chunk or template
$tplRow = $modx->getOption('tplRow', $scriptProperties, '');
// get template
if (substr($tplRow, 0, 6) == "#CODE:") {
$tplRow = substr($tplRow, 6);
} elseif ($chunk = $modx->getObject('modChunk', array('name' => $tplRow), true)) {
$tplRow = $chunk->getContent();
} else {
$tplRow = false;
}
// render template
$field = array(); // your fields
if ($tplRow) {
$chunk = $modx->newObject('modChunk');
$chunk->setCacheable(false);
$chunk->setContent($tplRow);
$output[]= $chunk->process($fields);
} else {
$output[]= '<pre>' . print_r($fields, 1) . '</pre>';
}
You do realize you could have done this with getResources, don't you?
http://rtfm.modx.com/display/ADDON/getResources
&sortby=`RAND()`&limit=`1`

Layout disappears for certain action in cake

This was just working perfectly fine 5 minutes ago... I can't for the life of me figure out why this is happening. Basically, I start on a page, with a layout (== 'default'), no where in the controller code do I change the layout, in fact, no where in the application do I change the layout, but for some reason when I hit this certain action named 'addQuestion', it renders the view without a layout...
<?php
// medicalCasesController.php
public function addQuestion($caseId) {
if ($this->request->is('post')) {
$this->MedicalCase->Question->create();
if ($this->MedicalCase->Question->saveAll($this->request->data)) {
$this->request->data['Question']['id'] = $this->MedicalCase->Question->getLastInsertId();
$this->MedicalCase->Question->Image->processData($this->request->data);
$this->Session->setFlash(__('The question has been saved successfully.', true), 'flash/success');
$this->redirect(array('action' => 'addAnother', $caseId));
} else {
$this->Session->setFlash(__('There was a problem saving the question.', true), 'flash/failure');
}
}
$this->MedicalCase->id = $caseId;
$this->MedicalCase->contain(array('Question'));
$mc = $this->MedicalCase->read();
$count = $this->MedicalCase->getQuestions('count') + 1;
// mc, count, caseId
$this->set(compact('mc', 'count', 'caseId'));
}
?>
Keep in mind that I see the issue without POST data.. before the form is submitted. Let me know what else you need from me as I'm not quite sure how to diagnose/debug this issue.
Thanks
-Andrew
Figured it out, there was an error in a hidden divide.
Silly me..

How to validate a POST of a non standard action in Cake PHP 1.3

Using CakePHP 1.3
I have a controller with a non standard action name - say:
class WidgetsController extends AppController {
function modifyColor($id = null) {
// Some code that modifies the background color of a widget
}
}
and a companion view views/widgets/modifyColor.ctp
The modifyColor template POSTS to the action:
echo $this->Form->create('User',array('url' => array('controller' => 'widgets', 'action' => 'modifyColor')));
I get a 404 on the POST since the CakePHP Security component is trying to validate the form
and I would like to be able to validate the POST.
The only way I can get this to work seems to be to turn off POST validation
if ($this->action == 'modifyColor') {
$this->Security->validatePost = false;
}
This seems a bad solution.
How do I use the Security component on a non standard action?
allowedActions doesn't seem to work
Thanks
Danny
Answering my own question.
A. There is no problem with using any-named actions in CakePHP
B. A conceptual bug with CakePHP related to using the same function for the Form GET and Form POST
On the Form GET I had this:
if (empty($this->data)) {
$this->data = $this->Widget->read(null, $id);
}
The Form itself had some code like this:
echo $this->Form->input('id');
echo $this->formView('Current color', 'CurrentColor');
echo $this->formView('New color', 'NewColor');
echo $this->formView('New background color', 'BackgrdColor');
Which was fine, except that none of these fields appear in the Widget model - and the CakePHP Security component interprets this as a sort of XSRF attack - since it is finding fields in the form that don't belong to the model. That's why:
$this->Security->validatePost = false;
solved the "problem".
The correct solution is simply not to populate $this->data with the model in the controller action and handle the field assignments on the POST:
function modcolor () {
$this->layout = 'app_ui_listview';
if (!empty($this->data)) {
$id = $this->Session->read('Auth.User.id');
$u = $this->Widget->read(null, $id);
// Assign fields from the form to the model....
}
}
the problem is you are trying to pass url and you are trying to pass the controller stuff too.
echo $this->Form->create('Widget', array('controller' => 'widgets', 'action' => 'modifyColor'));
or
echo $this->Form->create('Widget', array('url' => '/widgets/modifyColor'));

Kohana 3: How can I pass full control to another action within my controller?

In my controller, I have a before() function that calls parent::before() and then does some additional processing once the parent returns. based on a specific condition, I want to "save" the original request and pass execution to a specific action. Here is my before() function.
public function before() {
parent::before();
$this->uri = Request::Instance()->uri;
$match = ORM::factory('survey_tester')
->where('eid','=',$this->template->user->samaccountname)
->find();
if (!$match->loaded()) {
self::action_tester("add",$this->template->user);
}
}
And the action that is being called..
public function action_tester($op=null,$user=null) {
$testers = ORM::factory('survey_tester')->find_all();
$tester = array();
$this->template->title = 'Some new title';
$this->template->styles = array('assets/css/survey/survey.css' => 'screen');
$this->template->scripts = array('assets/js/survey/tester.js');
$tester['title'] = $this->template->title;
$tester['user'] = $this->template->user;
switch ($op) {
case "add":
$tester = ORM::factory('survey_tester');
$tester->name = $user->displayname;
$tester->email = $user->mail;
$tester->division = $user->division;
$tester->eid = $user->samaccountname;
if ($tester->save()) {
$this->template->content = new View('pages/survey/tester_add', $admin);
} else {
$this->template->content = new View('pages/survey/tester_error', $admin);
}
break;
default:
break;
}
}
This all seems to work fine. This is designed to prompt the user for a specific piece of information that is not provided by $user (populated by LDAP) if this is the first time they are hitting the controller for any reason.
The problem is the views are not rendering. Instead control passes back to whatever action was originally requested. This controller is called survey. If i browse to http://my.site.com/survey and login with new user info, the record gets written and i get the action_index views instead of my action_tester views.
I cannot figure out what I am doing wrong here. Any ideas will be appreciated. Thank you.
EDIT: I managed to get this working (sort-of) by using $this->request->action = 'tester'; but I'm not sure how to add/set new params for the request yet.
The issue is that you are calling your method (action_tester), but then Kohana is still going to call the original action after the before method is called, which is going to change the response content overwriting the changed made in action_tester().
You can change the action being called (after before is called) inside your before() method:
$this->request->action('action_tester');
After the before method is called, it should then call the new Action (action_tester) rather than the old one, but then you need to do something about the way you are passing your parameters then.
Or you could just redirect the request upon some condition:
if($something) {
$this->request->redirect('controller/tester');
}
This doesn't seem like a nice way to do it anyway.

Resources