angoose
Version:
Angoose is a Remote Method Invocation module that comes with built-in mongoose/angular support. Now you can call server side module in browser just like you're in the server side!
319 lines (288 loc) • 12.2 kB
JavaScript
;(function(){
var serviceProvider = function () {
var uiOptions ={
'$query-error': 'throw' // throw | alert | none, used to specify what happens when there is error loading data
};
var service = {
getReference: getRef,
getPathSchema:getPathSchema,
debounce:debounce,
//initQuery:initQuery,
//defineQuery:initQuery,
isCustomRef:isCustomRef,
apiCamelcase:apiCamelcase,
camelcase:camelcase,
decamelcase:decamelcase,
extractTemplate:extractTemplate,
getter:getter,
setter:setter,
getCustomRefKeyfieldPath:getCustomRefKeyfieldPath,
getCustomRefValue:getCustomRefValue,
filterPath:filterPath,
resolveAttribute: resolveAttribute,
getFieldSchema:getFieldSchema
}
function getFieldSchema(scope){
if(scope.fieldSchema) return scope.fieldSchema;
if(!scope.path) return null;
var modelClass = scope.instance && scope.instance.constructor;
var modelSchema = scope.modelSchema || (modelClass && modelClass.schema);
return modelSchema && modelSchema.paths[scope.path];
}
this.$get = function ($http, $templateCache, $q,$compile, AngooseForm, AngooseQuery, angoose) {
service.loadFieldTemplate = function(fieldTemplate){
fieldTemplate = fieldTemplate.replace(".html", "");
fieldTemplate = 'deform.field.' + fieldTemplate+".tpl";
return this.loadTemplate( fieldTemplate);
}
service.loadTemplate = function(templateName){
angoose.logger.trace("Loading template", templateName)
var deferred = $q.defer();
var html = $angooseTemplateCache(templateName);
if(html){
deferred.resolve( angular.element(html));
return deferred.promise;
}
angoose.logger.trace("$http loading template", templateName);
return $http.get(templateName, {cache: $templateCache }).then(function(response) {
//console.log(response.data, "got template $http")
return angular.element(response.data);
}, function(response) {
throw new Error('Template not found: ' + templateName);
//console && console.error("teamplte note found", templateName)
});
};
// service.resolveTemplateUrl = function(templateName){
// var templateUrl = deformOptions.templateDir +"/"+ templateName +".tpl.html";
// return templateUrl;
// }
service.defineForm = function($scope, options){
//@todo: use auto merge/extend
var formSpec = $scope.$form;
if(!formSpec)
formSpec = new AngooseForm($scope);
formSpec.update(options);
return formSpec;
};
service.initQuery = service.defineQuery = function($scope, options){
//@todo: use auto merge/extend
var querySpec = $scope.dmeta;
if(!querySpec)
querySpec = new AngooseQuery($scope);
querySpec.update(options);
return querySpec;
};
service.resolveTemplate = function resolveTemplate($element, $attrs, config, defaultTemplateUrl){
// templateUrl could be specified in the directive <ang-edit template-url='xxx' template='xxx'>
/** template resolving order
//todo: consolidate with field directive template
1) config.template // config is from custom controller, if available
2) config.templateUrl
3) $attrs.templateUrl
4) inline template
*/
var inline = $element.html();
var template = config && config['template'];
var url = config && config['templateUrl'];
url = url || $attrs.templateUrl;
var deferred = $q.defer();
if(url || template) inline = ""; // ignore inline template if template/templateUrl is specified
if(!inline && !template && !url){
// no template specified anywhere, use the default
angoose.logger.trace("Using default template", defaultTemplateUrl);
url = defaultTemplateUrl;
}
var em = '';
if(template){
// template is specified as string content
angoose.logger.trace("Compiling template content");
em = angular.element(template);
}
else if(url){
// second order
console.log("loadTemplate", url);
return service.loadTemplate(url);
}
else{
// inline doesn't need special handling
angoose.logger.trace("Did not find configured template, using inline template" );
em = "";
}
deferred.resolve(em); // nextTick?
return deferred.promise;
};
service.resolveAndCompile = function($scope, $element, $attrs, config, defaultTemplate){
angoose.logger.trace("Resolving template[default: ", defaultTemplate+"]");
service.resolveTemplate($element, $attrs, config, defaultTemplate).then(function(em){
angoose.logger.trace("resolveTemplate completed[default: ", defaultTemplate+"]. Got content: ", !!em);
if(em){
$element.html("<!-- CLEARED -->"); // first clear the inline template
$element.append(em);
$compile(em)($scope);
}
}, function(err){
console.error("Error resolving template(list.tpl)", err);
});
}
return service;
};
this.config = function(name, val){
if(typeof(name) === 'object')
uiOptions = angular.extend(uiOptions, opts)
else if(name){
if( val === undefined) return getter(uiOptions, name);
setter(uiOptions, name, val)
}
};
};
angular.module('angoose.ui.services').provider('$ui', serviceProvider).provider('$deform', serviceProvider).run(['$ui','$rootScope', function($ui, $rootScope){
$rootScope.defineQuery = function(meta){
return $ui.defineQuery(this, meta);
};
$rootScope.defineForm = function(meta){
return $ui.defineForm(this, meta);
}
}]);
// a helper function to determine the attribute value based on following order:
// $scope (from custom controller) -> $routeParam -> directive
function resolveAttribute(name, $scope, $routeParams, $attrs ){
return getter($scope.dmeta ? $scope.dmeta: $scope.$form, name) || getter($routeParams, name) || $attrs[name];
}
function filterPath(path, data, schema){
if(data.options.editable === false) return true;
if(schema && schema.options && schema.options.discriminatorKey == path) return true;
if(path.indexOf("-")>0 || path == 'type') return true; /** cannot handle hyphen */
if(getter(data, "schema.options.editable") === false) {
return true
}
return false;
}
function setter(doc, path, val){
if(!path || !doc ) return;
var pieces = path.split('.');
var obj = doc;
for (var i = 0, len = pieces.length; i < len; i++) {
if(i+1 == len ) // last one
{
obj[ pieces[i]] = val;
return;
}
obj[pieces[i]] = obj[pieces[i]] || {};
obj = obj[pieces[i]] || {};
}
}
function getter(obj, p){
if(!p || !obj) return undefined;
var p = p.split('.');
var o = obj;
for (var i = 0; i < p.length; i++){
if(!o || typeof o != "object") return undefined;
o = o[p[i]]
}
return o;
}
function getRef(pathSchema){
var opts = pathSchema.options;
/** Mongoose, array of ObjectIDs */
if( Array.isArray(opts.type) && opts.type.length>0 && opts.type[0] && opts.type[0].ref )
return opts.type[0].ref;
/** Single ObjectID Reference*/
if(pathSchema.instance == 'ObjectID' && opts.ref) return opts.ref;
if(pathSchema.options.ref && pathSchema.instance == 'CustomRef'){
/** deform custom ref*/
return pathSchema.options.ref;
}
/** deform rich reference, array */
if(Array.isArray(opts.type ) && pathSchema.caster &&
pathSchema.caster.instance == 'CustomRef' && pathSchema.caster.options.ref ){
return pathSchema.caster.options.ref;
}
return null;
}
function isCustomRef(pathSchema){
return pathSchema && (pathSchema.instance == 'CustomRef' || (pathSchema.caster && pathSchema.caster.instance == 'CustomRef'));
}
function getCustomRefKeyfieldPath(pathSchema){
if(!isCustomRef(pathSchema)) return undefined;
return pathSchema.path +"." + (getter(pathSchema, 'options.keyField') || 'name');
}
function getCustomRefValue(instance, pathSchema){
var path = getCustomRefKeyfieldPath(pathSchema);
if(!path) return undefined;
var name = getter(instance, path);
if(path.indexOf("sortable")>=0)
return formatSortable(name);
if(name && (name.first || name.last)) name =( name.first || "")+" "+ (name.last || "");
return name;
}
function apiCamelcase(name){
if(!name) return name;
var name = camelcase(name);
return name.substring(0,1).toLowerCase() + name.substring(1);
}
function camelcase(name){
// converting client-user to ClientUser
if(!name) return name;
var parts = name.split("-");
name = "";
for(var i=0;i< parts.length;i++){
if(parts[i] && parts[i].length>0) name+= parts[i].substring(0,1).toUpperCase() + parts[i].substring(1);
}
return name;
}
function decamelcase(name){
// convert ClientUser to client-user
if(!name) return name;
var ret = "";
for(var i=0;i<name.length;i++){
var c = name.charAt(i);
if(c.toLowerCase() != c && ret.length>0) ret+="-"
ret += c;
}
return ret.toLowerCase();
}
function trim(str){
return str? str.replace(/^\s*(.*?)\s*$/, "$1"): str;
}
function formatSortable(sortable){
if(!sortable || typeof(sortable)!=='string') return sortable;
if(sortable.indexOf(",")<0) return sortable;
sortable = trim(sortable.toLowerCase()).replace(/null/i, '');
var parts = sortable.split(",");
if(parts.length != 2)
return sortable;
if(parts.length >1){
return camelcase(trim(parts[1])) +" "+ camelcase( trim(parts[0]));
}
return sortable;
}
function getPathSchema(modelClass, path){
var modelSchema = modelClass.schema;
return modelSchema.paths && modelSchema.paths[path];
}
/**
* Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
* within the last quietMillis milliseconds.
*
* @param quietMillis number of milliseconds to wait before invoking fn
* @param fn function to be debounced
* @param ctx object to be used as this reference within fn
* @return debounced version of fn
*/
function debounce(quietMillis, fn, ctx ) {
ctx = ctx || undefined;
var timeout;
return function () {
var args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
fn.apply(ctx, args);
}, quietMillis);
};
}
function extractTemplate(f){
return f.toString().
replace(/^[^\/]+\/\*!?/, '').
replace(/\*\/[^\/]+$/, '');
}
})();