How to validate knockout data-bind nestings? - html

Recently ran into a problem where knockout binding to html comments were not well formed because of a nesting problem. I used where an html comment to house a ko if: and then used ko foreach which was bound to an html <div>, but managed to get the nesting wrong. Below is an example of the issue:
<!-- ko if: isGuestCheckout() -->
<div data-bind="foreach: formSection()">
<!-- /ko>
Simple malformed knockout</div>
In this simple example it is easy to see what the issue is, but in a much larger html block it is much harder to spot the error. I eventually resorted to have html comments for the end of each knockout binding for example <!-- end isGuestCheckout() --> which seems inefficient.
Is anyone aware of a way of validating that knockout data-binds are nested correctly?

I've run into similar issues and what I've done to troubleshoot in those cases is add a "whoami" observable for each nested KO-observable object and drop a simple span in the various nested levels to give me some insight as to what is being bound and where.
For example, if I have a view model like this...
var myViewModel = function() {
var self = this;
self.whoami = ko.observable("I am the root view model");
self.items = ko.observableArray(); //This holds an array of myItem
};
var myItem = function() {
var self = this;
self.whoami = ko.observable("I am an item");
}
Then my HTML looks something like this...
<div>
<!-- At this level I expect to see the whoami from the root VM -->
<span data-bind="text: whoami"/>
<div data-bind="foreach: listOfItems">
<!-- At this level I expect to see the whoami from the item -->
<span data-bind="text: whoami"/>
</div>
</div>
Doing this has helped me quickly discover Knockout nesting issues. Once I find it then I just pull out the whoami span tags, but I usually end up keeping the whoami observable in the JS in case I need it again.
I hope this helps! :-)

Related

How to dynamically add options to a select element in an Angular component?

I currently have an Angular component that contains a solutions array that I want users to be able to manually alter. I already have a button that allows users to dynamically add to this array, but I'm trying to implement deletion. I want a select box to be displayed that contains all of the solutions, then when the user clicks one of the options and hits "delete solution", it will remove that element from the array.
Currently the html of my component looks as follows:
<div *ngIf="logged" class="solutionsInput">
<div>
New Solution:
<div>
<textarea id="Solution" [(ngModel)]="newSolution" placeholder="None"></textarea>
</div>
</div>
<button class="add-solutions" (click)="addSolutions(defect)">
Add Solution
</button>
<!-- BELOW IS THE PART THAT NEEDS TO BE FIXED -->
<select id = "solutions"></select>
<button class="delete-solutions" (click)="deleteSolutions(defect)">
Delete Solution
</button>
</div>
The typescript of my component looks as follows:
defect.solutions = [] //THIS IS WHAT I WANT TO ALTER
newSolution = "";
addSolutions(defect: Defect): void {
if(this.newSolution !== "") {
this.defectService.getSolutionsHelper(defect).subscribe((currSolutions) => {
//not necessary to see all of this
})
});
}
}
deleteSolutions(defect: Defect): void {
//THIS NEEDS TO BE IMLPEMENTED
}
Are there any ideas for what I should do? Thank you so much in advance for your help!
When I run into these situations, I use a multiselect drop down list. My team uses the Kendo UI for Angular pack, but there are other free choices, like this one:
https://www.npmjs.com/package/ng-multiselect-dropdown
With this approach, you can simply bind your results from the call to this.defectService.getSolutionsHelper to the control (defect.solutions), and then the user can delete individual members from easily selectable items. Since the control is bound to defect.solutions, the control will natively trim the array.
This may work for you. Good luck!

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.

Reusing pages in WinJS's Pivot

