API Docs for:
Show:

File: lib/model.js

/*
Provides declarative model functionality

HopJS allows you to define models which can then be associated with various API calls to:
 * Improve client side stub generation
 * Reduce duplicate parameter descriptions

@module Hop
@submodule Model
*/
var Hop = require("./api");


Hop.addToJSONHandler(function(obj){
	obj.Models=Hop.Models;
});


Hop.camelHump=function(name){
	var fs = [".","_","-"];
	var str = "";
	for(var i =0 ; i<name.length;i++){
		if(i==0){
			str+=name[i].toUpperCase();	
		} else if(fs.indexOf(name[i])!=-1){
			i++;
			str+=name[i].toUpperCase();	
		} else {
			str+=name[i];
		}	
	}
	return str;
}

Hop.Type = function(){
}

Hop.Type.prototype.password=function(){
	if(this.type==undefined) this.string();
	this.subType=Hop.Type.password;
	return this;
}

Hop.Type.prototype.ID=function(){
	this.subType=Hop.Type.ID;
	return this;
}

Hop.Type.prototype.model=function(modelName){
	this.object();
	this.model=modelName;
	return this;
}

Hop.Type.prototype.object=function(){
	this.type="Object";
	return this;
}

Hop.Type.prototype.file=function(){
	this.type="File";
	return this;
}

Hop.Type.prototype.getConverterLogic=function(){
	var str="if(typeof #VALUE!=\'undefined\'){\n";
	if(this.converters!=undefined){
		for(var i in this.converters){
			str+="\t"+this.converters[i].convert+"\n";
		}
	}
	str+="}";
	return str;
}

Hop.Type.prototype.boolean=function(){
	this.type="Boolean";
	this.converters=this.converters||[];
	this.converters.push({ convert: "#VALUE=(#VALUE=='true' || #VALUE===true || #VALUE=='1' || #VALUE==true|| #VALUE=='checked');", priority:1 });
	return this;
}

Hop.Type.prototype.string=function(){
	this.type="String";
	return this;
}

Hop.Type.prototype.double=function(){
	this.type="Number";
	this.subType=Hop.Type.Float;
	this.converters=this.converters||[];
	this.converters.push({ convert: "#VALUE=parseFloat(#VALUE);", priority:1 });
	return this;
}

Hop.Type.prototype.float=function(){
	this.type="Number";
	this.subType=Hop.Type.Float;
	this.converters=this.converters||[];
	this.converters.push({ convert: "#VALUE=parseFloat(#VALUE);", priority:1 });
	return this;
}

Hop.Type.prototype.integer=function(){
	this.type="Number";
	this.subType=Hop.Type.Integer;
	this.converters=this.converters||[];
	this.converters.push({ convert: "#VALUE=parseInt(#VALUE);", priority:1 });
	return this;
}


Hop.Type.prototype.date=function(){
	this.type="Date";
	this.converters=this.converters||[];
	this.converters.push({ convert: "#VALUE=(/[0-9]+/.test(#VALUE)?new Date(parseInt(#VALUE)):new Date(#VALUE));", priority:1 });
	return this;
}


Hop.Type.prototype.isArray=function(){
	this.isArray=true;
	return this;
}

Hop.ValidatedType = function(){
}

Hop.ValidatedType.prototype = Object.create(Hop.Type.prototype);

Hop.ValidatedType.prototype.getValidatorLogic=function(){
	var str="";
	var preStr="";
	var t="\t";
	if(this.isArray===true){
		preStr+="\tif(!(#VALUE instanceof Array)) throw 'Invalid value: #VALUENAME';\n";
		if(this.validators!=undefined && this.validators.length>0){
			preStr+="\tfor(var i in #VALUE){\n";
			preStr+="\t\tvar _val = #VALUE[i];\n";
			t="\t\t";
		}
	}
	if(this.validators!=undefined){
		for(var i in this.validators){
			str+=t+"if("+this.validators[i].test+") throw "+JSON.stringify(this.validators[i].msg)+";\n";
		}
	}

	if(this.isArray===true){ 
		str=str.replace("#VALUE","_val");
		str=preStr+str;
		if(this.validators!=undefined && this.validators.length>0){
			str+="\t}\n";
		}
	}

	if(str.length>0){
		str="if(typeof #VALUE!=\'undefined\'){\n"+str+"}";
	}
	return str;
}

