Silverstripe 3: removeByName not working - field

Good morning,
I've been trying to use the removeByName method and it doesn't work.
I'm basically trying to hide a field in my DataObject within the forms that's generated by ModelAdmin, which manages the object.
See sample code below:
///DataObject snippet...
class MyObject extends DataObject{
public static $db = array(
'Title' => 'Varchar',
'Desc' => 'Text',
'Template' => 'HTMLText',
);
//#Override
public function getCMSField(){
$fields = parent::getCMSField();
$fields->removeByName('Template'); /// DOESN'T WORK!!!
return $fields;
}
}//class
Note: I'm not getting any errors. I'm just still seeing the field on the forms (Add and Edit) as usual.
Any help appreciated, thanks.

Okay, I found the issue.
I was just going over the API again for the millionth time, and recognized that I've named the function wrong. See correction below:
///Correction, forgot to add the 's' at the end of both the function and the parent call.
public function getCMSFields(){
$fields = parent::getCMSFields();
}
I can understand an error not being generated in Apache logs for the function because it's legit. But as for the parent call, it should of generated an error since the method don't exists. (Theory: Perhaps, since the function was never actually being called, the parent call wasn't being executed and thus no errors [run-time error]).

Related

Adding Value Options to Select Element

I'm trying to set multiple values for a select object with Zend Framework 2's form class but it's only passing one value. Here is my code:
public function addphotosAction()
{
$identity = $this->identity();
$files = array();
$album_name = array();
foreach (glob(getcwd() . '/public/images/profile/' . $identity . '/albums/*', GLOB_ONLYDIR) as $dir) {
$album_name = basename($dir);
$files[$album_name] = glob($dir . '/*.{jpg,png,gif,JPG,PNG,GIF}', GLOB_BRACE);
}
$form = new AddPhotosForm();
$form->get('copy-from-album')->setValueOptions(array($album_name));
return new ViewModel(array('form' => $form, 'files' => $files));
}
I know it has to do with $album_name but am at a loss about how to use it to grab all the directories (if I try to write to $album_name via []), I get an warning of
`Warning: Illegal offset type in C:\xampp\htdocs\module\Members\src\Members\Controller\ProfileController.php on line 197`
which is the $files[$album_name] = glob($dir . '/*.{jpg,png,gif,JPG,PNG,GIF}', GLOB_BRACE); line.
As I said, I am at a loss about how to edit this to grab all the directories.
Any help would be appreciated.
Thanks!
here is a screenshot of what I am trying to describe: http://imgur.com/OGifNG9
(there is more than one directory that exists but only one is being listed in the select menu).
I really recommend to do it with a factory. With a factory you 'll write this code once and can use it everywhere else in your code. For object orientated reasons, in which everything should be an object, I recommend using PHP 's own DirectoryIterator class instead of glob. The code in the controller should be kept as small as possible. Please have a look at the following example code.
The Form Factory with the Directory Iterator
The form factory intializes the form class with everything you need for the form instance for you, so this code won 't show up in the controller. You can re-use it for an inherited edit form for example.
<?php
namespace Application\Form\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Application\Form\AddPhotosForm;
class AddPhotosFormFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $oServiceLocator)
{
$oParentLocator = $oServiceLocator->getServiceLocator();
// please adjust the dir path - this is only an example
$aDirectories = [];
$oIterator = new \DirectoryIterator(__DIR__);
// iterate and get all dirs existing in the path
foreach ($oIterator as $oFileinfo) {
if ($oFileinfo->isDir() && !$oFileinfo->isDot()) {
$aDirectories[$oFileinfo->key()] = $oFileinfo->getFilename();
}
}
// set option attribute for select element with key => value array of found dirs
$oForm = new AddPhotosForm();
$oForm->get('mySelectElement')
->setAttributes('options', $aDirectories);
return $oForm;
}
}
That 's all for the factory itself. The only thing you have to do is writing it down in your module.config.php file.
...
'form_elements' => [
'factories' => [
AddPhotosForm::class => AddPhotosFormFactory::class,
],
],
...
Using ::class not just cleans things up, it will lead to using fewer strings and this makes things easy to remember in an IDE with autocompletion for class names.
The Controller
With the factory we cleaned up the controller. In a controller code should be as small as possible. Using factories is the solution for many problems, which may happen in a later process of coding. So keep it always clean and simple.
...
public function indexAction()
{
$oForm = $this->getServiceManager()
->get('FormElementManager')
->get(AddPhotosForm::class);
return [
'form' => $oForm,
}
}
That 's all for the controller so far. Your select element was populated in the factory and your controller is easy to understand and as small as it should be.