I'm developing app for Windows Phone 8.1 using WinJS and I used Visual Studio's template for pivot application. My Applications queries external API and displays results in PivotItem. Since there are three very similar queries that reurn same type of data, I'd like to reuse one code for all the sections in Pivot. The PivotItem page consist basically only of ListView with items received from API. My section page javascript looks like this:
var ControlConstructor = WinJS.UI.Pages.define("/pages/bookmarks/sectionPage.html", {
ready: function(element, options) {
//Here I call API based on received option and render the page
}
}
WinJS.Namespace.define("bookmarksApps_SectionControls", {
SectionControl: ControlConstructor
});
My page declaring the Pivot looks like this:
<div class="bookmarks" data-win-control="WinJS.UI.Pivot" data-win-res="{ winControl: {'title': 'BookmarksTitle'} }">
<div class="section1 section" data-win-control="WinJS.UI.PivotItem" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'BookmarksNew'} }">
<div class="sectioncontrol" id="section1contenthost" data-win-control="bookmarksApps_SectionControls.SectionControl" data-win-options="{'section': 'new'}"></div>
</div>
<div class="section2 section" data-win-control="WinJS.UI.PivotItem" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'BookmarksAll'} }">
<div class="sectioncontrol" id="section2contenthost" data-win-control="bookmarksApps_SectionControls.SectionControl" data-win-options="{'section': 'all'}"></div>
</div>
<div class="section3 section" data-win-control="WinJS.UI.PivotItem" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'BookmarksHistory'} }">
<div class="sectioncontrol" id="section3contenthost" data-win-control="bookmarksApps_SectionControls.SectionControl" data-win-options="{'section': 'history'}"></div>
</div>
</div>
Now, when I open the app,pivot page correctly loads and displays first section with data. But when I swipe the different section, new data is loaded (so the ready function is called, but nothing is displayed (page is blank, only PivotItems' headers are visible). But if I swipe back to section1, it contains data, that I want to display in section2.
Is it possible to reuse my SectionPage.html and SectionPage.js in different PivotItems, preferably without too much of boilerplate code?
You need to create custom HTML control which will host these pages, custom control can accept uri as data-win-options, then inside your control you can have updateLayout() which will render the page and append to parentElement.
Sample code in update layout method:
var options = {} //Page options
if (!this._isLoaded) {
this._isLoaded = true;
WinJS.UI.Pages.render(this.uri, this._pageElement, options);
}
I found source of my problem. In page /pages/bookmarks/sectionPage.html I had <div> with an id meant for holding my ListVIew. And I was getting win control for listview using document.getElementById("listViewId").winControl. This is wrong, because then I had three divs with same id (each for every section), so getElementById was always returning same list (the one on the first section).
So I changed getting of the wincontrol to
var discussionList = document.querySelector("#" + contentHost + " .disucssionsListView").winControl;
where contentHost depends on data-win-options received from main page and everything works as expected.

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 : "");
},

How do I generate nested DOM elements based on an AJAX result in Angular?

I'm new to Angular so be gentle with me! I'm looking at rendering subsection DOM elements based on an AJAX response, how do I go about implementing the below in Angular?
On page load a list of section headers is returned from the controller:
Clicking on any of these sections (red) would show a subsection list (blue), each of these blue headers can be clicked to show another list (black), bearing in mind I only want to show the immediate-child sections for each section/subsection/sub-subsection header click:
I've got the template code I want to use for each of these, but how do I go about bringing these templates together?
So far from looking around I get the impression I should be creating a directive for the section, sub-section and sub-sub-section (yes?), can I then bind a template to the result of an HTTP Service call? I.e expanding as the detail screenshot above:
<div area="PSED">
Personal, Social and Emotional Development
<div aspect="MH">
Making Relationships
<div goal="BLAH">
<input type="checkbox"> Blah, Blah, Blah
</div>
</div>
</div>
I was hoping to reduce page load time by returning as little data as necessary and populating sections as-required by the user.
I hope this is a reasonable question as I couldn't find anything demonstrating what I need (perhaps my ignorance of ng was causing me to omit an important keyword from my searches).
Thanks in advance for any advice provided.
Andy
If I understand the question, you are trying to dynamically add nodes to a tree-like structure after an ajax call. You can use a combination of ng-include and a recursive template to do this. Here's a rough example that doesn't include the logic for collapsing nodes but I think it gets the idea across.
View:
<script type="text/ng-template" id="tree_item_renderer.html">
<span ng-click="add(data)">{{data.name}}</span>
<ul>
<li ng-repeat="data in data.nodes" ng-include="'tree_item_renderer.html'">
</li>
</ul>
</script>
<ul ng-app="Application" ng-controller="TreeController">
<li ng-repeat="data in tree" ng-include="'tree_item_renderer.html'"></li>
</ul>
Controller:
angular.module("myApp", []).
controller("TreeController", function($scope, $http) {
$scope.delete = function(data) {
data.nodes = [];
};
$scope.add = function(data) {
var post = data.nodes.length + 1;
var newName = data.name + '-' + post;
//make your call here and set your child node data
//$http.get('...').then(function(res){
// data.nodes.push({name: newName,nodes: res.data});
//});
//test data
data.nodes.push({name: newName,nodes: []});
};
$scope.tree = [{name: "Top Node", nodes: []}];
});
Working jsFiddle: http://jsfiddle.net/nfreeze/c9mrhxf2/1/