Durandal widget "this.settings is undefined" - widget

I have a Durandal widget (hot towel template) named "selector" in App>durandal>widgets>selector
The code of controller.js of widget:
define(function (require) {
var widget = require('durandal/widget');
var selector = function (element, settings) {
settings.selectedTopLevel = ko.observable();
settings.showTopLevel = ko.observable(true);
this.settings = settings;
};
selector.prototype.enableSelect = function() {
this.settings.showTopLevel(true);
this.settings.selectedTopLevel(null);
};
selector.prototype.showSelect = function () {
var selected = this.settings.selectedTopLevel;
alert(this.selected.name().toString());
};
return selector;
});
The view.html of widget:
<span>
Click to enable
<span data-bind="visible: settings.showTopLevel">
<select data-bind="options: settings.items, optionsText: 'name', optionsCaption: 'Select...', value: settings.selectedTopLevel"></select>
</span>
<br/>
<span data-bind="foreach: settings.items">
<a href="#" data-bind="click: $parent.showSelect">
<span data-bind="text: name"></span>
</a>
</span>
The use of widget:
<div>
<h2>Widget Selector:</h2>
<div data-bind="widget: { kind: 'selector', items: $root.projects }"></div>
But I have some problems in function selector.prototype.showSelect in line var selected = this.settings.selectedTopLevel; the principal error is:
this.settings is undefined
The other problem appear in that line of html:
Click to enable
The function selector.prototype.enableSelect isn't call when I clicked in "Click to enable".
I am new in Durandal widget, please any help much appreciated!

It's a KO issue. You need to wrap those calls to $parent in a function in order to preserve the "this" context I believe.

Reposted from comment above to make easier to read:
I think the issue is these are not updating:
this.settings.showTopLevel(true);
this.settings.selectedTopLevel(null);
For some reason observables that are declared in durandal widgets don't enter the knockout stack or aren't bound to the view (not sure which one)...
Not sure why yet. To get knockout observables I had to declare any observables needed at the widget level at the parent viewmodel's level and pass it in using the settings.
Let me know if you get it to work!

Related

Adding HTML content to angular material $mdDialog

