I am creating a custom MVC framework.
I verrrrry loosely modeled it after the codeIgniter framework, but it's ground-up custom for the most part.
I'm at the point where I have URL's routing to the appropriate controller actions, but I'm stuck at the point where I generate a view that can utilize data generated by the controller.
I have views defined (static HTML with inline php ready to populate dynamic data), and I have the destructor of my base controller require()'ing the view in order to populate the browser with the view... here's the code:
public function __destruct()
{
if ($this->bSuppressView === false)
{
require(APP_PATH.'views/layout/header.php');
require(APP_PATH.'views/'.$this->sController.'/view.'.$this->sController.'.'.$this->sAction.'.php');
require(APP_PATH.'views/layout/footer.php');
}
}
Basically, when the controller is done executing, the teardown process of the base controller will then include the global header view, the controller's action's view, and then the global footer view, which should populate the webpage with everything for the URL that was requested...
HOWEVER, I cannot access any globally defined variables from the embedded php in the view code. In my bootstrap class, I define a bunch of local variables such as my config variable, etc., but the view seems to consider those variables undefined. Additionally, i'm unsure how to allow the view to access data that the controller may have generated. Where do I "stick" it to make it available to the view?
Let me know if this isn't clear, and i'll update.
Thanks!
UPDATE: I've discovered that while doing it this way, the "environment" of the views is within the controller object, which, as far as I can tell is a great thing! I don't have to propogate anything anywhere but in the controller, and I can use "$this->" in the views to get access to anything public or private from within the controller class!!!
That leaves the question: is this "normally" how it's done in MVC? What's the BEST way to propogate a view? I think this will suit my purposes, and I will post back if I discover a limitation to just treating the embedded view php as "within the scope of the calling controller"...
The way this is generally done, is that the view is actually an object. You pass that object you're variables, and that view object takes the template you gave it, includes it so that it's in the current scope, and grab the output into a variable using output buffering.
To give you a basic idea:
// controller object
$this->set('key','val');
$this->render('mytemplate');
// controller base class
$view = new View;
$view->setData($this->getData());
// view class
class View {
....
function render() {
ob_start();
include $this->resolveTemplate();
$out = ob_get_contents();
ob_end_clean();
return $out;
}
Related
I am new to ASP.NET Core and have some trouble with binding global models and viewmodels to razor views. My application is a mixture of Web API and MVC with razor views.
My goal
When I open an ASP.NET MVC page, I need to instantiate a model by loading it from the database (DbContext service) based on an id received in a cookie. I want to use this model object globally in every view or partial view.
Current Implementation
I can access the cookies in action methods of page controllers, so that I have to load the model from the DbContext in every action method and bind it as viewmodel to target view. This is not practical, because I have to do this in every page controller, because I need that model object on all pages in my navigation pane.
Idea
I think it should be possible to access to Cookies and dbcontext within Startup.cs and bind the model object to _ViewStart.cshtml, so that it is accessible globally in every view or partial view. Even this approach were correct, I do not have any idea how the code would look like. Because I am learning Web-Apps with .NET Core by learning by doing and try and error at the moment. :(
UPDATE
I have a layout page _Layout.cshtml, which includes partial views like the _NavPane.cshtml. My goal is to pass a Model object to the _Layout, which is instantiated via loading from the database (I have a service IMandantRepository for this purpose) and dependent on a cookie.
That model object is needed on every page request. That's why it would be a better practice to load the model object outside the MVC page controllers and pass it to them (what I can not implement technically).
I tried to find a solution by myself and ended up in following interim ugly solution. Following is the content of the _ViewStart file. On the bottom I assign the needed global variables, which I can use in every view or partial view.
This solution has at least two disadvantages:
The model object is possibly loaded redundantly.
Too many program logic in a view file.
#inject MyProject.Data.IMandantRepository mandantRepo
#{
// Main layout template
Layout = "_Layout";
// Define default values
bool showAdminSection = false;
string src = "/images/logos/nologo.png";
// Read cookie value
string currentMandantUid;
Context.Request.Cookies.TryGetValue("currentMandant", out currentMandantUid);
// Load mandant from the database
var thisMandant = mandantRepo.GetMandantByUid(currentMandantUid);
if(thisMandant is Mandant){
src = "data:image/*;base64," + thisMandant.GetBase64Logo();
showAdminSection = thisMandant.Abbr == "AdminMandant";
}
// Assing global variables to ViewData
ViewData["CurrentMandant"] = thisMandant;
ViewData["logoSrc"] = src;
ViewData["showAdminSection"] = showAdminSection;
}
This is an example code in ConfigureService() of Startup.cs. You can register your dbContext class in this way.
services.AddDbContext<BookStoreContext>( options =>
options.UseSqlServer(_configuration.GetConnectionString("DefaultConnection")));
I'm still learning Laravel and I'm working on a small project to help me understand better. In the project, I am in need of a global array, so that I may display it or its attributes on every view rendered. sort of on a notification bar, so that each page the user visits, he/she can see the number of notifications (which have been fetched in the background and are stored in the array).
I have done some research, and realized that I have to fetch and compile the array in a view composer I think. But everywhere I go, I cant seem to understand how to make a view composer.
I need to fetch the relevant rows from the database table, and make the resulting array available to each view rendered (I'm thinking attaching it somehow to my layouts/default.blade.php file.). Please help, any and all advice is greatly appreciated:)
You can now inject services on your view
More info here: https://laracasts.com/series/whats-new-in-laravel-5-1/episodes/2
You have to use Sub-Views of laravel blade. I guess your functionality is like a sidebar or like a top bar which will be rendered at every page.
//Your Controller pass data
class YOUR_CONTROLLER extends Controller {
public function index()
{
$data = YOUR_DATA;
return view('YOUR_VIEW_FILE', get_defined_vars());
}
}
//In Your View File
#extends('LAYOUTS_FILE')
#section('YOUR_SECTION')
#include('YOUR_SUB_VIEW_FOR_NOTIFICATION')//You need not pass any data passed all data will be available to this sub view.
#endsection
In your sub view
//Do what ever you want looping logic rendering HTML etc.
//In your layout file just yield or render the section that's it
#yield('YOUR_SECTION')
More explanation can be found Including Sub-Views
So I have a problem...I am separating concerns in my web app: HTML in razor pages and JS in js files. My problem lies in the fact that I want to use data from my controller passed down in the view model from the server as the options of a select list. The problem lies in the fact that I have separated my js from my HTML and I can't access Razor inside the js files.
I have a list of items coming down into my view model...
public List Stuffs { get; set; }
I json encode it server side and make sure to take care of circular refrences, so it looks like this
[{"id":1,"name":"blah"},{"id":2,"name":"blah2"},{"id":3,"name":"blah3"},{"id":4,"name":"blah4"}]
The problem is, I want to keep my separation of concerns, so how do I get that array into my js file so I can bind it to a select list using knockout? I definitely don't want to do another round trip to the server.
So lets say you have some view that looks like this:
<div> (Some stuff) </div>
<script type="text/javascript">
var initialData = //Your JSON
<script>
And then in your javascript (which you have linked below your view, so that it loads after it), you have something like this:
var data = initialData || {} //Or some other default value, in case initialData wasn't set
This will load in the initialData if it was set, or use nothing (or a default your define) if it wasn't. Loosely coupled.
Of course, more complex data will require you to standardize the strucure, but this is essentially one method that allows you to pull in data from the razor generated View without tightly coupling it to your javascript file.
Does your Javascript file contain a view model in the form of an instantiable function, e.g. something like:
function MasterVM(data)
or an object literal, e.g.
var masterVM = { ...
? I tend to go for instantiable view models (partly because they chain down through a hierarchy of view models using the mapping plugin - the top level one builds its children) and that being the case I think it's nice for the Razor view to instantiate the view model with the JSON rendered from the MVC view model, e.g.:
var masterVM = new MasterVM(#(Html.Raw(JsonConvert.SerializeObject(Model.ProductViewModel))));
Or even have the Knockout view model "owned" by a revealing module and initialise it like:
productModule.init(#(Html.Raw(JsonConvert.SerializeObject(Model.ProductViewModel))));
Then productModule is also responsible for other things like mediating between your view model(s) and the server, watching for dirty state, etc.
Also, doing another round trip to the server is not necessarily bad if you are sourcing a massive set of reusable options which you'd like the browser to cache. If you are, you might want to have an MVC controller which emits a big object literal containing all of your commonly used options, which you can then use as the "options" property across multiple selects. But if your options are specific to the particular view model, then I guess they have to be part of your view model.
I'm trying to restructure tightly coupled portions of an ember.js app, particularly views and templates to controllers.
All the examples I've seen bind views directly to controllers in the view's class definition, or by pass the (global) controller path to the view in the template itself.
The TargetActionSupport mixin (DelegateSupport in sproutcore, I think) seems like a good candidate, but would still require the target (controller) and action to be set in the template itself.
Ideally, I would like to instantiate my views in my controller (somehow), set targets and actions, but also set presentational variables in my templates (ex: static classes, id), but I'm not sure how to accomplish something this, or whether it's the right approach.
You can programmatically instantiate views and insert them into the DOM wherever you'd like:
var view = Ember.View.create();
view.appendTo('#someElement');
If you want to eliminate global binding paths, you can pass in a controller reference to the view on instantiation:
var controller = Ember.Object.create({content: {}}),
view = Ember.View.create({
controller: controller,
contentBinding: 'controller.content'
});
I'd also suggest looking into the Ember StateManager.
I'm trying to determine the best practice for calling multiple views from the same method in a controller.
Is it preferable in the controller to make one view call, then have that view call all the views it needs, or call all the views you need in sequence in the controller?
Example:
function index(){
//set all data variables
//then send them to the view
$this->load->view($index_view, $data);
}
or
function index(){
//set variables
//then call each view
$this->load->view($header_view, $header_data);
$this->load->view($body_view, $body_data);
$this->load->view($footer_view, $footer_data);
The Codeigniter guide shows both ways, but does not seem to advise to the best practice...is there one?
I didn't like the way of including the header/footer within the view, and I didn't like loading the footer and header each time in every single Controller function.
To fix this, I extended the Controller class with my own view display function.
<?php
// in application/libraries/MY_Controller.php
class MY_Controller extends Controller {
function _displayPage($page, $data = array()) {
$this->view->load('header', $data);
$this->view->load($page, $data);
$this->view->load('footer', $data);
}
}
?>
// in application/controllers/home.php
<?php
class Home extends MY_Controller {
function index() {
$this->_displayPage('home/index', array('title' => 'Home'));
}
}
?>
Not sure if this is CodeIgniter "best practice" but it makes sense to me.
I don't think there is a definitive answer for that. Choose one and stick with it, it's important to be consistent.
Anyway, I'd prefer the second one.
I would say that the controller should only display one view. Then it's up to the view if it wants to show a header, footer, sidebar or whatever. The controller shouldn't have to care, its job is to get data from a model and hand it to a view. Not decide if the view should have a header and a footer.
Agree with Christian Davén: its view / display logic not data or business / logic. essentially its the same as using php includes for snippets like navigation, footer etc. you're just embedding markup.
This is expected behavior. Once variables are set they become available within the controller class and its view files. Sending an array in $this->load->view() is the same as sending an array directly to $this->load->vars() before calling the view file. This simplifies things for most people using multiple views in a controller. If you are using multiple view files in a single controller and want them to each have their own set of variables exclusively, you’ll need to manually clear out the $this->load->_ci_cached_vars array between view calls.
A code comment in the Loader class describes another situation showing why this is the desired default behavior:
You can either set variables using the dedicated $this->load_vars()
function or via the second parameter of this function. We'll merge
the two types and cache them so that views that are embedded within
other views can have access to these variables.