execute action (javascript function) after ember template is rendered - javascript

I have very small web page with emberjs, where I want to show some item list and openlayers map for them, and another map for selected item.
Something like that:
<script type="text/x-handlebars" id="list">
<div class="list">
<div id="list_map"></div>
</div>
{{outlet}}
</script>
<script type="text/x-handlebars" id="list/item" >
<div class="item">
<div id="item_map"></div>
</div>
</script>
<script>
function showListMap() {
listMap = new ol.Map({target:'list_map'});
}
function showItemMap() {
itemMap = new ol.Map({target:'item_map'});
}
</script>
There is no problem to display map for list:
model: function(params) {
var content = [];
var url = 'http://localhost:8080/app/list';
$.ajax({
url: url,
success: function(surveys) {
content.pushObjects(surveys);
showListMap();
}
});
return content;
}
and I have action in item controller that is executed, when opening selected item, but if I try to create item map there (in controllers action) it fails (afaik because there is no such div at that moment in DOM).
So if I could execute action or my function after div is already add to DOM, it should work.
And question would be, how to execute something after template is added to DOM, or that's completely wrong way to do such stuff (than what would be correct ember way)?

I can't say much with seeing full code. But to execute some code after the DOM is rendered you schedule a function on the the run loops afterRender queue.
Ember.run.scheduleOnce('afterRender', this, function() {
//The div should be available now.
});
But if you really need to touch the DOM I recommend you wrap your map code in a component. A component gets a didInsertElement where you can write your maps initialization code.
var component = Em.Component.extend({
setup: function() {
//Do you map init here.
}.on('didInsertElement')
});

There unfortunately isn't a really good route or controller hook that fires off after a page has already rendered. I believe the reason for this is that the developers of Ember think it is an anti-pattern to directly talk to the DOM.
That being said, I think it sometimes is quite handy for complex UI on otherwise static web pages. If you want to do some sort of jquery or use the DOM API after a route has rendered, you can do the following in your route file (as #Dainius correctly points out)
routeName.js
import Route from '#ember/routing/route';
import jQuery from 'jquery';
export default class myRouteFile extends Route {
manipulateDom = function() {
$("#myDiv").css( "color", "red" );
}
init() {
this._super(...arguments)
Ember.run.scheduleOnce('afterRender', this, this.manipulateDom)
}
}

Related

How to replace a child view with another in AngularJS

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);
}

Ember - Resize DIV on load, transition and resize?

