I am new to the Angular world, and currently trying to finalize a clean design for our ASP.NET MVC project whereby each Angular controller is in a separate file. My question is this, is it possible to lazy load the Angular controllers? I am currently struggling with RequireJS. My proof of concept code is as follows:
CSHTML VIEW - Index.cshtml
<script src="~/Scripts/Controllers/mainController.js"></script>
<div class="row" ng-controller="MainController">
<div class="col-md-4">
<span>{{1+1}}</span>
Message Test: {{testmessage}}
<button ng-click="ucase()">Upper</button>
</div>
_Layout.cshtml (first line only to show ng-app)
<html ng-app="mainModule">
app.js
var mainModule = angular.module("mainModule", []);
mainController.js
angular.module('mainModule').controller('MainController', ['$scope', function ($scope) {
$scope.testmessage = "hello world!";
$scope.ucase = function () {
$scope.testmessage = angular.uppercase($scope.testmessage);
}
}]);
How can I get rid of the reference to mainController.js in the Index.cshtml file? Is there a way to lazy load the controller when it is needed? Can RequireJS help with this?
In the end I was able to implement lazy loading of Angular modules in my POC project by using ocLazyLoader:
https://github.com/ocombe/ocLazyLoad
http://plnkr.co/edit/aGxuXMiPgYA0TFc67YL4 (demo)
However, I am still not sure whether we will implement this going forward.
Related
I am working on replacement ASP.NET MVC+Knockout with just Knockout, I want to remove ASP.NET and get just static js + html.
My ASP.NET views consist of Partial views (I call them widgets in my project), this Partial views easily replaced with Knockout components.. but I have a problem: ASP.NET Partial views have a Layout (some html decoration for every widget), how can I achieve similar for Knockout component view?
Simplified example. Old asp.net scheme:
View.cshtml:
<div>
#Html.Partial("SomeWidget")
</div>
SomeWidget.cshtml:
#{
Layout = "~/Views/Shared/_WidgetLayout.cshtml"; <!-- parent layout for widget -->
}
<span>This is some widget</span>
_WidgetLayout.cshtml:
<div>
<span>This is decorator for every widget</span>
#RenderBody() <!-- render widget view here (SomeWidget.cshtml in this example) -->
</div>
New knockout-only scheme:
View.html:
<div>
<some-widget></some-widget>
</div>
View.js:
ko.components.register('some-widget', { require: 'app/SomeWidget' });
SomeWidget.html:
<span>This is some widget</span>
SomeWidget.js:
var view = require('text!/views/SomeWidget.html');
return { template: view };
How to replace _WidgetLayout.cshtml in Knockout?
There are several ways you could possibly do this. The simplest way I can think of is to have a template component, and you nest the widget inside this. KO Components support nesting.
You can define a template component thus:
ko.components.register("widget-template", {
viewModel: function(params) {
var self=this;
self.WidgetName = params.widget;
},
template: "<div class='b'><span>This is decorator for every widget</i>
<div data-bind='component: { name: WidgetName }'></div></div>"
});
To use this, you put the template-widget into your HTML, and pass the name of the widget as a parameter:
<widget-template params="widget: 'widget1'"></widget-template>
Then you define a widget as another component:
ko.components.register("widget1", {
template: "<h3>Widget One</h3>"});
So now you have a re-usable template that can wrap any component. You can see more about this binding in the Knockout documentation.
See the full JS fiddle here: http://jsfiddle.net/Quango/a8h2bwtc/
Note that you can also make the name an observable rather than a static value, as seen here:
http://jsfiddle.net/Quango/tnphvvgd/
Dear fellow programmers
I got myself into a pickle. Ive been in to knockout for like 2 weeks now and im afraid i dont understand the basic idea of it. So be gentle with me.
Situation:
I got a master view layout page /Master to make it simple. Here i got 2 column layout.
On the left a listbox with patients, after clicking one, you will see prescriptions added to the listbox below it. pretty simple...
Dont mind the renderbody here, this is faulty. Just use this image to see 2 big parts. The red en and the yellow.
Now i got the Master working pretty well, when i click on a patient i want to load a specific partial view on the yellow part of the screen. The same with clicking a prescription , then i want to load the prescription partial on the yellow part.
I got these 2 editscreens working but without the master included. these pages look like this:
#model FysioNotes.WebMVC.Models.ViewModels.EditPatientViewModel
form + bindings here
#section scripts {
<script src="~/MyScripts/patientVm.js" />
<script>
$(function () {
ko.applyBindings(new editPatientVm(#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(this.Model))));
});
</script>
}
You see that i use razor here to fill in the data in the editPatientVm. the js file looks like this :
var patientVm = function (data) ...
var editPatientVm = function (data) ...
var createPatientVm = function (data) ...
This is the same with the prescriptions .
Now when i try to load the prescription partial into the yellow part of the screen, i try to do it like this. And this is probably very faulty....
-- this is at the bottom of the master view --
<script type="text/javascript">
$(document).ready(function () {
$('#tablePrescriptions').on('click', 'tr', function (event) {
var selectedId = $(this).data("prescriptionid");
// this data gets filled without troubles
$(this).addClass('selectedrow').siblings().removeClass('selectedrow');
openDetail("prescription", selectedId);
});
and then this function
function openDetail(type, selectedId) {
if (debug)
alert(type + " : " + selectedId);
var url = baseurl + "/Prescription/Edit?prescriptionId=" + selectedId;
$("#mainContent").load(url);
//CHECK THIS
ko.cleanNode($("#mainContent")[0]);
$("#mainContent").load(url, function () {
//ko.applyBindings(new viewModel(), $("#mainContent")[0]);
ko.applyBindings(new editPrescriptionVm(#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(new FysioNotes.WebMVC.Models.ViewModels.EditPrescriptionViewModel(12836)))), document.getElementById("mainContent"));
});
}
like you see , the fixed number 12836 is totally wrong to do it like this. But i wanted to just try if this would work and it did. But apparantly i cant send a js var to razor , because this is impossible.... so this let me to the idea that im doing something completely wrong
the master view has this at the bottom:
<script src="~/MyScripts/patientVm.js"></script>
<script src="~/MyScripts/prescriptionVm.js"></script>
<script>
$(function () {
ko.applyBindings(new masterVm(#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(this.Model))));
})
</script>
I think i need to put the viewmodels that i need in the partial into my viewmodel of the master maybe ? and then send it trough but this will mean that EVERY viewmodel needs to be on the master. This cant be good for initial loading ! And this seems wrong to me to do it like this , but ive been wrong before....
please help :(
#section scripts cannot work in partials views that are retrieved through ajax... you'll have to execute your code between those tags after the code that retrieves the view in the first place. #section scripts is kept into the request items and rendered when the page is rendered. But since this is ajax, the server has long passed the point where it would keep into account scripts defined between this section
I have a simple view which renders set of images depending on given items array (simplified code), using this as I need to collect some other data to 'build' required class name(s):
App.MyView = Ember.View.extend({
buildTemplate: function () {
var itemz = this.get('items');
var classname = 'classNameDependingOnSomeCalculations...';
var out = '<div>';
$.each(itemz, function (index, obj) {
out += '<img {{action myActionHere}} src="' + obj.href + '" alt="" class="'+classname+'"/>';
});
out += '</div>';
return out;
}.property('view'),
defaultTemplate: Ember.Handlebars.compile(
"<div>{{{view.buildTemplate}}}</div>"
)
});
And in template I'm using it as
{{#each myObj in myCollection}}
{{view App.MyView itemsBinding="myObj.items" otherBinding="otherProps" }}
{{/view}}
Unfortunately this way Ember instead of binding the action puts {{action myActionHere}} directly into code...
How can I bind an action instead while building dynamic template?
I'm using Ember 1.1.2
P.S. Or maybe I should use quite other approach for building this view?
There is a workaround to make this work with the view as you've laid it out here... But this is really not the ember way of doing it. If for some reason you need this kind of an approach, I'll append an answer for that, but I'm going to aim to fix the underlying issue.
Instead of doing this as shown here, you should instead have code that looks like the following directly in your JSP:
{{#each myObj in myCollection}}
<div>
{{#each item in myObj.items}}
<img {{action myActionHere}} src={{item.href}} alt='' class={{classNameFunction}}/>
{{/each}}
</div>
{{/each}}
If your reason for wanting to do this as a view is so that you can reuse this functionality without rewriting the code, take a look at partials which are specifically designed for that purpose.
I'm using Handlebars.js, and currently all my templates live inside script tags which live inside .html files housing dozens of other templates, also inside script tags.
<script type="text/template" id="template-1">
<div>{{variable}}</div>
</script>
<script type="text/template" id="template-2">
<div>{{variable}}</div>
</script>
<script type="text/template" id="template-3">
<div>{{variable}}</div>
</script>
...
Then I include this file on the server-side as a partial.
This has the following disadvantages:
A bunch of templates are crammed into HTML files.
Finding a given template is tedious.
I'm looking for a better way to organize my templates. I'd like each each template to live in its own file. For example:
/public/views/my_controller/my_action/some_template.html
/public/views/my_controller/my_action/some_other_template.html
/public/views/my_controller/my_other_action/another_template.html
/public/views/my_controller/my_other_action/yet_another_template.html
/public/views/shared/my_shared_template.html
Then at the top of my view, in the backend code, I can include these templates when the page loads, like this:
SomeTemplateLibrary.require(
"/public/views/my_controller/my_action/*",
"/public/views/shared/my_shared_template.html"
)
This would include all templates in /public/views/my_controller/my_action/ and also include /public/views/shared/my_shared_template.html.
My question: Are there any libraries out there that provide this or similar functionality? Or, does anyone have any alternative organizational suggestions?
RequireJS is really good library for AMD style dependency management. You can actually use the 'text' plugin of requireJS to load the template file in to your UI component. Once the template is attached to the DOM, you may use any MVVM, MVC library for bindings OR just use jQuery events for your logic.
I'm the author of BoilerplateJS. BoilerplateJS reference architecture uses requireJS for dependency management. It also provides a reference implementations to show how a self contained UI Components should be created. Self contained in the sense to handle its own view template, code behind, css, localization files, etc.
There is some more information available on the boilerplateJS homepage, under "UI components".
http://boilerplatejs.org/
I ended up using RequireJS, which pretty much let me do this. See http://aaronhardy.com/javascript/javascript-architecture-requirejs-dependency-management/.
I use a template loader that loads the template using ajax the first time it is needed, and caches it locally for future requests. I also use a debug variable to make sure the template is not cached when I am in development:
var template_loader = {
templates_cache : {},
load_template : function load_template (params, callback) {
var template;
if (this.templates_cache[params.url]){
callback(this.templates_cache[params.url]);
}
else{
if (debug){
params.url = params.url + '?t=' + new Date().getTime(), //add timestamp for dev (avoid caching)
console.log('avoid caching url in template loader...');
}
$.ajax({
url: params.url,
success: function(data) {
template = Handlebars.compile(data);
if (params.cache){
this.templates_cache[params.url] = template;
}
callback(template);
}
});
}
}
};
The template is loaded like this:
template_loader.load_template({url: '/templates/mytemplate.handlebars'}, function (template){
var template_data = {}; //get your data
$('#holder').html(template(template_data)); //render
})
there's this handy little jquery plugin I wrote for exactly this purpose.
https://github.com/cultofmetatron/handlebar-helper
I'm using the "rails-backbone" gem in my Rails 3.2 project. By this design my Backbone Views are loaded in the <head> tag. When using Routes everything works fine, but Views do not render if I try to use them directly without Routes. If I put the Views code in <body> everything works as expected.
Is there a way to make the Views work when defined in <head>?
Update:
in <body>:
<script type="text/javascript">
var lv = new ListView();
</script>
in javascript file included in <head>:
window.ListView = Backbone.View.extend({
el: $("#node"), // This does not work
initialize: function(){
_.bindAll(this, 'render');
this.el = $("#node"); // Works if I add this line
this.render();
},
render: function(){
$(this.el).append("<ul> <li>hello world</li> </ul>");
}
});
As I understand it #node does not exist yet, which is why it is not bound to el. I don't quite understands when happens here: var lv = new ListView(), I thought I was creating an instance from a Class (I guess I'm getting a clone of an Object?). Is there another way of making the code work by modifying the code in <body> instead of the included javascript?
Your problem is caused by using a jQuery selector as the value for el in the view.
Read this:
http://lostechies.com/derickbailey/2011/11/09/backbone-js-object-literals-views-events-jquery-and-el/
if you want something to work in the head, but after the DOM is ready, (And apparently since you're using jQuery) just set up your views in the DOMReady event via:
$(document).ready(function(){
...
//set up view here
...
});