加载ApplicationRoute的状态

时间:2014-10-23 20:13:57

标签: ember.js

使用:ember 1.7.0

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

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

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

感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

更新

1.11.0 release开始,应该可以为应用程序路由定义 loading 子状态。

TL; DR;

我认为这是设计上的,但不是设计上的缺陷。这个特殊问题正在发生,因为ApplicationRoute#model中的长模型请求应该在IndexRoute#model中。将该promise / request移动到索引路由中它应该没问题。如果您必须向应用程序控制器添加内容,请考虑this,并结合使用" loading"在应用等待的时候在index.html文件中。

为什么

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



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>
&#13;
&#13;
&#13;

由于renderTemplate是要被调用的最后,因此只有在给定路线中的承诺得到解决之前,它才会呈现任何内容才有意义。

对于儿童路线,这是完全正常的,因为他们的loading子状区将有某种类型的画布可以绘制,因为父母已经装载了生活和-breathing-route已经在此路由之前被加载,甚至被实例化。但ApplicationRoute的情况并非如此,因为它没有父路线或模板可供依赖,因此在所有承诺得到解决之前呈现空白页。

工作代码段

下一个最好的事情是将任何长时间运行的请求移动到子路由。作为建议的解决方案,我已将您的3秒承诺移至IndexRoute#model,因为此路线无论如何都将运行,并且默认为ApplicationRoute的直接子项。我会说保留应用程序路由或控制器来代替处理error事件。

&#13;
&#13;
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' 
  }
];
&#13;
/* 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 transparent\9; /* 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);
  }
}
&#13;
<!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>
&#13;
&#13;
&#13;

答案 1 :(得分:1)

只需在应用程序路径下创建一个不同的资源,然后在那里完成所有的加载。不要使用索引路由,只有当你点击特定资源的根时才会被命中(App.IndexRoute就是当你点击应用程序的根目录时)。

App = Ember.Application.create();

App.Router.map(function() {
  this.resource("top",function(){ // a place you can grab things for the app and block
    this.resource('home');  // a place you want to get when everything is ready
  });
});

App.ApplicationRoute = Ember.Route.extend();

App.IndexRoute = Ember.Route.extend({
  redirect: function() {
    this.transitionTo("home");
  }
});
App.TopRoute = Ember.Route.extend({
//   This does trigger a loading state
  model: function(params){
    return new Ember.RSVP.Promise(function(resolve){
        setTimeout(function(){ 
            resolve();
        }, 3000); // 3 second delay, wooh, your server is slow!!!
    });
  }
});

http://jsbin.com/mivul/edit?html,js,output