Zend framework common code for all the controllers - zend-form

I have a login button in the header of the website. This header's html is programmed into Zend framework views/layouts/home.phtml.
I have a hidden form in this layout that is triggered by jQuery thickbox inline content display integration. Reason, I dont want to make a ajax call to just fetch a small login form.
I create the form using Zend_Form and the problem is that I have to do it in all the controllers after checking if the user is logged in or not. I want to place this form generation in one single place, say in bootstrap and then have a logic in bootstrap to say that if user is logged in dont generate the form.
I don't know if bootstrap is the right place to do so or should I do it in some other place.
So, where should I instantiate the form so that its available everywhere if user is not logged in.

Create your own base controller which extends Zend_Controller_Action then have your controllers extend off of your base controller. I don't know what "jQuery thickbox inline content display integration" is...but you have several sections you can put it in depending when you need your code to run. init(), preDispatch(), postDispatch() etc... Just make sure when you extend off your base controller that you do sthing like:
parent::init()
parent::preDispatch()
parent::postDispatch()
etc... within each section so that the base code runs as well...

Be careful about Pradeep Sharma's solution (the answer he wrote himself and accepted below).
All the code code below is for ZF 1.12, and not ZF 2.0
In the bootstrap, Zend_Layout's MVC instance might not have been created yet. You should use Zend_Layout::startMvc() instead :
$view = Zend_Layout::startMvc()->getView() ;
And tbh I prefer executing this code in the preDispatch() function. New users of ZF might be interested in this :
application/plugins/HeaderForm.php :
class Application_Plugin_HeaderForm extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$view = Zend_Layout::startMvc()->getView() ;
$view->headerForm = new Application_Form_HeaderForm() ;
}
}
Calling new Application_Form_HeaderForm() will autoload by default into application/forms/ folder. You can also create the form directly into the plugin with new Zend_Form(), and addElement() etc. but it won't be reusable.
Of course, you need to register this plugin in your bootstrap!
application/Bootstrap.php :
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initPlugin()
{
$front = Zend_Controller_Front::getInstance() ;
$front->registerPlugin(new Application_Plugin_HeaderForm()) ;
}
}
Calling new Application_Plugin_HeaderForm() will autoload by default into application/plugins/ folder

I did it in a different way, extendingZend_Controller_Plugin_Abstract to implement a plugin and register it with front controller.
public function routeStartup(Zend_Controller_Request_Abstract $request) { }
generated the form inside the above mentioned method and by setting the form in $view object.
$view can be retrived using :
$view = Zend_Layout :: getMvcInstance()->getView();

Related

Module Creation: post process problems

