I am trying to create an Angular service that runs before a controller is executed. I know I could use the 'resolve' properties of the controller but that makes me repeat that across all my controller.
So how can I create a service that uses some Ajax requests and have the controller wait for its execution?
Thanks
It's not possible to delay a controller however, you can ensure that a service is loaded before passing it to scope or refresh the scope when it changes. There are a few different ways to do this:
Create a variable in scope only if the service is loaded:
MyService.get({}, function(serv) {
self.original = serv;
$scope.serv = new MyService(self.original);
});
If you are using $http instead and wait for success to pass value to scope.
$http({method: 'GET', url: '/someUrl'}).
success(function(data, status, headers, config) {
// --- do your thing here ---
}).
error(function(data, status, headers, config) {
});
Use $watch to see if a variable was updated or not:
scope.$watch('myVariableToWatch', function(value) {
scope.myVariableToWatch = value;
})
Lastly you could also choose to hide any view elements until the service is loaded, so the HTML would be:
<div ng-show="serviceDidLoad"> some content here </div>
Put $scope.serviceDidLoad = true in the controller where you test to see the service has been loaded
Related
Is there a way with Kuzzle, to make two plugins communicate with each other?
Let's say a plugin A wants to call a method of a plugin B at boot time, or even runtime for some use cases. How can I do that ?
For now, there is no way to retrieve a particular plugin instance from another plugin. Plugins manager isn't reachable at plugin initialization, but in some way via a Kuzzle request (not the proper way of doing it)
function (request) {
const kSymbol = Object.getOwnPropertySymbols(request.context.user)[0];
request.context.user[kSymbol].pluginsManager.MyPlugin.someMethod(...args);
...
}
The idea behind this question would be to do something like this, when initializing the plugin
function init (customConfig, context) {
const { pluginsManager } = context;
const result = pluginsManager.MyPlugin.someMethod(...args);
// make something with the result ?
// For later use of plugins manager perhaps ?
this.context = context
}
Looks like Kuzzle Pipes would be the right thing to do it, cause they are synchronous and chainable, but pipes don't return anything when triggering an event
function init (customConfig, context) {
const result = context.accessors.trigger('someEvent', {
someAttribute: 'someValue'
});
console.log(result) // undefined
}
Thanks for your help !
full disclosure: I work for Kuzzle
Even if accessing the pluginManager like this may work at this time, we may change the internal implementation without warning since it's not documented so your plugin may not work in next version of Kuzzle.
The easiest way to use a feature from another plugin is exposing a new API method through a custom controller and then call it with the query method of the embedded SDK.
For example if your plugin name is notifier-plugin in the manifest:
this.controllers = {
email: {
send: request => { /* send email */ }
}
};
Then you can call it in another plugin like this:
await this.context.accessors.sdk.query({
controller: 'notifier-plugin/email',
action: 'send'
});
Please note that you can't make API calls in the plugin init method.
If you need to make API calls when Kuzzle start, then you can add a hook/pipe on the core:kuzzleStart event.
this.hooks = {
'core:kuzzleStart': () => this.context.accessors.sdk.query({
controller: 'notifier-plugin/email',
action: 'send'
});
};
Finally I noticed that you can't use pipes returns like in your example but I have proposed a feature to allow plugin developers to use the pipe chain return.
It will be available in the next version of Kuzzle.
I would like to retrieve the id of a newly created record using javascript when I click on save button and just before redirecting page.
Do you have any idea please ?
Thank you !
One way to do this in Sugar 7 would be by overriding the CreateView.
Here an example of a CustomCreateView that outputs the new id in an alert-message after a new Account was successfully created, but before Sugar gets to react to the created record.
custom/modules/Accounts/clients/base/views/create/create.js:
({
extendsFrom: 'CreateView',
// This initialize function override does nothing except log to console,
// so that you can see that your custom view has been loaded.
// You can remove this function entirely. Sugar will default to CreateView's initialize then.
initialize: function(options) {
this._super('initialize', [options]);
console.log('Custom create view initialized.');
},
// saveModel is the function used to save the new record, let's override it.
// Parameters 'success' and 'error' are functions/callbacks.
// (based on clients/base/views/create/create.js)
saveModel: function(success, error) {
// Let's inject our own code into the success callback.
var custom_success = function() {
// Execute our custom code and forward all callback arguments, in case you want to use them.
this.customCodeOnCreate(arguments)
// Execute the original callback (which will show the message and redirect etc.)
success(arguments);
};
// Make sure that the "this" variable will be set to _this_ view when our custom function is called via callback.
custom_success = _.bind(custom_success , this);
// Let's call the original saveModel with our custom callback.
this._super('saveModel', [custom_success, error]);
},
// our custom code
customCodeOnCreate: function() {
console.log('customCodeOnCreate() called with these arguments:', arguments);
// Retrieve the id of the model.
var new_id = this.model.get('id');
// do something with id
if (!_.isEmpty(new_id)) {
alert('new id: ' + new_id);
}
}
})
I tested this with the Accounts module of Sugar 7.7.2.1, but it should be possible to implement this for all other sidecar modules within Sugar.
However, this will not work for modules in backward-compatibility mode (those with #bwc in their URL).
Note: If the module in question already has its own Base<ModuleName>CreateView, you probably should extend from <ModuleName>CreateView (no Base) instead of from the default CreateView.
Be aware that this code has a small chance of breaking during Sugar upgrades, e.g. if the default CreateView code receives changes in the saveModel function definition.
Also, if you want to do some further reading on extending views, there is an SugarCRM dev blog post about this topic: https://developer.sugarcrm.com/2014/05/28/extending-view-javascript-in-sugarcrm-7/
I resolved this by using logic hook (after save), for your information, I am using Sugar 6.5 no matter the version of suitecrm.
Thank you !
I've got two models defined in my Component.js. One is a list of all contacts and the other is only the logged in contact. Now i want to check in my controller if the logged in contact is already existing in the list of all contacts. I compare the registrationToken from the list agains the token from the logged in contact. But when i loop through the list the length is 0 because of asynchronous communication.
I saw the attachRequestCompleted function but now i got another problem... the onInit-function is already finished when my attach-function is fill my view-Model..
onInit : function(){
var gLocalContact = sap.ui.getCore().getModel("gLocalContact");
var gRemoteContacts = sap.ui.getCore().getModel("gRemoteContacts");
gRemoteContacts.attachRequestCompleted( function() {
... if ... setProperty to gLocalContact.getProperty("/registrationToken")...
console.log("I should be the first log to get the data in view");
});
console.log("I should be the second log!");
this.getView().setModel(gLocalContact, "localContact");
}
The first log in the attach-function should be first because there i define some data to gLocalContact which i need in my view. Another problem is that i have no access to my gLocalContact variable....
This is a little bit ugly because SAPUI5 does not support promises. So inside your view you don't know if the requestCompleted event will be fired or if the data has already been loaded. There are some solutions comming to my mind:
Attach the requestCompleted eventhandler in your component before you call loadData(). Then you can be shure that you will get the event.
You would have to build your view to handle an empty gLocalContact model though. But as soon as the model is populated with data the bindings will update the view.
Put the remaining stuff of your onInit() into your eventhander. To be sure to get the event do a check if there is already data in your model, and if so call your eventhandler manually to have it run at least once.
Use jQuerys Promises to synchronize. This allows you to wait for the second model too:
onInit : function(){
var gLocalContact = sap.ui.getCore().getModel("gLocalContact");
var gRemoteContacts = sap.ui.getCore().getModel("gRemoteContacts");
console.log("Wait some seconds for the data...");
var localContactPromise = this.getPromise(gLocalContact, "/origin");
localContactPromise.done(function() {
//same code as before but this time you can be shure its called.
//... if ... setProperty to
//gLocalContact.getProperty("/registrationToken")...
console.log("I should be the first log to get the data in view");
});
var remoteContactsPromise = this.getPromise(gRemoteContacts,"/origin"); //Wait for the other model to
$.when(localContactPromise, remoteContactsPromise).done(function(){
//When both models are loaded do this
console.log("I should be the second log!");
this.getView().setModel(gLocalContact, "localContact");
this.byId("label").setText("all loaded");
}.bind(this));
},
getPromise:function(oModel, pathToTestForData){
var deferred = $.Deferred();
if (oModel.getProperty(pathToTestForData))
deferred.resolve(); //Data already loaded
else
oModel.attachRequestCompleted(deferred.resolve); //Waiting for the event
return deferred.promise();
}
Full example on JSBin
A Promise is a object that has a done event. A Deferred is an object that has a Promise and a resolve() method that will raise the done event on that Promise. If you first call resolve() on the Deferred and then register a handler for the done the handler is immediately called. So you won't miss the event even if you were slower than the asynchronous load request.
But: If your model could not even been set on the component/core when your view initializes you have a severe problem as there is no such thing as a modelChanged event. I would recommend to create a empty model and assign it to the component in the components init-method and then use loadData() on that model.
I'm using Extjs5 and Sencha Cmd, and I'm working on a l10n engine (over gettext) to implement localization.
Suppose I want to offer a translation function to every class of my project, named _().
In every controller, view, model and any class, I'd like to be able to write something like that:
Ext.define('FooClass', {
someStrings: [
_('One string to translate'),
_('A second string to translate'),
_('Yet another string to translate')
]
});
First problem: _() must exist before all the Ext.define() of my project are executed. How to achieve that?
Second problem: _() is looking in "catalogs" that are some JavaScript files generated from .po files (gettext). So, those catalogs must have been loaded, before all the Ext.define() of my app are executed.
_() is a synchronous function, it musts immediately return the translated string.
Edit concerning the edited question
You have at least two ways to load External libraries:
Ext.Loader.loadScript
loadScript( options )
Loads the specified script URL and calls the supplied callbacks. If
this method is called before Ext.isReady, the script's load will delay
the transition to ready. This can be used to load arbitrary scripts
that may contain further Ext.require calls.
Parameters
options : Object/String/String[] //The options object or simply the URL(s) to load.
// options params:
url : String //The URL from which to load the script.
onLoad : Function (optional) //The callback to call on successful load.
onError : Function (optional) //The callback to call on failure to load.
scope : Object (optional) //The scope (this) for the supplied callbacks.
If you still run into problems you can force the loader to do a sync loading:
syncLoadScripts: function(options) {
var Loader = Ext.Loader,
syncwas = Loader.syncModeEnabled;
Loader.syncModeEnabled = true;
Loader.loadScripts(options);
Loader.syncModeEnabled = syncwas;
}
Place this in a file right after the ExtJS library and before the generated app.js.
Old Answer
You need to require a class when it is needed, that should solve your problems. If you don't require sencha command/the ExtJS class system cannot know that you need a specific class.
Ext.define('Class1', {
requires: ['Class2'],
items: [
{
xtype: 'combo',
fieldLabel: Class2.method('This is a field label')
}
]
});
For further reading take a look at:
requires
requires : String[]
List of classes that have to be loaded before instantiating this
class. For example:
Ext.define('Mother', {
requires: ['Child'],
giveBirth: function() {
// we can be sure that child class is available.
return new Child();
}
});
uses
uses : String[]
List of optional classes to load together with this class. These
aren't neccessarily loaded before this class is created, but are
guaranteed to be available before Ext.onReady listeners are invoked.
For example:
Ext.define('Mother', {
uses: ['Child'],
giveBirth: function() {
// This code might, or might not work:
// return new Child();
// Instead use Ext.create() to load the class at the spot if not loaded already:
return Ext.create('Child');
}
});
Define the translate function outside the scope of the ExtJs project and include it before the Ext application is included in the index.html.
The scripts are loaded in the right order and the _() function is ready to use in your whole project.
i18n.js
function _() {
// do the translation
}
index.html
<html>
<head>
<script src="i18n.js"></script>
<script id="microloader" type="text/javascript" src="bootstrap.js"></script>
</head>
<body>
</body>
</html>
I've got a localization httphandler that's running in the context of my ASP.Net MVC2 Content folder (part of what it's doing is compiling .less files that are in /Content/css). My default route for this particular set of requests looks like this:
context.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
context.MapRoute(
"Area_default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { controller = new VirtualDirectoryConstraint("VDirectory1") },
new string[] { "Namespace.One.MVC" }
);
(As an aside - I don't think it's relevant, but just in case - the VirtualDirectoryConstraint rejects matches on this route if the request is not coming from the passed-in application path/virtual directory)
With this configuration a call to http://server.net/VDirectory1/Content/css/LessCompiler.axd fails because there's no ContentController class. All well and good.
When I add
context.Routes.IgnoreRoute("{Content}/{*pathInfo}");
that call succeeds, but subsequent calls to
http://server.net/VDirectory1/Localization/ClientScript/en-US.js
and
http://server.net/VDirectory1/Authorization/ClientScript
fail. Looking at Phil Haack's RouteDebugger tool, those calls are matching the Content IgnoreRoute route:
True {Content}/{*pathInfo} (null) (null) (null)
and are therefore not being routed to the LocalizationController and AuthorizationController, respectively.
Clearly I'm misunderstanding something about how the IgnoreRoute is supposed to be used and why that particular IgnoreRoute is matching those requests. What am I missing?
Shouldn't your IgnoreRoute use Content instead of {Content} ?
context.Routes.IgnoreRoute("Content/{*pathInfo}");
At the moment, {Content} is probably being expanded as a variable to nothing, which makes the pathinfo match everything.