Why doesn't src work for templates? - html

The HTML script tag can be used to define named templates:
<script type="text/html" id="Lookup">
<label class="field-title" data-bind="text: Metadata.FieldTitle, css: { hasError: !IsValid() }"></label>
<div data-bind="html: Metadata.FieldIntro"></div>
<select class="form-control" data-bind="attr: { id: 'ff' + Metadata.FormFieldID }, options: Lookup, optionsText: 'Caption', optionsValue: 'Id', optionsCaption: Metadata.FieldIntro || 'Select an item...', value: Value, css: { hasError: !IsValid() }"></select>
</script>
According to MDN and msdn, I should be able to move the content of a template into a file and reference it like this:
<script type="text/html" id="Lookup" src="Lookup.html"></script>
According to Fiddler template files are being loaded (I have several). What stops knockout from using the templates when they aren't inline, and is there anything that can be done about it?
From the comments, script tags don't parse what they load into the DOM. I could use something like jQuery ajax to load, parse and inject but knockout uses the script node's id to reference the template, so the next question is how to make visible the fragment so loaded.
At a stab I surmise that I need to do something like this:
$(document.body)
.append("<div id='Lookup' style='display:none'></div>")
.load("Templates/Lookup.html" );
Close, but no cigar. Here it is corrected and wrapped up as a function:
function loadTemplate(name) {
if (name)
switch (name.constructor) {
case String:
$(document.body).append("<div id='" + name + "' style='display:none'></div>");
$("#" + name).load("Templates/" + name + ".html");
break;
case Array:
name.map(loadTemplate);
break;
default:
throw "Must be a string or an array of strings";
}
}
You can pass a single template name or an array of names.
Knockout happily uses the template.

Browsers don't know how to process programs written in text/html (yes, that sentence doesn't make a whole lot of sense, this is a hack you are using).
Whatever JavaScript you are using to process the templates is reading the childnodes of the script element in the DOM. It hasn't been written to check for a src attribute and load external content.

Related

AngularJS dynamic additions to page

