passing a callback function to vue.js directive - javascript

so i have a directive in vue.js which is pretty handy for having a single point that handles all request through out the app. you can view it at this gist
https://gist.github.com/jkirkby91-2/261fee5667efcf81648ab2a1a1c33c1b
but every form that uses this to process the request handles the response data completely different.
so is it possible i can pass a call back function to the ajax directive to handle the response data.
so for example i have a form that creates a new posts id like to pass a function that handles that response, i also have a search form that with the response data id like to handle to add markers to my map.

Can you provide an example of how you are using the directive?
I see that you have a parameter called "complete" in your gist. Do you intend to use it like this?
<your-ajax-component v-bind:complete="some_callback_fn()"></your-ajax-component>
This is not the intended use for params. [params] is only for passing data to a child component.
You should use Custom Events to pass data from your child component to parent. The button counter (with two buttons and a main counter) is a great example.
Similarly, you can use $emit() from your ajax component as follows:
// your-ajax-component
export default {
methods: {
doSomething: function (e) {
this.$http.post("/api/some-url", {data}).then(response => {
// your http action is done
// now use $emit to communicate back to parent component
this.$emit("ajax-complete", response) // and you can pass the response data back
}, error => {
// your http action failed
this.$emit("ajax-failed", error) // parent component can handle this error from server
})
}
}
}
Now from the template of your other components / routes, you can insert your-ajax-component and listen to events as follows:
<your-ajax-component v-on:ajax-complete="some_callback" v-on:ajax-failed="error_callback"></your-ajax-component>
Note: directives serve a very different purpose. It is for getting access to DOM element, so that you can do something like focusing the element - put the cursor into a text box.
The documentation for Custom Directives provides examples that are related only to DOM manipulation, and not parent-child communications.

Related

Reading OData contexts in onInit of controller

I've tried to prepare data from an OData source to show it in a bar graph in my fiori app. For this, I setup the OData model in the manifest.json. A test with a list, simply using
items="{path : 'modelname>/dataset'}
works fine and shows the content.
To prepare data for a diagram (VizFrame), I used the onInit() function in the controller of the view (mvc:XMLView). The data preparation is similar to the one discussed in question.
At first I obtain the ODataModel:
var oODataModel = this.getOwnerComponent().getModel("modelname");
Next I do the binding:
var oBindings = oODataModel.bindList("/dataset");
Unfortunately, the oBindings().getContexts() array is always empty, and also oBindings.getLength() is zero. As a consequence, the VizFrame shows only "No Data".
May it be that the data model is not fully loaded during the onInit() function, or do I misunderstand the way to access data?
Thanks in advance
Update
I temporary solved the problem by using the automatically created bind from the view displaying the data as list. I grep the "dataReceived" event from the binding getView().byId("myList").getBindings("items") and do my calculation there. The model for the diagram (since it is used in a different view) is created in the Component.js, and registered in the Core sap.ui.getCore().setModel("graphModel").
I think this solution is dirty, because the graph data depends on the list data from a different view, which causes problems, e.g. when you use a growing list (because the data in the binding gets updated and a different range is selected from the odata model).
Any suggestions, how I can get the odata model entries without depending on a different list?
The following image outlines the lifecycle of your UI5 application.
Important are the steps which are highlighted with a red circle. Basically, in your onInit you don't have full access to your model via this.getView().getModel().
That's probably why you tried using this.getOwnerComponent().getModel(). This gives you access to the model, but it's not bound to the view yet so you don't get any contexts.
Similarly metadataLoaded() returns a Promise that is fullfilled a little too early: Right after the metadata has been loaded, which might be before any view binding has been done.
What I usually do is
use onBeforeRendering
This is the lifecycle hook that gets called right after onInit. The view and its models exist, but they are not yet shown to the user. Good possibility to do stuff with your model.
use onRouteMatched
This is not really a lifecycle hook but an event handler which can be bound to the router object of your app. Since you define the event handler in your onInit it will be called later (but not too late) and you can then do your desired stuff. This obviously works only if you've set up routing.
You'll have to wait until the models metadata has been loaded. Try this:
onInit: function() {
var oBindings;
var oODataModel = this.getComponent().getModel("modelname");
oODataModel.metadataLoaded().then(function() {
oBindings = oODataModel.bindList("/dataset");
}.bind(this));
},
May it be that the data model is not fully loaded during the onInit()
function, or do I misunderstand the way to access data?
You could test if your model is fully loaded by console log it before you do the list binding
console.log(oODataModel);
var oBindings = oODataModel.bindList("/dataset");
If your model contains no data, then that's the problem.
My basic misunderstanding was to force the use of the bindings. This seems to work only with UI elements, which organize the data handling. I switched to
oODataModel.read("/dataset", {success: function(oEvent) {
// do all my calculations on the oEvent.results array
// write result into graphModel
}
});
This whole calculation is in a function attached to the requestSent event of the graphModel, which is set as model for the VizFrame in the onBeforeRendering part of the view/controller.

