move code up one directory
[atutor.git] / jscripts / infusion / framework / core / js / DataBinding.js
1 var fluid_1_4=fluid_1_4||{};(function($,fluid){fluid.BINDING_ROOT_KEY="fluid-binding-root";fluid.findData=function(elem,name){while(elem){var data=$.data(elem,name);if(data){return data}elem=elem.parentNode}};fluid.bindFossils=function(node,data,fossils){$.data(node,fluid.BINDING_ROOT_KEY,{data:data,fossils:fossils})};fluid.boundPathForNode=function(node,fossils){node=fluid.unwrap(node);var key=node.name||node.id;var record=fossils[key];return record?record.EL:null};fluid.findForm=function(node){return fluid.findAncestor(node,function(element){return element.nodeName.toLowerCase()==="form"})};fluid.value=function(nodeIn,newValue){var node=fluid.unwrap(nodeIn);var multiple=false;if(node.nodeType===undefined&&node.length>1){node=node[0];multiple=true}if("input"!==node.nodeName.toLowerCase()||!/radio|checkbox/.test(node.type)){return newValue===undefined?$(node).val():$(node).val(newValue)}var name=node.name;if(name===undefined){fluid.fail("Cannot acquire value from node "+fluid.dumpEl(node)+" which does not have name attribute set")}var elements;if(multiple){elements=nodeIn}else{elements=document.getElementsByName(name);var scope=fluid.findForm(node);elements=$.grep(elements,function(element){if(element.name!==name){return false}return !scope||fluid.dom.isContainer(scope,element)})}if(newValue!==undefined){if(typeof (newValue)==="boolean"){newValue=(newValue?"true":"false")}$.each(elements,function(){this.checked=(newValue instanceof Array?$.inArray(this.value,newValue)!==-1:newValue===this.value)})}else{var checked=$.map(elements,function(element){return element.checked?element.value:null});return node.type==="radio"?checked[0]:checked}};fluid.applyChange=function(node,newValue,applier){node=fluid.unwrap(node);if(newValue===undefined){newValue=fluid.value(node)}if(node.nodeType===undefined&&node.length>0){node=node[0]}var root=fluid.findData(node,fluid.BINDING_ROOT_KEY);if(!root){fluid.fail("Bound data could not be discovered in any node above "+fluid.dumpEl(node))}var name=node.name;var fossil=root.fossils[name];if(!fossil){fluid.fail("No fossil discovered for name "+name+" in fossil record above "+fluid.dumpEl(node))}if(typeof (fossil.oldvalue)==="boolean"){newValue=newValue[0]?true:false}var EL=root.fossils[name].EL;if(applier){applier.fireChangeRequest({path:EL,value:newValue,source:node.id})}else{fluid.set(root.data,EL,newValue)}};fluid.pathUtil={};var getPathSegmentImpl=function(accept,path,i){var segment=null;if(accept){segment=""}var escaped=false;var limit=path.length;for(;i<limit;++i){var c=path.charAt(i);if(!escaped){if(c==="."){break}else{if(c==="\\"){escaped=true}else{if(segment!==null){segment+=c}}}}else{escaped=false;if(segment!==null){accept+=c}}}if(segment!==null){accept[0]=segment}return i};var globalAccept=[];fluid.pathUtil.getPathSegment=function(path,i){getPathSegmentImpl(globalAccept,path,i);return globalAccept[0]};fluid.pathUtil.getHeadPath=function(path){return fluid.pathUtil.getPathSegment(path,0)};fluid.pathUtil.getFromHeadPath=function(path){var firstdot=getPathSegmentImpl(null,path,0);return firstdot===path.length?null:path.substring(firstdot+1)};function lastDotIndex(path){return path.lastIndexOf(".")}fluid.pathUtil.getToTailPath=function(path){var lastdot=lastDotIndex(path);return lastdot===-1?null:path.substring(0,lastdot)};fluid.pathUtil.getTailPath=function(path){var lastdot=lastDotIndex(path);return fluid.pathUtil.getPathSegment(path,lastdot+1)};var composeSegment=function(prefix,toappend){for(var i=0;i<toappend.length;++i){var c=toappend.charAt(i);if(c==="."||c==="\\"||c==="}"){prefix+="\\"}prefix+=c}return prefix};fluid.pathUtil.composePath=function(prefix,suffix){if(prefix.length!==0){prefix+="."}return composeSegment(prefix,suffix)};fluid.pathUtil.matchPath=function(spec,path){var togo="";while(true){if(!spec||path===""){break}if(!path){return null}var spechead=fluid.pathUtil.getHeadPath(spec);var pathhead=fluid.pathUtil.getHeadPath(path);if(spechead!=="*"&&spechead!==pathhead){return null}togo=fluid.pathUtil.composePath(togo,pathhead);spec=fluid.pathUtil.getFromHeadPath(spec);path=fluid.pathUtil.getFromHeadPath(path)}return togo};fluid.model.mergeModel=function(target,source,applier){var copySource=fluid.copy(source);applier=applier||fluid.makeChangeApplier(source);if(!fluid.isPrimitive(target)){applier.fireChangeRequest({type:"ADD",path:"",value:target})}applier.fireChangeRequest({type:"MERGE",path:"",value:copySource});return source};fluid.model.isNullChange=function(model,request,resolverGetConfig){if(request.type==="ADD"){var existing=fluid.get(model,request.path,resolverGetConfig);if(existing===request.value){return true}}};fluid.model.applyChangeRequest=function(model,request,resolverSetConfig){var pen=fluid.model.getPenultimate(model,request.path,resolverSetConfig||fluid.model.defaultSetConfig);if(request.type==="ADD"||request.type==="MERGE"){if(request.path===""||request.type==="MERGE"){if(request.type==="ADD"){fluid.clear(pen.root)}$.extend(true,request.path===""?pen.root:pen.root[pen.last],request.value)}else{pen.root[pen.last]=request.value}}else{if(request.type==="DELETE"){if(request.path===""){fluid.clear(pen.root)}else{delete pen.root[pen.last]}}}};function bindRequestChange(that){that.requestChange=function(path,value,type){var changeRequest={path:path,value:value,type:type};that.fireChangeRequest(changeRequest)}}fluid.makeChangeApplier=function(model,options){options=options||{};var baseEvents={guards:fluid.event.getEventFirer(false,true),postGuards:fluid.event.getEventFirer(false,true),modelChanged:fluid.event.getEventFirer(false,false)};var that={model:model};function makeGuardWrapper(cullUnchanged){if(!cullUnchanged){return null}var togo=function(guard){return function(model,changeRequest,internalApplier){var oldRet=guard(model,changeRequest,internalApplier);if(oldRet===false){return false}else{if(fluid.model.isNullChange(model,changeRequest)){togo.culled=true;return false}}}};return togo}function wrapListener(listener,spec){var pathSpec=spec;var transactional=false;var priority=Number.MAX_VALUE;if(typeof (spec)!=="string"){pathSpec=spec.path;transactional=spec.transactional;if(spec.priority!==undefined){priority=spec.priority}}else{if(pathSpec.charAt(0)==="!"){transactional=true;pathSpec=pathSpec.substring(1)}}return function(changePath,fireSpec,accum){var guid=fluid.event.identifyListener(listener);var exist=fireSpec.guids[guid];if(!exist){var match=fluid.pathUtil.matchPath(pathSpec,changePath);if(match!==null){var record={changePath:changePath,pathSpec:pathSpec,listener:listener,priority:priority,transactional:transactional};if(accum){record.accumulate=[accum]}fireSpec.guids[guid]=record;var collection=transactional?"transListeners":"listeners";fireSpec[collection].push(record);fireSpec.all.push(record)}}else{if(accum){if(!exist.accumulate){exist.accumulate=[]}exist.accumulate.push(accum)}}}}function fireFromSpec(name,fireSpec,args,category,wrapper){return baseEvents[name].fireToListeners(fireSpec[category],args,wrapper)}function fireComparator(recA,recB){return recA.priority-recB.priority}function prepareFireEvent(name,changePath,fireSpec,accum){baseEvents[name].fire(changePath,fireSpec,accum);fireSpec.all.sort(fireComparator);fireSpec.listeners.sort(fireComparator);fireSpec.transListeners.sort(fireComparator)}function makeFireSpec(){return{guids:{},all:[],listeners:[],transListeners:[]}}function getFireSpec(name,changePath){var fireSpec=makeFireSpec();prepareFireEvent(name,changePath,fireSpec);return fireSpec}function fireEvent(name,changePath,args,wrapper){var fireSpec=getFireSpec(name,changePath);return fireFromSpec(name,fireSpec,args,"all",wrapper)}function adaptListener(that,name){that[name]={addListener:function(spec,listener,namespace){baseEvents[name].addListener(wrapListener(listener,spec),namespace)},removeListener:function(listener){baseEvents[name].removeListener(listener)}}}adaptListener(that,"guards");adaptListener(that,"postGuards");adaptListener(that,"modelChanged");function preFireChangeRequest(changeRequest){if(!changeRequest.type){changeRequest.type="ADD"}}var bareApplier={fireChangeRequest:function(changeRequest){that.fireChangeRequest(changeRequest,true)}};bindRequestChange(bareApplier);that.fireChangeRequest=function(changeRequest,defeatGuards){preFireChangeRequest(changeRequest);var guardFireSpec=defeatGuards?null:getFireSpec("guards",changeRequest.path);if(guardFireSpec&&guardFireSpec.transListeners.length>0){var ation=that.initiate();ation.fireChangeRequest(changeRequest,guardFireSpec);ation.commit()}else{if(!defeatGuards){var prevent=fireFromSpec("guards",guardFireSpec,[model,changeRequest,bareApplier],"listeners");if(prevent===false){return false}}var oldModel=model;if(!options.thin){oldModel={};fluid.model.copyModel(oldModel,model)}fluid.model.applyChangeRequest(model,changeRequest,options.resolverSetConfig);fireEvent("modelChanged",changeRequest.path,[model,oldModel,[changeRequest]])}};bindRequestChange(that);function fireAgglomerated(eventName,formName,changes,args,accpos){var fireSpec=makeFireSpec();for(var i=0;i<changes.length;++i){prepareFireEvent(eventName,changes[i].path,fireSpec,changes[i])}for(var j=0;j<fireSpec[formName].length;++j){var spec=fireSpec[formName][j];if(accpos){args[accpos]=spec.accumulate}var ret=spec.listener.apply(null,args);if(ret===false){return false}}}that.initiate=function(newModel){var cancelled=false;var changes=[];if(options.thin){newModel=model}else{newModel=newModel||{};fluid.model.copyModel(newModel,model)}var internalApplier={fireChangeRequest:function(changeRequest){preFireChangeRequest(changeRequest);fluid.model.applyChangeRequest(newModel,changeRequest,options.resolverSetConfig);changes.push(changeRequest)}};bindRequestChange(internalApplier);var ation={commit:function(){var oldModel;if(cancelled){return false}var ret=fireAgglomerated("postGuards","transListeners",changes,[newModel,null,internalApplier],1);if(ret===false){return false}if(options.thin){oldModel=model}else{oldModel={};fluid.model.copyModel(oldModel,model);fluid.clear(model);fluid.model.copyModel(model,newModel)}fireAgglomerated("modelChanged","all",changes,[model,oldModel,null],2)},fireChangeRequest:function(changeRequest){preFireChangeRequest(changeRequest);if(options.cullUnchanged&&fluid.model.isNullChange(model,changeRequest,options.resolverGetConfig)){return }var wrapper=makeGuardWrapper(options.cullUnchanged);var prevent=fireEvent("guards",changeRequest.path,[newModel,changeRequest,internalApplier],wrapper);if(prevent===false&&!(wrapper&&wrapper.culled)){cancelled=true}if(!cancelled){if(!(wrapper&&wrapper.culled)){fluid.model.applyChangeRequest(newModel,changeRequest,options.resolverSetConfig);changes.push(changeRequest)}}}};bindRequestChange(ation);return ation};return that};fluid.makeSuperApplier=function(){var subAppliers=[];var that={};that.addSubApplier=function(path,subApplier){subAppliers.push({path:path,subApplier:subApplier})};that.fireChangeRequest=function(request){for(var i=0;i<subAppliers.length;++i){var path=subAppliers[i].path;if(request.path.indexOf(path)===0){var subpath=request.path.substring(path.length+1);var subRequest=fluid.copy(request);subRequest.path=subpath;subAppliers[i].subApplier.fireChangeRequest(subRequest)}}};bindRequestChange(that);return that};fluid.attachModel=function(baseModel,path,model){var segs=fluid.model.parseEL(path);for(var i=0;i<segs.length-1;++i){var seg=segs[i];var subModel=baseModel[seg];if(!subModel){baseModel[seg]=subModel={}}baseModel=subModel}baseModel[segs[segs.length-1]]=model};fluid.assembleModel=function(modelSpec){var model={};var superApplier=fluid.makeSuperApplier();var togo={model:model,applier:superApplier};for(var path in modelSpec){var rec=modelSpec[path];fluid.attachModel(model,path,rec.model);if(rec.applier){superApplier.addSubApplier(path,rec.applier)}}return togo}})(jQuery,fluid_1_4);