ApplicationRoute 的加载状态 [英] Loading state for ApplicationRoute

查看:23
本文介绍了ApplicationRoute 的加载状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用:ember 1.7.0

Using: ember 1.7.0

我有一些服务器端数据,我想在任何路由转换到之前加载到我的 ember 应用程序中.我的多个(但不是全部)其他路由/控制器需要这些数据.我想我可以在 ApplicationRoute model 方法中加载这些数据.它工作正常,但不显示加载状态.

I have some server-side data that I want to load up in my ember app before any routes are transitioned to. Multiple (but not all) of my other routes / controllers need this data. I was thinking that I could just load up this data in the ApplicationRoute model method. It works fine, but does not display a loading state.

是否可以让 ApplicationRoute 显示加载状态,直到它的 model 承诺得到解决.

Is it possible to have the ApplicationRoute display a loading state until it's model promise gets resolved.

这是一个说明问题的 jsbin:http://jsbin.com/soqivo/1/

Here's a jsbin illustrating the problem: http://jsbin.com/soqivo/1/

感谢您的帮助!

推荐答案

更新:

截至 1.11.0 release,应该可以为应用路由定义一个loading子状态.

Update:

As of 1.11.0 release, it should be possible to define a loading substate for the application route.

我认为这是设计使然,但不是设计缺陷.之所以会出现这个特殊问题,是因为该长模型请求发生在 ApplicationRoute#model 中,而本应在 IndexRoute#model 中.将该承诺/请求移动到索引路由中,应该没问题.如果您必须向应用程序控制器添加内容,请考虑 这个,结合在应用程序等待时在 index.html 文件中加载"的内容.

I think this is by design, but not a design flaw. This particular issue is happening because that long model request is taking place in ApplicationRoute#model when it should be in IndexRoute#model. Move that promise/request into the index route and it should be fine. If you must add stuff to the application controller, consider this, combined with something that says "loading" in your index.html file while the app is waiting.

Ember.Route 有许多我们经常覆盖的钩子,所以它可以做我们想要的,而不是默认的实现.最明显的钩子是modelsetupController.但有时我们只是不编写 setuptController 方法,因为它已经做了我们想要它做的事情(假设一个人只想将 model 设置到 controller).但是不管这些方法被覆盖,它们无论如何都会作为内部工作流的一部分运行.这个工作流程有许多不常讨论的步骤,因为它们已经做了我们想要的,而我们往往会忘记它们及其重要性,以及对于这个特定问题,这些步骤的顺序方法在路由生命周期中被调用.

Ember.Route has a number of hooks that we often override so it does what we want instead of a default implementation. The most obvious hook being model and setupController. But sometimes we simply don't write the setuptController method because it does what we want it to do already (given one just wants to set the model into the controller). But regardless of these methods being overridden, they will run as part of an internal workflow anyway. This workflow has a number of steps that are not often discussed because they already do what we want and we tend to forget about them and their importance, and, as for this particular issue, the order in which these methods get called in the route life cycle.

App = Ember.Application.create();

App.logs = Ember.ArrayProxy.create({
  content: []
});

App.Router.map(function() {
  this.resource('posts', function() {});
});

function loggingAlias(property) {
  return function() {
    App.logs.pushObject(this._debugContainerKey + ' ' + property);
    return this._super.apply(this, arguments);
  };
}

App.LoggingRoute = Ember.Route.extend({
  enter: loggingAlias('enter (private)'),
  exit: loggingAlias('exit (private)'),
  activate: loggingAlias('activate'),
  deactivate: loggingAlias('deactivate'),
  serialize: loggingAlias('serialize'),
  deserialize: loggingAlias('deserialize (private)'),
  model: loggingAlias('model'),
  setupController: loggingAlias('setupController'),
  afterModel: loggingAlias('afterModel'),
  beforeModel: loggingAlias('beforeModel'),
  renderTemplate: loggingAlias('renderTemplate'),
  redirect: loggingAlias('redirect')
});

App.LogsController = Ember.ArrayController.extend({
  content: App.logs,
  actions: {
    clearLogs: function() {
      App.logs.clear();
    }
  }
});

App.ApplicationRoute = App.LoggingRoute.extend();
App.PostsRoute = App.LoggingRoute.extend();
App.PostsIndexRoute = App.LoggingRoute.extend();

/* Put your CSS here */

html,
body {
  margin: 20px;
}

<!DOCTYPE html>
<html>

<head>
  <meta name="description" content="Ember Route Hook Order" />
  <meta charset="utf-8">
  <title>Ember Route Hook Order</title>
  <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/2.1.0/normalize.css">
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
  <script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.2.1.js"></script>
  <script src="http://builds.emberjs.com/beta/ember.js"></script>