I have written the following piece of code to display some contents in angular material dialog box. it works fine when i add plain text to textContent . when i add HTML its displays HTML as text. how do i bind HTML to textContent
This Works
Sample Link
$scope.Modal = function () {
$mdDialog.show(
$mdDialog.alert()
.parent(angular.element(document.querySelector('body')))
.clickOutsideToClose(true)
.textContent('sample text')
.ok('Ok')
);
}
This Doesn't Works
Sample Link
$scope.Modal = function () {
$mdDialog.show(
$mdDialog.alert()
.parent(angular.element(document.querySelector('body')))
.clickOutsideToClose(true)
.textContent('<div class="test"><p>Sample text</p></div>')
.ok('Ok')
);
}
Thanks in advance
You need to append to the template,
$mdDialog.show({
parent: angular.element(document.body),
clickOutsideToClose: true,
template: '<md-dialog md-theme="mytheme">' +
' <md-dialog-content>' +
'<div class="test"><p>Sample text</p></div>' +
' <md-button ng-click="closeDialog();">Close</md-button>' +
' </md-dialog-content>' +
'</md-dialog>',
locals: {
},
controller: DialogController
});
DEMO
You can add html in template and just add variable in displayOption. This will work.
Template Code
<script type="text/ng-template" id="confirm-dialog-answer.html">
<md-dialog aria-label="confirm-dialog">
<form>
<md-dialog-content>
<div>
<h2 class="md-title">{{displayOption.title}}</h2>
<p>{{displayOption.content}} <img src="{{displayOption.fruitimg}}"/></p>
<p>{{displayOption.comment}}</p>
</div>
</md-dialog-content>
<div class="md-actions" layout="row">
<a class="md-primary-color dialog-action-btn" ng-click="cancel()">
{{displayOption.cancel}}
</a>
<a class="md-primary-color dialog-action-btn" ng-click="ok()">
{{displayOption.ok}}
</a>
</div>
</form>
</md-dialog>
</script>
Controller Code
$mdDialog.show({
controller: 'DialogController',
templateUrl: 'confirm-dialog-answer.html',
locals: {
displayOption: {
title: "OOPS !!",
content: "You have given correct answer. You earned "+$scope.lastattemptEarnCount,
comment : "Note:- "+$scope.comment,
fruitimg : "img/fruit/"+$scope.fruitname+".png",
ok: "Ok"
}
}
}).then(function () {
alert('Ok clicked');
});
Use template instead of textContent, textContent is used for show plan text in a model. It does not render HTML code
$mdDialog.show({
controller: function ($scope) {
$scope.msg = msg ? msg : 'Loading...';
},
template: 'div class="test"><p>{{msg}}</p></div>',
parent: angular.element(document.body),
clickOutsideToClose: false,
fullscreen: false
});
You can use htmlContent instead of textContent to render HTML. Heres an excerpt from the documentation available at https://material.angularjs.org/latest/#mddialog-alert
$mdDialogPreset#htmlContent(string) - Sets the alert message as HTML.
Requires ngSanitize module to be loaded. HTML is not run through
Angular's compiler.
It seems a bit counter intuitive to use a template when you only need to inject one or two things in. To avoid using a template, you need to include 'ngSanitize' for it to work.
angular.module('myApp',['ngMaterial', 'ngSanitize'])
.controller('btnTest',function($mdDialog,$scope){
var someHTML = "<font>This is a test</font>";
$scope.showConfirm = function(ev) {
// Appending dialog to document.body to cover sidenav in docs app
var confirm = $mdDialog.confirm()
.title('Please confirm the following')
.htmlContent(someHTML)
.ariaLabel('Lucky day')
.targetEvent(ev)
.ok('Please do it!')
.cancel('Sounds like a scam');
//Switch between .htmlContent and .textContent. You will see htmlContent doesn't display dialogbox, textContent does.
$mdDialog.show(confirm).then(function() {
$scope.status = 'Saving Data';
},
function() {
$scope.status = 'You decided to keep your debt.';
});
};
})
Notice the injected HTML:
var someHTML = "<font>This is a test</font>";
I found this example here.
The latest version of Angular Material Design API has predefined function for add HTML content to the alert dialog:
an $mdDialogPreset with the chainable configuration methods:
$mdDialogPreset#title(string) - Sets the alert title.
$mdDialogPreset#textContent(string) - Sets the alert message.
$mdDialogPreset#htmlContent(string) - Sets the alert message as HTML. Requires ngSanitize module to be loaded. HTML is not run through Angular's compiler.
$mdDialogPreset#ok(string) - Sets the alert "Okay" button text.
$mdDialogPreset#theme(string) - Sets the theme of the alert dialog.
$mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option, the location of the click will be used as the starting point for the opening animation of the the dialog.
The link to the documentation: Angular MD API

AngularJS ng-click seems to not work

