Ember.js: How can I decouple my views from my controllers? - model-view-controller

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.

Related

How to set global viewmodels in ASP .NET Core 3.1?

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")));

Make a label a global element

I have some code to make global variables that I can use to access variables across multiple .swift files. It looks like this:
struct globalValues {
static var test:Double = 1
}
And then I call it using
globalValues.test = 2
Can I do something similar to an #IBOutlet label?
A label cannot be global because it belongs only to one view controller. Moreover, it is very wrong to talk directly to a label (e.g. to read its text, or even worse to write to its text) from outside the view controller that owns it. Only a view controller should be in any way aware of its views as views.
Instead, your other code needs to figure out how to get a reference to the view controller that has the label. And then the view controller must have accessor functions or public properties that allow other code to talk to it about the data underlying the label in a coherent manner. If the view controller wants to respond by changing the label in some way, that is its business.

Ember.js recursive controllers and views

Say I have a list of Action objects, which corresponds to a Ember model. Each has a few properties (timestamps) and a detail attribute, which can can recursively contain more details (arbitrarily deep nesting). You can think of the details as nested lists.
I want to write a UI that allows easy editing (auto completion of values, easy copy and paste, reorder elements, etc) of the detail for any Action object.
Right now, my DetailView template will recursively render additional DetailViews:
{{#if view.content.hasChildren}}
{{#each child in view.content.children}}
{{#DetailView contentBinding=child}}
{{/each}}
{{#else}}
{{#EditDetailView contentBinding=view.content.value}}
{{/if}}
So each DetailsView corresponds to a node in the Details object tree.
But I am unclear how to add controllers to the mix -- there is additional state I need to store / functionality to implement (e.g., transforming values from the Detail object for display in the DetailsView; handling events for inserting/deleting/reordering elements; changing the structure of the Details tree) that belongs neither in the model nor the view.
Ideally I would have a DetailsController serving as a proxy a Details per DetailsView. Can I dynamically instantiate controllers and set up their contents within a view template? My understanding of the new Ember Router is to setup controllers and outlets in in a given route; however, that doesn't seem to apply here because no routing is being done at all. All suggestions / insight about how to handle recursive controllers / views / routes welcome.
I've taken a look at EmberJS Nested Views and Controllers, but that proposes I have a single ArrayController for all Details, even across Actions ... this would not preserve the tree structure of the nested details either.
I've also looked at Recursive view in handlebars template not working after upgrading to Ember 0.9.6 but the solution doesn't say anything about controllers.
** UPDATE Feb 20, 2013 **
API documentation for the {{control}} helper is now available here. It warns that "The control helper is currently under development and is considered experimental."
To enable it, set ENV.EXPERIMENTAL_CONTROL_HELPER = true before requiring Ember.
** UPDATE Feb 3, 2013 **
A new helper {{control}} has been added to ember, implementing the Reusable Views proposal. So to have a DetailsController proxy a Details per DetailsView you can just:
{{control 'detail' child}}
See the {{control}} tests for example
Ideally I would have a DetailsController serving as a proxy a Details per DetailsView. Can I dynamically instantiate controllers and set up their contents within a view template?
Typically the way to do this would be via the handlebars {{render}} helper, which renders a template with an appropriate view and controller. Unfortunately you cannot use {{render}} to insert the same template more than once, so it cannot be used within an {{each}} block.
There was a lengthy discussion on the topic recently. See: Non-Singleton Controller Discussion!
Two solutions were proposed. The first involves adding an itemControllerClass property to ArrayController. If this property was set, the ArrayController would automatically wrap new contents in the specified class. This was added to ember a few weeks ago and takes care of most use cases, where you have a flat-list of items and want each to be wrapped in a proxy.
The second proposal, Reusable Views, allows you to provide a controller class to a view.
{{view UI.Calendar dateBinding="post.startDate" controllerClass="App.CalendarController"}}
This would create an instance of App.CalendarController for each UI.Calendar widget, which would be tied to the lifecycle of the widget. As of today this has not been implemented. See {{view}} should have an option to create a controller! for the latest status.
So AFAIK there is not a good way to accomplish this for the use case you outlined. Meantime, binding data to the view:
{{view App.DetailView contentBinding="child"}}
and then having some logic in the view itself seems reasonable. If/when Reusable View support is added to master you can pull that logic up into the controller.
See: https://github.com/emberjs/ember.js/issues/1766

Knockout JS, MVC 3 and View Model Server Side Data as JSON

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.

Custom MVC Framework: How do I propogate data to a view?

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;
}

Resources