</head>

<body>

  <script type="text/x-handlebars">

    <strong>Note:</strong>  <em>
    MilkyWayJoe says: I didn't write this. I have found in jsbin at: <a href="http://jsbin.com/rolo/2/edit?output">http://jsbin.com/rolo/2/edit?output</a>
    <br /> 
    Added here in case the link goes kaput just to point out the order of Ember.Route internal workflow steps 
    </em>
    <br />
    <br />{{link-to 'Index' 'index'}} {{link-to 'Posts' 'posts'}} {{outlet}} {{render 'logs'}}
  </script>

  <script type="text/x-handlebars" id='logs'>
    <h3>Logged Method Calls</h3>

    <a href="#" {{action 'clearLogs'}}>Clear Logs</a>
    <ul>
      {{#each}}
      <li>{{this}}</li>
      {{/each}}
    </ul>
  </script>
</body>

</html>

因为 renderTemplate 是被调用的最后,所以只有在给定路由中的承诺得到解决之前它不会渲染任何东西才有意义.

Since renderTemplate is the last to get called so it only makes sense that it doesn't render anything until the promise(s) within a given route get resolved.

对于子路由,这完全没问题,因为它们的 loading 子状态将有某种类型的 canvas 可以绘制,因为父级已经加载-生活和-呼吸路线已在此路线甚至被实例化之前加载.但是对于 ApplicationRoute 来说不是这样,因为它没有父路由或模板可以依赖,因此在所有承诺得到解决之前呈现一个空白页面.

For child routes, that's totally fine because their loading substate will have some type of canvas to draw onto, since a parent-already-loaded-living-and-breathing-route has been loaded prior this route even being instantiated. But that's not true for the ApplicationRoute since it has no parent route or template to rely upon, thus rendering a blank page until all promises get resolved.

接下来最好的事情是将任何长时间运行的请求移动到子路由.作为建议的解决方案,我已将您的 3 秒承诺移至 IndexRoute#model,因为该路由将反正运行并且是 ApplicationRoute 的直接子级> 默认情况下.我会说保留应用程序路由或控制器来处理 error 事件.

The next best thing is moving any long running requests to a child route. As the proposed solution, I've moved your 3 sec promise to IndexRoute#model since this route will run anyway and is the direct child of ApplicationRoute by default. I would say reserve the application route or controller for handling error events instead.

App = Em.Application.create({
  displayName: 'Test.App'
});

App.Router.map(function() {
  this.resource('files', function() {
    this.route('index', {path: '/'});
    this.resource('file', { path: ':file_id' }, function() {
      this.route('index', {path: '/'});
      this.route('detail');
    });
  });
});

App.FilesController = Em.ArrayController.extend();
App.FilesFileController = Em.ObjectController.extend();

App.Person = Ember.Object.extend({}); 

App.IndexRoute = Ember.Route.extend({
    model: function(params, transition){
      return new Ember.RSVP.Promise(function(resolve){
        Ember.run.later(function(){ 
          var model =  App.Person.create();
          resolve(model);
        }, 3000); 
      });
    } 
});

App.LoadingRoute = Em.Route.extend({
  renderTemplate: function() {
    this.render('loading', {
      into: 'application',
      outlet: 'loading'
    });
  }
});

App.FileLoadingRoute = App.LoadingRoute.extend();
 
App.FilesRoute = Em.Route.extend({
  model: function() {
    var selfie = this;
    return new Ember.RSVP.Promise(function(resolve){
        Ember.run.later(function() {  
            var model = selfie.store.find('file');
            resolve(model);
        }, 800);  
    });
  }
});

App.FilesIndexRoute = Em.Route.extend({
  model: function(){
    return this.store.all('file');
  }
});

App.FileRoute = Em.Route.extend({
  model: function(params) { 
    return this.store.find('file', params.file_id);
  } 
});

App.FileIndexRoute = Em.Route.extend({
  model: function() {  
    return this.modelFor('file');  
  },
  renderTemplate: function() {
    
    this.render('files/index', {
      into: 'application'
    });
    
    this.render('file/index', {
      into: 'files/index',
      outlet: 'file'
    });
    
  }
});

App.FileDetailRoute = Em.Route.extend({
  model: function() {
    var selfie = this;
    return new Ember.RSVP.Promise(function(resolve){
        Ember.run.later(function(){ 
            var file = selfie.modelFor('file');
            var model = selfie.store.find('fileDetail', file.id);
            resolve(model);
        }, 800);  
    });
  },
  renderTemplate: function() {
    
    this.render('files/index', {
      into: 'application'
    });
    
    this.render('file/index', {
      into: 'files/index',
      outlet: 'file'
    });
    
    this.render('file/detail', {
      into: 'file/index',
      outlet: 'detail'
    });
  },
  actions: {
    loading: function() {
      return true;
    }
  }
});

App.RlLoadIndicatorComponent = Em.Component.extend({
  
  classNames: ['rl-load-indicator'],
  classNameBindings: ['isLoading:rl-overlay:rl-silent'],
  overlay: true,
  spinner: true,
  message: 'Loading...',
  loading: false,
  
  isLoading: function() {
    return this.get('loading');
  }.property('loading'),
  
  spinnerClass: function() {
    if (this.get('loading')) {
      if (this.get('spinner')) {
        return 'rl-spinner';
      }
    }
    return "";
  }.property(),
  
  actions: {
    setLoading: function() {
      this.set('loading', true);
    },
    setDone: function() {
      this.set('loading', false);
    }
  }
  
});

App.ApplicationAdapter = DS.FixtureAdapter.extend();

App.File = DS.Model.extend({
  name: DS.attr('string'), 
  text: DS.attr('string'),
  detail: DS.belongsTo('fileDetail', {async: true})
});

App.FileDetail = DS.Model.extend({
  owner: DS.attr('string'),
  canEdit: DS.attr('bool'),
  file: DS.belongsTo('file'),
  property1: DS.attr('string'),
  property2: DS.attr('string'),
  property3: DS.attr('string'),
  property4: DS.attr('string'),
  property5: DS.attr('string')
});

App.File.FIXTURES = [
  {id: 1, name: 'File 1', text: 'Blah 1', detail: 1},
  {id: 2, name: 'File 2', text: 'Blah 2', detail: 2},
  {id: 3, name: 'File 3', text: 'Blah 3', detail: 3},
  {id: 4, name: 'File 4', text: 'Blah 4', detail: 4},
  {id: 5, name: 'File 5', text: 'Blah 5', detail: 5},
  {id: 6, name: 'File 6', text: 'Blah 6', detail: 6},
  {id: 7, name: 'File 7', text: 'Blah 7', detail: 7},
  {id: 8, name: 'File 8', text: 'Blah 8', detail: 8},
  {id: 9, name: 'File 9', text: 'Blah 9', detail: 9},
  {id: 10, name: 'File 10', text: 'Blah 10', detail: 10}
];

App.FileDetail.FIXTURES = [
  {
    id: 1, 
    owner: 'Spiderman', 
    canEdit: true, 
    file_id: 1, 
    property1: 'Value 1', 
    property2: 'Value 2', 
    property3: 'Value 3', 
    property4: 'Value 4', 
    property5: 'With great values, comes great bindings'
  },
  {
    id: 2, 
    owner: 'Iron Man', 
    canEdit: true, 
    file_id: 2, 
    property1: 'Value 1', 
    property2: 'Value 2', 
    property3: 'Value 3', 
    property4: 'Value 4', 
    property5: 'Another Value'
  },
  {
    id: 3, 
    owner: 'Thor', 
    canEdit: false, 
    file_id: 3, 
    property1: 'Value 1', 
    property2: 'Value 2', 
    property3: 'Value 3', 
    property4: 'Value 4', 
    property5: 'Another Value'
  },
  {
    id: 4, 
    owner: 'Captain America', 
    canEdit: false, 
    file_id: 4, 
    property1: 'Value 1', 
    property2: 'Value 2', 
    property3: 'Value 3', 
    property4: 'Value 4', 
    property5: 'Another Value'
  },
  {
    id: 5, 
    owner: 'Neil DeGrasse Tyson', 
    canEdit: true, 
    file_id: 5, 
    property1: 'Value 1', 
    property2: 'Value 2', 
    property3: 'Value 3', 
    property4: 'Value 4', 
    property5: 'Another Value'
  },
  {
    id: 6, 
    owner: 'Dr. Doom', 
    canEdit: false, 
    file_id: 6, 
    property1: 'Value 1', 
    property2: 'Value 2', 
    property3: 'Value 3', 
    property4: 'Value 4', 
    property5: 'Another Value'
  },
  {
    id: 7, 
    owner: 'Reed Richards', 
    canEdit: true, 
    file_id: 7, 
    property1: 'Value 1', 
    property2: 'Value 2', 
    property3: 'Value 3', 
    property4: 'Value 4', 
    property5: 'Another Value'
  },
  {
    id: 8, 
    owner: 'Walter White', 
    canEdit: true, 
    file_id: 8, 
    property1: 'Value 1', 
    property2: 'Value 2', 
    property3: 'Value 3', 
    property4: 'Value 4', 
    property5: 'Say My Name!' 
  },
  {
    id: 9, 
    owner: 'Jesse Pinkmann', 
    canEdit: true, 
    file_id: 9, 
    property1: 'Value 1', 
    property2: 'Value 2', 
    property3: 'Value 3', 
    property4: 'Value 4', 
    property5: 'Bitch'
  },
  {
    id: 10, 
    owner: 'Hawk Barton', 
    canEdit: false, 
    file_id: 10, 
    property1: 'Value 1', 
    property2: 'Value 2', 
    property3: 'Value 3', 
    property4: 'Value 4', 
    property5: 'Another Value' 
  }
];

/* Put your CSS here */
html, body {
  margin: 20px;
}

.rl-load-indicator {
  text-align: center;
}

.rl-overlay {
    position:fixed;
    top:0;
    left:0;
    right:0;
    bottom:0;
    background-color:rgba(0, 0, 0, 0.85);
    background: url(data:;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABl0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuNUmK/OAAAAATSURBVBhXY2RgYNgHxGAAYuwDAA78AjwwRoQYAAAAAElFTkSuQmCC) repeat scroll transparent9; /* ie fallback png background image */
    z-index:9999;
    color:white;
}

.rl-silent {
  display: none;
  visibility: hidden;
}

.rl-spinner {
  width: 30px;
  height: 30px;
  background-color: #27ae60;
  margin: 100px auto;
  margin-bottom: 8px;
  -webkit-animation: rotateplane 1.2s infinite ease-in-out;
  animation: rotateplane 1.2s infinite ease-in-out;
}

.arrow-right {
	width: 0; 
	height: 0; 
	border-top: 5px solid transparent;
	border-bottom: 5px solid transparent;
	border-left: 5px solid green;
}

@-webkit-keyframes rotateplane {
  0% { -webkit-transform: perspective(120px) }
  50% { -webkit-transform: perspective(120px) rotateY(180deg) }
  100% { -webkit-transform: perspective(120px) rotateY(180deg)  rotateX(180deg) }
}

@keyframes rotateplane {
  0% { 
    transform: perspective(120px) rotateX(0deg) rotateY(0deg);
    -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg) 
  } 50% { 
    transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
    -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) 
  } 100% { 
    transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
    -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
  }
}

