AngularJS template directive with Java Play - javascript

I have an AngularJS application. Within that app, I am trying to load up a piece of HTML with a custom directive. My directive (in app.js) is as follows:
app.directive('mytable', function() {
return {
restrict: 'E',
templateUrl: '/mytable'
};
});
Within my HTML file (index.html) then, I just specify the custom tag.
<mytable></mytable>
The implementation details within mytable.html are just static HTML. On the routing side with Java Play, I have:
GET /mytable Application.mytable
And within my Play controllers (Application.java), I have:
public static void mytable() { render(); }
However, when I try to load a page, I get:
GET http://localhost:9000/mytable 500 (Internal Server Error)
XHR finished loading: GET "http://localhost:9000/mytable".
Upon closer inspection, within the console, I see:
Template not found
The template Application/mytable.txt does not exist.
How can I fix my code? And why is it trying to render mytable.txt instead of mytable.html, when all my other controllers in Application.java are the same and render .html files correctly?
Just a side note: http://localhost:9000/mytable does render the static content for <mytable> correctly.

Edit: this applies only to Play versions 2.x
I have the feeling that your controller's method is a bit wrong. I would rewrite this
public static void mytable() { render(); }
to:
public static Result mytable() {
ok(index.render());
}
where index is your view, render is the method you call to render this view, and your controller returns a Result (via the ok() method)

Related

Where should my Javascript go for View Components?

I'm getting used to view components in MVC 6, and I asked a similar question a few years ago about partial views. If I build a view component encapsulating a common use-case that requires its own Javascript, where do I put that Javascript? I know that it is dangerous at best to have Javascript in partial views, but it would be a lot simpler to include it in the view component, rather than in the containing view or a separate file that has to be referenced by the containing view.
For example, say I have a view component that has two drop-downs. The selection in the first drop-down determines what items appear in the second drop-down. This is easily handled in Javascript, of course, but where do I put it?
From my experience with ASP.NET 5 View Components, I would say that the best thing to do with them is to keep them isolated and in one place, so they will be easily to manage in long-term projects.
In one of my ASP.NET projects, I've developed View Components structure like this one:
View, Backend code and Model are all in one place, so when you move around the folder, you are sure that you move whole component. Moreover, when you are modyfying them, you have quick access to all of their parts.
It will be convinient to put JavaScript which is highly coupled with a component also in such structure. You can do this by simply creating the file under the component's folder, and then writing a GULP TASK that will copy JS file to wwwroot. From that point, you will be able to link that JavaScript code on component's .cshtml using standard syntax:
<script src="~/Components/yourcomponent.js"></script>
To obtain such a structure in my project, I've extended Razor, to be able to search for my component's CSHTML's in proper place. To do this, I've added this code in Startup.cs:
public partial class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//non relevant code skipped
services.AddMvc().AddRazorOptions(ConfigureRazor);
}
public void ConfigureRazor(RazorViewEngineOptions razor)
{
razor.ViewLocationExpanders.Add(new ViewLocationExpander());
}
}
and the ViewLocationExpander class is:
public class ViewLocationExpander : IViewLocationExpander
{
protected static IEnumerable<string> ExtendedLocations = new[]
{
"/{0}.cshtml"
};
public void PopulateValues(ViewLocationExpanderContext context)
{
//nothing here
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
//extend current view locations
return viewLocations.Concat(ExtendedLocations);
}
}
Then, you invoke component like this (from any .cshtml view):
#await Component.InvokeAsync("NavigationComponent",new NavigationComponentModel())

Invoke ViewComponent using javascript

I have a web page with a couple of view components, when I click on these components I open a simple editor for it, see image below. If I edit the text and hit enter, I would like to rerender the view component and not the hole page. Is it possible to invoke a view component using javascript to get this behaviour?
With updates, you should now be able to do this with asp.net core. You can return a ViewComponent from an IActionResult, then just call it with jQuery:
[Route("text-editor")]
public IActionResult TextEditor()
{
return ViewComponent("TextEditorViewComponent");
}
Then this can be called from your View with something as simple as this:
function showTextEditor() {
// Find the div to put it in
var container = $("#text-editor-container");
$.get("text-editor", function (data) { container.html(data); });
}
If you need to pass parameters to the ViewComponent you can do so in the IActionResult (maybe they trickle through from the View to there, to the view component, etc).
This is not possible today but you could build a standard proxy for viewcomponents if you'd like though (similarly you could build a proxy to partial views). e.g. you a catchall attribute route on an action.

Render speed issue when resuing a directive in AngularJS

