What is root admin in knockout.js and what it points to? - javascript

data-bind="visible: $root.isAdministrator"
Can someone please explain what the above line means in knockout.js?

$root is a variable that saves the root ViewModel. In this page there is anywhere defined a ViewModel like this:
var vm = {
isAdministrator:ko.observable(true);
aRootObserable:ko.observable("ROOT");
childVm: {
childObservable:ko.observable("CHILD");
}
}
ko.applyBindings(vm);
It is used in knockout.js when the scope is on an underlying ViewModel but you want to access the root ViewModel. Example:
<div data-bind="with: childVm">
<div data-bind="text: childObservable"><!-- result is CHILD --></div>
<div data-bind="text: $root.aRootObserable"><! -- result is ROOT --></div>
</div>
If you are reengineering a knockout webpage you can search in all JS files for applyBindings. The parameter that is passed to this function is the root ViewModel.

Related

Using html as input for a knockout-component

I am in the process of implementing some components in my codebase. However, I have ran into an smaller issue with the template part. I would like to send in the template as an input to a knockout-component but I am not sure how to do it or if it even is possible.
Taking an example from http://knockoutjs.com/documentation/component-overview.html I hope that I can do something like this:
<like-or-dislike params="value: userRating">
<div class="like-or-dislike" data-bind="visible: !chosenValue()">
<button data-bind="click: like">Like it</button>
<button data-bind="click: dislike">Dislike it</button>
</div>
<div class="result" data-bind="visible: chosenValue">
You <strong data-bind="text: chosenValue"></strong> it.
And this was loaded from an external file.
</div>
</like-or-dislike>
But I cannot find any documentation if that works at all. The reason why I want to implement it that way is simply because I am having some server generated html that I want to still be a part of a component. Otherwise I will have to make it a json-object and render the html inside the component which seems like a unnecessary extra step. The good thing about using components is that the logic is seperated in it's own file and it is easier to seperate logic between different components. I understand that if I do it like this I have to copy the html if I want to reuse the component.
Am I thinking of this the wrong way or is this possible?
Thanks for your sage advice and better wisdom.
I can't say I fully understand your situation but I think I may have the answer. You can actually have the server generate <script type="text/html"> and use that (by id of course) with a component. The KO documentation is pretty poor on component templating, but here is an example using an element.
A couple of things I've learned with components. The viewmodel must be declared before declaration, and the <script> must be in the dom prior to binding.
function ComponentViewModel() {
var self = this;
self.Title = ko.observable("This is a Component VM");
}
function ViewModel() {
var self = this;
self.ExampleComponent = ko.observable({
name: 'Example'
});
}
ko.components.register('Example', {
template: {
element: 'ComponentTemplate'
},
viewModel: ComponentViewModel
})
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script id="ComponentTemplate" type="text/html">
<span data-bind="text: Title"></span>
</script>
<div data-bind="component: ExampleComponent"> </div>
I won't devalue components, but I also would point you to using templates with a data binding, it's essentially the same thing (please correct me if I'm wrong). and doesn't require the component be established. This is better for situations where the would-be component occurs less frequently.
function ComponentViewModel() {
var self = this;
self.Title = ko.observable("This is a Template with a VM");
}
function ViewModel() {
var self = this;
self.ComponentVM = ko.observable(new ComponentViewModel());
self.ExampleComponent = ko.observable({
name: 'ExampleTemplate', // This is the ID
data: self.ComponentVM
});
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script id="ExampleTemplate" type="text/html">
<span data-bind="text: Title"></span>
</script>
<div data-bind="template: ExampleComponent"> </div>
I hope these help!

Open up content in another div with AngularJS

I have some content I wish to load into a div using Angular. For example:
<div class="number-and-description" ng-controller="thingDetails">
<div class="number" ng-click="loadContent(thing.number, thing.description, thing.status)" ng-cloak>{{ thing.number }}</div>
<p class="description" ng-click="loadContent(thing.number, thing.description, thing.status)" ng-cloak>{{ thing.description }}</p>
</div>
I have another view outside of this div like this:
<!-- Main Content Area -->
<section class="main-content group" ng-controller="thingDetails">
<h1 ng-cloak>{{ thingNumber }}</h1>
<div ng-cloak>{{ thingDescription }}</div>
<div ng-cloak>{{ thingStatus }}</div>
</section>
In my app.js file, I have this code:
thingApp.controller('thingDetails', ["$scope", function($scope) {
$scope.loadContent = function(number, description, status) {
$scope.thingNumber = number;
$scope.thingDescription = description;
$scope.thingStatus = status;
return $scope;
};
}]);
My question is, why doesn't the main content area div update when these values are changed? Right now it remains blank. thing.number, thing.description loads fine in the div I am clicking on - this data is coming from hardcoded JSON which appears fine. The issue is that when I click on the div, the OTHER main content div doesn't show/update the data. Could someone please help me out? Note that when I console.log(number) or console.log(description) etc. I can see the correct values, so they are being passed correctly to the loadContent() function.
You have two separate instances of controller thingDetails , not one. They each have their own scope and do not share anything directly. A simple way to see this is put a console.log() in your controller and you will see it run each time the controller initializes
For controllers to share data you use a service and inject that service wherever it needs to be accessed.
Alternatively perhaps you don't need two instances and can wrap them in one scope in your view
You are basically creating two controllers here with two different scopes. Thus, in the outer div, $scope.thingNumber, description, etc. are undefined as you are only changing the values on the inner scope.
A quick but messy fix to this problem would be to change your $scope's to $rootScopes. The proper way to do this though is via services/factories.

Scope of ngController doesn't update with ngInclude

Here is a Plunker: http://plnkr.co/edit/F6QM4KUU8DPNq6muInGS?p=preview
Including an html file with ng-include, having set the ng-controller in the same tag, doesn't update the controller's scope models. Using the ng-model directly inside of the html works perfectly fine, and also setting the controller inside of the included html file is working. But ng-include together with ng-controller $scope.models don't update and stay as they are.
For whatever reason if you set the model inside of the controller, it is done suring it's loading. But having a method setting the model (not included in the plunker) only changes the mdoel inside of the controller's scope and not the html one.
Also if I use an ng-include in the scope of another controller and want to access the included models return undefined or the value you set the model to. Calling methods from the included html works fine in both cases, but they can't really operate as the values are wrong.
I saw that a similar issue has already been postet and should have been resolved (https://github.com/angular/angular.js/issues/4431), but as you can see in the plunker for me it doesn't.
Do I miss something, or is this a problem of angular?
Kind Regards,
BH16
PS: Here is the code from the Plunker:
index.html - body
<body ng:controller="MainCtrl">
<input type="text" ng:model="input">{{input}} {{getInput()}}
<div ng:include="'main2.html'" ng:controller="Main2Ctrl"></div>
</body>
main2.html
<div>
<input type="text" ng:model="input2">{{input2}} {{getInput2()}}
</div>
script.js
angular.module('testApp', [])
.controller('Main2Ctrl', function($scope) {
$scope.input2 = 1234;
$scope.getInput2 = function() {
console.log("input2");
return $scope.input2;
};
})
.controller('MainCtrl', function($scope) {
$scope.input = 1234;
$scope.getInput = function() {
console.log("input");
return $scope.input;
}
});
For all of those who are are also having this issue: Just use the controllerAs syntax: http://toddmotto.com/digging-into-angulars-controller-as-syntax/
This solves all of the issues above and simplifies the code A LOT!
This is the basic idea of controllerAs (taken from the site above):
<div ng-controller="MainCtrl as main">
{{ main.title }}
<div ng-controller="AnotherCtrl as another">
Scope title: {{ another.title }}
Parent title: {{ main.title }}
<div ng-controller="YetAnotherCtrl as yet">
Scope title: {{ yet.title }}
Parent title: {{ another.title }}
Parent parent title: {{ main.title }}
</div>
</div>
</div>
It's related to that ng-include creates it's own scope.
To see what's actually happens you can try plugin(for google chrome, probably something similar exists for others browser's too): "AngularJS Batarang", it will include additional tab to dev tools.
And possible solution will be:
main2.html:
<div ng:controller="Main2Ctrl">
<input type="text" ng:model="input2"> {{input2}} {{getInput2()}}
</div>
http://plnkr.co/edit/daIehNjxWdam3NyH3ww4?p=preview

how to get ng-included url value in angularjs

I have a main file which includes a file inside a subfolder using ng-include, like this,
<p ng-include=" 'activity/act01/' "></p>
where the ng-include value will change dynamically
then, how can i get current value of ng-include.
ng-include binds to an expression. This is why your example requires single quotes. It wants an expression returning a string. This can be function returning a string, a variable, or a literal string. Because of this, it is easy to bind ng-include to a scope variable.
Here is an example of how to do it:
function TestCtrl($scope) {
$scope.actPage = 'activity/act01/';
$scope.goToActTwo = function(){
$scope.actPage = 'activity/act02';
};
}
And the HTML:
<div ng-app ng-controller="TestCtrl">
<div ng-include="actPage"></div>
<button ng-click="goToActTwo()">Change Act</button>
</div>

knockout: accessing the View Model through an iframe?

The app I'm building requires the use of iframes for wysiwyg text editing, and these iframes need to be wired up to the viewModel, but I'm finding that this clashes with knockout... or at least knockout seems not to apply bindings when I try to access them through the parent object.
Here's some code...
<script type="text/javascript">
$(function(){
ko.applyBindings(parent.model.project, $('#root')[0]);
});
</script>
<ul id="root" data-bind="template: {name: function(){return type()},
foreach: populate() }"></ul>
<script id="document" type="text/html">
<li class="draft" draft="${draft()}" data-bind="css: {expanded: $data.expanded}">
<span data-bind="click: function(){parent.model.project.expand($data, 'draft')}">
${ordinal(draft())} Draft
<img src="icons/close-black.png"
data-bind="click: function(){parent.model.project.deleteDraft($data)},
css:{ only: function() {parent.model.project.drafts > 1} }"/>
</span>
<div>
<ul data-bind="css: {expanded: $data.expanded},
template: {
name: 'draft',
foreach: $data.draftItems,
}"
>
</ul>
</div>
</li>
</script>
<script id="draft" type="text/html">
{{if $data.name}}
<li class="${name}">${name}</li>
{{/if}}
</script>
OK, this isn't a wysiwyg text editor, but it still illustrates my point.
Now the thing is, when I wrote this it worked perfectly. I had the part of viewModel that all the bindings refer to defined in a js file accessed only by this html... but I need the same ViewModel to be accessed by the parent window, as I would with a wysiwyg editor for toolbar buttons and other external controls, so I moved that part of the viewModel to the file where the rest of it was defined... and now it doesn't work!
In the external file that I had previously I was still accessing the parent view model using parent.model, but now not having direct exclusive access to that model it doesn't seem to work. The thing is though that I can access the view model with console.log, I can document.write from it too, it fires off events back to the viewModel, and my view updates initially, but after that initial one it no longer updates.
Is there a way to solve this?
iframes won't inherit bindings from parent elements.
You can't make it work that way, as iframes really are separate pages within another page.
Each iframe will need to have its own view model. If that viewmodel needs to come from another view model, you'll need to share that data via global JS objects or message passing or some other mechanism.

Categories

Resources