I have an md-select that has inside it an md-option. In the md-option tag I have an ng-click that points to my function that is defined. The problem is that it seems to never get to my function when I click an option from the list.
Here is my html:
<div class="panel-body">
<md-select placeholder="{{getIntervalName()}}" ng-model="intervalSelected">
<md-option ng-repeat="item in intervals" value="{{item.name}}" ng-click="changeInterval(item)">
{{item.name}}
</md-option>
</md-select>
<div style="overflow: auto;">
<div id="parentDivChart" style="{{changeWidth()}};height:400px">
<canvas id="bar" class="chart chart-bar" chart-data="datta"
chart-labels="labels" chart-options="myoptions" chart-legend="legend">
</canvas>
<div tc-chartjs-legend chart-legend="legend"></div>
</div>
</div>
</div>
And this is my function defined in the controller:
function changeInterval(item) {
console.log("change intervaaaalll");
$scope.default_interval ="day";
intrvl = item.value;
setItem("interval", item.value);
$scope.loadChart();
};
It does not write to the console anything, that means it never reaches my function. Does anybody have an idea why ?
Thanks in advance!
It would have been better if you had provided the code where you create the controller.
However, I think you should create the method changeInterval(item) in this way:
var nameOfYourApp = angular.module(‘nameOfYourApp’, []);
nameOfYourApp.controller('nameOfYourController', function($scope) {
$scope.changeInterval = function(item) {
console.log("change intervaaaalll");
$scope.default_interval ="day";
intrvl = item.value;
setItem("interval", item.value);
$scope.loadChart();
}
});
Hope this helps!
As you want to use the changeInterval method on the scope you have to add it to the scope as follows:
In the controller:
$scope.changeInterval(item) {
console.log("change intervaaaalll");
$scope.default_interval ="day";
intrvl = item.value;
setItem("interval", item.value);
$scope.loadChart();
};
Don't forget to include $scope as a dependency for your controller.
E.g.:
app.controller('myCtrl', function($scope) {
});
Now as the changeInterval function is assigned to a variable on the scope it can be invoked on the view.
Since you changeInterval() is a private function to the controller, you can't access it. You need to make it public (bind it to a variable of the controller).
You should go with this approach:
$scope.changeInterval = changeIntervalapproach;
If you're using the view model approach with controllerAs then use:
vm.changeInterval = changeInterval;
Write "$scope.changeInterval=changeInterval;" in your controller
or
use "$scope.changeInterval=function (item) {...};"

Is it possible to open a dialog or go to a new page based on the value of an object using just one button?

I'm trying to use a single "create" button that either opens a dialog or goes to a new page using ui-router, depending on the value of an object.
I'm not sure of the best way to accomplish this, I have something that seems to work, but I'm wondering if there's a better way. I'm currently using two buttons and an ng-if to hide the button I don't need at the time, but that seems messy to me.
Here's a very simple mock up of what I'm trying to do. Rather than actually opening a dialog or changing page content, I've just added alerts.
http://codepen.io/jwelker9/pen/QNPgyE?editors=1010
In short, I'm not sure how to get ui-sref and a dialog to play together on the same button.
HTML:
<div ng-controller = "lists-detail" ng-app="app">
<table class="listing">
<thead>
<tr>
<th>Item-Id:</th>
<th>Ordinal:</th>
<th>Type:</th>
<th>Content-Id:</th>
<th>
<button ng-if="!boolean"
class="button"
aria-label="Add list item"
ui-sref="site.lists">
Add New
</button>
<button ng-if="boolean"
class="button"
aria-label="Add list item"
ng-click="dialog()">
Add New
</button>
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
JS:
var app = angular.module('app', []);
app.controller('lists-detail', function ($scope) {
//actual code looks like:
//This calls an API to determine if it is correct type or not
//If boolean is true, it should open a new dialog
//if(listType == 'Episode'){
$scope.boolean = false;
//}
$scope.goToNewPage = function() {
alert("NewPage");
}
$scope.dialog = function() {
alert("Open Dialog");
}
})
If I'm understanding your question correctly, you could just call a function when the value changes and make the decision in the function like this:
https://jsfiddle.net/tma739u9/
Angular
vm.boolean = false;
vm.watchBoolean = function() {
var decision = (!vm.boolean) ? newPage() : dialog();
}
function newPage() {
$location.path('/page');
}
function dialog() {
alert('Open dialog');
}
HTML
<input
type="checkbox"
ng-model="ctrl.boolean"
ng-click="ctrl.watchBoolean()"> Click Me
In your ng-click, you can use an Angular expression like this:
ng-click="boolean==true? dialog() : gotoNewPage()"
This is a shorthand way of saying this in Javascript:
if (boolean) {
dialog();
} else {
gotoNewPage();
}

Kendo MVVM Binding to Self Executing Anonymous Module Function

