github.com/vugu/vugu@v0.3.5/domrender/callback-manager.go (about) 1 package domrender 2 3 import ( 4 "fmt" 5 6 "github.com/vugu/vugu" 7 js "github.com/vugu/vugu/js" 8 ) 9 10 // callbackManager handles the element lifecycle related events for browser DOM elements in order to 11 // implement vg-js-create and vg-js-populate and anything else like this. 12 type callbackManager struct { 13 nextCallbackID uint32 // large enough and representable as float64 without loss 14 callbackInfoMap map[uint32]callbackInfo 15 } 16 17 type callbackInfoType int 18 19 const ( 20 callbackCreate = iota + 1 21 callbackPopulate 22 ) 23 24 type callbackInfo struct { 25 typ callbackInfoType // create or populate 26 f vugu.JSValueHandler // to be called when we get the js.Value 27 el js.Value // the js element 28 // for populate, the ID of the corresponding create callbackInfo 29 // (so we can grab the element from it) 30 createID uint32 31 } 32 33 // startRender prepares for the next render cycle 34 func (cm *callbackManager) startRender() { 35 cm.nextCallbackID = 1 36 if l := len(cm.callbackInfoMap); l > 0 { 37 cm.callbackInfoMap = make(map[uint32]callbackInfo, l) 38 } else { 39 cm.callbackInfoMap = make(map[uint32]callbackInfo) 40 } 41 } 42 43 func (cm *callbackManager) doneRender() { 44 } 45 46 // sets the create and populate handlers and returns a callbackID for each, 47 // if no instruction is needed for something then 0 will be returned for its ID 48 func (cm *callbackManager) addCreateAndPopulateHandlers(create, populate vugu.JSValueHandler) (uint32, uint32) { 49 50 if create == nil && populate == nil { 51 return 0, 0 52 } 53 54 // for either one to work, we need the element reference, so we always register the first one 55 // (even if the create function is empty) 56 cid := cm.nextCallbackID 57 cm.nextCallbackID++ 58 cm.callbackInfoMap[cid] = callbackInfo{ 59 typ: callbackCreate, 60 f: create, 61 } 62 63 // if populate is not nil then set it up too with it's own ID and it's createID pointing 64 // back to the other one (so it can get at the js.Value later) 65 var pid uint32 66 if populate != nil { 67 pid = cm.nextCallbackID 68 cm.nextCallbackID++ 69 cm.callbackInfoMap[pid] = callbackInfo{ 70 typ: callbackPopulate, 71 f: populate, 72 createID: cid, 73 } 74 } 75 76 return cid, pid 77 } 78 79 // callback is call when we get a callback from the render script 80 func (cm *callbackManager) callback(this js.Value, args []js.Value) interface{} { 81 82 if len(args) < 1 { 83 panic(fmt.Errorf("no args passed to callbackManager.callback")) 84 } 85 86 cbIDVal := args[0] 87 cbID := uint32(cbIDVal.Int()) 88 if cbID == 0 { 89 panic(fmt.Errorf("callbackManager.callback got zero callback ID")) 90 } 91 92 cbInfo := cm.callbackInfoMap[cbID] 93 switch cbInfo.typ { 94 95 case callbackCreate: 96 jsVal := args[1] 97 cbInfo.el = jsVal 98 cm.callbackInfoMap[cbID] = cbInfo 99 // f can be nil when vg-js-populate is set but vg-js-create is not, 100 // we still need the element to be recorded above 101 if cbInfo.f != nil { 102 cbInfo.f.JSValueHandle(jsVal) 103 } 104 105 case callbackPopulate: 106 createID := cbInfo.createID 107 if createID == 0 { 108 panic(fmt.Errorf("callbackManager.callback handling populate found createID==0")) 109 } 110 createInfo := cm.callbackInfoMap[createID] 111 jsVal := createInfo.el 112 if jsVal.IsUndefined() { // if we got all the way here, there should always be a value 113 panic(fmt.Errorf("callbackManager.callback handling populate got undefined jsVal")) 114 } 115 // and f should always be set, panic if not so we can track down why 116 if cbInfo.f == nil { 117 panic(fmt.Errorf("callbackManager.callback handling populate got nil JSvalueHandler")) 118 } 119 cbInfo.f.JSValueHandle(jsVal) 120 121 default: 122 panic(fmt.Errorf("unknown callback type %#v", cbInfo.typ)) 123 124 } 125 126 return nil 127 }