Hop.ValidatedType.prototype.regexp=function(regex,regexMsg){
	this.regex=regex;
	this.regexMsg=regexMsg;
	this.validators=this.validators||[];
	this.validators.push({ test: "(!"+regex.toString()+".test(#VALUE.toString()))", msg: ((regexMsg||"Invalid value")+": #VALUENAME")});
	return this;
}

Hop.ValidatedType.prototype.range=function(min,max){
	this.range={ min: min, max: max};
	this.validators=this.validators||[];
	if(min!=null){
		this.validators.push({ test: "(#VALUE < "+min.toString()+")", msg: "Value must be greater than "+min+": #VALUENAME" });
	}
	if(max!=null){
		this.validators.push({ test: "(#VALUE > "+max.toString()+")", msg: "Value must be less than "+max+": #VALUENAME" });
	}
	return this;
}

Hop.ValidatedType.prototype.values=function(values){
	this.values=values;
	
	this.validators=this.validators||[];
	if(values instanceof Array){
		this.validators.push({ test: "("+JSON.stringify(values)+".indexOf(#VALUE)==-1)", msg: "Valid values are: "+values.join(", ")+": #VALUENAME" });
	} else if(values instanceof Object){
		this.validators.push({ test: "("+JSON.stringify(Object.keys(values))+".indexOf(#VALUE)==-1)", msg: "Valid values are: "+Object.keys(values).join(", ")+": #VALUENAME" });
	}
	return this;
}



Hop.Field=function(name){
	this.name=name;
}

Hop.Field.prototype = Object.create(Hop.ValidatedType.prototype);

Hop.Type.JSON="JSON";
Hop.Type.ID="ID";
Hop.Type.Float="Float";
Hop.Type.Integer="Integer";
Hop.Type.Password="Password";

Hop.Field.prototype.title=function(name){
	this.displayName=name;
	return this;
}

Hop.Field.prototype.description=function(desc){
	this.desc=desc;
	return this;
}