Slim framework - Why does not halt method work if the controller extend the Slim class?

I have seen a very interesting thing. I did a little mvc/rest framework based on the Slim framework.
$app->put('/:id', function ($id) {
$app->halt(500, "Error") // Here this is working.
(new RestController)->editItem($id);
})->via('put');
So I wrote a RestController which extends the BaseController, and my BaseController extends the Slim framework.
class BaseController extends \Slim\Slim {
/**
* #var \app\models\AbstractModel
*/
protected $model;
public function __construct() {
$settings = require(__DIR__ .'/../configurations/slim.php');
parent::__construct($settings);
}
}
So my BaseController can uses the Slim class's methods and properties.
class RestController extends BaseController {
public function editItem($id) {
$data = $this->getRequestBody();
$result = $this->model->update($id, $data['data']);
// This is absolutely not working, but it seems my application will die in this case.
// Because I cannot see any written message (with echo or print methods...)
// This will always return with a 200 staus code and blank page!
$this->halt(404, json_encode(array('status' => "ERROR")));
}
}
But this will work fine... and I do not get it, why?
class RestController extends BaseController {
public function editItem($id) {
$data = $this->getRequestBody();
$result = $this->model->update($id, $data['data']);
// This will work.
$app = Slim::getInstance();
$app->halt(204, json_encode(array('status' => "ERROR")));
}
}
Anyone has a good ide?
You construct a new RestController in your route by doing (new RestController). Although it extends Slim/Slim in the class hierarchy, it is a new instance of it; it is not a copy of or reference to $app, the Slim application that is actually being run.
This causes issues in the first case: you say $this->halt(...) while $this is the newly constructed RestController which is a new instance of a Slim app, not the one that is currently running. Therefore the halt call has no effect on your the output of your application.
In the second case, you say $app->halt(...) where $app is Slim::getInstance(), the global instance of your Slim application, which is the actual running Slim app. Therefore the halt call has effect on the output of your application.
You can solve the issue either by using the second approach, or by instantiating your global $app variable as a RestController, and then you can do:
$app->put('/:id', function ($id) use ($app) {
$app->halt(500, "Error") // Here this is working.
$app->editItem($id);
})->via('put');
NB; you forgot to put use ($app) in the code posted to your question, I added it in my code above. Is it present in the code you are actually running? Otherwise the $app->halt() call should result in an error in any case.

Provide callback for custom component

I made a custom component which basically wraps a d3 line chart. Now I want to be able to register a callback for clicks on the lines in the chart.
I gave the component a #NgCallback parameter, which I then send events to:
class NetworkSummaryComponent implements NgShadowRootAware {
#NgCallback('callback')
Function callback;
void onShadowRoot(ShadowRoot shadowRoot) {
...
chart.callMethod('listen', ['line-click', (ev) {
var name = ev.callMethod('getLineName');
print(name);
callback({'name': name});
}]);
}
}
When using the component, I specify a function of my controller as callback:
<network-summary
...
callback="ctrl.lineClicked">
</network-summary>
However, that function is never actually called, put I know the callback arrives from the JS side because the print in the first snippet is executed.
If I instead specify the attribute as callback="ctrl.lineClicked()" I get a strange exception:
Closure call with mismatched arguments: function 'call'
I could not find any official documentation on how to properly do callbacks, so I'm not exactly sure what I'm doing wrong.. Any ideas?
It turns out that I had to explicitly name the expected arguments in the attributes:
<network-summary
...
callback="ctrl.lineClicked(name)">
</network-summary>
Hope this is useful to the next person having this problem.

Return response object in event listener not working when triggered in controller plugin

