6 * var aou = new egIDL.aou();
7 * var fullIDL = egIDL.classes;
11 * 1. selector field only appears once per class. We could save
12 * a lot of IDL (network) space storing it only once at the
14 * 2. we don't need to store array_position in /IDL2js since it
15 * can be derived at parse time. Ditto saving space.
17 angular.module('egCoreMod')
19 .factory('egIDL', ['$window', function($window) {
23 // Clones data structures containing fieldmapper objects
24 service.Clone = function(old, depth) {
25 if (depth === undefined) depth = 100;
27 if (typeof old == 'undefined' || old === null) {
29 } else if (old._isfieldmapper) {
30 obj = new service[old.classname]()
31 if (old.a) obj.a = service.Clone(old.a, depth); // pass same depth because we're still cloning this same object
33 if(angular.isArray(old)) {
35 } else if(angular.isObject(old)) {
38 return angular.copy(old);
42 if (old[j] === null || typeof old[j] == 'undefined') {
44 } else if( old[j]._isfieldmapper ) {
45 if (depth) obj[j] = service.Clone(old[j], depth - 1);
47 obj[j] = angular.copy(old[j]);
54 service.parseIDL = function() {
55 //console.debug('egIDL.parseIDL()');
57 // retain a copy of the full IDL within the service
58 service.classes = $window._preload_fieldmapper_IDL;
60 // keep this reference around (note: not a clone, just a ref)
61 // so that unit tests, which repeatedly instantiate the
63 //$window._preload_fieldmapper_IDL = null;
66 * Creates the class constructor and getter/setter
67 * methods for each IDL class.
69 function mkclass(cls, fields) {
71 service.classes[cls].core_label = service.classes[cls].core ? 'Core sources' : 'Non-core sources';
72 service.classes[cls].classname = cls;
74 service[cls] = function(seed) {
77 this._isfieldmapper = true;
80 /** creates the getter/setter methods for each field */
81 angular.forEach(fields, function(field, idx) {
82 service[cls].prototype[fields[idx].name] = function(n) {
83 if (arguments.length==1) this.a[idx] = n;
88 // global class constructors required for JSON_v1.js
89 $window[cls] = service[cls];
92 for (var cls in service.classes)
93 mkclass(cls, service.classes[cls].fields);
97 * Generate a hash version of an IDL object.
99 * Flatten determines if nested objects should be squashed into
100 * the top-level hash.
102 * If 'flatten' is false, e.g.:
104 * {"call_number" : {"label" : "foo"}}
106 * If 'flatten' is true, e.g.:
108 * e.g. {"call_number.label" : "foo"}
110 service.toHash = function(obj, flatten) {
111 if (!angular.isObject(obj)) return obj; // arrays are objects
113 if (angular.isArray(obj)) { // NOTE: flatten arrays not supported
114 return obj.map(function(item) {return service.toHash(item)});
117 var field_names = obj.classname ?
118 Object.keys(service.classes[obj.classname].field_map) :
126 var val = service.toHash(
127 angular.isFunction(obj[field]) ?
128 obj[field]() : obj[field],
132 if (flatten && angular.isObject(val)) {
133 angular.forEach(val, function(sub_val, key) {
134 var fname = field + '.' + key;
135 hash[fname] = sub_val;
138 } else if (val !== undefined) {
147 service.toTypedHash = function(obj) {
148 if (!angular.isObject(obj)) return obj; // arrays are objects
150 if (angular.isArray(obj)) { // NOTE: flatten arrays not supported
151 return obj.map(function(item) {return service.toTypedHash(item)});
154 var field_names = obj.classname ?
155 Object.keys(service.classes[obj.classname].field_map) :
160 angular.extend(hash, {
161 _classname : obj.classname
168 var val = service.toTypedHash(
169 angular.isFunction(obj[field]) ?
170 obj[field]() : obj[field]
173 if (val !== undefined) {
175 switch(service.classes[obj.classname].field_map[field].datatype) {
177 // aou fieldmapper objects get used as is because
178 // that's what egOrgSelector expects
179 // TODO we should probably make egOrgSelector more flexible
180 // in what it can bind to
181 hash[field] = obj[field]();
184 hash[field] = (val === null) ? val : new Date(val);
189 } else if (val == 'f') {
208 // returns a simple string key=value string of an IDL object.
209 service.toString = function(obj) {
212 service.classes[obj.classname].fields.sort(
213 function(a,b) {return a.name < b.name ? -1 : 1}),
215 s += field.name + '=' + obj[field.name]() + '\n';
221 // hash-to-IDL object translater. Does not support nested values.
222 service.fromHash = function(cls, hash) {
223 if (!service.classes[cls]) {
224 console.error('No such IDL class ' + cls);
228 var new_obj = new service[cls]();
229 angular.forEach(hash, function(val, key) {
230 if (!angular.isFunction(new_obj[key])) return;
231 new_obj[key](hash[key]);
237 service.fromTypedHash = function(hash) {
238 if (!angular.isObject(hash)) return hash;
239 if (angular.isArray(hash)) {
240 return hash.map(function(item) {return service.fromTypedHash(item)});
242 if (!hash._classname) return;
244 var new_obj = new service[hash._classname];
245 var fields = service.classes[hash._classname].field_map;
246 angular.forEach(fields, function(field) {
247 switch(field.datatype) {
249 if (angular.isFunction(hash[field.name])) {
250 new_obj[field.name] = hash[field.name];
252 new_obj[field.name](hash[field.name]);
256 if (hash[field.name] instanceof Date) {
257 new_obj[field.name](hash[field.name].toISOString());
261 if (hash[field.name] === true) {
262 new_obj[field.name]('t');
263 } else if (hash[field.name] === false) {
264 new_obj[field.name]('f');
268 new_obj[field.name](service.fromTypedHash(hash[field.name]));
271 new_obj.isnew(hash._isnew);
272 new_obj.ischanged(hash._ischanged);
273 new_obj.isdeleted(hash._isdeleted);
277 // Transforms a flattened hash (see toHash() or egGridFlatDataProvider)
280 // e.g. {"call_number.label" : "foo"} => {"call_number":{"label":"foo"}}
281 service.flatToNestedHash = function(obj) {
283 angular.forEach(obj, function(val, key) {
284 var parts = key.split('.');
287 for (var i = 0; i < parts.length; i++) {
289 if (i == parts.length - 1) {
290 sub_hash[part] = val;
295 sub_hash = sub_hash[part];
303 // Using IDL links, allow construction of a tree-ish data structure from
304 // the IDL2js web service output. This structure will be directly usable
305 // by the <treecontrol> directive
306 service.classTree = {
310 function _sort_class_fields (a,b) {
311 var aname = a.label || a.name;
312 var bname = b.label || b.name;
313 return aname > bname ? 1 : -1;
316 service.classTree.buildNode = function (cls, args) {
317 if (!cls) return null;
319 var n = service.classes[cls];
323 args = { label : n.label };
327 args.id = args.from + '.' + args.id;
329 return angular.extend( args, {
335 fields : n.fields.sort( _sort_class_fields ),
337 .filter( function(x) { return x.type == 'link'; } )
338 .sort( _sort_class_fields ),
343 service.classTree.fleshNode = function ( node ) {
344 if (node.children.length > 0)
345 return node; // already done already
348 node.links.sort( _sort_class_fields ),
350 var nlabel = n.label ? n.label : n.name;
352 service.classTree.buildNode(
366 service.classTree.setTop = function (cls) {
367 console.debug('setTop: '+cls);
368 return service.classTree.top = service.classTree.fleshNode(
369 service.classTree.buildNode(cls)
373 service.classTree.getTop = function () {
374 return service.classTree.top;