Hashbang URLs using Ember.js - ember.js

I am trying to set up my Router to use "hashbang" URLs (#!).
I tried this, but obviously it doesn't work:
App.Router.map(function() {
this.route("index", { path: "!/" });
this.route("otherState", { path: "!/otherState" });
});
Is this possible to do in Ember?

Teddy Zeenny's answer is mostly correct, and registerImplementation seems to be a clean way to implement this. I tried to just edit his answer to make it fully answer the question, but my edit got rejected.
Anyway here is the full code to make Ember use hashbang URLs:
(function() {
var get = Ember.get, set = Ember.set;
Ember.Location.registerImplementation('hashbang', Ember.HashLocation.extend({
getURL: function() {
return get(this, 'location').hash.substr(2);
},
setURL: function(path) {
get(this, 'location').hash = "!"+path;
set(this, 'lastSetURL', "!"+path);
},
onUpdateURL: function(callback) {
var self = this;
var guid = Ember.guidFor(this);
Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
Ember.run(function() {
var path = location.hash.substr(2);
if (get(self, 'lastSetURL') === path) { return; }
set(self, 'lastSetURL', null);
callback(location.hash.substr(2));
});
});
},
formatURL: function(url) {
return '#!'+url;
}
}));
})();
Then once you create your app you need to change the router to utilize the "hashbang" location implementation:
App.Router.reopen({
location: 'hashbang'
})