We have this AngularJS SP application (smart-mirror) in electron browser, which has user createable extensions.
the extensions are small snippets of html that use angular directives
and use controllers and services.
to install an extension, one has to edit the main page and insert the script tags for the controller and service functions and a <div ng-include= ...> for the snippet of HTML
hardcoded this single page app works great.
but I want to add the capability to this app (opensource) to dynamically load those elements somehow...
adding the tags to the dom works, BUT are not processed correctly.
the HTML is processed before the scripts (from the inserted tags) are run, and when the ng-include inserts the HTML snippet, then controllers are not defined yet...
the body (with the extensions in hard-coded positions commented out)
<body ng-controller="MirrorCtrl" ng-cloak>
<div class="top">
<div class="top-left">
<!-- <div ng-include="'plugins/datetime/index.html'"></div>
<div ng-include="'plugins/calendar/index.html'"></div> -->
</div>
<div class="top-right">
<!-- <div ng-include="'plugins/weather/index.html'"></div>
<div ng-include="'plugins/traffic/index.html'"></div>
<div ng-include="'plugins/stock/index.html'"></div>
<div ng-include="'plugins/tvshows/index.html'"></div>
<div ng-include="'plugins/ha-display/index.html'"></div> -->
</div>
</div>
...
...
<script src="filename.service"/>
<script src= filename.controller"/>
</body>
the calendar extension html (inserted into specific div area of the page)
<ul ng-controller="Calendar" class="calendar fade" ng-show="focus == 'default'" ng-class="config.calendar.showCalendarNames ? 'show-calendar-names' : ''">
<li class="event" ng-repeat="event in calendar" ng-class="(calendar[$index - 1].label != event.label) ? 'day-marker' : ''">
<div class="event-details">
<span class="day">
<span ng-bind="event.startName"></span>
<span ng-if="event.startName != event.endName"> - <span ng-bind="event.endName"></span></span>
</span>
<div class="details calendar-name" ng-bind="event.calendarName"></div>
<span class="summary" ng-bind="event.SUMMARY"></span>
<div class="details" ng-if="event.start.format('LT') != event.end.format('LT')">
<span ng-if="event.startName != event.endName"><span ng-bind="event.start.format('M/D')"></span> <span ng-bind="event.start.format('LT')"></span> - <span ng-bind="event.end.format('M/D')"></span> <span ng-bind="event.end.format('LT')"></span></span>
<span ng-if="event.startName == event.endName"><span ng-bind="event.start.format('LT')"></span> - <span ng-bind="event.end.format('LT')"></span></span>
</div>
<div class="details" ng-if="event.start.format('LT') == event.end.format('LT')">All day</div>
</div>
</li>
</ul>
the calendar extension controller (used by the html)
function Calendar($scope, $http, $interval, CalendarService) {
var getCalendar = function(){
CalendarService.getCalendarEvents().then(function () {
$scope.calendar = CalendarService.getFutureEvents();
}, function (error) {
console.log(error);
});
}
getCalendar();
$interval(getCalendar, config.calendar.refreshInterval * 60000 || 1800000)
}
console.log("registering calendar controller")
angular.module('SmartMirror')
.controller('Calendar', Calendar);
the calendar extension service (used by the controller, shortened for this discussion)
(function () {
'use strict';
function CalendarService($window, $http, $q) {
...
...
return service;
}
console.log("registering calendar service")
angular.module('SmartMirror')
.factory('CalendarService', CalendarService);
} ());
so a user wanting to add an extension would have to create these files,
and edit the main page HTML and insert them
<div ng-include src="filename.html"></div>
in the right place and then add the
<script src="filename.service" >
and
<script src="filename.controller">
in the right place and order, service needs to be done before the controller,
as controller uses service.
anyhow, it's easy to add code to locate all the extensions and dynamically insert elements into the dom in their respective places... but...
in the hard coded, the scripts are added after the html in the body
so, I added a new script (processed when the page is loaded), which locates and inserts all the elements to support the extensions in the right places..
and then the script ends.... (last one in the hard-coded HTML) and the HTML directives are processed and boom, the dynamically added scripts have not been loaded or processed, so the controllers are not found...
I CAN create a temp HTML file with all this info in it and load THAT instead of dealing with the dynamic loading, but I think its better to resolve this
I have tried creating my own angular directive and compiling that in, but get stuck in a loop
<divinc src="filename.service"></divinc>
the inserted div is correct, as a child of the divinc directive
angular.module('SmartMirror')
.directive("divincl", ["$compile" ,function($compile){
return {
priority: 100,
terminal: true,
compile: function(scope, element, attrs) {
var html = "<div ng-include=\"" + element['incl']+ "\" onload='function(){console.log(\'html loaded\')}'></div>"
var templateGoesHere = angular.element(document.getElementById(element['id']));
templateGoesHere.html(html);
//document.body.innerHTML='';
var v= $compile(templateGoesHere);
//scope.$apply();
return function linkFn(scope) {
v(scope) // Link compiled element to scope
}
}
}
}]);
advice on how to solve this problem.. Thanks
In order to make an angularjs 1.7 application load dynamically extensions, there are 2 ways:
either use "nested angularjs applications", which is clearly an advanced use of angularjs and will require you to communicate between 2 angularjs applications, to use $scope.$apply to tell the other app to update etc..
either don't load them dynamically in the frontend, but in your backend when generating the html page which contains the application. Try to list all the extensions from the start.
I recommend you to forget the use of ng-include too, and the fact of trying to add <script></script> inside a directive of your application.
First, you need to re-understand how an angularjs application is started.
When you load your main application, you have a script in which angular.module, angular.directive, angular.value, angular.config, angular.run ... calls are made. This is the declaration step
If you declare a module MyApp and that in your html you have a DOM element with ng-app="MyApp", angularjs will automatically run angular.bootstrap() on this DOM element in order to start MyApp. The execution of the application starts here. You cannot declare anything anymore in the module MyApp.
Secondly, I think that <script></script> code inside templates is sanitized and removed by angular. Plus, even if you execute the code, since the declaration step has finished, you are not supposed to create new directives or register new services, it won't work.
A good way is that when you load your plugin, you:
Load the script of the plugin from the start, it must declare a new module like MyPlugin1.
In the directive which will contain the plugin, put the code of the link I sent you, which makes possible to insert a sub-application. In the end you will have a <div ng-app="MyPlugin1"></div> inside your directive's template
Then call angular.bootstrap on that node, which will make possible to start the sub application.
If you do this, you can run the sub application, but you didn't pass it parameters. In order to pass it parameters, you can put the code of the module MyPlugin1 inside a function, in order to have an application factory. Then use app.value('param1', parameter1) to initialize the app.
For example:
function declarePlugin1(myParam1, myParam2) {
var app = angular.module('MyPlugin1', []);
// app.directive();
app.value('myParam1', myParam1);
app.value('myParam2', myParam2);
}
And inside the directive call declarePlugin1("test", 42);, which will declare the application MyPlugin1 with the initialized values, and then angular.bootstrap to tell angularjs to start this application.
You can pass callbacks too, in order to communicate between the 2 applications.

Dynamic pass value in a Play2 scala template