Hop.Field.prototype.getConverter=function(){
	var logic = this.getConverterLogic();
	logic=logic.replace(/\#VALUE/g,"input."+this.name);
	return logic;
}

Hop.Field.prototype.getValidator=function(){
	var logic = this.getValidatorLogic();
	logic=logic.replace(/\#VALUENAME/g,this.name);
	logic=logic.replace(/\#VALUE/g,"input."+this.name);
	return logic;
}

Hop.Models={};
Hop.Model=function(name){
	this.name=name;
	this.tableName=name.toLowerCase();
	this.fields={};
	Hop.Models[name]=this;
}

Hop.Model.applyToMethod=function(method,model){
	var validator="";
	var converter="";
	for(var paramName in method.params){
		if(model.fields[paramName]){
			if(method.params[paramName].desc==undefined && model.fields[paramName].desc!=undefined)
				method.params[paramName].desc=model.fields[paramName].desc;

				validator+=model.fields[paramName].getValidator();	
				converter+=model.fields[paramName].getConverter();	
			
		}	
	}
	if(validator!=""){
		var func = new Function("req","input","onComplete","next",'try {\n' +validator +'\n} catch(e){ return onComplete(e); } next();');
		method.addPreCall(func,"validation");
	}	
	if(converter!=""){
		var func = new Function("req","input","onComplete","next",'try {\n' +converter+'\n} catch(e){ return onComplete(e); } next();');
		method.addPreCall(func,"conversion");
	}	
	method.addAfterTemplate("JavaScript","model/postJSMethod.comb");
}

Hop.Model.prototype.field=function(name,title,description){
	var field = new Hop.Field(name);
	this.fields[name]=field;

	if(title!=undefined){
		field.title(title);
	}
	
	if(description!=undefined){
		field.description(description);
	}

	return field;
}

Hop.addBeforeTemplate("JavaScript","model/preJSHop.comb");

Hop.defineModel=function(name,onDefine){
	var model = new Hop.Model(name);
	onDefine(model);	
	return model;
}



/**
Use a model for both input and output

Models are used to provide meta data for both UI 
and api generation 

@param {object} inputObject Input model
@param {object} [outputModel] Output model

 * Models require a field called _name which specifies the name of the model
 * Models can have the following fields on types: 
   * type - The class name of the type, valid values are ( String, Number, Array, Object, Date, Boolean )
   * subtype - A subtype for the field "ID", "Float", "JSON", "IDRef", "Tags" 
   * regex - A regex used to validate the fields
   * regexMsg - A message which is displayed when the regex is not matched
   * title - A title for the field, for UI purposes
   * desc - A description of the field for UI purposes
   * values - An array or object which contains possible values for this field 

@example
	Hop.defineModel("User",function(model){
		model.field("name").string().regex(/[A-Za-z]{3,10}/,"Usernames must be between 3 - 10 characters long and can only contain A-Z  and a-z");
		model.field("id").integer().ID();
		model.field("email").string().title("Email");
	});

	Hop.defineClass("User",new User(),function(api){
		api.post("create","/user").useModel("User");
		api.get("list","/user").inputModel(SearchModel).outputModel(UserModel);
	});
 

@for Hop.Method
@chainable
@method model
**/
Hop.Method.prototype.useModel=function(inputModel,outputModel){
	if(outputModel==undefined)
		outputModel=inputModel;

	if(inputModel){
		if(!Hop.Models[inputModel]){
			throw "Invalid model specified:"+inputModel;
		}
		this.input=new Hop.Type();
		this.input.model(inputModel);
		Hop.Model.applyToMethod(this,Hop.Models[inputModel]);
	}
	if(outputModel){
		if(!Hop.Models[outputModel]){
			throw "Invalid model specified:"+outputModel;
		}
		this.output=new Hop.Type();
		this.output.model(outputModel);
	}
	return this;
}

/**
Use a model for the input 

@param {string} model Name of the model that is used as an input
@param {class} What the model is inputted as (Array is the only valid value)

@for Hop.Method
@method inputModel
@chainable
**/
Hop.Method.prototype.inputModel=function(inputModel,asWhat){
	if(inputModel){
		if(!Hop.Models[inputModel]){
			throw "Invalid model specified:"+inputModel;
		}
		this.input=new Hop.Type();
		Hop.Model.applyToMethod(this,Hop.Models[inputModel]);
		this.input.model(inputModel);

		if(asWhat==Array){
			this.input.isArray();
		}

	}
	
	return this;
}


/**
Use a model for the output

@param {string} model Name of the model that is returned
@param {class} What the model is returned as (Array is the only valid value)

@example
	//Returns an array of vehicles
	api.get("list","/vehicles").outputModel("Vehicle",Array);

@for Hop.Method
@method inputModel
@chainable
**/
Hop.Method.prototype.outputModel=function(outputModel,asWhat){
	if(outputModel){
		if(!Hop.Models[outputModel]){
			throw "Invalid model specified:"+outputModel;
		}
		this.output=new Hop.Type();
		this.output.model(outputModel);
		
		if(asWhat==Array){
			this.output.isArray();
		}
	}
	return this;
}

/**
 This call returns a boolean value

@for Hop.Method
@method returnsBoolean
@chainable
*/
Hop.Method.prototype.returnsBoolean=function(){
	this.output=new Hop.Type();
	this.output.boolean();
	return this;
}

/**
 This call returns a string value

@for Hop.Method
@method returnsString
@chainable
*/
Hop.Method.prototype.returnsString=function(){
	this.output=new Hop.Type();
	this.output.string();
	return this;
}

/**
 This call returns a file

@for Hop.Method
@method returnsString
@chainable
*/
Hop.Method.prototype.returnsFile =function(){
	this.output=new Hop.Type();
	this.output.file();
	return this;
}


/**
 This call returns a number value

@for Hop.Method
@method returnsNumber
@chainable
*/
Hop.Method.prototype.returnsNumber=function(){
	this.output=new Hop.Type();
	this.output.number();
	return this;
}

module.exports=Hop;