I implemented a shared event listener that manipulates and returns the response object with an error template showing the message of the triggered error code. (I'm not talking about throwing exceptions and catching them in a dispatch.error listener!)
This works fine when I call this event in a controller action. However, when I trigger my error event in a controller plugin that is called in the onDispatch method of the controller, only the status code is set correctly. But the called action is fully executed and no error page is shown.
I have absolutely no idea why this happens and I hope, someone is able to explain me the dispatch/event triggering/short circuiting/returning response issue here.
Following extracts might give you an impression of my code:
Listener:
class SharedErrorListener implements SharedListenerAggregateInterface {
...
public myErrorFunction(EventInterface $e) {
// get error code
// set status code to injected response object
// add error template to injected view model
// return response
}
}
Random controller action (works fine):
return $this->getEventManager()->trigger('error', $this, array(
'errorCode' => my_error_code
));
onDispatch() of controller:
// call plugin, if return value given return it (response must be returned?!).
$r= $this->plugin('myplugin')->doIt();
if (isset($r)) {
return $r;
}
class myplugin doIt() where error is triggered, but error template not showing up:
return $this->getController()->getEventManager()->trigger('error', $this, array(
'errorCode' => my_error_code
));
As the code in the controller and the controller plugin ist pretty much the same, I think it must depend on some application state. I did a lot of research, but couldn't find, what the problem might be. But as the event is triggered correctly and also the right status code is set to the response, I am just very confused. I don't want to implement an ErrorController (which would allow to call a redirect), because I think the solution via EventManager is actually very nice.
I'm too busy to actually read all the above, but from what my impression is, this code-snipped may actually help you out:
$trigger = $this->getEventManager()->trigger(self::EVENT_IDENTITY_GET, $identity);
if ($trigger instanceof ResponseCollection && !is_null($trigger->last())) {
return $trigger->last();
}
I finally found the problem here. By returning the response during the onDispatch process, the onDispatch() of the AbstractActionController wasn't called. In that function the return value of the controller action, e.g. the view model, is set as "result" of the MvcEvent. I just had to set the template that was set in the listener as the result for the event:
$r = $this->plugin('my_plugin')->doIt();
if ($r instanceof ResponseCollection) {
$e->setResult($this->getServiceLocator()->get('view_manager')->getViewModel()->getChildren());
return $r;
}
Hope this helps someone.

Symfony: Sharing a partial between two component actions

I have a component that has been happily building and rendering menus for a while now. Now I have to provide for a special case that shares all of the same logic, but requires a little bit of work in front of what already exists. What I'd like to do is create a new component action that will do the necessary preprocessing, punt to shared logic to complete the computational side and then render through the existing template partial (when all is said and done, it's still a menu like any other--it just take a little more work to build it).
Unfortunately, I can't find any way of doing this.
Here's the high level file/code breakdown that I have right now:
#
# navigation/actions/components.class.php
#
public function executeMenu() {
/**
* This method runs most of the menus and does most of the work
* that's required of the special case.
*
* Once complete, of course, it renders through navigation/templates/_menu.php
*/
}
public function executeSpecialMenu() {
/**
* Do some preparatory work and delegate to executeMenu()
* to finish up and render the menu. I'd like this action
* to render through the _menu.php partial as well.
*/
}
#
# templates/layout.php
#
<?php include_component( 'navigation', 'menu', array( 'menu' => 'Entity Type' ) ) ?>
/** SNIP */
<?php include_component( 'navigation', 'SpecialMenu' ) ?>
Any input would be much appreciated.
A simple if non-optimal way would be to create the _SpecialMenu.php partial and just place an include inside it:
<?php include_partial('navigation/menu', array('menu' => 'Entity Type', 'other_var' => $other_var) ?>
Where each of your variables will need to be passed to the partial as in $other_var. Does this at least solve the problem?
A more elegant solution is to use the get_partial inside the "second" component's execute function, like so:
public function executeSpecialMenu() {
//forces SpecialMenu to render _menu.php
echo get_partial('menu', $this->varHolder->getAll());
return sfView::NONE;
}
The call to varHolder->getAll gets all the variables that were going to be passed to the "normal" partial, since get_partial requires that.
Or, as a new method:
public function executeSpecialMenu() {
return $this->renderAlternatePartial('menu');
}
protected function renderAlternatePartial($partial) {
echo get_partial($partial, $this->varHolder->getAll());
return sfView::NONE;
}
Also there exists a renderPartial('xxx') method in the action class which is useful when it is needed to generate a part without template in cases such as XmlHttpRequest s:
if ($request->isXmlHttpRequest())
{
return $this->renderPartial('module/action', array('param' => 'value'));
}
I haven't tested if this works in the component execute methods. If this does not work it is a good idea to add such a functionality to symfony sfComponent class.
In the action/template mode, there exists a $this->setTemplate('xxx') method (in the action class) which can use a same template for different actions. (e.g same template for new or edit actions). Would that there was such a method in the component classes.
I wouldn't recommend to use different actions to render the same entity. Try to combine them and call a component with different parameters, and if it is heavy, move all the logic to a separate class.

Resources