IE9 ignores angular objects (within ngRepeat) - javascript

I've got an ng-include and ng-controller within an ng-repeat, and IE randomly craps itself when it sees the child instance object of the repeat:
inside main.html
<section ng-repeat="panel in sidepanels">
<h2 class="twelve columns">
<span class="twelve">
<i class="icon {{panel.icon}}"></i> <!-- resolves properly -->
{{panel.controller.name}} <!-- empty -->
</span>
</h2>
<div
ng-include src="'views/'+panel.controller.name.toLowerCase()+'.html'"
ng-controller="panel.controller"
></div>
</section>
inside controllers.js
function Main($scope) {
…
$scope.sidepanels = [
{
"controller": Alerts,
"icon": "icon-warning-sign"
}
];
…
}
function Alerts($scope,WebSocket) {
$scope.alerts = [];
WebSocket.on('…', function(data) { … });
WebSocket… //WebSocket is a Service
}
Except instead of throwing an error in console, it just silently ignores the fact that it sometimes can't resolve panel. I only noticed this was the case because I saw a failed GET on views/.html.
I checked MSDN, and supposedly IE supports the name property.

The property name doesn't work very well for functions on IE.
You can use the following snippet to retrieve a function's name (as described here):
func.toString().match(/^function ([^(\s]+)/)[1]
Add a function to your Main controller that creates the template path, like this:
$scope.getTemplatePath= function(controller) {
return 'views/' + angular.lowercase(controller.toString().match(/^function ([^(\s]+)/)[1] + '.html');
};
And on the HTML:
<div ng-include="getTemplatePath(panel.controller)" ng-controller="panel.controller"></div>
jsFiddle: http://jsfiddle.net/bmleite/faGRk/

Related

knockout js cannot delete after adding to observable array

I am trying to complete some basic add and delete functionality using knockout js.
I am using asp mvc, the knockout mappings plugin and returning a simple list of strings as part of my viewModel
Currently I get three items from the server and the functionality I've created allows me to delete each of those items. I can also add an item. But if I try to delete an item i have added in the KO script I cannot delete it.
After completing research and some tests I guess i'm using the observable's incorrectly. I altered my code to pass ko.observable(""), but that has not worked. What am I doing wrong?
values on load
Array[4]0: "test 1"1: "test 2"2: "test 3"length: 4__proto__: Array[0]
values after clicking add
Array[4]0: "test 1"1: "test 2"2: "test 3"3: c()length: 4__proto__: Array[0]
ko script
var vm = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
this.deleteBulletPoint = function (bulletPoint) {
self.BulletPoints.remove(bulletPoint)
}
this.addEmptyBulletPoint = function () {
self.BulletPoints.push(ko.observable(""));
console.log(self.BulletPoints())
}
}
HTML
<div class="col-lg-6 col-md-6 col-sm-12">
<h4>Bullet Points</h4>
<div id="oneLinedescriptions" class="input_fields_wrap">
<!-- ko foreach: BulletPoints -->
<div class="form-group">
<div class="input-group">
<input type="text" data-bind="value: $data" class="form-control">
<span data-bind="click: $parent.deleteBulletPoint" class="input-group-addon btn">-</span>
</div>
</div>
<!-- /ko -->
</div>
<button id="btnAddDescription" data-bind="click: addEmptyBulletPoint" type="button" class="btn btn-default add_field_button col-lg-12 animate-off">Add New Bullet Point</button>
</div>
EDIT
I have removed $parent but the below error was returned
Uncaught ReferenceError: Unable to process binding "foreach: function (){return BulletPoints }"
Message: Unable to process binding "click: function (){return deleteBulletPoint }"
Message: deleteBulletPoint is not defined
In addition to this I have been able to add new empty elements but they are not updated when a user changes the value. is this because the element I am adding is not observable? And if so how do I get round it?
I've added a snippet to ilustrate how you add and remove using an Observable Array with Knockout, the majority of the code is straight from yours so you are in the right track.
Note that when binding to the methods, in this case, there's no need to reference the $parent as you are not using nested scopes. Also, when adding, you just need to pass in plain text, as the observableArray is expecting an object.
If you work with more complex types, when adding you'll need to pass the object itself, and reference its properties from it inside the scope of the iterator, you can read more about it here.
Hope this helps.
var vm = function (data) {
var self = this;
this.BulletPoints = ko.observableArray(["test1", "test2", "test3"]);
this.deleteBulletPoint = function (bulletPoint) {
self.BulletPoints.remove(bulletPoint)
}
this.addEmptyBulletPoint = function () {
const c = self.BulletPoints().length + 1;
self.BulletPoints.push("test "+c);
}
}
ko.applyBindings(vm);
a {
cursor: pointer;
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h4>Bullet Points <span><a data-bind="click: addEmptyBulletPoint">Add +</a></span></h4>
<div data-bind="foreach: BulletPoints">
<div class="input-group">
<h3 data-bind="text: $data"></h3>
<a data-bind="click: deleteBulletPoint">Delete</a>
</div>
<div>
The problems you're having are related to the way ko.mapping treats arrays of primitive types. By design only properties that are part of an object get mapped to observables so an array of strings will become an observableArray of strings. It will not become an observableArray of observable strings.
In order to add/remove items from an array where the items themselves are observable you'll have to make your BulletPoints array an array of objects having the string as a property within:
data = { BulletPoints: [{value: "test1"}, {value: "test2"}] }
Here's a working example: jsfiddle

Async loading a template in a Knockout component

I'm pretty experienced with Knockout but this is my first time using components so I'm really hoping I'm missing something obvious! I'll try and simplify my use case a little to explain my issue.
I have a HTML and JS file called Index. Index.html has the data-bind for the component and Index.js has the ko.components.register call.
Index.html
<div data-bind="component: { name: CurrentComponent }"></div>
Index.js
var vm = require("SectionViewModel");
var CurrentComponent = ko.observable("section");
ko.components.register("section", {
viewModel: vm.SectionViewModel,
template: "<h3>Loading...</h3>"
});
ko.applyBindings();
I then have another HTML and JS file - Section.html and SectionViewModel.js. As you can see above, SectionViewModel is what I specify as the view model for the component.
Section.html
<div>
<span data-bind="text: Section().Name"></span>
</div>
SectionViewModel.js
var SectionViewModel = (function() {
function SectionViewModel() {
this.Section = ko.observable();
$.get("http://apiurl").done(function (data) {
this.Section(new SectionModel(data.Model)); // my data used by the view model
ko.components.get("dashboard", function() {
component.template[0] = data.View; // my html from the api
});
});
}
return SectionViewModel;
});
exports.SectionViewModel = SectionViewModel;
As part of the constructor in SectionViewModel, I make a call to my API to get all the data needed to populate my view model. This API call also returns the HTML I need to use in my template (which is basically being read from Section.html).
Obviously this constructor isn't called until I've called applyBindings, so when I get into the success handler for my API call, the template on my component is already set to my default text.
What I need to know is, is it possible for me to update this template? I've tried the following in my success handler as shown above:
ko.components.get("section", function(component) {
component.template[0] = dataFromApi.Html;
});
This does indeed replace my default text with the html returned from my API (as seen in debug tools), but this update isn't reflected in the browser.
So, basically after all that, all I'm really asking is, is there a way to update the content of your components template after binding?
I know an option to solve the above you might think of is to require the template, but I've really simplified the above and in it's full implementation, I'm not able to do this, hence why the HTML is returned by the API.
Any help greatly appreciated! I do have a working solution currently, but I really don't like the way I've had to structure the JS code to get it working so a solution to the above would be the ideal.
Thanks.
You can use a template binding inside your componente.
The normal use of the template bindign is like this:
<div data-bind="template: { name: tmplName, data: tmplData }"></div>
You can make both tmplData and tmplName observables, so you can update the bound data, and change the template. The tmplName is the id of an element whose content will be used as template. If you use this syntax you need an element with the required id, so, in your succes handler you can use something like jQuery to create a new element with the appropriate id, and then update the tmplname, so that the template content gets updated.
*THIS WILL NOT WORK:
Another option is to use the template binding in a different way:
<div data-bind="template: { nodes: tmplNodes, data: tmplData }"></div>
In this case you can supply directly the nodes to the template. I.e. make a tmplNodes observable, which is initialized with your <h3>Loading...</h3> element. And then change it to hold the nodes received from the server.
because nodesdoesn't support observables:
nodes — directly pass an array of DOM nodes to use as a template. This should be a non-observable array and note that the elements will be removed from their current parent if they have one. This option is ignored if you have also passed a nonempty value for name.
So you need to use the first option: create a new element, add it to the document DOM with a known id, and use that id as the template name. DEMO:
// Simulate service that return HTML
var dynTemplNumber = 0;
var getHtml = function() {
var deferred = $.Deferred();
var html =
'<div class="c"> \
<h3>Dynamic template ' + dynTemplNumber++ + '</h3> \
Name: <span data-bind="text: name"/> \
</div>';
setTimeout(deferred.resolve, 2000, html);
return deferred.promise();
};
var Vm = function() {
self = this;
self.tmplIdx = 0;
self.tmplName = ko.observable('tmplA');
self.tmplData = ko.observable({ name: 'Helmut', surname: 'Kaufmann'});
self.tmplNames = ko.observableArray(['tmplA','tmplB']);
self.loading = ko.observable(false);
self.createNewTemplate = function() {
// simulate AJAX call to service
self.loading(true);
getHtml().then(function(html) {
var tmplName = 'tmpl' + tmplIdx++;
var $new = $('<div>');
$new.attr('id',tmplName);
$new.html(html);
$('#tmplContainer').append($new);
self.tmplNames.push(tmplName);
self.loading(false);
self.tmplName(tmplName);
});
};
return self;
};
ko.applyBindings(Vm(), byName);
div.container { border: solid 1px black; margin: 20px 0;}
div {padding: 5px; }
.a { background-color: #FEE;}
.b { background-color: #EFE;}
.c { background-color: #EEF;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="byName" class="container">
Select template by name:
<select data-bind="{options: tmplNames, value: tmplName}"></select>
<input type="button" value="Add template"
data-bind="click: createNewTemplate"/>
<span data-bind="visible: loading">Loading new template...</span>
<div data-bind="template: {name: tmplName, data: tmplData}"></div>
</div>
<div id="tmplContainer" style="display:none">
<div id="tmplA">
<div class="a">
<h3>Template A</h3>
<span data-bind="text: name"></span> <span data-bind="text: surname"></span>
</div>
</div>
<div id="tmplB">
<div class="b">
<h3>Template B</h3>
Name: <span data-bind="text: name"/>
</div>
</div>
</div>
component.template[0] = $(data)[0]
I know this is old, but I found it trying to do the same, and the approcah helped me come up with this in my case, the template seems to be an element, not just raw html

Meteor helper function exception when retrieving the count of all users

On my website I'm trying to display the number of users registered. The problem is that I get a long exception on render. My question is how do you do this dynamically instead of statically?
The code works on start up but if I switch pages and come back it defaults to a blank. The code follows:
Template.Home.helpers({
UserAmount: function() {
var Count = Meteor.users.find().count();
document.getElementById("UsersSignedUp").innerHTML = Count;
console.log("Users-helper: "+Count);
}
});
the following is now the HTML section on the home page
<section class="home-main">
<div class="container">
<div class="stats">
<div class="row">
<div class="col-md-4">
<label id="UsersSignedUp">{{UserAmount}}</label>
<span>Users</span>
</div>
<div class="col-md-4">
0
<span>Listings</span>
</div>
<div class="col-md-4">
0
<span>Matches</span>
</div>
</div>
</div>
</div>
</section>
I was able to make an onRender function to supplement the error but I'd rather not have to rely on it. I know that listings and Matches are both set to 0 but if I can fix this error the others will follow the same format.
This is the exception:
[Log] Exception in template helper: UserAmount#http://localhost:3000/Nomad.js?1c729a36a6d466ebe12126c301d77cc72299c832:56:47 (meteor.js, line 888)
http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:2880:21
http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:1651:21
http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:2928:71
_withTemplateInstanceFunc#http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:3476:16
http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:2927:52
call#http://localhost:3000/packages/spacebars.js?7bafbe05ec09b6bbb6a3b276537e4995ab298a2f:172:23
mustacheImpl#http://localhost:3000/packages/spacebars.js?7bafbe05ec09b6bbb6a3b276537e4995ab298a2f:109:30
mustache#http://localhost:3000/packages/spacebars.js?7bafbe05ec09b6bbb6a3b276537e4995ab298a2f:113:44
http://localhost:3000/template.Nomad.js?68e12845c8ef55042e39b4867426aab290c3711d:95:30
doRender#http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:2011:32
http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:1865:22
_withTemplateInstanceFunc#http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:3476:16
http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:1864:54
_withCurrentView#http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:2197:16
lookup:UserAmount:materialize#http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:1863:34
_compute#http://localhost:3000/packages/tracker.js?6d0890939291d9780f7e2607ee3af3e7f98a3d9c:327:36
Computation#http://localhost:3000/packages/tracker.js?6d0890939291d9780f7e2607ee3af3e7f98a3d9c:243:18
autorun#http://localhost:3000/packages/tracker.js?6d0890939291d9780f7e2607ee3af3e7f98a3d9c:566:34
autorun#http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:1875:29
http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:2005:17
nonreactive#http://localhost:3000/packages/tracker.js?6d0890939291d9780f7e2607ee3af3e7f98a3d9c:593:13
_materializeView#http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:2004:22
materializeDOMInner#http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:1476:31
_materializeDOM#http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:1428:26
http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:2040:46
nonreactive#http://localhost:3000/packages/tracker.js?6d0890939291d9780f7e2607ee3af3e7f98a3d9c:593:13
_materializeView#http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:2004:22
render#http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:2296:25
insert#http://localhost:3000/packages/iron_dynamic-template.js?d425554c9847e4a80567f8ca55719cd6ae3f2722:522:15
insert#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:1632:22
maybeAutoInsertRouter#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:1622:20
If there is any other information I should provide please let me know.
Try using this as the helper instead:
Template.Home.helpers({
UserAmount: function() {
return Meteor.users.find().count();
}
});
The idea is this helper is called UserAmount so the value it returns on the Home template should replace itself into the handlebars expression {{UserAmount}}
You don't have to do the heavy lifting in changing the DOM yourself. Meteor probably gets confused as to why the dom is changing when it tries to change it and it gives up that error.

Change scope value in a different view

Ok, I have struggeled a lot with this thing for some days now and I'm stuck. Here is what I want; I want to change my scope value in a different view by clicking on a button. So if I'm in the index.html view and click on a button I want to change the value of a scope on the index2.html view, and then display that view. Here is an example of my code which is not working
index.html
<div ng-controller="IndexController">
<button class="button button-block button-assertive" ng-click="checkValues()" value="checkitems" >
check values
</button>
</div>
IndexController.js
angular
.module('legeApp')
.controller('IndexController', function($scope, supersonic, $filter) {
$scope.checkValues = function(){
$scope.Diagnose = 'test';
var view = new supersonic.ui.View("legeApp#index2.html");
var customAnimation = supersonic.ui.animate("flipHorizontalFromLeft");
supersonic.ui.layers.push(view, { animation: customAnimation });
};
});
index2.html
<div ng-controller="IndexController">
<div class="card">
<div class="item item-text-wrap">
Test<b>{{Diagnose}} </b>
</div>
</div>
</div>
I can give the value outside the checkValues method, but I want it to be two different values depending on the button you click. Please help
I tried the code suggested, but I received an error. What am I doing wrong?
I try the code below and receive this error "SyntaxError: Unexpected token ':' - undefined:undefined". I also did not quite understand how and why I want to target the new value with supersonic.ui.views.params.current in the new view. I want to get the new value in the new view, not in a controller?Do I need two different controllers? I just want to update my values in a html view without being in it.
supersonic.ui.layers.push:
( view: view,
options: { params: {$scope.Diagnose : 'test'}
animation: customAnimation
}) => Promise
According to the supersonic push docs, the params attribute is meant for passing parameters between views:
JSON string of optional parameters to be passed to the target View,
accessible via supersonic.ui.views.params.current.
Try calling
supersonic.ui.layers.push: (
view: view,
options: {
params: {valueToBeSentAccrossView: <Your Value>}
animation: customAnimation
}) => Promise
and then retrieving the value in the target view using supersonic.ui.views.params.current.

I have two divs with the same ng-controller in AngularJS, how can I make them share information?

I have two different div tags in my html code referencing the same controller in AngularJS. What I suspect is that since these divs aren't nested they each have their own instance of the controller, thus the data is different in both.
<div ng-controller="AlertCtrl">
<ul>
<li ng-repeat="alert in alerts">
<div class="span4">{{alert.msg}}</div>
</li>
</ul>
</div>
<div ng-controller="AlertCtrl">
<form ng-submit="addAlert()">
<button type="submit" class="btn">Add Alert</button>
</form>
</div>
I know this could easily be fixed by including the button in the first div but I feel this is a really clean and simple example to convey what I am trying to achieve. If we were to push the button and add another object to our alerts array the change will not be reflected in the first div.
function AlertCtrl($scope) {
$scope.alerts = [{
type: 'error',
msg: 'Oh snap! Change a few things up and try submitting again.'
}, {
type: 'success',
msg: 'Well done! You successfully read this important alert message.'
}];
$scope.addAlert = function() {
$scope.alerts.push({
type: 'sucess',
msg: "Another alert!"
});
};
}
This is a very common question. Seems that the best way is to create a service/value and share between then.
mod.service('yourService', function() {
this.sharedInfo= 'default value';
});
function AlertCtrl($scope, yourService) {
$scope.changeSomething = function() {
yourService.sharedInfo = 'another value from one of the controllers';
}
$scope.getValue = function() {
return yourService.sharedInfo;
}
}
<div ng-controller="AlertCtrl">{{getValue()}}</div>
<div ng-controller="AlertCtrl">{{getValue()}}</div>
If I understand the question correctly, you want to sync two html areas with the same controller, keeping data synced.
since these divs aren't nested they each have their own instance of the controller, thus the data is different in both
This isn't true, if you declare the controllers with the same alias (I'm using more recente angular version):
<div ng-controller="AlertCtrl as instance">
{{instance.someVar}}
</div>
<div ng-controller="AlertCtrl as instance">
{{instance.someVar}} (this will be the same as above)
</div>
However, if you WANT them to be different and comunicate each other, you will have to declare different aliases:
<div ng-controller="AlertCtrl as instance1">
{{instance1.someVar}}
</div>
<div ng-controller="AlertCtrl as instance2">
{{instance2.someVar}} (this will not necessarily be the same as above)
</div>
Then you can use services or broadcasts to comunicate between them (the second should be avoided, tough).

Categories