I'm building an Ember app that needs to size a container DIV to be full window height on load of the application, and then run the same resize function again when transitioning to a new route, and then also on window resize.
On a normal site, I'd do this:
var appUI = {
init: function(){
appUI.sizeContainer();
},
sizeContainer: function(){
var winHeight = jQuery(window).height();
jQuery('#container').height(winHeight);
},
onResize: function() {
appUI.sizeContainer();
}
}
jQuery(document).ready(function($){
appUI.init();
jQuery(window).resize(function(){
appUI.onResize();
});
});
But obviously this won't work in Ember.
This can't be a component, because the #container DIV wraps the entire current view. But with Ember moving away from views, how should I do this?
The only way I came up with was to use a view, and hook onto didInsertElement, but I couldn't figure out how can I do it without having to create a view.js file for every route, that contains the same resize code? And how about the resize event? I thought the application view didInsertElement might work for this, but it only runs once on load.
All my route templates basically follow this patten:
{{top-header}}
{{background-image image=backgroundImage}}
{{side-menu session=session menuOpen=menuOpen}}
<div id="container" class="vert-center route-name">
{{partial "_logo"}}
{{some-component}}
</div>
On loading the application and on window resize can be done pretty much the way you described.
One easy way is to override the renderTemplate hook inside the ApplicationRoute. Within this hook, you can render your application template and then initialize the resize listener on the window object:
// handles on document load and on window change events
App.ApplicationRoute = Ember.Route.extend({
renderTemplate: function(controller, model) {
this.render('application'); // render the application template
appUI.init(); // call the init event on application load
Ember.$(window).resize(function() { // setup resize listener on the window object that will be called when window resizes
appUI.onResize();
});
}
});
As far as resizing each time a route loads, you could implement a generic Ember.Route, let's call it ResizableRoute for example, that calls the appUI.resize() after its template is rendered. This can again be achieved with overriding the renderTemplate hook.
// calls onResize() each time the current route's template is rendered in the DOM
App.ResizableRoute = Ember.Route.extend({
renderTemplate: function() {
// render the template with the same name as the route (assumes you follow ember naming conventions)
this.render(this.routeName);
// call resize since the route is loaded
appUI.onResize();
}
});
Now you can make any other route extend this ResizableRoute and, every time that route's template is rendered, appUI.onResize() will be called.
App.AnyOtherRoute = App.ResizableRoute.extend({
// do other stuff
});
The reason all the calls are made AFTER the template is rendered is because that way the #container element is definitely inserted in the DOM already and can be grabbed using jQuery.
Here is a running jsFiddle example
EDIT
Instead of overriding the renderTemplate hook, another way you could achieve this is to create a ResizeUIComponent that will perform resizing each time your route is loaded. The flaw is that you have to remember to insert this component into each route's template.
App.ResizeUIComponent = Ember.Component.extend({
didInsertElement: function() {
this.$().hide(); // make the component invisible, probably better to do it with css but this is a quick example
appUI.onResize();
}
});
And add this component to all templates (including application) you want to call onResize() each time they load:
{{top-header}}
{{background-image image=backgroundImage}}
{{side-menu session=session menuOpen=menuOpen}}
<div id="container" class="vert-center route-name">
{{resize-ui}} {{!-- add the invisible resize component as the child of #container to ensure necessary rendering order --}}
{{partial "_logo"}}
{{some-component}}
</div>
And you can add a listener on the window object after the init event of the ApplicationController:
App.ApplicationController = Ember.Controller.extend({
onInit: function() {
Ember.$(window).resize(function() { // setup resize listener on the window object that will be called when window resizes
appUI.onResize();
});
}.on('init');
});

Iron meteor's Route doesn't fire rendered event of the template

Here's my case. I'm building an template that should render a chart based on the some data that I can get by identifier.
The example that I will show here is the simplified version of my code that fully discribe my problem.
Here is my HTML:
<head>
<title>example</title>
</head>
<body>
</body>
<template name="layout">
First text
Another text
{{> yield}}
</template>
<template name="example">
<span id="text">Basic Text</span>
</template>
And JavaScript:
if (Meteor.isClient) {
Router.configure({
layoutTemplate: 'layout'
});
Router.route('/:text', function() {
this.render('example', {
data: this.params.text
});
});
Template.example.rendered = function() {
$('#text').html(this.data);
}
}
This code renders whatever you put in url into "p" tag. If you will go over the first link you will get the text "firstText". But after it if you will go over another link the text will be the same. The text will be changed if only will you reload the page.
That is because when you render the template via the iron:router's method this.render('template') the meteor creates and renders this template that cause the fire of the created and rendered events, but if you go to the other url from the same router, the meteor doesn't destroy this template and recreates him, but just change the data. So the rendered event doesn't fire on change of the URL.
I can use only the rendered event and not the helpers because from helpers I can't get element with JQuery.
Now I'm using the solution where I'm wrap all the logic in rendered event into the Tracker.autorun() and use Session.set('text', this.params.text) in the router and Session.get('text') in the autorun function to rerender the text every time the URL will change. But this solution gives additional problems. And I think that Router's this.render('templateName') should fire the rendered event.
Could you please help me to make the first variant work without reactive data. Maybe there are some solutions that can truly rerender the template?
Meteor avoids re-rendering templates as much as possible and it is great because it makes code faster. The key here is to use reactivity. So you can write something like that in html:
<template name="example">
<span id="text">{{text}}</span>
</template>
and in js file:
Template.example.helpers({
text: function() {
return this;
}
});
You don't need rendered function for this and you don't need jQuery.
And if you want you can access element from helper with jQuery.
In rendered
Template.example.rendered = function() {
this.rendered = true;
}
and then in helper
whateverHelper: function() {
var tmpl = Template.instance;
tmpl.rendered && tmpl.$('#something').makeFun();
}

Angular: Run method every time a directive updates

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.

How to be notified when a view's template is (re)rendered in Ember?

When a view's template is refreshed due to changes of the model, the didInsertElement hook is not triggered as Ember tries to reuse views as much as possible. This causes some problems for me in that I need to select the first element of a list when the template is rerendered. This happens, for example, when new data is loaded from the server or the application transitions to a different route.
The odd thing is that the view's init method does get called when the application transitions to a different route. The issue is that the template hasn't been rendered at that point, that is, the view hasn't been populated with the new contents of the controller.
In short, how can I be notified after the template of a view has finished rerendering so that I can manipulate the view's contents?
For your information, observing the controller's model is not an option either as the view hasn't been updated by the time the callback is fired.
<script type="text/x-handlebars" id="list">
<div>
<ul>
{{#each model.items}}
<li class="item">
<p>{{title}}</p>
</li>
{{/each}}
</ul>
</div>
</script>
Update The view needs to be notified when the each block is rerendered. I wanted to use the solution in this question, but it seems that only the each block is rerendered and not the entire template (which is what I want). I could add a trigger event in the each block, but that is very expensive as the custom event would be triggered in each loop of the each block.
Maybe this could work:
App.ListView = Ember.View.extend({
templateName: 'list',
contentChanged: function() {
Ember.run.scheduleOnce('afterRender', this, 'selectFirstElement')
}.observes('controller.content.[]'),
selectFirstElement: function() {
// for simplicity
this.$('li:first').addClass('selected');
}
});
Using observes('controller.content.[]') make the contentChanged function be called, if some change is performed in the controller content. To don't get that function called a lot of times in the same runloop we use the Ember.run.scheduleOnce('afterRender', ...), so selectFirstElement is called just once, and because it's scheduled in the afterRender queue, you can manipule the dom.
Live demo http://jsfiddle.net/marciojunior/4b2V3/
App.MagicView = Ember.View.extend({
doMagic: function() {
// magic goes here
}.on('didInsertElement');
});
Update: I see now. Here's my brain dump: http://emberjs.jsbin.com/URidoBi/3/edit. Not the cleanest way, but should get you around the problem.

Categories

Resources