Goodmorning everyone,
I'm developing a module for prestashop 1.7, at the moment I'm having problems intercepting the postprocess method in the main class of my module.
I need to do the checks on submit the form (which are on the user profile page, where I set personal information).
From what I understand, in a form a submit is made, the first thing that is called in a class is precisely the postProcess () method that takes care of validating the data received from the form just submissive (correct me if I'm wrong).
The problem is that when I submit my form it does not enter the postPorcess () method (I checked for a die ("test") and it does not even show the latter), while if I do the check I need by invoking my method staff inside a hook is made,
Can you tell me where I'm wrong?
Thank you very much and have a nice day.
Daniel.
Daniel,
This might be an endpoint problem, however, if you are sure to just handle the request via this Class, just use Tools::getValue('something_in_form') / Tools::isSubmit('var') to check that it's sent.
You don't really need to apply this one. If you need example, you should check Prestashop's native modules or Admin controllers, as it depends a lot of where you need to do this.
My thought after some years of module dev is that you should use a module front controller endpoint as you would with an API and a do a response in JSON like this example :
<?php
class DummyModuleNameAjaxModuleFrontController extends ModuleFrontController
{
public function initContent()
{
$response = array();
require_once _PS_MODULE_DIR_.'dummymodulename/dummymodulename.php';
$mod = new dummymodulename;
if (Tools::isSubmit('action') && Tools::isSubmit('var') && Tools::getValue('var') == $mod->getSomethingForSecurity()) {
$context = Context::getContext();
$cart = $context->cart;
switch (Tools::getValue('action')) {
case 'dummy_action_name':
// Don't forget to type it with an INT or secure this entry with strip_tags
$my_var = Tools::getValue('var');
break;
default:
break;
}
}
echo Tools::jsonEncode($response);
die;
}
}

Where To Place Common Variables in ASP.NET Core

I'm starting a new ASP.NET project after a few years developing in MVC4, and I have a question regarding architecture.
At the top corner of each page, I will display details of the current logged in user.
In MVC4 I achieved something like this by creating a BaseController, which created an EF data connection, and set up some common variables that would be used on every page - CurrentUser being one of them.
Now that I'm using Core, this approach doesn't seem to work, and certainly isnt mockable.
What would be the correct way to achieve something like this via ASP.NET Core?
I need the same variables on every view, and certainly dont want to have to write the code in each controller action!
You can use View Components feature in asp.net core to implement that functionality.
//In your ConfigureServices method , add your services that will be injected whenever view component is instantiated
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton<IUserRespository, UserRepository>();
}
//Now Create a view component
public class LoggedInUser : ViewComponent
{
private IUserRespository userRepository;
//Services can be injected using asp.net core DI container
public LoggedInUser(IUserRepository userRepository,SomeOtherService service)
{
//assign services to local variable for use later
this.userRepository = userRepository;
}
//This method can take any number of parameters and returns view
public async Task<IViewComponentResult> InvokeAsync(int param1,string param2,etc )
{
//get the logged in user data here using available services
var loggedInUserData = GetSomeData(context);
return View(loggedInUserData );
}
}
Create view file # View/Shared/Components/LoggedInUser/Default.cshtml.View can be strongly typed.
#model LoggedInUserModel
<div>
<!-- html here to render model -->
</div>
Now, since you use to display this data on every page , you need to apply _Layout.chstml to all your pages . In the _Layout.chstml , you can render view component defined above with any additional parameter you would like to pass as anonymous type.
#await Component.InvokeAsync("LoggedInUser", new { param1=value,param2=value,etc })
Testing the View Component:
var mockRepository = Mock of ICityRepository;
var viewComponent= new LoggedInUser(mockRepository);
ViewViewComponentResult result
= viewComponent.Invoke() as ViewViewComponentResult; //using Invoke here instead of InvokeAsnyc for simplicity
//Add your assertions now on result
Note :
It is also possible to decorate a controller with [ViewComponent(Name = "ComponentName")] attribute and define public IViewComponentResult Invoke()
or public IViewComponentResult InvokeAsync() to turn them in to hybrid controller - view component.

Switching MVC view on Post back using strongly typed views/view models

User requests page for Step1, fills out and submits form that contains selected person, so far so good. After validation of ModelState the next viewmodel is constructed properly using the selected person. I then attempt a redirect to action using the newVM but find on entry to Step2 that MVC wipes out the viewmodel attempted to be passed in. I suspect this is due to how MVC attempts to new up and instance based on query string results. I'll put a breakpoint in and check that, but am wondering how does one change a view from a post back with a new view model passed in?
public ActionResult Step1()
{
var vm = new VMStep1();
return View(vm);
}
[HttpPost]
public ActionResult Step1(VMStep1 vm)
{
if (ModelState.IsValid)
{
var newVM = new VMStep2(vm.SelectedPerson);
return RedirectToAction("Step2", newVM);
}
return View(vm);
}
public ActionResult Step2(VMStep2 vm)
{
return View(vm);
}
I can fix this by containing VMStep2 and a partial to Step2 in Step1 view, but that requires hide and seek logic when really I just want user to see Step2.
I don't see why you should want to call RedirectToAction! What it does it the following:
it tells your browser to redirect and like it or not your browser doesn't understand how to handle your object -- what it does understand is JSON. So if you really insist on using return RedirectToAction("Step2", newVM); you should consider a way to serialize your VMStep2 object to JSON and when the browser requests the Redirect, it will be properly passed and created in your action method public ActionResult Step2(VMStep2 vm)
HOWEVER I'd use a much simpler way ---
instead of
return RedirectToAction("Step2", newVM);
I would use
return View("Step2", newVM);
Thanks to everyone for the great input!
Here's what I did...
I created three views MainView, Step1View, Step2View (Step 1 and 2 were partial strong typed views)
I created a MainViewModel that contained VMStep1 and VMStep2
When controller served Step1 the MainViewModel only initialized VMStep1 and set state logic to tell MainView Step1 was to be shown.
When user posted back the MainView containing the MainViewModel, the MainViewModel knew what to do by the answers provided in VMStep1.
VMStep2 was initialized on the post back, and state was set to tell MainView to show Step2. VMStep1 was no longer relevant and was set to null.
User was now able to answer using VMStep2 and all was well.
The key to this working is that some flag tells the view which partial to show, the partial takes a model supporting it's strong type which is initialized at the right time. End result is fast rendering and good state machine progression.

Display the login form in the layouts main.php in Yii