<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Loading Thingy" />
  <meta charset="utf-8">
  <title>Ember Starter Kit</title>
  <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
  <script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
  <script src="http://builds.emberjs.com/tags/v1.7.0/ember.js"></script>
  <script src="http://builds.emberjs.com/beta/ember-data.js"></script>
</head>
<body>
 
  <script type="text/x-handlebars">
    <h1>{{unbound App.displayName}}</h1>
    {{partial "menu"}}
    <hr />
    {{outlet}} 
    {{outlet "loading"}} 
  </script>
 
  <script type="text/x-handlebars" data-template-name="loading">
  {{rl-load-indicator loading=true}}
  </script>
  
  <script type="text/x-handlebars" data-template-name="_menu">
{{#link-to 'index'}}Home{{/link-to}} |    {{#link-to 'files.index'}}Files{{/link-to}}
  </script>
  
  <script type="text/x-handlebars" data-template-name="index">
    <h3>Index</h3>
    Content goes here
  </script>
  
  <script type="text/x-handlebars" data-template-name="files/index">
    <h3>Files</h3>
    <table class="table table-hover">
      <thead>
        <tr>
          <th>Id</th>
          <th>Name</th>
          <th>&nbsp;</th>
        </tr>
      </thead>
      <tbody>
      {{#each file in model}}
        <tr>
          <td>{{file.id}}</td>
          <td>{{file.name}}</td>
          <td>
            {{#link-to 'file.index' file}}
            <p class="arrow-right"></p>
            {{/link-to}}
          </td>
        </tr>
      {{/each}}
      </tbody>
    </table>
    {{outlet "file"}} 
  </script>
  
  <script type="text/x-handlebars" data-template-name="file/index">
    <h3>{{name}}</h3>
    {{text}}
    <hr />{{#link-to 'file.detail'}}Detail{{/link-to}}
    {{outlet "detail"}}
    
  </script>
  
    <script type="text/x-handlebars" data-template-name="file/detail">
    <h5>Details</h5>
    <hr />
    <ul>
    <li>owner: {{owner}}</li>
    <li>can edit: {{canEdit}}</li>
    <li>property 1: {{property1}}</li>
    <li>property 2: {{property3}}</li>
    <li>property 3: {{property3}}</li>
    <li>property 4: {{property4}}</li>
    <li>property 5: {{property5}}</li>
    </script>
  
  
  <script type="text/x-handlebars" data-template-name="components/rl-load-indicator">
  <div {{bind-attr class=spinnerClass}}></div>
  {{unbound message}}
  </script>

</body>
</html>

这篇关于ApplicationRoute 的加载状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