How to set up layout for all controllers in Yii module? - yii

in my Yii app I have one module called admin, so the module class is AdminModule, it extends the CWebModule class and is located in the AdminModule.php file. According the documentation the CWebModule has a layout property which is shared among all module controllers in case the controllers itself do not have any layout defined.
My controller does not have any layout defined and in AdminModule.php i put this:
$this->layout='webroot.themes.bootstrap.views.layouts.column2';
$this->layoutPath = Yii::getPathOfAlias('webroot.themes.bootstrap.views.layouts');
However, my controllers in admin module are still using some other layout, i think it is the one defined in the Controller.php in components directory. Why is that? How do I setup shared layout for a particular module?

The solution is to slightly change my code, like this:
$this->layoutPath = Yii::getPathOfAlias('webroot.themes.bootstrap.views.layouts');
$this->layout = 'column2';
as with the path specified I do not need to specify whole path alias for a layout. I have these to lines in init() function of my AdminModule.php and it works fine.

try setting the layout path as shown below
$this->layout="webroot/themes/bootstrap/views/layouts/column2";

The solution is set the layout on beforeControllerAction in your module. It should work.
I've answer the similar question, please refer to it
Yii module layout

if you use Module and Theme(like bootstrap) at the same time,it means Yii will find view file(include layout file, a special view file) in Theme folder firstly,there is an important function named resolveViewFile in CController, you can add debug point on this to watch out how it works, and below is my solution:
1. I have a module named "admin".
2. In AdminModule's init function, add:
$this->layout = 'column2';
3. remove module's all view files to theme folder

you can put the following code in your controller.
public function init()
{
Yii::$app->setLayoutPath($this->module->getBasePath().'/views/layout');
}
or you can put the following code in your module bootstrap class
public function init()
{
parent::init();
Yii::$app->setLayoutPath($this->getBasePath().'/views/layout');
}

Related

Access Views and Controllers which are under Sub folders for specific scenarios?

On my all actions methods I use this route attribute to use - sign.
[HttpPost]
[Route("lower-case-converter")]
public IActionResult Index(BassModel model)
Since I have many case converters I want to put them inside a folder called "CaseConverters". I made a folder in views folder called ""CaseConverters". Note that there are other tools too.
So I changed the route attribute like this
[Route("~/CaseConverters/lower-case-converter")]
Above not worked. So I changed it to
[Route("/CaseConverters/lower-case-converter")]
Still its not working. Note that i want to add this folder to Controllers folder too. How to acehve this?
To specify a route for lower-case-converter action inside your CaseConverters controller you can specify the route as follows, (I'm assuming CaseConverters is your controller. If yes It's better to use the correct naming convention. Ex: CaseConvertersController)
[Route("CaseConverters/lower-case-converter")]
You can also specify a route template for your controller class. as follows,
[Route("CaseConverters")]
public class CaseConvertersController : Controller
{
[Route("lower-case-converter")]
public IActionResult LowerCaseConverter()
{
//you implementation
}
}
For the above code route template for the action is CaseConverters/lower-case-converter. And also make sure use have used app.UseMvc() and MapRoute() methods inside Configure() method inside the startup.cs

How to override PrestShop 1.7.7 module classes?

I want to override class MySQL of PrestaShop module ps_facetedsearch that is located in file: mystore/modules/ps_facetedsearch/src/Adapter/MySQL.php
How do I achieve this?
EDIT:
Class MySQL that I want to override is defined in file mystore/modules/ps_facetedsearch/src/Adapter/MySQL.php.
This class is used by class Search of same module and is defined in file mystore/modules/ps_facetedsearch/src/Product/Search.php. Class Search is used by another module class and so on, so there is a long 'chain' of classes.
Do I need to extend all chained classes or can I somehow override only the MySQL class that really requires modification?
Override Module Classes
To modify behavior of a module’s class you have to put your modified class at
ROOT/override/modules/MODULENAME/OVERRIDECLASS.php
You have to create a PHP file on the location below and call it ps_facetedsearch.php
ROOT/override/modules/ps_facetedsearch/ps_facetedsearch.php
Inside the overriding class you can add the code below.
<?php
class Ps_FacetedsearchOverride extends Ps_Facetedsearch
{
public function getContent()
{
return "Hello store owner! this code is brought to you by crezzur.com";
}
}
?>
After saving your ps_facetedsearch.php override and clearing the Prestashop cache you will see the message "Hello store owner! this code is brought to you by crezzur.com" when you open the module ps_facetedsearch.