What's the best way of achieving common tasks from within a Vuex store

I'm currently trying to achieve a common task when making API calls from within a Vuex store action object, my action currently looks like this:
/**
* check an account activation token
*
*/
[CHECK_ACTIVATION_TOKEN] ({commit}, payload) {
Api.checkActivationToken(payload.token).then((response) => {
if (response.fails()) {
return commit('NEW_MESSAGE', {message: responses.activation[response.code]})
}
return commit('SET_TOKEN')
})
}
I have several such methods carrying out various actions. What I want to be able to do is present a loader when each API call is made, and hide it again once the response is received. I can achieve this like so:
/**
* check an account activation token
*
*/
[CHECK_ACTIVATION_TOKEN] ({commit}, payload) {
commit('SHOW_LOADER')
Api.checkActivationToken(payload.token).then((response) => {
commit('HIDE_LOADER')
if (response.fails()) {
return commit('NEW_MESSAGE', {message: responses.activation[response.code]})
}
return commit('SET_TOKEN')
})
}
But I would need to repeat these SHOW_LOADER/HIDE_LOADER commits in each API call.
What I would like to do is centralise this functionality somewhere so that whenever API calls are made the showing and hiding of the loader is implicitly bound to the calls and not have to include these additional lines each time.
For clarity; the instantiated API is a client layer that sits on top of Axios so that I can prepare the call before firing it off. I've found I can't directly import the store into the client layer or where the Axios events are fired (so that I could centralise the loader visibility there) because Im instantiating the client layer within the vuex module and therefore creates a circular reference when I tried to do so, meaning the store is returned as undefined.
Is what I am trying to do possible through some hook or event that I have yet to come across?
I actually took a different path with this "issue" after reading this GitHub thread and response from Evan You where he talks about decoupling.
Ultimately I decided that by forcing the API layer to have direct knowledge of the store I am tightly coupling the two things together. Therefore I now handle the SHOW and HIDE feature I was looking for in each of the components where the store commits are made, like so:
/**
* check the validity of the reset token
*
*/
checkToken () {
if (!this.token) {
return this.$store.commit('NEW_MESSAGE', {message: 'No activation token found. Unable to continue'})
}
this.showLoader()
this.$store.dispatch('CHECK_ACTIVATION_TOKEN', {token: this.token}).then(this.hideLoader)
},
Here I have defined methods that shortcut the Vuex commits in a Master vue component that each of my components will extend. I then call showLoader when needed and use the promise to determine when the process is complete and call hideLoader there.
This means I have removed presentation logic from both the store and the API layer and kept them where they, arguably, logically belong.
If anyone has any better thoughts on this I'm all ears.
#wostex - thanks for your response!

Synchronous component registration

I have a knockout component which looks something like this:
define(['knockout', 'text!./my-component.html', 'pubsub'], function(ko, htmlString, pubsub) {
function viewModel(params) { }
return {
viewModel: {
createViewModel: function(params, componentInfo) {
var vm = new viewModel(params);
pubsub('updateViewModel').subscribe(function(){
// update vm
});
return vm;
}
},
template: htmlString
};
});
I use the createViewModel function to subscribe to an update event, which I use later on to trigger an update of the components viewmodel from other components.
I include the compnent on my page like this:
<!-- ko component: "my-component" -->
<!-- /ko -->
What I would like some verification on is the load order of things. I want to be sure that the createViewModel has been invoked before I might trigger the event.
This is my current order of calls:
// register my-component here
ko.applyBindings(myMainViewModel);
// code that might trigger the component update event here
I've read that ko.applyBindings is sychronous. But does that also include an implicit applybindings to all registered components, like my-component above? Do I need to set the synchronous property to true on the component in order to achieve this? If I understand it correctly, that flag is only related to rendering.
What I want to avoid is a race condition where I trigger the update event before it has been subscribed.
ko.applyBindings can act synchronously if the following conditions are satisfied BEFORE calling:
All components are registered
ALL component viewmodels are loaded in memory (fetched from network..)
ALL component templates are loaded in memory
Its when the component viewmodel and templates are not in memory that applyBindings becomes async (event if you set the synchronous=true).
This synchronous flag comes in to play in applybindings from the component binding. Notice that component binding does a ko.components.get call and passes a call back which will render the component on the DOM.
knockout/src/components/loaderRegistry.js has the definition of ko.components.get. The synchronous flag says that if the component is already cached (in memory) DON'T relinquish control of the thread. Its only when you release control of the thread (setTimeout, DOM insert/wait, ..) that applyBindings will return.
The only thing I'm not too sure about is how RequireJS will interact in here. There is code in knockout which will try to resolve the component using require first.
In any case the following steps will bring you closer (NOT PERFECT. See notes bellow)
//Load component vm, template and register it with synchronous=true
ko.appplyBinding(....)
ko.components.get("my-component" , function() {
//trigger component update event
})
There are few problems with this, and there are solutions to all of them.
Need to wait for multiple components to finish loading
[to solve this you can create a promise array for each component and resolve each of them via ko.components.get. Finally you can $.when(mypromiseArray, myCallback) to synch up all the promises]
ko.component.get does NOT tell you when the component is finally rendered on the DOM.
This is a much more challenging problem. I will share the solution if you need this level of precision (you need to know with in 50ms of when the component is loaded, and rendered on the UI).