I'm working with the Kendo UI MVVM and I'm trying to bind it to a self executing anonymous modular function. Long story short, it's only kind of working. The module is being updated if I inspect the page but the UI isn't. All I'm using is a short HTML file with a script tag to wire up the MVVM and an external JavaScript file to bring the module in.
HTML and JS on page
<!-- Adding information -->
<input data-bind="value: DemoField" />
<!-- Update Button -->
<button data-bind="events: { click: updateModule }">Update</button>
<!-- Trying to update this field -->
<input data-bind="value: module.Model.Demo.DemoField" />
<!-- Observable -->
<script type="text/javascript">
var model = kendo.observable(
{
DemoField: "",
updateModule: function () {
module.updateInformation({
demoField: this.get("DemoField")
)};
}
},
module
);
kendo.bind($("#form"), invoiceModel);
</script>
Module JS file
var module = (function () {
// private information
var _demo = (function () {
var _demoObject = {},
_demoField = null;
Object.defineProperty(_demoObject, "DemoField", {
get: function () { return _demoField; }
});
_demoObject.updateInformation = function (updatedObject) {
if (updatedObject.demoField) {
_demoField = updatedObject.demoField;
}
};
return _demoObject;
}());
var _model = { Demo: _demo };
// public information
return {
Model: _model
updateInformation: _demo.updateInformation
}
}());
If I enter "module.Model.Fields.FieldName" in the inspector, I see the information I'm expecting, but the UI just isn't playing nice. I've been to many pages on Telerik's website and I've consulted Google, but typically my searches yield little to no results and the results I do get are less than useful.
My thoughts are that kendo won't observe a module like it would a regular property, but then again I haven't worked with any kind of JS module before and I'm very new to MVVM.
Your thoughts are correct. Kendo will not observe a nested property, even if it is not nested you always have to use "get" and "set" words, for reference in Angular you don't need to do that.
So your code should look something like that:
<input data-bind="value: DemoField" />
<!-- Update Button -->
<button data-bind="events: { click: updateModule }">Update</button>
<!-- Trying to update this field -->
<input data-bind="value: updatedValue" />
And the view Model:
var model = kendo.observable({
DemoField: "",
updateModule: function () {
module.updateInformation({
demoField: this.get("DemoField")
});
this.set("updatedValue", module.Model.Demo.DemoField);
},
updatedValue: "",
});
kendo.bind($("#form"), model);
Here is a link to dojo with working example:
http://dojo.telerik.com/UzUhi

How to create a separate scope isolated from ng-repeat in Angular?

I am new to AngularJS and have some trouble understanding the concept of scope in Angular. I have read some posts on stackoverflow as well as online articles, which advise me to create a custom directive to create an isolate scope, but I am getting nowhere...
As for the project I'm working on, I am trying to make a button that when clicked, will trigger a textarea. However, because of ng-repeat, the textarea is triggered for all buttons while I click only one.
My .js file:
angular.module('myApp')
.controller('myCtrl', function ($scope, Question) {
scope.visible = false;
scope.toggle = function() {
scope.visible = !scope.visible;
};
.directive("myDirective", function () {
return {
scope: {
ngClick: '&',
ngShow: '&'
}
}
});
Here is my HTML file:
<ul>
<li ng-repeat="object in objectList">
<button type="text" myDirective ng-click="toggle()">Click</button>
<textarea myDirective ng-show="visible"></textarea>
</li>
</ul>
Angular is creating child (NOT isolated) scope when ng-repeating, try this out, when you ng-init a variable, it is only visible within that repeat div.
<div ng-repeat="i in [0,1,2,3,4,5,6,7,8,9]" ng-init="visible=false">
<button ng-click="visible=!visible">Toggle</button>
<h1 ng-show="visible">look at me!</h1>
</div>
Plunker
There is no need to use a directive. You need to use object in the foreach to refer each item in the loop.
Add visible to each object in objectList:
$scope.objectList = [
{ visible: false },
{ visible: false },
{ visible: false }
];
Then the toggle button will need to pass the object to toggle:
$scope.toggle = function (object) {
object.visible = !object.visible;
};
The ng-show will need to check object.visible and ng-click will need to pass the object:
<button type="text" ng-click="toggle(object)">Click</button>
<textarea ng-show="object.visible"></textarea>
Plunkr