How are Razor Page View Component paths searched? I fail to get them working, .NET Core tries to include irrelevant model

I cannot get ViewComponent to be included in a page.
I have a folder structure:
- Pages
- Components
- ExternalSystems
- Default.cshtml
- Views
- Shared
- Components
- ExternalSystems
- Default.cshtml
Class file
public class Default : ViewComponent
{
private readonly Models.PermissionRegisterContext _context;
public Default(Models.PermissionRegisterContext context)
{
_context = context;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var externalSystems = await _context.ExternalSystem.ToListAsync();
return View("Default", externalSystems);
}
}
within Pages/Index.cshtml i'v tried to include this component by trial & error (i'm learning this stuff after all):
#await Component.InvokeAsync("ExternalSystems");
#await Component.InvokeAsync("/Pages/Components/ExternalSystems");
#await Component.InvokeAsync("/Pages/Components/ExternalSystems/Default");
#await Component.InvokeAsync("/Pages/Components/ExternalSystems/Default.cshtml");
#await Component.InvokeAsync("/Views/Shared/ExternalSystems");
I expected that exception will show places searched like it does for #Html.Partial:
InvalidOperationException: The partial view 'none' was not found. The following locations were searched: /Pages/none.cshtml /Views/Shared/none.cshtml
However for every single call to #await Component.InvokeAsync it spits this exception:
InvalidOperationException: Could not find an 'Invoke' or 'InvokeAsync' method for the view component '.Pages.Identities.IndexModel'.
First of all, it doesn't show me paths searched. Second, wait what? Why are you telling me something about Pages.Identities? Yes, I have that model, but it is nowhere referenced in neither Pages/Index.cshtml or View Component i'm trying to include.
Could someone please provide me with a guidance to determine: How view component paths are searched? For Razor Pages it is not documented, only in 3rd party site.
Anyway it doesn't work that way for me - what would be the debugging steps? Console debug doesn't show anything useful.
dotnet 2.0.7
Let's say you have created a view component as follows :
public class ExternalSystems : ViewComponent
{
public ExternalSystems()
{
//constructor can have dependencies injected.
}
public IViewComponentResult Invoke()
{
//View is a helper method available in classes inherited
//from ViewComponent that returns an instance of
//ViewViewComponentResult. It has multiple overloads as described
//later.
return View(viewName,viewModel);
}
}
View method has multiple overrides :
View() - use default view for view component without any viewmodel
View(viewModel) - use default view for view component with specified viewmodel
View(viewName) - use specified view for view component without any viewmodel
View(viewName,viewModel) - use specified view for view component with specified viewmodel
When you try to render this view component from a Controller, view will be looked up at following locations :
"/Views/{ControllerName}/Components/ExternalSystems/{ViewName}.cshtml" .
So, if you are using HomeController and have specified viewName as ExternalSystemsView in the View(viewName,viewModel) call , your path becomes
/Views/Home/Components/ExternalSystems/ExternalSystemsView.cshtml . This allows each controller to have its own custom view for the view returned by view component.
If the ExternalSystemsView.cshtml is not located at above path , it will be looked up at /Views/Shared/Components/ExternalSystems/ExternalSystemsView.cshtml
You can override the lookup position by passing the complete path of view - View("Views/Shared/Components/Common/YourView.cshtml") while calling View(viewName,viewModel) from your ViewComponent's Invoke method.
Note : If you don't specify a viewName, it defaults to Default.cshtml which is different from Index.html used for controllers
For your case, #await Component.InvokeAsync("ExternalSystems") is the correct call as it expects the viewcomponent name as parameter. ViewName will be picked up from what you have passed as the viewName parameter value to View(viewName,viewModel) call in your ViewComponent's Invoke method and will default to Default.cshtml if no viewname has been specified.
Alright, my mistake was that I didn't see I have decorated ...Pages.Identities.IndexModel with [ViewComponent]. It was unintentional and was just trying to make ViewComponents work by trial & error and failed to see this mistake.
Basically dotnet discovered type that didn't have either Invoke/InvokeAsync function and thus was stuck on that exception.
I validated that these paths are searched when providing "HelloWorld" as view name:
/Pages/Components/HelloWorld/HelloWorld.cshtml
/Views/Shared/Components/HelloWorld/HelloWorld.cshtml