I try to dynamically change value of its variable. Once onclick (Change Ticket ID ) button then execute onClickSendEmail and variable value should be change of tickedId.
Its unable to update with newTickedId. I tried while create variable using #defining and individual calling by function also.
So, Basically I got stuck. how it will be solve.
#(sender: String)
<!--#{var tickedId = "tickedId"}-->
#defining(sender.contains("#")) {isEmail =>
#main((if(isEmail) "Email" else "Chat") + " Messages - " + sender) {
...
...
...
<div>
<a onclick="onClickSendEmail();return false;">
Change Ticket ID
</a>
</div>
#defining("getTicketId()") { tickedId =>
#views.html.common.form.panel("Reply",controllers.routes.ChatMessageController.sendEmail(tickedId,sender),"Send"){
<textarea id="emailArea" cols="100" rows="4" name="emailArea"></textarea>
}
<script type="text/javascript">
function onClickSendEmail() {
tickedId= "NewUpdatedTicketId";
}
function getTicketId() {
return "NewUpdatedTicketId";
}
</script>
}
}
}
You should not mix Twirl templating with Javascript. It's a bad approach.
The role for Twirl is to render HTML blocks. You can define conditions and variables here in order to dynamically change the HTML output. While with Javascript you can modify this rendered HTML output without reloading the page.
There are cases where you need to use a Twirl variable in Javascript, then you can do something like:
#(chartData: Html)
<script>
let jsData = #twirlData; // where twirlData is an existing variable
console.log(jsData)
</script>
Here's a link where you can read more.

ng-bind-html not working with my $scope.variable

I am trying to add something like dynamic HTML using ng-bind-html but its not working with $scope variable
Here is my Angular code
1)My controller
$scope.name = "Parshuram"
$scope.thisCanBeusedInsideNgBindHtml = $sce.trustAsHtml("<div>{{name}}</div>");
Also that my string is dynamic
"<div><table class=" + "\"table table - bordered table - responsive table - hover add - lineheight table_scroll\"" + "><thead><tr><td>Name</td><td>Age</td></tr></thead><tbody><tr ng-repeat=" + "\"tr in dyna\"" + "><td>{{tr.name}}</td><td>{{tr.age}}</td></tr></tbody></table></div>"
So i cant replace every variable with $scope
2)- My HTML
<div ng-app="mymodule" ng-controller="myModuleController">
<div ng-bind-html="thisCanBeusedInsideNgBindHtml"></div>
</div>
I got this output
{{name}}
My expected output is
Parshuram
Please can anyone help i am stuck at this point,does that $sce does not bind scope variable?? ..
I've created a working plnkr here: https://plnkr.co/edit/uOdbHjv1B7fr0Ra1kXI3?p=preview
the problem is that ng-bind-html is not bound to the scope.
you should manually compile the content.
a valid and reusable solution should be creating a directive, whitout using any external modules.
function compileTemplate($compile, $parse){
return {
link: function(scope, element, attr){
var parsed = $parse(attr.ngBindHtml);
function getStringValue() { return (parsed(scope) || '').toString(); }
scope.$watch(getStringValue, function() {
$compile(element, null, -9999)(scope);
});
}
}
}
<div ng-app="mymodule" ng-controller="myModuleController">
<div ng-bind-html="thisCanBeusedInsideNgBindHtml" compile-template></div>
</div>
ng-bind-html does what it says on the tin: it binds html. It doesn't bind angular template code into your dom.
You need to do this instead:
$scope.thisCanBeusedInsideNgBindHtml =
$sce.trustAsHtml("<div>"+$sanitize(name)+"</div>");
To do this you'll want to include the module ngSanitize from the javascript angular-sanitize.js. See https://docs.angularjs.org/api/ngSanitize
If you want to insert some html that includes angular directives then you should write your own custom directive to do it.
In your html just use
{{name}}
The {{var}} notation is to be used in the HTML code to evaluate that variable.
You can do :
$scope.thisCanBeusedInsideNgBindHtml = $sce.trustAsHtml('<div ng-bind="name"></div>');
Sorry I make another answer.
If you have
$scope.thisCanBeusedInsideNgBindHtml = $sce.trustAsHtml("<div>{{name}}</div>");
Then you can do
var str = "<div>{{name}}</div>";
var output = str.replace('{{name}}', $scope.name);
It seems to be the only option.

Polymer 1.0 - Binding css classes

