Is there a way to have multiple functions that are all called on a route's didTransition event?
Here is an example where the actions.didTransition is run normally, but "someHook" is not: http://emberjs.jsbin.com/hedebigedi/1/edit?html,js,console,output
Is running arbitrary functions even supposed to be possible by using ".on()"?
Have I misunderstood what an event means in this case?
The reason I wanted to do this is because I wanted to make a mixin that would get added to certain routes that would then do some general setup after didTransition, but the routes would also need to do some custom setup as well. I can copy-paste the same bit of code into each route's actions.didTransition, but I'd rahter have it only in one place.
It doesn't work. What would work is on('init'), but that is a method not an event, see: http://emberjs.com/api/classes/Ember.Route.html
To solve your problem do something like this:
import CleverMixin from 'path/to/mixin';
import AnotherMixin from 'path/to/mixin';
App.IndexRoute = Ember.Route.extend(CleverMixin, AnotherMixin, {
model: function() {
return ['red', 'yellow', 'blue'];
},
actions: {
didTransition: function () {
// This function will be provided by a mixin
this.setupStuff();
// This function will be provided by another mixin
this.doMoreStuff();
}
}
});
Related
I am doing a project using Nodejs for the backend, vanilla JS compiled with Parcel Bundler for the client side JS and PUG template engine to generate the views.
Note that I also use the FullCalendar v5 plugin. I don't think this is relevant as I feel this situation could happen without it, but still, it is the reason I encounter this problem at the moment.
Let's get straight to the point : I have a "main" parent function called initCalendar().
It initializes the calendar, creates the FullCalendar instance (along with all the calendars methods), sets it up depending on the configs given and renders it on the view. This function is the top level one of events.js, the "main" function as I like to call it.
initCalendar() is exported from events.js using the export keyword : export const initCalendar = () => { … }.
After this, I coded the calendar proprietary functions, which allow me to perform whatever action I want based on the ones done on the calendar. Like eventClick() for example, which executes whenever an event from the calendar is clicked, as its name suggests.
The point is, I created some functions in this eventClick() function (which itself is in initCalendar()), some of which I need to use in index.js. Therefore the need to export them. Also, I can't move these functions outside of initCalendar() scope, as I will loose important variables needed for my functions to run properly, and I would like to avoid using global variables.
My custom functions are nested like so : initCalendar() -> eventClick() -> myFunction() ("main" exported parent function -> intermediate calendar function -> my functions (to be exported)).
In case you're wondering why I have to do it this way, it is to keep the same workflow I have been using so far for all the client side JS of the project, trying to do it "the Parcel way". I have lots of exported functions that are imported in index.js from many different files, but this problem only got here when I included FullCalendar to the mix.
So the solution I found for now is to export my functions directly from eventClick(), using the exports keyword this time : exports.myFunction = myFunction. Doing this, I can then import them in index.js and continue to use the same workflow I used for all the client side JS (remember, compiled with Parcel Bundler).
What do you think about this "technique" ? Isn't it bad practice to export a child function from an already exported parent function ?
It seems quite hacky to me and I don't really like that… But I didn't find any better solution yet. Maybe someone could give me some insight on wether or not it is OK to do so and if not, how to solve the problem another way ? I thought maybe using callback functions, but I can not get it to work this way.
------- EDIT : Some code -------
Here is some code. I tried to cut it to the minimum, because the code of the clickEvent() function is literally hundred of lines long, and the one for FullCalendar is even bigger.
events.js : As you can see, the eventClick() function first opens a Modal which contains all the event info (that I didn't write because not relevant) and one button to delete the clicked event.
This is this button that should have his listener set from index.js calling the "exported child function" removeEvent() on a click event, to delete the associated event from DB and calendar.
There is other functions in the same style in there but this one should be enough to see what I'm talking about.
// events.js
// … All the es6 imports : { Calendar } - { Modal } - axios; etc …
// As you can see, if I try to export the removeEvent() function from here,
// it would work as exporting goes but I won't have the Modal instance of
// `eventInfoModal` used in `.then()`. Same thing with `const calendar`,
// because they would not be in the scope of `initCalendar()`.
// Therefore I won't be able to call the functions I do on them in `.then()`
export const initCalendar = () => {
const calendarEl = document.getElementById('calendar');
const calendar = new Calendar(calendarEl, {
// … Ton of code to config FullCalendar, import the events, other calendar functions etc…
eventClick: function(eventInfo) {
const eventId = eventInfo.event.id;
const infoModalContainer = document.getElementById('event-info-modal');
// Modal created from an imported class
const eventInfoModal = new Modal(
infoModalContainer,
this.el
);
eventInfoModal.init();
// … Lots of code to populate the modal with the event data, buttons etc …
// So the point here is to call this function from index.js (code of index.js below)
function removeEvent() {
if (confirm('Êtes-vous sûr de vouloir supprimer ce RDV ?')) {
deleteEvent(eventId)
.then(() => {
eventInfoModal.close();
calendar.refetchEvents();
});
}
}
// The "exported child function" I was talking about
exports.removeEvent = removeEvent;
// Then other functions defined and exported the same way, to be used just like removeEvent() in index.js
});
calendar.render();
// Function called from removeEvent() in eventClick() above, just doing an axios DELETE request, no need to write it
async function deleteEvent(eventId) {
}
};
index.js : Here I import all the exported functions from the other files (only showing the one we are talking about obviously) and try to group and set my listeners together by view or "by category", listeners that will then call the corresponding functions imported from the other files, to execute the needed actions.
// index.js
// … All the es6 imports, including :
import { removeEvent } from './events';
const userEventsPage = document.getElementById('user-events');
if (userEventsPage) {
const deleteEventBtn = document.getElementById('delete-event');
userEventsPage.addEventListener('click', evt => {
if (evt.target === deleteEventBtn) {
removeEvent();
}
});
}
Thank you very much
Posting my comment as an answer, as I believe that's the right way to solve this.
You should add a click handler to the delete-event button when you create the modal.
Also, from the look of the code shared, your Modal should have like an onRemoveButtonClicked property, that should be assigned to the removeEvent function that you're writing right now. I can't see why you need to export it.
Having a route like 'dogs': 'process', I need to rewrite it to 'animals': 'process'.
Now, I need the router to recognize both routes, but always display the url like /animals, it is sort of aliasing, but could not find any info on how to solve this without placing an url redirect in 'process' handler.
I'm assuming that the real need for aliases is different than dogs to animals, so I'll answer regardless of if the use-case here is good or not. But if you don't want to change the hash but want to trigger different behaviors in the app, using the router is probably not the route to go.
Route aliases don't really exist in Backbone, other than defining different routes using the same callback. Depending on your exact use-case, there are multiple ways to handle similar routes.
Replace the hash
To display the same hash for a generic route coming from different routes, use the replace option of the navigate function.
routes: {
'lions': 'animalsRoute',
'animals': 'animalsRoute'
},
animalsRoute: function() {
this.navigate("#/animals", { replace: true });
// or using the global history object:
// Backbone.history.navigate("#/animals", { replace: true });
}
then handle the animals route, regardless of which route was initially used to get in this callback.
Some other answers or tutorials will say to use window.location.hash but don't. Manually resetting the hash will trigger the route regardless and may cause more trouble than it'll help.
Different behaviors but showing the same route
Just use different callbacks, both using the replace trick above.
routes: {
'lions': 'lionsRoute',
'tigers': 'tigersRoute'
},
showGenericRoute: function() {
this.navigate("#/animals", { replace: true });
},
tigersRoute: function() {
this.showGenericRoute();
// handle the tigers route
},
lionsRoute: function() {
this.showGenericRoute();
// handle the lions route
}
Notice the inexistent animalsRoute. You could add the route if there's a generic behavior if no specific animal is chosen.
Use the route params
If you want to know which animal was chosen but still use the same callback and remove the chosen animal from the hash, use the route params.
routes: {
'animals/:animal': 'animalsRoute',
},
animalsRoute: function(animal) {
// removes the animal from the url.
this.navigate("#/animals", { replace: true });
// use the chosen animal
var view = new AnimalView({ type: animal });
}
Redirect to the generic route
If you want a different behavior but always show the same route, use different callbacks, then redirect. This is useful if the generic route is in another router instance.
var Router = Backbone.Router.extend({
routes: {
'animals': 'animalsRoute'
},
animalsRoute: function() {
// handle the generic behavior.
}
});
var PussyRouter = Backbone.Router.extend({
routes: {
'lions': 'lionsRoute'
// ...
},
lionsRoute: function() {
// handle lions, then redirect
this.navigate("#/animals", { trigger: true, replace: true });
}
});
Using the trigger options will call the animalsRoute in the other router and the replace option will avoid making an entry in the history, so pushing the back button won't go to lions to get back to animals and being caught in the animals route.
OK. I am speechless. I encountered this weird behavior like two days ago and I can't really tell what is going on.
In my code I have:
character: Characters.find({
'user._id': Meteor.userId(),
'gameId': this.props.gameId
}).fetch(),
It is inside the getMeteorData function (I use Meteor with React), mixin [ReactMeteorData] is also present.
Now in the componentWillMount() function I have this piece of code. What I want to do is to check if there is a character inside created by this user and in this game.
componentDidMount: function() {
console.log(this.data.character);
}
It returns [Class] with the character I was looking for. Great! So now I add this piece of code and it looks like this:
componentDidMount: function() {
console.log(this.data.character);
if (this.data.character.length > 0) {
console.log('yay!');
} else {
console.log('nay...');
}
}
So that's a normal, unsuspicious if(). Guess what I get from that first console.log(): []. WHY? Why is it that this if is changing what I get from my DB?!
The problem was that subscriptions were not ready when I tried to use them. What I did is rewrite the way subscriptions are made. I moved it from the router (so no subscriptions there) to the component itself. Like this:
data = {}
subs = {}
subs.something = Meteor.subscribe("something", argument);
if (subs.something.ready()) {
data.something = Somethings.find({}).fetch();
// the same query I use in publish method but with .fetch()
// because otherwise Meteor throws a warning
}
This is the code that goes to the getMeteorData function. And then inside the render() I can use those subscriptions like this:
render: function() {
return(
<p>{this.data.something ? this.data.something.property : 'Loading...'}</p>
);
}
And then it works perfectly fine. I had rewritten all of my components to use this way of doing things and now I have NO problems with subscriptions whatsoever. It also feels more "componentish" and "reactish" as everything including subscriptions is included in the parent component and the relevant data is being passed to children via props. No need to look for code and subscription methods in the router.
// Main class
function App() {
this.task = new Task(this); // pass the instance of this class to Task so
// it has access to doSomething
}
App.prototype.doSomething = function () {
alert("I do something that Task() needs to be able to do!");
};
function Task(app) {
// This class needs access to App()'s doSomething method
this.appInstance = app;
this.appInstance.doSomething(); // Great, now Task can call the method
}
var app = new App();
The aim of the code above is to give Task access to one of App's methods called doSomething. The code is the current way I'd go about it and I'm posting this to see if it's the best way...
To give Task access I simply pass the whole instance of App, is this efficient or is there a better way to go about it? Is the code above general practice in going about doing something like this?
Yes, what you have is fine. It is a circular dependency, however because of JavaScript's dynamic nature there aren't really any issues.
Another way you could reference App from Task would be a Singleton pattern or something similar, but that would probably be harder to test.
jsFiddle Demo
Generally bind would be used in this scenario assuming that the Task "class" didn't also setup other facilities which were not shown here.
Bind allows for the context to be provided for a function. This could be done in app's constructor. At which point only a function task would be required to call "someMethod".
function task(){
return this["someMethod"]();
}
function App(){
task.bind(this)();
}
App.prototype.someMethod = function(){
alert("Task needed access to this");
};
var a = new App();
However, if task must be a "class", and have other responsibilities then the prototype function could be shared.
function Task(){}
function App(){}
App.prototype.someMethod = Task.prototype.someMethod = function(){
alert("Task needed access to this");
};
var a = new App();
a.task();//->"Task needed access to this"
var t = new Task();
t.someMethod();//->"Task needed access to this"
Your app instances and task instances are tightly bound. App instances have tasks and this can be fine.
A design of loosely coupled objects is more flexible and easier to extend but more complicated to initially create. One such pattern is using a mediator/publish subscriber and have app raise an event/publish message any other object function can listen to this and take action on the event.
For example: your app creates an Ajax instance and when that instance is done it raises some event (fetchedData for example). A listener could be DomDependent.updateView function but later you may want to add/remove/change the order of tasks to do after data is fetched. This can all be configured in a app.init function or per procedure in a controller that kicks of certain procedures (like log in, search, ...).
Instead of creating a whole bunch of specific functions in Ajax (fetchUserPrefs, login, search, ...) you can create one general function and have the controller add listeners or pass the next event when fetchData is complete to run the correct next function.
Here is some pseudo code:
var app = {
init:function(){
mediator.add("updateLogin",domDependent.updateView);
mediator.add("updateLogin",app.loadUserPrefs);
mediator.add("failLogin",domDependent.updateView);
},
login: function(){
mediator.trigger("loadingSometing",{type:"login"});
ajax.fetch({
onComplete:"updateLogin",//what listens to updateLogin you decided in init
onFail:"failLogin",
loginDetails:domDependent.getLogin(),
url:settings.loginUrl,
type:"post"
});
}
}
var ajax = {
fetch:function(data){
data = data || {};
//simple check for onComplete, it's mandatory
var complete = data.onComplete || app.raiseError("ajax.fetch needs onComplete");
//other code to validate data and making ajax request
onSuccess:function(resp){
//mutate data object as the mediator will pass it to
// whatever other function is called next
// you don't hard code domDependent.updateView and
// app.loadUserPrefs because fetch can be used generally and
// success may have to do completely different things after its done
// and you want to define procedures in init, not all over your code
data.response=resp;
//trigger event to do whatever needs to be done next
mediator.trigger(complete,data);
}
}
}
As you can see it gets complicated and maybe doesn't look like code you're used to but it's highly configurable.
I may have misunderstood the advantages of the mediator pattern to loose couple and if so please comment. I use it to:
Make methods more general instead of copying a lot of logic only
because what to do after it's done is different. In fetch the ajax
object just fetches, this would be the same for login or getting
user preferences, the only thing different is what function to call
next/on error when it's done.
A procedure like login involves multiple functions in multiple
objects if this function chain hard code what to do next once a
particular function is done your procedure of login is defined all
over your code. When defining it in init/config you can easily change the
order or add/remove functions in the chain.
I would like to include multiple mixins within a view in Ember.js and more than one of the mixins and/or the view uses a same event (e.g. willInsertElement). I'm running Ember 1.4.0-beta.5.
I understand that the event in each mixin will be overridden by the view. However, I have read that it is possible to use the same event hook in the mixin and view, or multiple mixins included in the same view, by calling this._super(); at the start of the mixin's aforementioned event method. However, I have not been able to successfully make this happen. My question is, thus, how can I write logic within the same event hook in a view and mixin (or multiple mixins included in the same view) so that all the logic within each occurrence of the event hook will be called.
Here is an example:
App.StatsView = Em.View.extend(
App.DateFormatting, {
willInsertElement: function() {
// Some view-specific logic I want to call here
},
});
App.DateFormatting = Em.Mixin.create({
willInsertElement: function() {
this._super(); // This doesn't work.
// Some mixin logic I want to call here
},
});
N.B. One approach here might be to not use a mixin and extend a view instead (because willInsertElement is specific to Em.View), but that isn't maintainable in our apps.
If the different functions you're using are not dependent on each other, it's the best solution to not override the willInsertElement hook, but to tell the function to be raised when the event/hook gets called.
Like:
App.StatsView = Em.View.extend(App.DateFormatting, {
someSpecificFunction: function () {
console.log('beer me');
}.on('willInsertElement')
});
App.DateFormatting = Em.Mixin.create({
dateFormattingFunction: function () {
console.log('beer you');
}.on('willInsertElement')
});