I have the following simple base directive:
angular.module("base", [])
.directive("base", function() {
return {
restrict: "A",
scope: true,
controller: function($scope) {
this.setHeader = function(header) {
$scope.header = header;
}
this.setBody = function(body) {
$scope.body = body;
}
this.setFooter = function(footer) {
$scope.footer = footer;
}
},
templateUrl: "base.html"
}
});
I am passing data to this directive in the following way:
.directive("custom", function() {
return {
restrict: "E",
require: "^base",
scope: {
ngModel: "="
},
link: function($scope, $element, $attrs, baseCtrl) {
//Do something with the data or not...
baseCtrl.setHeader($scope.ngModel.header);
baseCtrl.setBody($scope.ngModel.body);
baseCtrl.setFooter($scope.ngModel.footer);
}
}
});
When I create a list of my custom directives, I notice the custom directives aren't rendering immediately. I have made a Plunker demonstrating this behavior. (You will see the list cells empty for a split second, then the directives will appear)
My goal behind this design is to reuse the template of the base directive and only pass in the data needed for display. In this simple example, $scope.data is exactly what I need to pass in but it may be the case some rules or manipulation need to happen first. Rather than have the controller which queried the data handle this, I wanted to pass it off into the directive, separating the concerns.
So my questions are:
Is there any way to make the directives render faster and avoid the flickering shown in the Plunker?
Is this a best practice for reusing directives with Angular?
The flickering is being caused by the async http request to the "base.html" file. Since the HTML for the base directive has to be loaded from the server, there will be a fraction of time where no content will be displayed.
In order to render the data, Angular will go though these 3 stages:
Fetch the HTML file/template from the server (no content will be displayed)
Compile the HTML template (the DOM is updated but the scope isn't yet linked)
Link the scope to the DOM/template (expected data is displayed)
Option 1 - Use the template attribute
Just replace the templateUrl: "base.html" for the direct HTML content:
//templateUrl: "base.html"
template: '<div class="base"><div class="header bottom-border"><h2>{{header}}</h2><div><div class="body bottom-border"><p>{{body}}</p></div><div class="footer">{{footer}}</div></div>',
You will notice that there won't be any flickering this time (check this plunker).
Option 2 - Pre-load template files
Angular has a built-in template cache ($templateCache) that it uses to check if any HTML template file/content has already been fetched from the server or not. If you populate that cache while the app is loading then Angular will not need to fetch the template to render the directive, it will read it directly from the cache.
You can use Angular's $templateRequest (if you are using the latest beta version of Angular) or $templateCache (for any version).
The difference is that $templateRequest automatically makes the HTTP GET request and stores the result in the $templateCache. On the other one you will have to do it manually, like this:
loadTemplate = function(tpl) {
$http.get(tpl, { cache : $templateCache })
.then(function(response) {
$templateCache.put(tpl, html);
return html;
});
};
loadTemplate('base.html');
Note however that this approach needs your app to have a "loading phase".
Regarding the best practices for reusing directives, you seem to be on the right path. The example is to simple to give any advices... Nevertheless, check the "Best practice" notes in this Angular "Creating Custom Directives" guide.
Edit
(my personal preferences regarding template vs templateUrl)
If the main goal is solely performance (i.e. page render speed) then template seems to be the best choice. Fetching one file will always be faster than fetching two... However, as the app grows, the need for a good structure is mandatory, and template files are one of the best practices when it comes to that.
Normally I follow this "rules":
If there are only a few lines of HTML in the template, then just use the template
If the HTML template will not be constantly changing over time (i.e. post structure, contact details, etc...), use template, otherwise (i.e. templates containing banners/ads) use templateUrl
If the app has a loading phase, use templateUrl with cache

Rendering all PartialViews for a Controller at one time

I am using .NET MVC4 and have created a number of PartialViews that are all used as templates. I use Backbone.js and Backbone.Marionette.js for my client-side code and have created the templates used in my Backbone Views as RAZOR PartialViews.
I created a Controller called TemplatesController that has a ChildAction and given a name of PartialView it returns the MvcHtmlString for that PartialView which is my template. I can then use the template along with Handlebars to create my View in Backbone.Marionette.
This all works great for me but I have to call the Controller action for every PartialView that exists. My list of templates is growing and I would like to not have to add a new call render the template every time to my Index View.
My question is, is there a better way to just call the Controller once and return all PartialViews that exist? Thanks for any suggestions.
Here is my Controller method
[ChildActionOnly]
public ActionResult GetTemplate(string name) {
return PartialView(name);
}
Here is a HtmlHelper Method I use to call the Controller Action
public static MvcHtmlString Template(this HtmlHelper htmlHelper, string name) {
// call ChildAction on our controller to get the HTML for this template
return htmlHelper.Action("GetTemplate", "Templates", new { name = name });
}
Here is example of how I load templates into Index or _Layout view using Helper. For each PartialView I create I add a line but I would like to remove these and do it in one line such as Html.AllTemplates or something.
#Html.Template("SiteHeader")
#Html.Template("SiteFooter")
I stripped all the content out but this would be example of what would be in each of the partial views, the main thing to note is it is wrapped inside a script tag
<script id="site-header" type="text/x-handlebars-template">
<h3>{{headertext}}</h3>
<div>{{somevalue}}</div>
</script>

angular.js - update scope and template after jsonp request

I created a directive that loads some html code inside a tag:
directive:
var appDirectives = angular.module("skdApp", ["offerServices"]);
appDirectives.directive("widget", function() {
return {
restrict: 'A',
template: '<div ng-controller="OfferCtrl"><p>{{offer.price}}</p></div>';
}
service
Besides, I'm using a service to fetch some json data
angular.module('offerServices', ['ngResource']).
factory('Offer', function ($resource) {
return $resource('url/offer.json', {},
{
get: {method: 'JSONP', params: {callback: 'JSON_CALLBACK'}}
}
);
});
controller
The controller adds the received data to the scope
function OfferCtrl($scope, Offer) {
$scope.offer = Offer.get();
}
Now I'm facing the following problem. By the time the template is being loaded, the json request is not finished yet. This results in showing the template with an empty string for {{offer.price}}.
I would need a way to update the template with the corresponding values, once the offerServices has received a response (i.e. the offerService response is available)
Any ideas?
How about using ngCloak
Description
The ngCloak directive is used to prevent the Angular html template
from being briefly displayed by the browser in its raw (uncompiled)
form while your application is loading. Use this directive to avoid
the undesirable flicker effect caused by the html template display.
The directive can be applied to the element, but typically a
fine-grained application is prefered in order to benefit from
progressive rendering of the browser view.

Categories

Resources