I am obviously missing some concept/understanding and most definitely javascript OO basics!
I am loving using RequireJS, and my web app now looks more like a structured app now rather than a whole heap of crazy code.
I am just struggling to understand how/if the following is possible.
I have a module which acts as a base dataservice module called dataservice_base as follows:
define(['dataservices/dataservice'], function (dataservice) {
// Private: Route URL
this.route = '/api/route-not-set/';
var setRoute = function (setRoute) {
this.route = setRoute;
return;
}
// Private: Return route with/without id
var routeUrl = function (route, id) {
console.log('** Setting route to: ' + route);
return route + (id || "")
}
// Private: Returns all entities for given route
getAllEntities = function (callbacks) {
return dataservice.ajaxRequest('get', routeUrl())
.done(callbacks.success)
.fail(callbacks.error)
};
getEntitiesById = function (id, callbacks) {
return dataservice.ajaxRequest('get', routeUrl(this.route, id))
.done(callbacks.success)
.fail(callbacks.error)
};
putEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('put', routeUrl(this.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
};
postEntity = function (data, callbacks) {
return dataservice.ajaxRequest('post', routeUrl(this.route), data)
.done(callbacks.success)
.fail(callbacks.error)
};
deleteEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('delete', routeUrl(this.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
};
// Public: Return public interface
return {
setRoute: setRoute,
getAllEntities: getAllEntities,
getEntitiesById: getEntitiesById,
putEntity: putEntity,
postEntity: postEntity,
deleteEntity: deleteEntity
};
});
As you can see, I am referencing dataservices/dataservice, which is actually the core AJAX call mechanism (not shown, but really just basic jQuery ajax call in a wrapper).
What I am trying to do is allow this base dataservice module to be "instanced" as follows (within another module - snippet code only):
define(['dataservices/dataservice_base', 'dataservices/dataservice_base', 'dataservices/dataservice_base'], function (dataservice_profile, dataservice_qualifications, dataservice_subjects) {
// Set the service route(s)
dataservice_profile.setRoute('/api/profile/');
dataservice_qualifications.setRoute('/api/qualification/');
dataservice_subjects.setRoute('/api/subject/');
As you can see, I am trying to include the same dataservice_base(defined above) 3 times, but in the function references, I am trying to refer to each instance by named vars i.e:
dataservice_profile, dataservice_qualifications, dataservice_subjects
.. and of course I am trying be able to set a unique setRoute value for each of those instances to use further on in the module.. whilst leveraging the common calls (get,puts,posts etc).
Obviously I am missing a few things here.. but any help to point me back on the road would be very gratefully received!!
Kind Regards,
David.
I think you need to include your dependency only once and use the new keyword. Possibly you will need to refactor so that the common functions are in a depending module:
define(['dataservices/dataservice'], function (dataservice) {
var dataservice_profile = new dataservice();
var dataservice_qualifications = new dataservice();
var dataservice_subjects = new dataservice();
// Set the service route(s)
dataservice_profile.setRoute('/api/profile/');
dataservice_qualifications.setRoute('/api/qualification/');
dataservice_subjects.setRoute('/api/subject/');
// define needs to return something
return {
profile: dataservice_profile,
qualifications: dataservice_qualifications,
subjects: dataservice_subjects
};
});
Yes, brain-freeze or whatever.. problems of working alone sometimes!
So, as #asgoth mentioned, quite rightly had to clear my mind and think things through a bit!
I ended up with a re-factored dataservice_base module as follows:
define(['dataservices/dataservice'], function (dataservice) {
// Set any class/static vars
// Set the instance function
function dataservice_base(setRoute) {
var self = this;
self.route = setRoute;
console.log('setting route: ' + self.route);
function routeUrl(route, id) {
console.log('** Setting route to: ' + route);
return route + (id || "")
}
self.getAllEntities = function (callbacks) {
return dataservice.ajaxRequest('get', routeUrl())
.done(callbacks.success)
.fail(callbacks.error)
}
self.getEntitiesById = function (id, callbacks) {
return dataservice.ajaxRequest('get', routeUrl(self.route, id))
.done(callbacks.success)
.fail(callbacks.error)
}
self.putEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('put', routeUrl(self.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
}
self.postEntity = function (data, callbacks) {
return dataservice.ajaxRequest('post', routeUrl(self.route), data)
.done(callbacks.success)
.fail(callbacks.error)
}
self.deleteEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('delete', routeUrl(self.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
}
} // eof instance
return dataservice_base;
}
and of course again as #asgoth mentioned, I only need to of course include one reference to the dataservice_base module, and instance it for my needs as follows:
define(['dataservices/dataservice_base','viewmodels/viewmodel_profile', 'viewmodels/viewmodel_qualifications', 'viewmodels/viewmodel_subjects', 'app/common'], function (dataservice_base, viewmodel_profile, viewmodel_qualifications, viewmodel_subjects, common) {
var dataservice_profile = new dataservice_base('/api/profile/');
var dataservice_qualifications = new dataservice_base('/api/qualification/');
var dataservice_subjects = new dataservice_base('/api/subject/');
// do whatever now with those instance objects...
}
SO.. now all working!
I guess the only other thing I need to do is looking up about cleaning up process to ensure these objects are released.. however there will only ever be a few.. but still..
thanks again #asgoth
Just return a function instead of a object like this
return function(){
return {
// your public interface goes here
};
}
Now you can create new instances of your plugin with new componentName().
Related
This is a strange one, but I'm exploring it to see if it's possible.
Let's say that I have a .NET application where I am using PubSub. I want a way to define the topic string using chained objects (not functions). The goal is to allow me a way of defining strings that lets me to take advantage of Visual Studio's IntelliSense and reduce the likelihood of spelling errors.
Here's an example:
/* Manual way */
var topic = "App.Navigation.CurrentItem"
/* Desired Solution */
// ... define the objects here ...
var topic = App.Navigation.CurrentItem;
console.log(topic); // "App.Navigation.CurrentItem"
var topic2 = App.Footer.CurrentItem;
console.log(topic2); // "App.Footer.CurrentItem"
I'd like each object to be responsible for outputing it's own value, and have the chaining process responsible for joining itself to the previous chained object via a predefined separator (in my case, a period [.]).
I've been playing with JavaScript getter syntax, but I'm curious if there's a better way.
Has anyone done something like this before, and if so, how did you solve it?
You're requirements aren't totally clear to me, but are you looking for something like this?
function namespace(ns) { this._ns = ns; }
namespace.prototype.toString = function() {return this._ns};
namespace.prototype.extend = function(suffix) {
return new namespace(this._ns + "." + suffix)
};
Usage:
App = new namespace('App');
App.Navigation = App.extend('Navigation');
App.Navigation.CurrentItem = App.Navigation.extend('CurrentItem');
console.log(App.Navigation.CurrentItem.toString()); // "App.Navigation.CurrentItem"
This is what I ended up with after reviewing StriplingWarrior's answer:
function Namespace(name, config) {
if (typeof name === "object") {
config = name;
name = null;
}
config = config || {};
this._ns = name;
this.define(config);
}
Namespace.prototype.toString = function() { return this._ns };
Namespace.prototype.define = function(config, base) {
base = base || this;
for (key in config) {
var name = (base._ns) ? base._ns + "." + key : key;
base[key] = new Namespace(name);
base.define(config[key], base[key]);
}
return base;
};
Usage:
var App = new Namespace("App", {
Navigation: {
Items: {
Current: {}
}
},
Content: {},
Footer: {
Items: {
Current: {}
}
}
});
console.log(App.toString()); // App
console.log(App.Navigation.Items.Current.toString()); // App.Navigation.Items.Current
console.log(App.Footer.toString()); // App.Footer
I also wrote a convenience method to reduce the toString():
function NS(namespace) {
return namespace.toString();
}
console.log(NS(App.Navigation.Items.Current));
Thanks again to StriplingWarrior for the the help!
I'm attempting to call a service from within another service, then use the returned object to perform some operations. I keep running into a TypeError: getDefinitions is not a function error, however.
Below is my service is called, the service doing the calling, and my relevant controller code:
definitions.service.js:
'use strict';
angular.module('gameApp')
.factory('definitionsService', ['$resource',
function($resource) {
var base = '/api/definitions';
return $resource(base, {}, {
get: {method: 'GET', url: base}
});
}]);
utilities.service.js:
'use strict';
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
return {
description: description,
detail: detail,
severity: severity,
};
function description(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].description;
}
function detail(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].detail;
}
function severity(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].severity;
}
var getDefinitions = function() {
definitionsService.get().$promise.then(function(data) {
return data;
});
};
}]);
controller.js:
'use strict';
angular.module('gameApp')
.controller('AccountsController', AccountsController);
AccountsController.$inject = ['$routeParams', 'customersService', 'utilitiesService'];
function AccountsController($routeParams, playersService, utilitiesService) {
var vm = this;
var playerId = $routeParams.playerId;
var getAccounts = function() {
playersService.getAccounts({
playerId: playerId
}).$promise.then(function(accounts) {
for (var i = 0; i < accounts.length; i++) {
if (angular.isDefined(accounts[i].secCode)) {
accounts[i].code = accounts[i].secCode;
accounts[i].severity = utilitiesService.severity(accounts[i]);
accounts[i].detail = utilitiesService.detail(accounts[i]);
accounts[i].description = utilitiesService.description(accounts[i]);
}
}
vm.accounts = accounts;
});
};
var init = function() {
getAccounts();
};
init();
}
Currently your service returns before your variable gets defined. That means the definition is never reached. So it is declared, as the function executes, but is undefined. Just move your variable definition to the top.
This will only prevent the definition error. Another problem is that your getDefinitions function doesn't return anything but you're calling a property on it. One solution I can think of is using a callback, that gets executed when your data is loaded:
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var data;
reload();
var utils = {
description: description,
detail: detail,
severity: severity,
reload: reload,
loaded: null
};
return utils;
function reload() {
definitionsService.get().$promise.then(function(data) {
data = data;
if (utils.loaded && typeof utils.loaded === "function") {
utils.loaded();
}
});
}
function description(account) {
var key = angular.isDefined(data.ABC[account.code]) ? account.code : '-';
return data.IDV[key].description;
}
}]);
Then in your controller you could use the service like this:
utilitiesService.loaded(function(){
accounts[i].description = utilitiesService.description(accounts[i]);
})
old question but still relevant. To expand on Florian Gl's answer above if you have a service with multiple functions and one or more of those functions requires a "pre-service" function to be called for example to load some resource data in like configuration information move that service call to the top, outside of the nested function (in this case below I am dealing with the promise scenario in JavaScript):
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var myFirstConfigValue = '';
// call any all services here, set the variables first
configurationService.GetConfigValue('FirstConfg')
.then(function (response) {
// set the local scope variable here
myFirstConfigValue = response;
},
function() { });
function myTestFunction() {
// make an ajax call or something
// use the locally set variable here
ajaxService.functionOneTwo(myFirstConfigValue)
.then(response) {
// handle the response
},
function(err) {
// do something with the error
});
}
}]);
Key point to note here is that if you need to load in some data you do that first outside of any other functions inside your service (e.g. you want to load some JSON data).
What I need to know is how can I use a service like $http outside of the $get function or is it even possible? My code goes and loads a json file that provides a dictionary that my application makes use of in various ways. I want users to be able to customize this dictionary so I'm using jquery's extend method to allow users to add values to an object and extend the dictionary. The instantiate method in the code below handles all of this. What I'd like to be able to do is configure my service like so
config(['_sys_dictionaryProvider', function(_sys_dictionaryProvider) {
_sys_dictionaryProvider.instansiate('config/dictionary/custom/dictionary.json');
}])
But this requires the $http service to be available at the time of configuration and I don't think it is. If I put the $http service as part of the $get property it will work, as explained here, except then the network has to be queried every time the service is used. Is there any way to use a service in the configuration of another service?
Full code below, let me know if I need to clarify.
app.provider("_sys_dictionary", ['$http',
function ($http) {
var dictionary,
DictionaryService = function () {
this.definitions = dictionary;
this.define = function (what) {
var definitions = this.definitions;
if (what instanceof Array) {
for (var i = 0; i < what.length; i++) {
definitions = definitions[what[i]];
}
return definitions;
}
return this.definitions[what];
};
};
return {
$get: function () {
return new DictionaryService();
},
instansiate: function (path) {
$http.get('config/dictionary/dictionary.json').success(function (data) {
dictionary = data;
$http.get(path).success(function (data) {
jQuery.extend(true, dictionary, data)
});
});
}
};
}
]);
Seeing as I don't believe it is possible to use a service in the configuration stage, since there is no way to guarantee that the service your using itself has been configured, I went this route instead
app.provider("_sys_dictionary", function () {
var dictionary,
DictionaryService = function () {
this.definitions = dictionary;
this.define = function (what) {
var definitions = this.definitions;
if (what instanceof Array) {
for (var i = 0; i < what.length; i++) {
definitions = definitions[what[i]];
}
return definitions;
}
return this.definitions[what];
};
};
return {
$get: [
function () {
console.log(dictionary);
return new DictionaryService();
}
],
instansiate: function (path) {
jQuery.ajax({
url: 'config/dictionary/dictionary.json',
success: function (data) {
dictionary = data;
jQuery.ajax({
url: path,
success: function (data) {
jQuery.extend(true, dictionary, data);
},
async: false
});
},
async: false
});
}
};
});
I ended up using jquery's ajax object and turned async to false since I need the dictionary to be ready before the service gets used. Hope this helps someone. If anyone knows a better way of doing this I'd love to know.
I have a utility to augment an object to express the current ajax state as follows:
define(['ko'], function (ko) {
var ajaxStatus = {
success: 2,
failure: -1,
working: 1,
none: 0,
};
return {
ajaxStatusHelper: init,
};
function init(target) {
if (target == undefined) throw new Error("Target cannot be undefined.");
target.ajaxStatus = ko.observable(ajaxStatus.none);
target.indicateSuccess = success.bind(target);
target.indicateWorking = working.bind(target);
target.indicateFailure = failure.bind(target);
target.clearStatus = clear.bind(target);
// computed properties
target.ajaxSuccess = ko.computed(function () {
return this.ajaxStatus() === ajaxStatus.success;
}, target);
target.ajaxFailure = ko.computed(function () {
return this.ajaxStatus() === ajaxStatus.failure;
}, target);
target.ajaxWorking = ko.computed(function () {
return this.ajaxStatus() === ajaxStatus.working;
}, target);
target.ajaxInactive = ko.computed(function () {
return this.ajaxStatus() === ajaxStatus.none;
}, target);
return target;
}
// functions
function clear() {
this.ajaxStatus(ajaxStatus.none);
}
function success() {
this.ajaxStatus(ajaxStatus.success);
}
function working() {
this.ajaxStatus(ajaxStatus.working);
}
function failure() {
this.ajaxStatus(ajaxStatus.failure);
}
});
This utility might be utilized to extend multiple objects within the same view model object. For instance, I'll typically extend command members which make an async ajax call so that the view can display in icon indicating the result of the call on the command (with a KO binding not shown here).
My question is what responsibility do I have for cleaning up these augmented nodes and what mechanism is in place to manage this cleanup.
According to Michael Best,
The JavaScript garbage collector can only dispose a computed observable once all references to it and its dependencies are dropped.
Does this mean that I need to manually dispose these augmented members from the view model when the model is being disposed? Or will Knockout automatically delete all members when the associated UI elements are removed?
P.S. If it makes a difference, I'm currently using Knockout 2.3.0 but I'm working on migrating to 3.?
I am trying to write server side for my web application using Node.js. The following code is extracted to simulate the situation. Problem is that the application crashes when trying to access this.actions.length in the actionExecuted "method". The property this.actions is undefined there (this == {} within the scope) even it was defined in the "constructor" (the Request function itself). How to make the actions property accessible from other "methods" as well?
var occ = {
exampleAction: function(args, cl, cb)
{
// ...
cb('exampleAction', ['some', 'results']);
},
respond: function()
{
console.log('Successfully handled actions.');
}
};
Request = function(cl, acts)
{
this.client = cl;
this.actions = [];
this.responses = [];
// distribute actions
for (var i in acts)
{
if (acts[i][1].error == undefined)
{
this.actions.push(acts[i]);
occ[acts[i][0]](acts[i][1], this.client, this.actionExecuted);
}
else
// such an action already containing error is already handled,
// so let's pass it directly to the responses
this.responses.push(acts[i]);
}
}
Request.prototype.checkExecutionStatus = function()
{
// if all actions are handled, send data to the client
if (this.actions == [])
occ.respond(client, data, stat, this);
};
Request.prototype.actionExecuted = function(action, results)
{
// remove action from this.actions
for (var i = 0; i < this.actions.length; ++i)
if (this.actions[i][0] == action)
this.actions.splice(i, 1);
// and move it to responses
this.responses.push([action, results]);
this.checkExecutionStatus();
};
occ.Request = Request;
new occ.Request({}, [['exampleAction', []]]);
The problem is the way you are defining your callback. It's called later so it loses context. You have to either create a closure or properly bind the this. To create a closure:
var self = this;
occ[acts[i][0]](acts[i][1], this.client, function() { self.actionExecuted(); });
To bind to this:
occ[acts[i][0]](acts[i][1], this.client, this.actionExecuted.bind(this));
Either one should work.