I have two buttons in the application template that fire different actions in my controller. I want to set up ladda.js to add a spinner to the buttons while the ajax function in the corresponding action is waiting to resolve. But to start ladda I have to do something like this...
$('#form-submit').click(function(e){
e.preventDefault();
var l = Ladda.create(this);
l.start();
});
So in my controller I have the typical action setup starting an ajax function. But what would be the best way to get the button that fired the action so I can start ladda like it shows above?
For reference here are the buttons in the template
// templates/application.hbs
<li class="navbar-right"><button {{action 'startBot'}} class="btn btn-success ladda-button" data-style="slide-down"><span class="ladda-label">Start Bot</span></button><button {{action 'stopBot'}} class="btn btn-danger ladda-button" data-style="slide-down"><span class="ladda-label">Stop Bot</span></button></li>
And these are the actions in the controller
// controllers/application.js
isConnected: false,
actions: {
startBot: function(){
var self = this;
console.log('starting bot');
Ember.$.ajax({
method: 'post',
dataType: 'json',
url: 'http://localhost:3000/api/v1/bot',
success: function(data) {
console.log(data);
self.toggleProperty('isConnected');
self.get('notify').success('Bot started');
},
error: function(err) {
console.log(err);
self.get('notify').error('There was a problem');
}
});
},
stopBot: function(){
var self = this;
console.log('stoping bot');
Ember.$.ajax({
method: 'delete',
dataType: 'json',
url: 'http://localhost:3000/api/v1/bot',
success: function(data) {
console.log(data);
self.toggleProperty('isConnected');
self.get('notify').success('Bot stopped');
},
error: function(err) {
console.log(err);
self.get('notify').error('There was a problem');
}
});
},
}
Related
I am trying to implement a knockoutjs form to my django site. Im new to knockout so followed an example but for some reason when I submit the page refreshes with no errors but nothing gets committed to the api.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>
<form data-bind="submit: mySubmit">
<input data-bind="value: firstname" />
<input data-bind="value: lastname" />
<button type="submit">Go</button>
</form>
<script type="text/javascript">
var viewModel = {
mySubmit : function(formElement) {
var formData = {
'firstname' : $('#firstname').val(),
'lastname' : $('#lastname').val()
};
$.ajax({
url: "127.0.0.1:8000/api/test",
type: "POST",
data: formData,
datatype: "json",
processData:false,
contentType: "application/json; charset=utf-8",
success: function (result){
alert(result);
}
});
}
};
ko.applyBindings(viewModel);
</script>
you haven't declared observable's in viewModel . so consider declaring to make things go smooth .
viewModel:
var viewModel = {
firstname:ko.observable(),lastname:ko.observable(),
mySubmit : function(formElement) {
var formData = {
'firstname' : viewModel.firstname() ,
'lastname' : viewModel.lastname()
};
$.ajax({
url: '/echo/json/', //mocking ajax request
type: "POST",
data: formData,
contentType: "application/json; charset=utf-8",
success: function (result){
alert("success");
}
});
}
};
ko.applyBindings(viewModel);
Working sample here
I am newbie with Ember and trying to implement a basic auth (username + password in the 'authentication' header) with a custom server using ember simple auth and ember-cli. The problem is that the credentials object is undefined when it is received within the 'authenticate' method defined in the CustomAuthenticator.
What's wrong with this code?
app/initializers/login.js
import Ember from 'ember';
import BaseAuthenticator from 'simple-auth/authenticators/base';
import BaseAuthorizer from 'simple-auth/authorizers/base';
window.ENV = window.ENV || {};
window.ENV['simple-auth'] = {
authorizer: 'authorizer:custom',
session: 'session:withCurrentUser'
};
export default {
name: 'authentication',
before: 'simple-auth',
initialize: function(container/*, application*/) {
container.register('authorizer:custom', CustomAuthorizer);
container.register('authenticator:custom', CustomAuthenticator);
}
};
var CustomAuthorizer = BaseAuthorizer.extend({
authorize: function(jqXHR/*, requestOptions*/) {
if (this.get('session.isAuthenticated') && !Ember.isEmpty(this.get('session.token'))) {
jqXHR.setRequestHeader('Authorization', 'Token: ' + this.get('session.token'));
}
}
});
var CustomAuthenticator = BaseAuthenticator.extend({
tokenEndpoint: '/v1/login',
restore: function(data) {
return new Ember.RSVP.Promise(function(resolve, reject) {
if (!Ember.isEmpty(data.token)) {
resolve(data);
} else {
reject();
}
});
},
authenticate: function(credentials) {
//*** HERE THE CREDENTIALS OBJECT IS NULL ***
var _this = this;
if(!Ember.isEmpty(credentials.identification)) {
return this._super(credentials);
} else {
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'POST',
data: JSON.stringify({ session: { identification: credentials.identification, password: credentials.password } }),
contentType: 'application/json'
}).then(function(response) {
Ember.run(function() {
resolve({ token: response.session.token });
});
}, function(xhr/*, status, error*/) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
}
},
invalidate: function() {
var _this = this;
return new Ember.RSVP.Promise(function(resolve) {
Ember.$.ajax({ url: _this.tokenEndpoint, type: 'DELETE' }).always(function() {
resolve();
});
});
},
});
app/pods/login/controller.js
import AuthenticationControllerMixin from 'simple-auth/mixins/authentication-controller-mixin';
import Ember from 'ember';
export default Ember.Controller.extend(AuthenticationControllerMixin, {
authenticator: 'authenticator:custom'
});
app/pods/login/template.hbs
<div class='container'>
<form {{action 'authenticate' on='submit'}}>
<label for="identification">Login</label>
{{input value=identification placeholder='Enter Login'}}
<label for="password">Password</label>
{{input value=password placeholder='Enter Password' type='password'}}
<button type="submit">Login</button>
</form>
{{#if errorMessage}}
<div class="alert alert-danger">
<p>
<strong>Login failed:</strong> <code>{{errorMessage}}</code>
</p>
</div>
{{/if}}
</div>
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
instead of
import AuthenticationControllerMixin from 'simple-auth/mixins/authentication-controller-mixin';
In the IndexRoute of my Ember hello world app, I start a setInterval function that I wish to allow the end user to turn off (with clearInterval) by clicking a dom element in the template, which triggers an action in the IndexController. So, the setIntervalId is set in the IndexRoute, and I need to pass it to clearInterval in the IndexController, but the way I have it below, the setIntervalId is undefined. I also tried to use App.IndexRoute.setIntervalId to no avail.
How would I accomplish this?
(function() {
window.App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_ACTIVE_GENERATION: true
});
App.IndexRoute = Ember.Route.extend({
setIntervalId: 0,
model: function() {
this.setIntervalId = setInterval(this.someInterval, 5000)
},
someInterval: function(){
var datasource = 'http://hackernews/blahblah';
return new Ember.$.ajax({url: datasource, dataType: "json", type: 'GET'}).then(function(data){
return data;
})
},
});
App.IndexController = Ember.ObjectController.extend({
actions: {
clearTimeout: function(){
console.log('clearing interval', this.setIntervalId); //undefined
clearInterval(this.setIntervalId);
}
}
})
})();
template
<script type="text/x-handlebars" data-template-name="index">>
<h1>Hi Babe</hi>
{{ outlet }}
<label {{action "clearTimeout" on="click"}}>clear timeout</label>
</script>
To set the model, you need to return the value in the route’s model function:
model: function() {
return this.setIntervalId = setInterval(this.someInterval, 5000)
}
To access the model in the controller, you need to use this.get('model').
actions: {
clearTimeout: function(){
console.log('clearing interval', this.get('model');
clearInterval(this.get('model'));
}
}
I'm having a problem doing integration testing with ember using Toran Billup's TDD guide.
I'm using Karma as my test runner with Qunit and Phantom JS.
I'm sure half of if has to do with my beginner's knowledge of the Ember runloop. My question is 2 parts:
1) How do I wrap a vist() test into the run loop properly?
2) How can I test for transitions? The index route ('/') should transition into a resource route called 'projects.index'.
module("Projects Integration Test:", {
setup: function() {
Ember.run(App, App.advanceReadiness);
},
teardown: function() {
App.reset();
}
});
test('Index Route Page', function(){
expect(1);
App.reset();
visit("/").then(function(){
ok(exists("*"), "Found HTML");
});
});
Thanks in advance for any pointers in the right direction.
I just pushed up an example application that does a simple transition when you hit the "/" route using ember.js RC5
https://github.com/toranb/ember-testing-example
The simple "hello world" example looks like this
1.) the template you get redirected to during the transition
<table>
{{#each person in controller}}
<tr>
<td class="name">{{person.fullName}}</td>
<td><input type="submit" class="delete" value="delete" {{action deletePerson person}} /></td>
</tr>
{{/each}}
</table>
2.) the ember.js application code
App = Ember.Application.create();
App.Router.map(function() {
this.resource("other", { path: "/" });
this.resource("people", { path: "/people" });
});
App.OtherRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('people');
}
});
App.PeopleRoute = Ember.Route.extend({
model: function() {
return App.Person.find();
}
});
App.Person = Ember.Object.extend({
firstName: '',
lastName: ''
});
App.Person.reopenClass({
people: [],
find: function() {
var self = this;
$.getJSON('/api/people', function(response) {
response.forEach(function(hash) {
var person = App.Person.create(hash);
Ember.run(self.people, self.people.pushObject, person);
});
}, this);
return this.people;
}
});
3.) the integration test looks like this
module('integration tests', {
setup: function() {
App.reset();
App.Person.people = [];
},
teardown: function() {
$.mockjaxClear();
}
});
test('ajax response with 2 people yields table with 2 rows', function() {
var json = [{firstName: "x", lastName: "y"}, {firstName: "h", lastName: "z"}];
stubEndpointForHttpRequest('/api/people', json);
visit("/").then(function() {
var rows = find("table tr").length;
equal(rows, 2, rows);
});
});
4.) the integration helper I use on most of my ember.js projects
document.write('<div id="foo"><div id="ember-testing"></div></div>');
Ember.testing = true;
App.rootElement = '#ember-testing';
App.setupForTesting();
App.injectTestHelpers();
function exists(selector) {
return !!find(selector).length;
}
function stubEndpointForHttpRequest(url, json) {
$.mockjax({
url: url,
dataType: 'json',
responseText: json
});
}
$.mockjaxSettings.logging = false;
$.mockjaxSettings.responseTime = 0;
I'm unfamiliar with Karma, but the portions of your test that needs to interact with ember should be pushed into the run loop (as you were mentioning)
Ember.run.next(function(){
//do somethin
transition stuff here etc
});
To check the current route you can steal information out of the ember out, here's some information I stole from stack overflow at some point.
var router = App.__container__.lookup("router:main"); //get the main router
var currentHandlerInfos = router.router.currentHandlerInfos; //get all handlers
var activeHandler = currentHandlerInfos[currentHandlerInfos.length - 1]; // get active handler
var activeRoute = activeHandler.handler; // active route
If you start doing controller testing, I wrote up some info on that http://discuss.emberjs.com/t/unit-testing-multiple-controllers-in-emberjs/1865
Given a view with a context like { id: 1, form_id: 5}, I want to create an {{action}} link to the form using the form_id.
My view code looks like:
<script type="text/x-handlebars" data-template-name="group">
{{action showForm form_id href=true}}
</script>
And the action in my router looks like:
showForm: function(router, event) {
var form_id = event.context;
router.transitionTo('root.form', { id: form_id });
},
I get an error that reads:
Uncaught Error: assertion failed: You must specify a target state for event 'showForm' in order to link to it in the current state 'root.index'.
I'm guessing that the problem is with the way I'm setting up the context for transitionTo, but I haven't been able to figure out the correct solution.
Here is the full code to reproduce the problem:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="group">
{{action showForm form_id href=true}}
</script>
MyApp = Ember.Application.create({
autoinit: false
});
MyApp.router = Ember.Router.create({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
// Throws error:
// You must specify a target state for event 'showForm' in
// order to link to it in the current state 'root.index'
//
showForm: function(router, event) {
var form_id = event.context;
router.transitionTo('root.form', { id: form_id });
},
// Won't work because form deserialize finds id, not form_id
//showForm: Em.Route.transitionTo('root.form'),
// This won't work either
// showForm: Em.Route.transitionTo('root.form', { id: this.form_id }),
connectOutlets: function( router, context ){
var group = Em.Object.create({ id:1, form_id: 5 });
router.get( 'applicationController' ).connectOutlet( 'group', group );
}
}),
form: Ember.Route.extend({
route: '/form/:id',
serialize: function( router, context ){
return { id: context.id }
},
deserialize: function( router, context ){
var form = Em.Object.create({ id: 5, name: 'my form' });
return MyApp.Form.find( context.id );
},
connectOutlets: function( router, context ){
// left out for fiddle example
}
})
})
});
MyApp.ApplicationController = Ember.Controller.extend({});
MyApp.GroupController = Em.ObjectController.extend({});
MyApp.GroupView = Em.View.extend({ templateName: 'group' });
MyApp.initialize(MyApp.router);
And the cooresponding fiddle:
http://jsfiddle.net/jefflab/LJGCz/
I was able to come up with an ugly solution to this problem using a computed property as the context of my action. The key snippets are:
<script type="text/x-handlebars" data-template-name="group">
<a {{action showForm view.formHash href=true}}>go to form</a>
</script>
MyApp.GroupView = Em.View.extend({
templateName: 'group',
formHash: function() {
return { id: this.get('controller').get('form_id') };
}.property('form_id')
});
And the working fiddle is here:
http://jsfiddle.net/jefflab/pGv8u/
However, after talking to Tom Dale, it is clear that the "right way" to solve to solve this problem is by using materialized objects instead of id values. If you are using Ember data, this is a great use case for the "sideloading" belongsTo feature.