Add method to be available in all Views - model-view-controller

I would like to have a method available to all Views in my app.
I would like to be able to make calls like this:
<span>${ getDynamicText() }</span>
The most obvious ways (to me) to implement this are:
Call the method in the controller and and pass it to the View.
Make the method static on some Util class and call it from the code ${ UtilClass.getDynamicText() }
Using meta programming to somehow make the method available to all Views.
The benefit of #3 is that the change would only have to be made in one place. #1 would have to be made in each controller action; and #2 would need an import on every View page which wants to use the method.
So is there a way to add a method to be available to all views in my app?
I have to admit I don't know in a lot of detail how .gsp files are processed behind-the-scenes so maybe they don't have a corresponding class and therefore can't be manipulated in this way. Links to good articles/docs will get extra good karma.

GSPs are compiled into classes that extend org.codehaus.groovy.grails.web.pages.GroovyPage, so you can add methods to that metaclass and they'll be available to all GSPs. The best place to do this is in BootStrap.groovy (or in a plugin's doWithDynamicMethods closure):
import org.codehaus.groovy.grails.web.pages.GroovyPage
class BootStrap {
def init = { servletContext ->
GroovyPage.metaClass.getDynamicText = { ... }
}
}

The recommended way to reuse functionality across GSPs is to define it as a tag, e.g.
class MyTagLib {
static namespace = 'my'
def dynamicText = {attrs ->
out << 'not very dynamic'
}
}
You can then call this tag in a GSP using:
<my:dynamicText/>

4th way: make a class/service that have method '.getDynamicText' and put it's intance into request at before filter ( request.setAttribute('x', myDynamicTextGeneratorObject) )
Now you can use x.dynamicText in any GSP

This is how I would do it:
Add a new class to your controllers folder containing your method
Do a grails install-templates
Navigate to the templates: \src\templatesscaffolding
Add the extends part to the controller template: class ${className}Controller extends NewController
re-generate your controllers
You can now use the method in every class and gsp.

Related

Composer/Laravel: Not update specific folder inside vendor

