Integrate into existing application


#1

I have an existing traditional application with server-side rendering. I’d like to eventually replace the front-end completely with an application written in Ember.JS. However such a rewrite won’t happen overnight and in the interim there will be a situation where we have to combine both applications.

I’m currently working out the following setup;

+-----------------_Layout.cshtml----------------+                                    
|+----------------_TopMenu.cshtml--------------+|                                    
||Link 1 | Link 2 | Ember Link 1 | Ember Link 2||                                    
|+---------------------------------------------+|                                    
|+------------------Ember.cshtml---------------+|                                    
|| <script src="assets/my-app.js" />           ||                                    
|| <script src="assets/vender.js" />           ||                                    
||+---------------application.hbs-------------+||                                    
|||                                           |||                                    
|||                                           |||                                    
|||                                           |||                                    
||+-------------------------------------------+||                                    
|+---------------------------------------------+|                                    
+-----------------------------------------------+                               

Thus, inside the existing application there will be a page embedding the Ember application. My question has to do with integrating both applications: how to deeplink between them?

In the top menu navigation there are two navigation items, when clicked they will open a different URL and ember will start loading the appropriate view, according to the router definition. However instead of re-loading Ember when going from “Ember Link 1” to “Ember Link 2”, I’d like to “stay within Ember” and not have the browser actual reboot the Ember application.

My first attempt at doing this is hooking into the router:

$("a:not(.ember-view)").on('click', function (event) {
  event.preventDefault();
  var url = this.pathname + this.search + this.hash;
  window.MyApp.__container__.lookup("router:main").router.transitionTo(url).then(
    function (transition) { /* succes */ },
    function (transition) { /* no such view in Ember, redirecting browser */ location.href = url; }
  );
});

While this does work for debug builds, it feels hacky and doesn’t work in production (see my other topic for details). Also the “active” state of {{#link-to}} helpers is not correctly reflected when a navigation is triggered by the code above. From the documentation, I can’t seem to find some guide on how to integrate ember into an existing application, so would really like to know how to achieve this.


#2

Hey @bouke!

Have you taken a look at ember-islands at all yet?

If you went this route, it may be as simple as making the link definition in your _TopMenu.cshtml template look something like this:

<div data-component="link-to" data-attrs='{"params": ["ember-route-1"]}'>
  <a href="/whatever/you/currently/have">Ember Link 1</a>
</div>
<div data-component="link-to" data-attrs='{"params": ["ember-route-2"]}'>
  <a href="/whatever/you/currently/have">Ember Link 2</a>
</div>

This issue seems like a similar use case.


#3

That’s a golden tip, thank you very much. One thing that took me some time to figure out was that the islands needed to reside inside the #rootElement. In retrospect the readme does mention this, but it wasn’t clear to me at first.

So big thank you!


#4

So far I’ve created the following setup. This allows us to render pages from MVC which can be enriched with Ember components. It also allows us to create pages inside Ember that can be navigated to, while tracking all page views in the browser’s history.

A catch-all route inside Ember:

# router.js

Router.map(function() {
  // ...
  // catch-all for mvc views (instead of 404)
  this.route('mvc-view', { path: '*:' });
})

The catch-all route that hides/shows the mvc page:

# routes/mvc-view.js
import Ember from 'ember';
import Route from '@ember/routing/route';

const { $ } = Ember;

# hide/show currently displayed mvc-view 
export default Route.extend({
    activate() {
        $("#content").show();
    },
    deactivate() {
        $("#content").hide();
    }
});

In the razor view we can now inject any components:

@* views/shared/_layout.cshtml *@
<div id="ember-app">
    <div data-component="menu-bar" />
    <div id="content">
        @RenderBody()
    </div>
    <!-- ember app will render here -->
</div>

Add a all ember pages to MVC routing:

routes.MapRoute(
    name: "Ember",
    url: "{route}/{a}/{b}/{c}/{d}",
    defaults: new { controller = "Ember", action = "Index", a = RouteParameter.Optional, b = RouteParameter.Optional, c = RouteParameter.Optional, d = RouteParameter.Optional },
    constraints: new { route = "(home|about|contact-us)" }
);

And an ember controller that does renders an empty page:

public class EmberController : BaseController
{
    public ActionResult Index()
    {
        return View();
    }
}

#5

This is great.

If you end up posting a more detailed blog post about this and the learnings, please share here! (I may be looking to do something like this in the not-too-distant future).