I have an SPA written in AngularJS. The main page uses an ng-include attribute to determine which view to load. This is set in JavaScript when someone clicks on a menu which is contained within the main page. However, I've come across a situation where I need to load a different view by clicking a button within another view, essentially replacing it.
I'm trying to figure out how to do this and from what I've researched, I have to use $rootScope and either an $emit or $broadcast call in the child view and a $rootScope.$on method to detect this event.
The thing is, this doesn't seem to work. I have set my breakpoints and stepped through the code, but I always get this error:
Error: [ngModel:datefmt] http://errors.angularjs.org/1.5.7/ngModel/datefmt?p0=2009-07-21T00%3A00%3A00
Here's the code in my parent page controller:
$rootScope.$on('viewChanged', function () {
var menuItem = {
template: 'customerOrders.html' // will be eventually dynamic
};
navigate(menuItem);
});
function navigate(menuItem) {
$scope.activeMenuItem = menuItem;
}
<div data-ng-include="activeMenuItem.template"></div>
In the child page controller:
function changeSelectedView(viewTemplate) {
$rootScope.$emit('selectedViewChanged', viewTemplate);
}
Obviously I'm doing something wrong here. How do I accomplish what I want, or is there a completely different way to do this?
you can use ng-route to work between views. check https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
First of all, the event name in the $emit function and in the $on function did not match, so I made that fix.
function navigateToNewTemplate(event, viewTemplate) {
var menuItem = {
template: buildTemplateUrl(viewTemplate)
};
navigate(menuItem);
}
$rootScope.$on('selectedViewChanged', navigateToNewTemplate);
function changeSelectedView(viewTemplate) {
$rootScope.$emit('selectedViewChanged', viewTemplate);
}
Related
I'm not sure why I can't get the button element using my UI hash. This is what my Layout looks like:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
ui: {
btnSave: "#btnSave"
},
events: {
"click #ui.btnSave": "onSave"
},
onInitialize: function () {
this.listenTo(App.vent, "DisableSaveButton", function(val) {
this.disableSaveButton(val);
},this);
},
disableSaveButton: function () {
this.ui.btnSave.prop("disabled",val).toggleClass("ui-state-disabled",val);
},
onSave: function () {
alert("saved!");
}
})
In VS2013, when my breakpoint hits the line inside disableSaveButton method, I entered $("#btnSave") into the Watch window and I was able to get the element back. I could tell because it had a length of 1. From this, I know the button is rendered. However, if I enter this.ui.btnSave into the Watch window, I would get an element with length of 0.
My BaseLayout object is basically a custom object extended from Marionette.Layout
Marionette version: 1.8.8
Any ideas why I can't find the button element using this.ui.btnSave?
Thanks in advance!
Got some help from a coworker and the issue might be because the element is out of scope. Basically, inside the Layout object, 'this' does not contain the element. We were able replace 'this.ui.btnSave' with '$("#btnSave",this.buttonset.el)' and that works fine. buttonset is the region that actually contains the html element.
This seems like an inconsistency because even though the ui hash didn't work, the click event utilizing the ui hash did work.
UPDATE 6/3/2015:
Another coworker of mine provided a better solution. Basically, in my Layout I use a display function to display my view. It looks something like this:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
display: function() {
$(this.buttonset.el).html(_.template($("#buttonset-view").html(), {"viewType": viewType}));
}
})
Basically, I'm saying to set the html of my region, which is this.buttonset.el, to my template's html. As of now, my layout doesn't know any of the elements inside the region. It just contains a region which displays the elements. So there is some sort of disconnect between my layout and the elements in my region.
The correct solution, as opposed to my earlier workaround, is to simply add the following line of code at the end:
this.bindUIElements();
From Marionette Annotated Source:
This method binds the elements specified in the “ui” hash inside the
view’s code with the associated jQuery selectors.
So this final code looks like this:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
display: function() {
$(this.buttonset.el).html(_.template($("#buttonset-view").html(), {"viewType": viewType}));
this.bindUIElements();
}
})
With this, I was able to finally able to retrieve my element using this.ui.btnSave.
I'm looking to have a function run every time an angular directive updates. In my case, I have an array of modal configurations that get used on a modal markup template.
Every time the template is used to generate a modal due to a change in the model, I want to run a positionModal() method.
scope.$watch in the link function doesn't seem to notice when I change the model, and I cant think of any other way of doing this. I tried a post-link function thinking that the compile function would get called when the directive was applied, but that doesn't seem to work either. Here is my example controller:
MyApp.controller("ModalController", function () {
//Define scope vars
$scope.modals = [];
$scope.$on("modalTrigger", function (event, settings) {
$scope.modals.push(settings);
});
});
Note: I've simplified the controller here- know that it DOES work.
Here is the template code:
<div class="modalParent" ng-controller="ModalController">
<div id="{{modal.id}}" class="modal" ng-class="modal.type" ng-repeat="modal in modals">
<div class="content">
<h2 ng-show="modal.title">{{modal.title}}</h2>
<p>{{modal.message}}</p>
<button>{{modal.button}}</button>
</div>
</div>
</div>
The directive is currently like this:
MyApp.directive("modalParent", function () {
var positionModals = function (element) {
element.find(".modal .content").each(function () {
//position logic here
});
};
return {
restrict: "C",
compile: function (tElement) {
positionModals(tElement);
}
};
});
Note: Also simplified for the purposes here.
The positionModals() call works when the first modal gets pushed to the array. After that, it stops working.
I've tried using the linking function as well, same result. scope.$watch(modals, function(){...}) does not work.
Can somebody help me figure out what I'm doing wrong?
Figured it out!
I was applying the directive to the parent, ".modalParent".
The ng-repeated element in this case is the modal itself ".modal".
You would want the directive to run on elements that get updates as the model changes, because then the linking function will get called each time the element is instantiated, rather than sitting and watching the parent and trying to update from there.
Hope this helps somebody.
Instead of calling like this, my approach is to write this in the services and inject that services in the controller wherever you want to get that function or the data to be notified as,
Services.js
as.service("xyzservice",function(factoryname){
//here the code goes...
})
Now inject in Controller,
ac.controller("controllername",function(xyzservice){
})
ac.controller("controllername",function(servicename){
})
ac.controller("controllername",function(xyzservice){
})
Here we have injected it in the two controller, we can get it.
So I have a clearNotification function I want to trigger when I click the '#upload' button. I set an ng-click on that button to trigger notify().
What I'm wondering is if it's a violation of separation of concerns to put that clearNotification function within notify(). Is it better to add that as another ng-click on '#upload'? Or is this where directives come in?
Would love any input on this.
So far, this is what my code looks like:
HTML:
<button id="upload" ng-click="notify()">Upload</button>
Controller:
$scope.clearNotification = function() {
$scope.notification = '';
};
$scope.notify = function() {
$scope.clearNotification();
...
};
There's nothing wrong with calling controller functions from other controller functions.
If the clearNotification function has some business logic, then add it to a service(and inject that service into your controller). Thats the correct way to seperate components.
Should this be a directive? Only if this is a re-usable component that can live alone.
My ember app is set up with a list of posts on the left and a view for an individual post on the right. When one of the posts on the left is clicked it's content is rendered in the view on the right.
This is the code I'm using to add syntax highlighting to a post.
App.PostView = Ember.View.extend({
didInsertElement: function() {
$('pre code').each(function(i, e) {hljs.highlightBlock(e)});
}
});
When the first post view is rendered, it has the syntax highlighting, but when I click on a different post and it's content gets loaded into the post view the syntax highlighting does not get applied. How can I make it so that the highlighting applied every time a post is rendered?
I can only guess without a more comprehensive example. Is PostView what gets created in the right panel? If so, then you need to constrain your view rendering to the stuff inside the view.
In your example, $('pre code') will target all pre code elements inside the document. Try this.$('pre code'), or whatever element/selector needs to be highlighted within the view.
This may be not the cleanest way to do the job, but you could try adding observer to the controller's model, and make required changes. But this will only work, if the model itself changes.
Like this:
postHasChanged: function() {
if (this.get('state') === 'inDOM') {
$('pre code').each(function(i, e) {
hljs.highlightBlock(e)
});
}
}.observes('controller.model')
I want to create a really simple confirmation box using UI-modal, which I have successfully used to make complicated modals that load their template and controller from external files in the past.
It's so simple though that I don't want to rely on external template and controller files, just a simple box with a close button which is somehow wired up to a controller declared directly on the modal instance.
Here is what I have tried unsuccessfully...
var modalInstance = $modal.open({
template: "<div>Message goes here...<button ng-click='cancel()'>Continue</button></div>",
controller: function(){
$scope.cancel = function(){
alert("Cancelled");
};
}
});
Looks like you need to inject $scope into your controller function
controller: function($scope){
The scope of the modal template is not the same as the scope in the controller that you've defined the modal instance in.
The reason you're not getting undefined errors is $scope is a closure variable so adding .cancel() to it works just fine. But, since it isn't the same scope of the modal, so the ng-click doesn't see a .cancel() on its scope.
I replicated in this jsbin: http://jsbin.com/gejuxije/1/edit
Edit:
Since you mentioned you didn't want external files for a template, here's a demo of how to define the template for the modal inside the template of the view it is used on.
http://jsbin.com/gejuxije/2/edit
You can put html inside of an inline script...
<script type="text/ng-template" id="myModalTemplateName.html"></script>
The value you pass to 'template' needs to be valid HTML, and ideally should contain the appropriate modal CSS classes.
You may also need to pass in the scope for the controller.
var modalInstance = $modal.open({
scope:$scope,
template: "<div>Message goes here...<button ng-click='cancel()'>Continue</button></div>",
controller: function(){
$scope.cancel = function(){
alert("Cancelled");
};
}
});
In general I have not had to do this, but since you are defining the controller in the open method it may be necessary. According to the docs it should create a new scope as a child of rootScope, but I suspect your mileage is varying. I wish the instructions on the website were a little more informative on this topic.
You may also want to try $close and $dismiss. I've never tried them, but since you are not having luck with the scope variable these might be what you need.
I am just trying to do something similar and stumbled across this. I know it's old but it might help someone.
Simply put
modalInstance.close();
in the cancel function