在准备这个大型项目的过程中,我正在研究一些 JS MV* 框架,这些框架将被大约 10 名开发人员的团队使用。
首先我尝试了 AngularJS,因为它的性能看起来是所有框架中最好的(SO 主题)。它有一些像 DI 一样美好的未来,但我真的不喜欢使用它,因为对我来说它有点混乱的代码很难理解,我喜欢代码尽可能干净,结构良好且易于读。
目前我正在尝试 EmberJS,它看起来像 ember,对开发人员非常友好,并且很容易使用它。所以我写了一个小的“登录小部件”来看看 ember 应用程序应该如何构建。在开发过程中,我试图解决一些问题,如异步请求、动画等......
“小部件”已经可以工作,并且可以完成我希望他做的所有事情,但在这一点上,我仍然有一些问题,我想听听经验丰富的 ember 开发人员关于当前代码结构、对象角色和问题的一些建议我可以稍后在使用 ember 时见面。
所以这里有一些问题:
1) Ember 视图很酷,但是如果我想让 ember 处理现有元素的动作和动画怎么办?我的意思是,例如我在服务器上呈现了链接(或通过 AJAX 接收到的 HTML),我如何绑定 ember 动作来处理它的点击动作并为它实例化我的自定义 LinkView?我确定我可以通过使用 jQuery 或其他东西“手动”绑定到 DOM 事件来找到解决方案,但是是否存在“Ember”方法来做到这一点?
2) 是否可以StateManager
在进入初始状态之前设置属性,但不在类级别设置?
3)有人可以指点我如何使用的例子Em.Deffered
吗?我做出了使用 jQuery 的承诺$.Deferred()
,但我不想在我的StateManager
.
4) 是否存在其他“Ember”方式来告诉框架在已经隐藏的状态下向 DOM 添加视图?我试图设置 isVisible: false,但它不起作用。
5) 是否存在其他方式然后再次添加隐藏视图,以解决错误“无法对不在 DOM 中的 Metamorph 执行操作。”?
以下是参考来源:
<script type="text/x-handlebars" data-template-name="GuestView">
User: {{view Ember.TextField valueBinding='controller.username' placeholderBinding='controller.usernameHint'}}
Pass: {{view Ember.TextField valueBinding='controller.password' placeholderBinding='controller.passwordHint' }}
<br/>
<button {{action login this}}>Login</button>
</script>
<script type="text/x-handlebars" data-template-name="UserView">
Hello {{ username }}
<button {{action logout this}}>Logout</button>
</script>
<script type="text/javascript">
var App = Em.Application.create({
authController: null,
currentUserBinding: 'authController.content',
ready: function () {
this.set("authController", App.AuthController.create());
}
});
App.AuthController = Em.ObjectController.extend({
content: null,
target: null,
init: function () {
this.set("content", this.getUserFromCookieOrDefault());
//Is it possible in Ember, to set the controller before entering initial state,
//but without setting it on Class level?
this.set("target", App.AuthManager.reopen({ controller: this }).create({
//initialState: this.get("content").isAuthorized ? "authorized" : "guest"
}));
this.get("target").transitionTo(this.get("content").isAuthorized ? "authorized" : "guest");
},
doLogin: function (mgr, context) {
console.log("logging in");
//Validation, etc...
//Imulating async Data loading, actually, it should be done with promises
setTimeout(function () { mgr.transitionTo("authorized"); }, 1000);
},
doLogout: function (mgr, context) {
console.log("logging out");
//logout logic
mgr.transitionTo("guest");
this.set("content", App.Guest.create());
},
getUserFromCookieOrDefault: function () {
return App.Guest.create();
//return App.User.create({ username: "TestUser" });
},
});
App.AuthManager = Em.StateManager.extend({
controller: null,
canTransist: true,
// { data, commitCallback, abortCallback }
transitionTo: function (path, context) {
var originalTransitionTo = this._super,
manager = this,
canTransist = "canTransist",
deffered = null,
originalTransitionToParams = context && (context.data || context.commitCallback || context.abortCallback) ?
context.data : context;
if (!manager.get(canTransist)) {
Em.assert("Already in Transition");
return;
}
manager.set(canTransist, false);
if (!manager.currentState) {
originalTransitionTo.apply(manager, [path, originalTransitionToParams]);
manager.set(canTransist, true);
return;
}
if (!manager.currentState.isAsyncExit) {
originalTransitionTo.apply(manager, [path, originalTransitionToParams]);
manager.set(canTransist, true);
}
else {
//How to use Em.Deffered?
deffered = $.Deferred();
deffered.done(commitTransition).fail(abortTransition);
manager.currentState.beforeExit(deffered, originalTransitionToParams);
}
function commitTransition() {
originalTransitionTo.apply(manager, [path, originalTransitionToParams]);
manager.set(canTransist, true);
}
function abortTransition() {
manager.set(canTransist, true);
}
},
states: {
guest: Em.State.create({
isAsyncExit: true,
enter: function (mgr) {
console.log("guest");
App.guestView = App.GuestView.create({
templateName: "GuestView",
controller: mgr.controller,
}).append();
},
beforeExit: function (promise, context) {
App.guestView.remove(promise);
},
login: function (mgr, context) {
mgr.controller.doLogin(mgr, context);
}
}),
authorized: Em.State.create({
isAsyncExit: true,
enter: function (mgr) {
console.log("authorized");
App.userView = App.UserView.create({
templateName: "UserView",
controller: mgr.controller,
}).append();
},
beforeExit: function (promise) {
App.userView.remove(promise);
},
logout: function (mgr, context) {
mgr.controller.doLogout(mgr, context);
}
})
}
});
App.FadingView = Em.View.extend({
//Does exists other "Ember" way to tell framework to add view to DOM in already hidden state?
//I tried to set isVisible: false, but it not works
classNames: ['initialHidden'],
didInsertElement: function () {
//If using this.$().hide().show('slow') without 'initialHidden' class then view is still visible when alert shown
//alert("View visible");
this.$().hide().fadeIn(500).removeClass("initialHidden");
},
remove: function (promise) {
var originalView = this,
originalViewElement = originalView.$(),
clonedView = originalView.$().clone(),
originalRemove = this._super;
$(clonedView).attr("id", $(clonedView).attr("id") + "_clone").removeClass('ember-view');
$("*", clonedView).removeClass('ember-view').remove("script[id^=metamorph]");
originalView.$().replaceWith(clonedView);
//This line exists, only because of Metamorph error:
//Cannot perform operations on a Metamorph that is not in the DOM.
$(originalViewElement).css("display", "none").appendTo("body");
clonedView.fadeOut(1000, function () {
$(this).remove();
originalRemove.apply(originalView);
promise.resolve();
});
}
});
App.GuestView = App.FadingView.extend();
App.UserView = App.FadingView.extend();
App.User = Em.Object.extend({
username: "",
isAuthorized: true
});
App.Guest = Em.Object.extend({
isAuthorized: false,
password: "",
usernameHint: "Username...",
passwordHint: "Password..."
});
</script>