I have installed laravel latest version ..In my application i have used barryvdh/laravel-dompdf package. And i made some font changes in the pdf packages..
Now my problem is while giving composer update this "barryvdh/laravel-dompdf" do not get update ..It should be ignore then only my changes are not affected..
composer update
And here is the package I tried to add to my project: https://github.com/barryvdh/laravel-dompdf
Any suggestions ?
You should never make any changes in classes inside vendor. You should extend those classes instead to change standard functionality.
#Trent is not so difficult to explain.
if you read about OOP, you will get the idea about hierarchy, which means that a class can have a "father" which it extends.
So in this case the vendors in Laravel are loaded by default. So lets say you have a vendor class called "FatherClass", if you want to extend the functionality of it you can create a "ChildClass" extending of it. This is a simple example:
<?php
Class FatherClass{
public function method_one(){
return "Hi, This is method 1";
}
}
Class ChildClass extends FatherClass{
public function method_two(){
return "Hi, This is method 2";
}
}
//So now you can create a child object and will have the father and its own methods.
$childObject = new ChildClass();
$childObject->method_one(); // Hi, This is method 1
$childObject->method_two(); // Hi, This is method 2
?>
So how is this good to understand it for vendors?
Well in your case this is the class that you should extend PDF (https://github.com/barryvdh/laravel-dompdf/blob/master/src/PDF.php).
So it should be something like :
<?php
class NewPdfClass extends PDF{
}
You should be able now to override the methods or create new ones for your purposes.
Let me know if this helps.

Load objects with parameters from Laravel IoC container

I need a way to load objects via IoC provider depending on a request parameter. Right now I'm loading my objects directly with App::make(xy, [$urlParamter] which I want to refactor, so that I can use dependency injection the way it is supposed to. To describe my current architecture I need to show you quiet some information and in the end you find my concrete questions I have about it.
I'm building up a general CMS framework which provides an import architecture that is extendable with a custom import implementation.
Now I'm struggling with properly loading the concrete classes via IoC container, because they always depend on the selected import.
To dig into my problem, here is my entry point in routes.php
Route::get('/import', ['as' => 'overview', 'uses' => '\CMSFramework\Http\Controllers\Import\ImportController#index']);
This generates a view where the user selects a concrete import to be triggered. After selecting a concrete import, the user should get individual views to prepare the appropriate import (i.e. Upload a CSV file, select an area to import, etc.)
In my concept an import implementation consist of:
A controller class, to implement specific (peraration-) tasks like uploading a CSV file. It inherits from a base controller of the cms framework
An import "business" or "service" class, that implements how the data is getting imported (and may further delegate to queued jobs etc.)
The CMS framework part consists of:
A base controller class for all common/shared import tasks like (start the prepared import, clean all working data, etc.)
A base service class ImportBase where all implementations inherit from. It provides an interface to receive a progress for any import and implements shared operations like cleaning up working data, etc.)
An ImportStatus class which is part of the ImportBase-Class via $ImportBase->status() to handle all runtime status informations (like "is the job still running, what is the progress). This class also provides a containter for a so called "payload" that allows any conrete import implementation to push and fetch custom status informations (ie. any sub-process has been finished)
So back to my IoC architecture. After the user selected a concrete import, the following route delegates the action to the custom import implementation's controller. If it's a framework supported standard-action like via URL /import/<importkey>/clean, the inherited BaseController of the cms framework takes over and handles the request
Route::get('/import/{key}/{method}', ['uses' => function($key, $method) {
return App::make('\\MadeleinePim\\Http\\Controllers\\Import\\'.ucfirst(camel_case($key)).'Controller')->$method($key);
}]);
I know that this direct binding via a naming convention can be improved (maybe via a custom configuration file), but for now this works for me.
Now I need to show an example of how I tried to implement a concrete import target in my controller via /import/<importkey>/seedCsvDataToDatabase:
public function seedCsvDataToDatabase($key)
{
// The IoC binding is shown in next code snippet. I did not found a good way to use method injection because
// of the route-specific parameters that control the responsible import implementation
$import = \App::make(Import::class, [$key]);
// Now trigger the import service operation of that concrete import implementation (probably bad design here)
$import->seed();
// Now, that this preparation task is done, I use the ImportStatus object which is part of the Import to store
// status informations. With this I can then decided in which step the user is (Think of it like a wizard to
// prepare any import)
$import->status()
->set(ConcreteImport::STATUS_SEEDED, true)
->set(ConcreteImport::STATUS_SEEDED_DURATION_SECONDS, (microtime(true) - $time_start) / 60);
// Back to controller method that determines in which status the import is to delegate/redirect to different
// views.
return redirect('/import/<importkey>');
}
My IoC binding for the Import class:
$this->app->singleton(Import::class, function ($app, array $parameters) {
$importKey = head($parameters);
// There is a config file that provides the class names of the concrete import implementations
$importClassName = config()->get('import.' . $importKey);
if (!$importClassName) {
throw new ImportNotFoundException($importKey, "Import with key '{$importKey}' is not setup properly'");
}
$importReflectionClass = new \ReflectionClass($importClassName);
return $importReflectionClass->newInstance($importKey);
});
And finally, the lazy loading of the import status, which is encapsulated in the ImportStatus object looks like this
public function status()
{
if (!$this->status) {
$this->status = \App::make(ImportStatus::class, [$this->key()]);
}
return $this->status;
}
I hope that demonstrates the way I try to resolve my import objects from the IoC container.
My learning so far is, that this is not the right way to inject my objects.
Is the assumption right, that I should not pass the $importKey at runtime to the App::make() and rather should try to make this independ?
My failed attempt on this was to make the IoC binding smarter and let it access the Request to properly inject my concrete import object with the required $importKey, like (pseudo code!):
$this->app->bind(ImportStatus::class, function(Container $app) {
// Did not find a good way to access the {key}-part of my route /import/{key}/{method}
$key = $app->make(Request::class)->get('key'); // Does not work like this
return new \Scoop\Import\ImportStatus($key);
});
Does this approach can work like this?
Can I somehow pass through the $importKey from my route to the ServiceProvider (or better pull it from there?)
Is there a better solution to initialize my concrete import implementations?
----------
UPDATE 1
For my lattest idea to access the Route in my IoC Binding, I got this way working:
$this->app->singleton(Import::class, function (Container $app) {
$importKey = \Route::current()->getParameter('key');
$importClassName = config()->get('import.' . $importKey);
$importReflectionClass = new \ReflectionClass($importClassName);
return $importReflectionClass->newInstance($importKey);
});
Nevertheless the idea of #Sandyandi N. dela Cruz to use a router binding prevents the direct coupling between the Binding and the Request which still doesn't feel right. Using router-binding to couple a request parameter to an implementation, sounds more appropriate.
I think you've dwelt to much on the IoC container there. Why not implement the Factory pattern and do a route binding instead of creating multiple controllers to handle different Imports? Crude example as follows:
Create a route binder - edit your app/Provider/RouteServiceProvider.php's boot() method
public function boot(Router $router)
{
parent::boot($router);
// Add the statement below.
$router->bind('import', 'App\RouteBindings#import');
}
Create the App\RouteBindings class as app/RouteBindings.php
Create an import() method with the following:
public function import($importKey, $route)
{
switch ($importKey) {
case 'import_abc':
return new ImportAbc;
break; // break; for good measure. ;)
case 'import_xyz':
return new ImportXyz;
break;
// and so on... you can add a `default` handler to throw an ImportNotFoundExeption.
}
}
Create a route for resolving an Import class.
Route::get('import/{import}/{method}', 'ImportController#handleImport');
Here, {import} will return the proper Import concrete class based on your URL.
In your ImportController's handleImport() you can do the following:
public function handleImport(Import $import, $method)
{
// $import is already a concrete class resolved in the route binding.
$import->$method();
}
So when you hit: http://example.com/import/import_abc/seed, the route binding will return a concrete class of ImportAbc and store it in $import on your handleImport() method, then your handleImport() method will execute: $import->seed();. Tip: you should probably move other controller logic such as $import->status()->set() into the Import class. Keep your controllers thin.
Just make sure your Import classes have the same signature.
It's kinda like Laravel's Route Model Binding except you create the logic for the bindings.
Again, this is just a crude example but I hope it helps.

Getting view to use correct region automatically

As mentioned in an earlier question of mine, I'm new to ATK4 and I'm currently learning, so there might come a few more question. Now to my issue.
I've created a region "Sidebar" in my template shared.html and adding the view to it like this:
class Frontend extends ApiFrontend {
function init(){
parent::init();
/*
Other stuff here
*/
$this->addLayout('Sidebar');
}
function layout_Sidebar() {
$this->add('View_Menu', null, 'Sidebar');
}
}
Then I'm creating the view like this:
class View_Menu extends AbstractView {
function init(){
parent::init();
$this->add('HtmlElement')
->setElement('a')
->setAttr('href', 'testurl')
->set('Link');
}
}
This gives me the following error:
Spot is not found in owner's template
Additional information:
spot: Content
Supplying the add function with $this->template->top_tag as third argument solves this problem:
$this->add('HtmlElement', null, $this->template->top_tag)
->setElement('a')
->setAttr('href', 'testurl')
->set('Link');
...but do I really have to add that to every add() call in the view? It doesn't seem right and I'm quite sure it's not!
When you are creating AbstractView, you need to specify a default template. By default your AbstractView will use the region of from your shared.html. In other words AbstractObject assumes the template of the region it replaces.
when you create defaultTemplate() or pass 4th argument to the add() you can specify a different file to be used for template of your sidebar menu.
In either way - the template should contain a where output of any sub-elements will be displayed.
You may inherit from "View" class which already relies on the custom template containing just a . Your idea of using HtmlElement is just like this, because HtmlElement extends View.

With Codeigniter, what's the difference if I wanted to set a method to private?

With callback functions that are within the same class, I can't set the following.
private function check_valid_image
{
...
}
I can get it to work if I do the following.
function _check_valid_image
{
...
}
By placing an underscore in front of the method name, is that the same as placing the word private in front?
It's a convention used with the form validation class for callbacks. It also makes that method not callable via the URL segments.
That being said, it is not the equivalent of making a method private, which has implications in how code can be run outside of the class.

MEFContrib.MVC3: Exporting Controllers with Base Classes

I have opened up a question on CodePlex but have not received any responses at all.
Basically, I have a base controller that all of my controllers inherit from. When I create a new MVC3 project, put my controllers in another assembly, and add MEFContrib.MVC3 to the project, everything works great. When I make any of the controllers inherit from my base class, they can no longer be found.
I am not familiar enough with MEFContrib to know what exactly is breaking, but I have tried to decorate my controllers with ExportAttributes and that has not worked, either.
All of this is a different assembly than the main MVC project:
public class MyBaseController : Controller
{
...
}
// This controller cannot be found.
public class HomeController : MyBaseController
{
public ActionResult Index ()
{
// Do Stuff
return View ();
}
}
// This controller can be found.
public class HomeController : Controller
{
// yada, yada, yada...
}
Edit:
counsellorben was exactly right. I had dependencies that were not properly marked for export and so the controllers could not be properly composed. I stepped through each of my dependencies, stepping through their own dependencies, and found the few I missed.
The most likely issue is that there is a problem with a dependency in your MyController class. This will cause a problem with any controller inheriting from MyController.
Please see this answer for some sample code you can use to try and diagnose where your problem lies.
I think you need to decorate with the InheritedExport attribute.

Resources