Reflux avoid hitting server every time, when data cached locally

I curious if there is any agreed upon pattern to check if data has been already loaded before hitting the server.
Say I have my action that looks like this:
Actions.loadRequest.preEmit = function () {
$.get('/store/', function (data) {
Actions.loadSuccess(data);
}.bind(this));
}
This is called from a component that is simply saying give me this data:
But I don't want to hit the server if that data is already in the store.
Should I store the logic of checking the store in the component:
render: function () {
var data = this.state.store.data;
if (!data) {
Actions.loadRequest();
}
Is there a better way to go about this?
In my project I use shouldEmit for this (see https://github.com/reflux/refluxjs#action-hooks). An example from my code:
var streamStore = Reflux.createStore({
[...]
});
actions.loadStream.shouldEmit = function(streamId) {
if(streamId in streamStore.data)
return false;
return true;
};
This lives in the same file as the store definition. I think this is conceptually the right approach because the store saves the data, so the store should be responsible for intercepting the request to load more data and saying not to, just as it's responsible for listening to the action saying more data is available and updating itself.
Unfortunately this won't work with your example because you bound the AJAX call to preEmit, which gets called before shouldEmit. I would suggest refactoring to make the API call in a normal listen call, like this:
Actions.loadRequest.listen(function () {
$.get('/store/', function (data) {
Actions.loadSuccess(data);
}.bind(this));
});
This saves preEmit for the rare case of needing to rewrite an action's arguments before emitting it. I do use this pattern in my code, for example when loading a second page of results, which relies on a next token that came with the first page and is thus in the store. But in the general simple case of "action triggered, so make a request", using listen makes more sense because then you can add preEmit and shouldEmit for more advanced behavior, like the caching you want.
Reflux also has a helper function, listenAndPromise, which further simplifies the common use case of "action fired, make AJAX call, then fire another action when it's done". Your example could become:
Actions.loadRequest.listenAndPromise(function () {
return $.get('/store/');
});
See this section of the docs for more info on how to set that up: https://github.com/reflux/refluxjs#asynchronous-actions

How do I let my controller know of the status of loading data?

I'm working on a large AngularJS app in which I am trying to encapsulate all my Ajax code into various services which the controllers get data from. The problem revolves around needing to know the status of any ajax calls and displaying the correct information to the user. There could be no data found, data currently loading, or an error that has occurred preventing data from being loaded. The user needs to be shown a loading message, a "no data found" message, or an error message.
Let's say I have a ProjectService. Ideally if there was a method called getAllProjects it would return an array of projects. But that way I have no idea what is happening with the server communication.
So how to I let the controller know if data is loaded, loading, or an error has occurred? The best way I can come up with is using callbacks like in the pseudo code below. Is there any better way to accomplish such a thing or anything I may be overlooking?
Thanks.
app.controller( "ProjectController", function( $scope, ProjectService ){
// Set the initial / default status
$scope.loadStatus = "loading";
// Return an empty array initially that will be filled with
// any data that is returned from the server
// The callback function will be executed when the ajax call is finished
$scope.projects = ProjectService.getProjects(function( status ){
// Alert the controller of a status change
setStatus( status );
});
function setStatus( ){
$scope.loadStatus = status;
// ... update the view or whatever is needed when the status changes....
}
});
app.service( "ProjectService", function( $resource ){
return {
getAllProjects: function(){
// ... load and return the data from the server ...
}
};
});
In our codebase we've just been doing
$scope.flags.loading = true;
$http(...).success(function(){
$scope.flags.loading = false;
});
Yes, this is sort of simplistic, but not all queries require a loading overlay (such as during pagination or refreshing). This is why we have opted not to simply use a decorator.
However, lets say you want to, I can think of a few ways of doing this. Lets say you're like us and keep your flags together in an object. Then you can use associations to your advantage:
MyService.flags = $scope.flags
... (inside the service) ...
this.flags.loading = true/false;
By establishing a reference as a property of the service, you can do all the state toggling from within the service, and avoid cluttering your controller. Again though, this might create the possible drawback of having 2 or more close-together queries conflicting (first query finishes and removes the loading state before the second one completes).
For this reason we have been find with setting the flag. We don't really check for 'loaded' we just check for data or use success callbacks.

Categories