I want to change the the "views/layouts/main.php" to display the login form whenever the user isn't authenticated.
So I changed the "siteController" actionIndex like that:
public function actionIndex() {
$loginForm = new LoginForm();
$this->render('index', array('loginForm'=>$loginForm));
}
And then call it in "views/layouts/main.php" like that:
if(Yii::app()->user->isGuest):
echo $loginForm;
else :
echo 'JJJ';
endif;
Then when I go to my website, It display the error: "Undefined variable: loginForm".
I don't know how to fix this? :(
Define new property in your controller class:
public $loginForm;
In your main.php access it like:
echo $this->loginForm;
If you pass variable in your render method it will be available inside view file only, but not in layout file.
It's because the index template is loaded before main template. So, better way to do hat you want, is to define a public property in your Controller. I suggest you to define this property in Controller class because SiteController and *Controller extends it.
Then, you can run this.
if(Yii::app()->user->isGuest) {
echo $this->loginForm;
} else {
echo 'JJJ';
}
Pay attention, because in this way of work you go out MVC pattern. This way of work force you to define a LoginForm in each action. I suggest you to do that:
Leave clean your calls to render file.
public function actionIndex() {
$this->render('index');
}
And add a getLoginForm method in you Controller class obtaining:
if(Yii::app()->user->isGuest) {
echo $this->getLoginForm();
} else {
echo 'JJJ';
}
There are a couple issues here. Firstly, you are creating an object called $loginForm and assigning it a value of new LoginForm();
$loginForm = new LoginForm();
I'm not sure if you are doing this on purpose and LoginForm() is a function or a method that returns something, but I have a feeling you were intending to do:
$loginForm = new LoginForm;
Which creates a new instance of the class LoginForm (which is a default Yii webapp CFormModel class). Even if that is the case, there are better ways to do this.
The easiest way is to call a renderPartial of the already existing login.php view (located in protected/views/site/login.php) inside your index.php view like so:
if(Yii::app()->user->isGuest) {
$this->renderPartial("loginform",array("model"=>new LoginForm));
} else {
echo 'JJJ';
}
This renders the view login.php (without rendering the layout because we have already rendered the layout - here's the docs on render and renderPartial) and pass it a new instance of the model LoginForm assigned to a variable called $model.
You will most likely have to edit the look of login.php view to make it "fit", but keep in mind that this view is being used in the SiteController actionLogin as well.
All that's left to do then is modify your actionIndex to handle the form submission (you can just copy the existing SiteController actionLogin functionality)
Another nicer solution would be to create a widget for the login form which can be used all over your application. I'm not going to go into that, but you can read up about it here on SO or check out this tutorial or this one.

Best way of global registering ClientScript?

I want to register user script globally, to be available all over the site. Now I insert in every action in my controllers:
Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl . '/js/script.js');
But really I understand that it's not good way...
If you are looking forward to use themes in your project, i would put some css and scripts in layout file (views/layouts/my-layout-file.php). Because if you changing theme you will be using another css and maybe sometimes another scripts, so you would not want to mix it together.
But some main css and scipts, that didn't change accross themes, i would put in main Controller (protected/components/Controller.php)
And all other controllers (/protected/controllers/) would extend this class Controller
class PageController extends Controller {
And so if all your controllers using on parent class, you can edit just parent class and add something like this
public function beforeRender( $view ) {
...
Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl . '/js/script.js');
...
return true;
}
And all your actions will be now using same script.
EDIT: #realtebo (in comments) pointed out to use 'beforeRender' not 'beforeAction'.
See more: Understanding the view rendering flow
You can do this in this way : initiate init function in base controller class having path protected/components/controller.php
public function init()
{
Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl . '/js/script.js');
parent::init();
}
The best way to register global js and css files I think is registering them in beforeRender() method (not in beforeAction() - because if you render json or xml this may destroy your structure) of some BaseController.
U can do like this:
1. create private attribute $_assetsUrl;
2. then in the module or controller
public function getAssetsUrl()
{
if ($this->_assetsUrl===null)
{
$assetsPath = $this->basePath.DIRECTORY_SEPARATOR.'assets';
$this->_assetsUrl = Yii::app()->assetManager->publish($assetsPath,false,-1,YII_DEBUG);
if (Yii::app()->theme!==null && is_dir($assetsPath.DIRECTORY_SEPARATOR.Yii::app()->theme->name))
$this->_assetsUrl .= DIRECTORY_SEPARATOR.Yii::app()->theme->name;
}
return $this->_assetsUrl;
}
Hope this was useful, see also this link http://www.yiiframework.com/wiki/148/understanding-assets/