How to add admin menu entry for a specific module on Prestashop1.6?

In a Prestashop 1.6 store, how can I add a new admin/backoffice menu entry for an existing/installed module (for example, bankwire or blocktopmenu modules)?
If you try to add the item menu in "Administration" -> "Menus" with the class of the module and the name, probably you get an error that says that it's impossible to find the controller. Then you need to create this controller in the module.
You need to create a file in /moudles/[name-of-the-module]/controllers/admin/ with the name of the principal class.
For example, to the blocklayered module you need to create a file called "blockLayered.php", and you need to put this code inside the file:
<?php
class BlockLayeredController extends AdminController {
public function __construct() {
$module_name = "blocklayered";
Tools::redirectAdmin('index.php?controller=AdminModules&configure=' . $module_name . '&token=' . Tools::getAdminTokenLite('AdminModules'));
}
}
Explanation:
Class name: Name of the principal class of the module followed by "Controller".
$module_name: Name of the directory of the module.
Then, you go to "Administration" -> "Menus" and create a new item with this data:
"Class: BlockLayered" -> Principal class.
"Module: blocklayered" -> Name of the directory.
I hope it has been helpful for you.

Base class for common YII functions?

I know how to create a class the will allow me to instantiate it and use across my project. What I want to be able to do is have functions without instantiating classes. For example, I know how to do this:
$core = new core();
$val = $core->convertToMyNotation($anotherval);
But what I want is to be able to do this ANYWHERE in any view, class whatever:
$val = convertToMyNotation($anotherval);
Where would I place these functions in order to be able to do that?
best way to do it, create a public function in components/Controller.php
public function globalFunction(){
// do something here.
}
and access it anywhere by
$this->globalFunction();
You can define a static method as an option.
class core{
public static function convertToMyNotation($value){
//do whatever here
return $value;
}
}
Then call it like so:
$val = core::convertToMyNotation($anotherval);
This requires no instantiation of the object to use. The only restriction is that you cannot use the $this property inside a static method.
Alternately, just define a file with your functions in it and include the file at some point early like, like within the boostrap script in your public_html/index.php file.
Edit: darkheir makes some good suggestions. Include such a class in your protected/components folder, and have it extend CComponent to gain some potentially useful enhancements.
By including the class in the protected/components folder, you gain the advantage of autoloading the class, by default.
There is no definitive question of your answer, it depends a lot on what the function will be doing!
If the function is performing some things specific to a model
(getting the last users, ...) this has to be in the User model as
Willem Renzema described:
class theModelClass {
public static function convertToMyNotation($value){
//do whatever here
return $value;
}
}
And you'll call it like
$val = theModelClass::convertToMyNotation($anotherval);
If the function is handling user inputs (sanitizing he inputs,
checking the values, ...) then it has to go to the controller and
you'll use Hemc solution:
Create a public function in components/Controller.php
public function globalFunction(){
// do something here.
}
and access it anywhere by
$this->globalFunction();
If the function is an Helper: performing some actions that do not
depend on models or user inoput then you can create a new class that
you'll put in your component directory:
class core extends CComponent{
public static function convertToMyNotation($value){
//do whatever here
return $value;
}
}
And
$val = core::convertToMyNotation($anotherval);
Actually, I think you're looking for this answer instead:
http://www.yiiframework.com/wiki/31/use-shortcut-functions-to-reduce-typing/
In essence, in your entry script, before you load up Yii, include a global functions file:
require('path/to/globals.php');
Then, any function defined in that file can be used as a shortcut. Be careful, but enjoy the power! :-)
Create something like
Class Core extends CApplicationComponent{
public function doSomething(){}
}
and in config main.php
'components'=>array(
'core'=>array(
'class' => 'Core'
),
),
and now you can call whenever you want
Yii::app()->core->doSomething();