Working on creating a dirt simply MVC framework for one of my own projects. Rather than using one that is public, I decided to create one since my needs are very unusual.
I've got my structure down for the Controllers and Views, however, I'm having some issues creating my model structure.
This is what I have for my model structure:
model.models = function(args){
init: function(){
this.on_init();
},
on_init: args.on_init || noop,
data: args.data || {},
};
So then, I would call this as a basic formula for all of the models I want to create. For example, I want to create employees, notifications and some other models using this as a basic blueprint, then make some basic adjustments.
I call:
model.employees = new model.models({
on_init: function(){
//something specific
},
data: {
//defaults
}
});
And we're all good up to this point, but here is where I'm having troubles. Now, when I want to create my end result, the model, I cannot create a new object from an object.. it must be a function.
The only thing I can think of is creating a return function for the second method, but that renders some issues in itself. I have done some research looking at other MVC code, but I was unable to wrap my head around it.
Any help would be very much appreciated!
is this what you want ?
model.models = function(args){
var noop = function(){};
var o = {};
var init = args.on_init || noop;
var data = args.data || {};
init();
//handle other initialization
//o.a = xx;
//o.b = xx;
//o.c = data.xxx;
//....
return o;
}
then you can use the new, and it can't appear syntax error
Did a lot of fiddling, came up with this:
var blueprint = function(args){
return {
data: args.data,
on_init: args.on_init,
create: function(args){
this.on_init();
return {
data: this.data,
whatever: function(){
console.log(args);
}
};
}
};
};
var notifs = new blueprint({
on_init: function(){
console.log('init');
},
data: {
test: 'test'
}
});
var res = notifs.create('test');
console.log(blueprint);
console.log(notifs);
console.log(res);
It comes out with a main function that works, the notifs function is customizable for each individual object type, then calling the create method will create the end method.
Boom!
Related
I have an object that looks like this:
var users = function(url){
this.users = []
console.time("api");
d3.json(url, function(data){
console.timeEnd("api");
this.users = data.data
})
}
it's instatiated like this:
var liveUsers = new users(apiPoint)
d3.json is an asynchonomous api-call. I wish to perform a callback once it's complete, preferably a chaned one, how can I do this?
All you need is to implement a callback system. Here's one simple way of doing it:
var users = function(url){
var self = this;
this.users = []
console.time("api");
d3.json(url, function(data){
console.timeEnd("api");
self.users = data.data
self.cb && self.cb();
})
this.complete = function (fn) {
this.cb = fn;
};
}
var liveUsers = new users(apiEndpoint).complete(function (){
console.log(this.users);
});
Still seems a bit overly-complicated to me, why do you need it to chain? why does users need to be a constructor? why does users even exist instead of simply using d3.json, which already has all of the functionality you are looking for in your question right out of the box?
Usually the point of abstracting a request behind a function is to avoid needing to specify the api endpoint so that if you need to change the endpoint you can do so all in one place. In this case you have to specify it with each request, making the name of the function kinda... pointless since it can be used to request from any endpoint.
If you want to chain, just return this!
var users = function(url){
this.users = []
console.time("api");
d3.json(url, function(data){
console.timeEnd("api");
this.users = data.data
})
return this;
};
users.prototype.somethingElse = function(){
console.log(this.users);
return this;
};
var liveUsers = new users(apiPoint).somethingElse();
The use of return this keeps the chain going, and you can add additional functions to the class by adding prototype methods. The this is retained by using that prototype capability, however if you wanted to use another function that isn't associated with the class and still use that same this, then you'd need to get a little trickier:
var users = function(url){
this.users = []
console.time("api");
d3.json(url, function(data){
console.timeEnd("api");
this.users = data.data
})
return this;
},
somethingElse = function(){
console.log(this.users);
return this;
};
var liveUsers = new users(apiPoint);
// bunch of other codey stuffs
somethingElse.call(liveUsers);
By using .call() applying liveUsers as the first argument, it overrides whatever this the somethingElse function originally had, and gives it the context that you want (making this === liveUsers).
Hope this helps!
I've been working on writing a custom jquery plugin for one of my web applications but I've been running into a strange error, I think it's due to my unfamiliarity with object-oriented programming.
The bug that I've been running into comes when I try to run the $(".list-group").updateList('template', 'some template') twice, the first time it works just fine, but the second time I run the same command, I get an object is not a function error. Here's the plugin code:
(function($){
defaultOptions = {
defaultId: 'selective_update_',
listSelector: 'li'
};
function UpdateList(item, options) {
this.options = $.extend(defaultOptions, options);
this.item = $(item);
this.init();
console.log(this.options);
}
UpdateList.prototype = {
init: function() {
console.log('initiation');
},
template: function(template) {
// this line is where the errors come
this.template = template;
},
update: function(newArray) {
//update code is here
// I can run this multiple times in a row without it breaking
}
}
// jQuery plugin interface
$.fn.updateList = function(opt) {
// slice arguments to leave only arguments after function name
var args = Array.prototype.slice.call(arguments, 1);
return this.each(function() {
var item = $(this), instance = item.data('UpdateList');
if(!instance) {
// create plugin instance and save it in data
item.data('UpdateList', new UpdateList(this, opt));
} else {
// if instance already created call method
if(typeof opt === 'string') {
instance[opt](args);
}
}
});
}
}(jQuery));
One thing I did notice when I went to access this.template - It was in an array so I had to call this.template[0] to get the string...I don't know why it's doing that, but I suspect it has to do with the error I'm getting. Maybe it can assign the string the first time, but not the next? Any help would be appreciated!
Thanks :)
this.template = template
Is in fact your problem, as you are overwriting the function that is set on the instance. You end up overwriting it to your args array as you pass that as your argument to the initial template function. It basically will do this:
this.template = ["some template"];
Thus the next time instance[opt](args) runs it will try to execute that array as if it were a function and hence get the not a function error.
JSFiddle
It seems that Angular does not provide a built-in solution to define class instances with properties and methods and that it's up the developer to build this.
What is the best practice to do this in your opinion?
How to you link this with the backend?
Some of the tips I have gathered use factory services and named functions.
Sources :
Tuto 1
Tuto 2
Thanks for your insights
I think that the closest structure to an Object it's probably a factory, for several reasons:
Basic Syntax:
.factory('myFactory', function (anInjectable) {
// This can be seen as a private function, since cannot
// be accessed from outside of the factory
var privateFunction = function (data) {
// do something
return data
}
// Here you can have some logic that will be run when
// you instantiate the factory
var somethingUseful = anInjectable.get()
var newThing = privateFunction(somethingUseful)
// Here starts your public APIs (public methods)
return {
iAmTrue: function () {
return true
},
iAmFalse: function () {
return false
},
iAmConfused: function () {
return null
}
}
})
And then you can use it like a standard Object:
var obj = new myFactory()
// This will of course print 'true'
console.log( obj.iAmTrue() )
Hope this helps, I perfectly know that the first impact with angular modules can be pretty intense...
You would use an angular service.
All angular services are singletons and can be injected into any controller.
Ideally you would keep only binding/actions on html in your controller and the rest of the logic would be in your service.
Hope this helps.
I got idea by evaluating this library : https://github.com/FacultyCreative/ngActiveResource
However this library assumes strict rest so I it wasn't work for me. What did work for is this:
I created base Model
var app = angular.module('app', []);
app .factory('Model', function(){
var _cache = {}; // holding existing instances
function Model() {
var _primaryKey = 'ID',
_this = this;
_this.new = function(data) {
// Here is factory for creating instances or
// extending existing ones with data provided
}
}
return Model;
});
Than I took simple function extensions "inherits"
Function.prototype.inherits = function (base) {
var _constructor;
_constructor = this;
return _constructor = base.apply(_constructor);
};
and now I cam creating my models like this
app.factory('Blog', [
'Model',
'$http',
function(Model, $http) {
function Blog() {
// my custom properties and computations goes here
Object.defineProperty(this, 'MyComputed' , {
get: function() { return this.Prop1 + this.Prop2 }
});
}
// Set blog to inherits model
Blog.inherits(Model);
// My crud operations
Blog.get = function(id) {
return $http.get('/some/url', {params: {id:id}}).then(function(response) {
return Blog.new(response.data);
});
}
return Blog;
}
]);
Finally, using it in controller
app.controller('MyCtrl', [
'$scope', 'Blog',
function($scope, Blog) {
Blog.get(...).then(function(blog) {
$scope.blog = blog;
});
}
])
Now, there is much more in our Model and extensions but this would be a main principle. I am not claiming this is best approach but I am working pretty big app and it really works great for me.
NOTE: Please note that I typed this code here and could be some errors but main principle is here.
As my question does not really reflect the issue I was facing, I'll just post my approach for the sake of it :
As Domokun put it, rule of thumb is to decouple front and back. But as I am only building a prototype and managing both ends, I would like to keep things in only one place and let the rest of the application use the central information as a service.
What I want to do here is to build a form through ng-repeat containing the model fields and most importantly how to display information in the form (e.g. 'Last name' instead of 'lastname')
So as I started working around with mongoose models here's what I have managed to do :
Firstly, it is possible to pass the mongoose schema of a model from node side to angular side with an app.get request with the following response :
res.send(mongoose.model('resources').schema.paths);
this spitts out an object containing all fields of the 'resources' collection. On top of that I included some additional information in the model like this :
var resourceSchema = new Schema({
_id: { type: Number },
firstname: { type: String, display:'First name' },
lastname: { type: String, display:'Last name' }
});
mongoose.model('resources', resourceSchema);
So basically I can retrieve this symmetrically on angular side and I have all I need to map the fields and display them nicely. It seems I can also describe the validation but I'm not there yet.
Any constructive feedback on this approach (whether it is valid or totally heretic) is appreciated.
I have a resource that returns an array from a query, like so:
.factory('Books', function($resource){
var Books = $resource('/authors/:authorId/books');
return Books;
})
Is it possible to add prototype methods to the array returned from this query? (Note, not to array.prototype).
For example, I'd like to add methods such as hasBookWithTitle(title) to the collection.
The suggestion from ricick is a good one, but if you want to actually have a method on the array that returns, you will have a harder time doing that. Basically what you need to do is create a bit of a wrapper around $resource and its instances. The problem you run into is this line of code from angular-resource.js:
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
This is where the return value from $resource is set up. What happens is "value" is populated and returned while the ajax request is being executed. When the ajax request is completed, the value is returned into "value" above, but by reference (using the angular.copy() method). Each element of the array (for a method like query()) will be an instance of the resource you are operating on.
So a way you could extend this functionality would be something like this (non-tested code, so will probably not work without some adjustments):
var myModule = angular.module('myModule', ['ngResource']);
myModule.factory('Book', function($resource) {
var service = $resource('/authors/:authorId/books'),
origQuery = service.prototype.$query;
service.prototype.$query = function (a1, a2, a3) {
var returnData = origQuery.call(this, a1, a2, a3);
returnData.myCustomMethod = function () {
// Create your custom method here...
return returnData;
}
}
return service;
});
Again, you will have to mess with it a bit, but that's the basic idea.
This is probably a good case for creating a custom service extending resource, and adding utility methods to it, rather than adding methods to the returned values from the default resource service.
var myModule = angular.module('myModule', []);
myModule.factory('Book', function() {
var service = $resource('/authors/:authorId/books');
service.hasBookWithTitle = function(books, title){
//blah blah return true false etc.
}
return service;
});
then
books = Book.list(function(){
//check in the on complete method
var hasBook = Book.hasBookWithTitle(books, 'someTitle');
})
Looking at the code in angular-resource.js (at least for the 1.0.x series) it doesn't appear that you can add in a callback for any sort of default behavior (and this seems like the correct design to me).
If you're just using the value in a single controller, you can pass in a callback whenever you invoke query on the resource:
var books = Book.query(function(data) {
data.hasBookWithTitle = function (title) { ... };
]);
Alternatively, you can create a service which decorates the Books resource, forwards all of the calls to get/query/save/etc., and decorates the array with your method. Example plunk here: http://plnkr.co/edit/NJkPcsuraxesyhxlJ8lg
app.factory("Books",
function ($resource) {
var self = this;
var resource = $resource("sample.json");
return {
get: function(id) { return resource.get(id); },
// implement whatever else you need, save, delete etc.
query: function() {
return resource.query(
function(data) { // success callback
data.hasBookWithTitle = function(title) {
for (var i = 0; i < data.length; i++) {
if (title === data[i].title) {
return true;
}
}
return false;
};
},
function(data, response) { /* optional error callback */}
);
}
};
}
);
Thirdly, and I think this is better but it depends on your requirements, you can just take the functional approach and put the hasBookWithTitle function on your controller, or if the logic needs to be shared, in a utilities service.
So, I have this constructor set up with some prototypes methods and because I need the behavior (that this object creates ) to apply to a few diff. elements, I was wondering if there is a better way than doing the following.
var MAINFUNC = function(opts){
this.options = {
item1 : 'somevalue'
},
this.init(opts);
}
MAINFUNC.prototype = {
someFunc1: function(){
// do stuff
},
someFunc2: function(){
// do stuff
},
someFunc3: function(){
// do stuff
},
init: function(data){
$.extend(this.options, data);
this.someFunc1();
}
};
var obj1Create = new MAINFUNC({ someoptions });
var obj2Create = new MAINFUNC({ someoptions });
var obj2Create = new MAINFUNC({ someoptions });
So, its the last three obj instantiations that seem a tad bit obtuse. Perhaps I am incorrect, but I am thinking there is a more refined way of doing this. And yes, each of thos obj*Create does represent a diff element which needs the behavior that is supplied by MAINFUNC.
Thank you.
var MAINFUNC = function(opts)
{
var m = Object.create(MAINFUNC.prototype);
m.options = { ... };
m.init(opts);
return m;
};
var o = [{someoptions}, {someoptions}, {someoptions}].map(MAINFUNC);
// objects are all now in array
A side benefit of this approach is it makes MAINFUNC work whether you use new or not. This makes it much more manageable, especially with collection functions.