Extending Ember.HashLocation would be the way to go.
For a clean implementation, you can do the following.
Ember.Location.registerImplementation('hashbang', Ember.HashLocation.extend({
// overwrite what you need, for example:
formatURL: function(url) {
return '#!' + url;
}
// you'll also need to overwrite setURL, getURL, onUpdateURL...
})
Then instruct your App Router to use your custom implementation for location management:
App.Router.reopen({
location: 'hashbang'
})

Related

How to render a view in EmberJS 1.13.8 without refreshing the page?

Sorry if this question is too naive,but I am getting confused a lot on rendering views in Ember.
I have a 'Person' route. I am able to do CRUD operations on it.
router.js
this.route('person', function() {
this.route('index', { path: '' });
});
controllers/person/index.js
actions: {
createPerson: function() {
var person = this.get('store').createRecord('person');
this.set('person', person);
this.set('editPersonPane', true);
},
editPerson: function(person) {
this.set('person', person);
this.set('editPersonPane', true);
},
closeEditPerson: function() {
this.get('person').rollback();
this.set('editPersonPane', false);
},
savePerson: function(person) {
var _this = this;
person.save().then(function() {
_this.set('editPersonPane', false);
Ember.get(_this, 'flashMessages').success('person.flash.personUpdateSuccessful');
}, function() {
Ember.get(_this, 'flashMessages').danger('apiFailure');
});
},
deletePerson: function(person) {
var _this = this;
person.destroyRecord().then(function() {
_this.set('editPersonPane', false);
Ember.get(_this, 'flashMessages').success('person.flash.personDeleteSuccessful');
}, function() {
Ember.get(_this, 'flashMessages').danger('apiFailure');
});
}
}
What I want to do now is when I want to create a new person, a form slides in to create it. After filling up the form, I want the list view of persons to be updated immediately, without refreshing the page. Right now, I have been able to add the form and when I add a new person, I get a successful flash message but it's not updated in the view immediately. I have to refresh the page.
It might have to do something with observers but I am still not sure how.
Reloading a saved object will allow you to avoid having to refresh the page:
savePerson: function(person) {
var _this = this;
person.save().then(function(saved) {
saved.reload();
_this.set('editPersonPane', false);
Ember.get(_this, 'flashMessages').success('person.flash.personUpdateSuccessful');
}, function() {
Ember.get(_this, 'flashMessages').danger('apiFailure');
});
}
Also, it's worth noting that if you destructure and use ES6 syntax, you can clean up your code a bit as follows:
//controllers/person/index.js
//at the top of the file
import Ember from 'ember';
const { get, set } = Ember;
//other code
actions: {
//other actions
savePerson(person): {
person.save().then((saved) => {
saved.reload();
set(this, 'editPersonPane', false);
get(this, 'flashMessages').success('person.flash.personUpdateSuccessful');
}, () {
get(this, 'flashMessages').danger('apiFailure');
});
}
}
Which route is displaying your persons list?
Wouldn't something like this work better, so you can display the list and then edit a person within the persons.hbs outlet?
this.route('persons', function() {
this.route('person', { path: 'id' });
});

How would one extend multiple mixins when creating a new mixin in Ember.js

I have previously discovered it is possible to extend mixins when creating a new mixin like such:
App.SomeNewMixin = Ember.Mixin.create(App.SomeOldMixin, {
someMethod: function() { return true; }
});
Now I am attempting to use two existing mixins, but it seems Mixin.create only supports 2 parameters.
App.SomeNewMixin = Ember.Mixin.create(App.SomeOldMixinOne, App.SomeOldMixinTwo, {
someMethod: function() { // No access to methods defined in SomeOldMixinOne }
});
This seems like a serious limitation of Ember Mixins. The Ember docs have little to no coverage of Ember.Mixin, so I'm not really sure how to proceed. I've tried using Ember.Mixin.apply within the init function of SomeNewMixin, also to no avail.
App.SomeNewMixin = Ember.Mixin.create({
init: function() {
this._super();
this.apply(App.SomeOldMixinOne);
this.apply(App.SomeOldMixinTwo);
}
someMethod: function() { return true; }
});
Any insight on possible solutions would be greatly appreciated!
Creating a mixin which extends multiple other mixins should work fine.
For example look at this:
var App = Ember.Application.create();
App.SomeOldMixin = Ember.Mixin.create({
someOldMethod: function() { return 'old'; },
someOldMethod2: function() { return 'old2'; }
});
App.SomeNewMixin = Ember.Mixin.create({
someNewMethod: function() { return 'new'; }
});
App.SomeNewerMixin = Ember.Mixin.create({
someNewerMethod: function() { return 'newer'; }
});
App.SomeNewestMixin = Ember.Mixin.create(App.SomeOldMixin, App.SomeNewMixin, App.SomeNewerMixin, {
someOldMethod: function() {
return this._super() + ' ' + this.someOldMethod2();
},
someNewestMethod: function() { return 'newest'; }
});
App.ApplicationController = Ember.Controller.extend(App.SomeNewestMixin, {
test: function() {
console.log(this.someOldMethod());
console.log(this.someNewMethod());
console.log(this.someNewerMethod());
console.log(this.someNewestMethod());
}.on('init')
});

Ember update property on the changes in array

I have following in my controller, and facing issue while updating property with array change..
import Ember from 'ember';
export default Ember.Controller.extend({
imageIds: Object.keys(JSON.parse(localStorage.image_ids || "{}")),
// imageIds = ['gnffffffffjdf', 'hzfyfsidfulknm', 'euriekjhfkejh']
previewImageId: function() {
return this.imageIds.get('firstObject');
}.property('imageIds.[]'),
actions: {
addDetails: function() {
this.transitionToRoute('items.add_item');
},
removeImage: function(image_id) {
var uploaded = JSON.parse(localStorage.image_ids || "{}");
delete uploaded[image_id]
localStorage.image_ids = JSON.stringify(uploaded);
this.get("imageIds").removeObject(image_id);
// this.set("imageIds", Object.keys(JSON.parse(localStorage.image_ids || "{}")));
},
updatePreview: function(image_id){
this.set("previewImageId", image_id);
var uploaded = JSON.parse(localStorage.image_ids || "{}");
uploaded[image_id] = image_id;
localStorage.image_ids = JSON.stringify(uploaded);
// this.set("imageIds", Object.keys(JSON.parse(localStorage.image_ids)));
this.get("imageIds").pushObject(image_id);
}
},
init: function(){
var controller = this;
Ember.$('body').on('click', ".current_image", function() {
var public_id = Ember.$(this).attr('id');
controller.set("previewImageId", public_id);
});
}
});
Whenever there is any change in the imageIds array, previewImageId should be updated.
tried using pushObject, removeObject, .get and .set options.
But still no luck
Can anyone pls help me?
ANSWER:
import Ember from 'ember';
export default Ember.Controller.extend({
imageIds: function() {
return Object.keys(JSON.parse(localStorage.image_ids || "{}"));
}.property(),
previewImageId: function() {
return this.get("imageIds.firstObject");
}.property('imageIds.[]'),
actions: {
addDetails: function() {
this.transitionToRoute('items.add_item');
},
removeImage: function(image_id) {
var uploaded = JSON.parse(localStorage.image_ids || "{}");
delete uploaded[image_id]
localStorage.image_ids = JSON.stringify(uploaded);
this.get("imageIds").removeObject(image_id);
},
updatePreview: function(image_id){
var uploaded = JSON.parse(localStorage.image_ids || "{}");
uploaded[image_id] = image_id;
localStorage.image_ids = JSON.stringify(uploaded);
this.get("imageIds").unshiftObject(image_id);
}
},
init: function(){
var controller = this;
Ember.$('body').on('click', ".current_image", function() {
var public_id = Ember.$(this).attr('id');
controller.get("imageIds").removeObject(public_id);
controller.get("imageIds").unshiftObject(public_id);
});
}
});
Here previously I tried with setting value to previewImageId.. which was wrong way, as it overrides my computed property.
I could see that you are setting the previewImageId cp in a couple of places. You should make the computed property as a setter and getter aware.
Take a look here for an example
If the cp is implemented without a setter, then setting some value on the cp will overwrite its computed function.
Here is a working demo for your use case.
Basically I made the imageIds a property. Here is the code snippet:
App.IndexController = Ember.ArrayController.extend({
imageIds: function() {
return this.get("content");
}.property(),
previewImageId: function() {
return this.get("imageIds").get("firstObject");
}.property("imageIds.[]"),
actions: {
remove: function(item) {
this.get("imageIds").removeObject(item);
}
}
});
Hope this helps!

How to get a parent routes model from inside controller action?

Inside my route for "appointments" below I can reach up and get the model for the parent "day"
App.Router.map(function(match) {
this.resource("day", { path: "/day/:day" }, function() {
this.resource("appointments", { path: "/appointments" }, function() {
this.route("new", { path: "/:appointment_start/new" });
this.route("edit", { path: "/:appointment_id/edit" });
});
});
});
But when I'm deep inside the new or edit routes, how can I reach up (from within the actions handler) and grab the parent "day" model like I did in the route?
App.AppointmentsEditController = Ember.Controller.extend({
actions: {
updateAppointment: function(appointment) {
var day = this.get('??');
}
}
});
Update
The final controller code now looks like this
App.AppointmentsEditController = Ember.Controller.extend({
needs: 'day',
actions: {
updateAppointment: function(appointment) {
var day = this.get('controllers.day');
}
}
});
Toran - sorry to add this as an extra answer, I can't comment yet - yes, it should work for free. You can access controllers.post from within the actions block like this:
var postController = this.get('controllers.post')
There is simple way to do it. In AppointmentsEditController add
needs: "day"
Then you can access to day controller via this.get('controllers.day').
I always use something like this:
App.CommentsControler = Ember.Controller.extend({
needs: "post",
postBinding: "controllers.post",
...
postName: function() {
return this.post.name;
}.property("post.name")
})
Take a look of this article http://emberjs.com/guides/controllers/dependencies-between-controllers/
I hope this help :)

Empty controller blocks view. Why?

I have been experimenting with using Ember with a JSON server but without without ember-data. My test app renders a directory of images from a small JSON structure (generated from a little Go server).
Can anyone explain to me why, if I uncomment the App.FileController in the code below, the corresponding File view fails to render?
window.App = Ember.Application.create();
App.Router.map(function() {
this.resource('files',function(){
this.resource('file',{path:':file_id'});
});
});
App.FilesRoute = Ember.Route.extend({
model: function() {
return App.File.findAll();
}
});
App.FileRoute = Ember.Route.extend({
setupController: function(controller, args) {
controller.set('model', App.File.find(args.id));
},
model: function(args) {
return App.File.find(args.file_id);
}
});
App.File = Ember.Object.extend({
urlPath: function(){
return "/pics/" + this.get('id');
}.property('id'),
});
If I uncomment this, things break:
// App.FileController = Ember.Controller.extend({
// });
(namely, the File sub-view no longer renders at all.)
App.File.reopenClass({
find: function(id){
file = App.File.create({'id':id});
return file;
},
findAll: function() {
return $.getJSON("http://localhost:8080/api/").then(
function(response) {
var files = [];
response.Files.forEach(function (filename) {
files.push(App.File.create({'id':filename}));
});
return files;
}
);
},
});
Also, is there something fundamental that I'm doing wrong here?
As noted by Finn MacCool and agmcleod I was trying to use the wrong type of controller. The correct lines should be:
App.FileController = Ember.ObjectController.extend({
});
Not that I need to explicitly set a FileController in this small example. However, should I go on to expand the code I will no doubt need one and will need to use the correct one.