I'm trying to include classes based on parameters of a json, so if I have the property color, the $= makes the trick to pass it as a class attribute (based on the polymer documentation)
<div class$="{{color}}"></div>
The problem is when I'm trying to add that class along an existing set of classes, for instance:
<div class$="avatar {{color}}"></div>
In that case $= doesn't do the trick. Is any way to accomplish this or each time that I add a class conditionally I have to include the rest of the styles through css selectors instead classes? I know in this example maybe the color could just simple go in the style attribute, it is purely an example to illustrate the problem.
Please, note that this is an issue only in Polymer 1.0.
As of Polymer 1.0, string interpolation is not yet supported (it will be soon as mentioned in the roadmap). However, you can also do this with computed bindings. Example
<dom-module>
<template>
<div class$="{{classColor(color)}}"></div>
</template>
</dom-module>
<script>
Polymer({
...
classColor: function(color) {
return 'avatar '+color;
}
});
<script>
Edit:
As of Polymer 1.2, you can use compound binding. So
<div class$="avatar {{color}}"></div>
now works.
Update
As of Polymer 1.2.0, you can now use Compound Bindings to
combine string literals and bindings in a single property binding or text content binding
like so:
<img src$="https://www.example.com/profiles/{{userId}}.jpg">
<span>Name: {{lastname}}, {{firstname}}</span>
and your example
<div class$="avatar {{color}}"></div>
so this is no longer an issue.
The below answer is now only relevant to versions of polymer prior to 1.2
If you are doing this a lot, until this feature becomes available which is hopefully soon you could just define the function in one place as a property of Polymer.Base which has all of it's properties inherited by all polymer elements
//TODO remove this later then polymer has better template and binding features.
// make sure to find all instances of {{join( in polymer templates to fix them
Polymer.Base.join = function() { return [].join.call(arguments, '');}
and then call it like so:
<div class$="{{join('avatar', ' ', color)}}"></div>
then when it is introduced by polymer properly, just remove that one line, and replace
{{join('avatar', color)}}
with
avatar {{color}}
I use this a lot at the moment, not just for combining classes into one, but also things like path names, joining with a '/', and just general text content, so instead I use the first argument as the glue.
Polymer.Base.join = function() {
var glue = arguments[0];
var strings = [].slice.call(arguments, 1);
return [].join.call(strings, glue);
}
or if you can use es6 features like rest arguments
Polymer.base.join = (glue, ...strings) => strings.join(glue);
for doing stuff like
<div class$="{{join(' ', 'avatar', color)}}"></div>
<img src="{{join('/', path, to, image.jpg)}}">
<span>{{join(' ', 'hi', name)}}</span>
of just the basic
Polymer.Base.join = (...args) => args.join('');
<div class$="{{join('avatar', ' ', color)}}"></div>
<template if="[[icon_img_src]]" is="dom-if">
<img class$="{{echo_class(icon_class)}}" src="[[icon_img_src]]">
</template>
<span class$="{{echo_class(icon_class, 'center-center horizontal layout letter')}}" hidden="[[icon_img_src]]">[[icon_text]]</span>
<iron-icon icon="check"></iron-icon>
</div>
</template>
<script>
Polymer({
echo_class: function(class_A, class_Z) {
return class_A + (class_Z ? " " + class_Z : "");
},

Creating search box using links in html file?

I will preface this question with the fact that I am extremely new to HTML and CSS.
I currently have an engineering page at my company I have inherited that has a ton of links. I have organized into some general categories. However, it has been expressed that they would love a searchbox to search links.
I do not have PHP available to me due to circumstances out of my control. What I do have is all the links in my index.html file with the text they display associated with them.
My thought it that I can create the engine such that it recognizes the tag, and then searches the "name" associated with the link in the tag. However, I really have no idea where to start in terms of implementing such a script.
Of course, there may be a much easier way. I am open to any new approaches. I am not biased toward any programming method or language. Thank you so much for the help everyone, and I can provide any other non-NDA information I can.
I would look at the jQuery UI Automcomplete library http://jqueryui.com/demos/autocomplete/, specifically the custom data demo.
I imagine the code something like this (note this is untested and definitely not complete for your purposes):
<head>
<script type='text/javascript'>
var urls = [
{
value: "url-text",
label: "URL Text",
desc: "URL"
},
{
value: "url2-text",
label: "URL2 Text",
desc: "URL2"
}
];
$('#search').autocomplete({
minLength: 0,
source: urls,
focus: function (event, ui) {
$('#search').val(ui.item.label);
return false;
},
select: function (event, ui) {
$('#search').val(ui.item.label);
$('#url').val(ui.item.desc);
return false;
}
})
.data ("autocomplete")._renderItem= function(ul,item) {
return $('<li></li>")
.data( 'item.autocomplete', item )
.append( '<a>' + item.label + '<br />' + item.desc + '&lt/a>' )
.appendTo( ul );
};
</script>
</head>
<body>
<input id="search" />
<p id='url'></p>
</body>
Doing it this way does mean you have to keep a separate list of URLs and text in a javascript variable.
You'll need to include jQuery in your index.html.
<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js' />
Give each link a common class. You can then use jQuery to find the link the user is searching for:
var search = $("#searchBox").val();
$("a.myLinks[href*="+search+"]"); // uses jQuery to select the link, see jQuery selectors
Now you can do things with that link, like show it or navigate to it.