diff --git a/README.md b/README.md index 2e86d2b..3db5893 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # To Do +- implement floating point UI numbers - UI needs a special UUID (not kInvalidId) to specify the 'root' UI element. See note in cwUi._createFromObj() - Look at 'BUG' warnings in cwNumericConvert.h. - cwObject must be able to parse without dynamic memory allocation into a fixed buffer @@ -33,7 +34,24 @@ This is easy to reproduce by simply decreasing the size of the buffers in the pr - (DONE) implement kTcpFl in cwTcpSocket.cpp +# UI Control Creation Protocol +The UI elements have four identifiers: + +uuId - An integer which is unique among all identifiers for a given cwUi object. +appId - A constant (enumerated) id assigned by the application. Unique among siblings. +jsId - A string id used by Javascript to identify a control. Unique among siblings. +jsUuId - An integer which is unique among all identifers for the browser representation of a given cwUi object. + +The 'jsId' is selected by the application when the object is created. +The 'jsUuId' is generated by the JS client when the UI element is created. +The 'uuId' is generated by the UI server when the JS client registers the control. +The 'appId' is assigned by the UI server when the JS client regsiters the control. + +Client sends 'init' message. +Server sends 'create' messages. +Client sends 'register' messages. +Server send' 'id_assign' messages. # Development Setup diff --git a/cwUi.cpp b/cwUi.cpp index 09a0c99..5c759e1 100644 --- a/cwUi.cpp +++ b/cwUi.cpp @@ -14,27 +14,48 @@ namespace cw { namespace ui { + typedef struct appIdMapRecd_str + { + struct appIdMapRecd_str* link; + unsigned parentAppId; + unsigned appId; + char* jsId; + } appIdMapRecd_t; + + typedef struct ele_str { struct ele_str* parent; // pointer to parent ele - or nullptr if this ele is attached to the root ui ele unsigned uuId; // UI unique id - automatically generated and unique among all elements that are part of this ui_t object. unsigned appId; // application assigned id - application assigned id - char* jsId; // javascript id + char* jsId; // javascript id } ele_t; typedef struct ui_str { - websockSrv::handle_t wssH; - unsigned eleAllocN; - unsigned eleN; - ele_t** eleA; - uiCallback_t cbFunc; - void* cbArg; + websockSrv::handle_t wssH; // websock server handle + unsigned eleAllocN; // size of eleA[] + unsigned eleN; // count of ele's in use + ele_t** eleA; // eleA[ eleAllocN ] + uiCallback_t cbFunc; // app. cb func + void* cbArg; // app. cb func arg. + appIdMapRecd_t* appIdMap; // map of application parent/child/js id's + char* buf; // buf[bufN] output message formatting buffer + unsigned bufN; // } ui_t; ui_t* _handleToPtr( handle_t h ) { return handleToPtr(h); } + void _print_eles( ui_t* p ) + { + for(unsigned i=0; ieleN; ++i) + { + ele_t* e = p->eleA[i]; + printf("%15s u:%i : u:%i a:%i %s\n",e->parent==nullptr?"" : e->parent->jsId,e->parent==nullptr? -1 :e->parent->uuId,e->uuId,e->appId,e->jsId); + } + } + rc_t _destroy( ui_t* p ) { rc_t rc = kOkRC; @@ -49,19 +70,74 @@ namespace cw mem::release(p->eleA[i]); } + appIdMapRecd_t* m = p->appIdMap; + while( m!=nullptr ) + { + appIdMapRecd_t* m0 = m->link; + mem::release(m->jsId); + mem::release(m); + m = m0; + } + mem::release(p->eleA); mem::release(p); return rc; } + appIdMapRecd_t* _findAppIdMap( ui_t* p, unsigned parentAppId, const char* jsId ) + { + appIdMapRecd_t* m = p->appIdMap; + for(; m != nullptr; m=m->link) + if( m->parentAppId==parentAppId && textCompare(jsId,m->jsId)==0 ) + return m; + return nullptr; + } + + appIdMapRecd_t* _findAppIdMap( ui_t* p, unsigned parentAppId, unsigned appId ) + { + appIdMapRecd_t* m = p->appIdMap; + for(; m != nullptr; m=m->link) + if( m->parentAppId==parentAppId && m->appId==appId ) + return m; + return nullptr; + } + + rc_t _allocAppIdMap( ui_t* p, unsigned parentAppId, unsigned appId, const char* jsId ) + { + rc_t rc = kOkRC; + + // The 'jsId' must be valid (or there is no reason to create the map. + // (since it will ultimately be used to locate the appId give the parentAppId and jsId) + if( jsId == nullptr || strlen(jsId) == 0 ) + return cwLogError(kInvalidIdRC,"Registered parent/child app id's must have a valid 'jsId'."); + + // verify that the parent/child pair is unique + if( _findAppIdMap(p,parentAppId,appId) != nullptr ) + return cwLogError(kDuplicateRC,"An attempt was made to register a duplicate parent/child appid pair. parentId:%i appId:%i jsId:'%s'.",parentAppId,appId,cwStringNullGuard(jsId)); + + // verify that the parent/js pair is unique + if( _findAppIdMap(p,parentAppId,jsId) != nullptr ) + return cwLogError(kDuplicateRC,"An attempt was made to register a duplicate parent app id/js id pair. parentId:%i appId:%i jsId:'%s'.",parentAppId,appId,cwStringNullGuard(jsId)); + + // allocate and link in a new appId map record + appIdMapRecd_t* m = mem::allocZ(); + m->parentAppId = parentAppId; + m->appId = appId; + m->jsId = mem::duplStr(jsId); + m->link = p->appIdMap; + p->appIdMap = m; + + return rc; + } + ele_t* _createEle( ui_t* p, ele_t* parent, unsigned appId, const char* jsId ) { ele_t* e = mem::allocZ(); - e->parent = parent; + e->parent = parent; e->uuId = p->eleN; - e->appId = appId; - e->jsId = mem::duplStr(jsId); + e->appId = appId; + e->jsId = mem::duplStr(jsId); if( p->eleN == p->eleAllocN ) { @@ -75,6 +151,7 @@ namespace cw return e; } + // Given a uuId return a pointer to the associated element. ele_t* _uuIdToEle( ui_t* p, unsigned uuId, bool errorFl=true ) { if( uuId >= p->eleN ) @@ -86,10 +163,26 @@ namespace cw return p->eleA[ uuId ]; } - - rc_t _websockSend( ui_t* p, const char* msg ) + // Given a parent UuId and a javascript id find the associated ele + ele_t* _parentUuId_JsId_ToEle( ui_t* p, unsigned parentUuId, const char* jsId, bool errorFl=true ) { - return websock::send( websockSrv::websockHandle( p->wssH ), kUiProtocolId, msg, strlen(msg) ); + for(unsigned i=0; ieleN; ++i) + if( ((p->eleA[i]->parent==nullptr && parentUuId == kRootUuId) || (p->eleA[i]->parent != nullptr && parentUuId == p->eleA[i]->parent->uuId)) && (strcmp(p->eleA[i]->jsId,jsId) == 0)) + return p->eleA[i]; + + if( errorFl ) + cwLogError(kInvalidIdRC,"The element with parent uuid:%i and jsId:%s is not found.",parentUuId,jsId); + + return nullptr; + } + + unsigned _findElementUuId( ui_t* p, const char* jsId ) + { + for(unsigned i=0; ieleN; ++i) + if( strcmp(p->eleA[i]->jsId,jsId) == 0 ) + return p->eleA[i]->uuId; + + return kInvalidId; } const char* _findEleJsId( ui_t* p, unsigned uuId ) @@ -101,6 +194,12 @@ namespace cw return nullptr; } + rc_t _websockSend( ui_t* p, unsigned wsSessId, const char* msg ) + { + return websock::send( websockSrv::websockHandle( p->wssH ), kUiProtocolId, wsSessId, msg, strlen(msg) ); + } + + // Print the attribute data value. template< typename T0 > unsigned format_attribute_data( char* buf, unsigned n, T0 t0 ) @@ -135,69 +234,137 @@ namespace cw } template< typename... ARGS> - rc_t _createOneEle( ui_t* p, unsigned& uuIdRef, const char* eleTypeStr, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, ARGS&&... args ) + rc_t _createOneEle( ui_t* p, unsigned& uuIdRef, const char* eleTypeStr, unsigned wsSessId, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, ARGS&&... args ) { // { op:create, parent:my_parent_id, value:{ button:{ jsId:my_jsId, appId:appId, uuId:uuId, class:clas, title:'my title' } } rc_t rc = kOkRC; const char* parentJsId = ""; ele_t* newEle = nullptr; ele_t* parentEle = nullptr; - const unsigned bufN = 1024; - char buf[ bufN ]; + //const unsigned bufN = 1024; // TODO: use preallocated buffer + //char buf[ bufN ]; uuIdRef = kInvalidId; + + if( parentUuId == kInvalidId ) + parentUuId = kRootUuId; + + // get the parent element + if(( parentEle = _uuIdToEle(p, parentUuId )) == nullptr ) + return cwLogError( kInvalidArgRC, "Unable to locate the parent element (id:%i).", parentUuId ); + + // get the parent jsId + parentJsId = parentEle->jsId; - // - if( parentUuId != kInvalidId ) - { - if(( parentEle = _uuIdToEle(p, parentUuId )) == nullptr ) - return cwLogError( kInvalidArgRC, "Unable to locate the parent element (id:%i).", parentUuId ); - - parentJsId = parentEle->jsId; - } - + // create the local representation of the new element newEle = _createEle( p, parentEle, appId, jsId ); - unsigned i = snprintf( buf, bufN, "{ \"op\":\"create\", \"parent\":\"%s\", \"children\":{ \"%s\":{ \"jsId\":\"%s\", \"appId\":%i, \"uuId\":%i, \"class\":\"%s\", \"title\":\"%s\" ", parentJsId, eleTypeStr, jsId, appId, newEle->uuId, clas, title ); + // form the create json message string + unsigned i = snprintf( p->buf, p->bufN, "{ \"op\":\"create\", \"parent\":\"%s\", \"children\":{ \"%s\":{ \"jsId\":\"%s\", \"appId\":%i, \"uuId\":%i, \"class\":\"%s\", \"title\":\"%s\" ", parentJsId, eleTypeStr, jsId, appId, newEle->uuId, clas, title ); - i = format_attributes(buf, bufN, i, std::forward(args)...); + // add the UI specific attributes + i += format_attributes(p->buf+i, p->bufN-i, 0, std::forward(args)...); - toText(buf+i, bufN-i, "}}}"); + // terminate the message + i += toText(p->buf+i, p->bufN-i, "}}}"); - printf("%s\n",buf); - - rc = _websockSend( p, buf ); + if( i >= p->bufN ) + return cwLogError(kBufTooSmallRC,"The UI message formatting buffer is too small. (size:%i bytes)", p->bufN); + + printf("%s\n",p->buf); + + // send the message + rc = _websockSend( p, wsSessId, p->buf ); uuIdRef = newEle->uuId; - return rc; } - - rc_t _createFromObj( ui_t* p, object_t* o, unsigned parentUuId ) + rc_t _decorateObj( ui_t* p, object_t* o ) { - rc_t rc = kOkRC; - object_t* pid; + rc_t rc = kOkRC; + const object_t* oo; + ele_t* parent_ele; + const char* jsId; - // BUG BUG BUG - if kInvalidId is used both to indicate a NULL valid - // and the root object then there is no way to override the parent - // that is coded into the file with the root object. A special - // rootId needs to be created. - - if((pid = o->find("parent",false)) != nullptr && parentUuId == kInvalidId ) + // find the parent pair + if((oo = o->find( "parent", kNoRecurseFl | kOptionalFl)) != nullptr ) { - // get the parent JS id from the cfg object - - // get the parent uuid from the JS id (verify that there is no ambiguity) - - // delete the 'parent' pair } - // form the msg string + // find the parent JsId + if((rc = oo->value(jsId)) != kOkRC ) + { + } + // find the parent element + //if((parent_ele = _jsIdToEle( p, jsId )) == nullptr ) + //{ + //} + + + + + } + + + rc_t _createFromObj( ui_t* p, object_t* o, unsigned wsSessId, unsigned parentUuId ) + { + rc_t rc = kOkRC; + const char* parentJsId = ""; + const int kBufN = 512; // TODO: preallocate this buffer as part of ui_t. + char buf0[ kBufN ]; + char buf1[ kBufN ]; + + // if a parentUuid was given ... + if( parentUuId != kInvalidId ) + { + // ... then find the associated JS id + if((parentJsId = _findEleJsId( p, parentUuId )) == nullptr ) + return cwLogError(kInvalidIdRC, "The JS id associated with the uuid '%i' could not be found for an resource object.", parentUuId ); + } + else // if no parentUuid was given then look for one in the resource + { + // get the parent JS id from the cfg object + rc = o->get("parent",parentJsId,kNoRecurseFl | kOptionalFl); + + switch(rc) + { + case kOkRC: + // get a pointer to the jsId from the local list (the copy in the object is about to be deleted) + parentJsId = _findEleJsId( p, _findElementUuId(p,parentJsId)); + + //remove the parent link + o->find("parent")->parent->free(); + break; + + case kLabelNotFoundRC: + parentJsId = _findEleJsId( p, kRootUuId ); + break; + + default: + rc = cwLogError(rc,"The resource object parent id '%s' could not be found.", parentJsId ); + goto errLabel; + } + } + + // form the msg string from the resource + if( o->to_string( buf0, kBufN ) >= kBufN ) + return cwLogError(kBufTooSmallRC,"The resource object string buffer is too small (buf bytes:%i).",kBufN); + + printf("buf0: %s\n",buf0); + if( snprintf( buf1, kBufN, "{ \"op\":\"create\", \"parent\":\"%s\", \"children\":%s }", parentJsId, buf0 ) >= kBufN ) + return cwLogError(kBufTooSmallRC,"The resource object string buffer is too small (buf bytes:%i).",kBufN); + + // send the msg string + printf("buf1: %s\n",buf1); + rc = _websockSend( p, wsSessId, buf1 ); + + + errLabel: return rc; } @@ -221,7 +388,7 @@ namespace cw if( s == nullptr || sscanf(msg, "value %i %c ",&eleUuId,&argType) != 2 ) { - cwLogError(kSyntaxErrorRC,"Invalid message from UI: %s.", msg ); + cwLogError(kSyntaxErrorRC,"Invalid message from UI: '%s'.", msg ); goto errLabel; } @@ -277,14 +444,123 @@ namespace cw return ele; } + + rc_t _send_app_id_msg( ui_t* p, unsigned wsSessId, ele_t* ele ) + { + rc_t rc = kOkRC; + + unsigned i = snprintf(p->buf,p->bufN,"{ \"op\":\"set_app_id\", \"parentUuId\":%i, \"jsId\":\"%s\", \"appId\":%i, \"uuId\":%i }", ele->parent->uuId, ele->jsId, ele->parent->appId, ele->appId ); + + if( i >= p->bufN ) + return cwLogError(kBufTooSmallRC,"The 'app_id' msg formatting buffer is too small (%i bytes).", p->bufN); + + if((rc = _websockSend( p, wsSessId, p->buf )) != kOkRC ) + return cwLogError(rc,"'app_id' msg transmission failed."); + + return rc; + } + + ele_t* _handle_register_msg( ui_t* p, unsigned wsSessId, const char* msg ) + { + printf("%s\n",msg); + return nullptr; + } - void _websockCb( void* cbArg, unsigned protocolId, unsigned connectionId, websock::msgTypeId_t msg_type, const void* msg, unsigned byteN ) + ele_t* _handle_register_msg0( ui_t* p, unsigned wsSessId, const char* msg ) + { + rc_t rc = kOkRC; + unsigned parentUuId = kInvalidId; + ele_t* parentEle = nullptr; + ele_t* ele = nullptr; + + const char* s0 = nextNonWhiteChar(msg + strlen("register")); + + const char* jsId = nextNonWhiteChar(nextWhiteChar(s0)); + + // verifity the message tokens + if( s0 == nullptr || jsId == nullptr ) + { + cwLogError(kSyntaxErrorRC, "'register' msg format error: '%s' is not a valid message.", cwStringNullGuard(msg) ); + goto errLabel; + } + + // verify the parentUuId parsing + if((rc = string_to_number(s0,parentUuId)) != kOkRC ) + { + cwLogError(kSyntaxErrorRC, "'register' msg parentUuId format error: '%s' does not contain a valid parentUuId.", cwStringNullGuard(msg) ); + goto errLabel; + } + + // get the parent ele + if((parentEle = _uuIdToEle( p, parentUuId)) == nullptr ) + { + cwLogError(kInvalidIdRC,"UI register msg parent element not found."); + goto errLabel; + } + + // if the child element does not already exist + if(( ele = _parentUuId_JsId_ToEle( p, parentUuId, jsId, false )) == nullptr ) + { + // look up the parent/jsId pair map + appIdMapRecd_t* m = _findAppIdMap( p, parentEle->appId, jsId ); + + // create the ele + ele = _createEle( p, parentEle, m==nullptr ? kInvalidId : m->appId, jsId ); + + printf("creating: parent uuid:%i js:%s \n", parentUuId,jsId); + + // notify the app of the new ele's uuid and appId + if( m != nullptr ) + _send_app_id_msg( p, wsSessId, ele ); + + } + else + { + printf("parent uuid:%i js:%s already exists.\n", parentUuId,jsId); + } + + if( ele != nullptr ) + _send_app_id_msg( p, wsSessId, ele ); + + return ele; + + errLabel: + return nullptr; + } + + + opId_t _labelToOpId( const char* label ) + { + typedef struct + { + opId_t id; + const char* label; + } map_t; + + map_t mapA[] = + { + { kConnectOpId, "connect" }, + { kInitOpId, "init" }, + { kValueOpId, "value" }, + { kRegisterOpId, "register" }, + { kDisconnectOpId, "disconnect" }, + { kEndAppIdUpdateOpId, "end_app_id_update" }, + { kInvalidOpId, "" }, + }; + + for(unsigned i=0; mapA[i].id != kInvalidOpId; ++i) + if( textCompare(label,mapA[i].label,strlen(mapA[i].label)) == 0 ) + return mapA[i].id; + + return kInvalidOpId; + + + } + + void _websockCb( void* cbArg, unsigned protocolId, unsigned wsSessId, websock::msgTypeId_t msg_type, const void* msg, unsigned byteN ) { ui_t* p = (ui_t*)cbArg; opId_t opId = kInvalidOpId; - unsigned eleUuId = kInvalidId; - unsigned eleAppId = kInvalidId; - unsigned parentEleAppId = kInvalidId; value_t value; switch( msg_type ) @@ -301,23 +577,54 @@ namespace cw { ele_t* ele; - if( textCompare((const char*)msg,"init",strlen("init")) == 0 ) + opId = _labelToOpId((const char*)msg); + + switch( opId ) { - opId = kInitOpId; - } - else - { - opId = kValueOpId; - if((ele = _parse_value_msg(p, value, (const char*)msg )) == nullptr ) - cwLogError(kOpFailRC,"UI Value message parse failed."); - else - { - eleUuId = ele->uuId; - eleAppId = ele->appId; - parentEleAppId = ele->parent == nullptr ? kInvalidId : ele->parent->appId; - } - } - } + case kInitOpId: + // Pass on the 'init' msg to the app. + p->cbFunc( p->cbArg, wsSessId, opId, kInvalidId, kInvalidId, kInvalidId, nullptr ); + + // The UI is initialized - begin the id update process + if( _websockSend( p, wsSessId, "{ \"op\":\"begin_app_id_update\" }" ) != kOkRC ) + cwLogError(kOpFailRC,"'begin_app_id_update' transmit failed."); + + break; + + case kValueOpId: + if((ele = _parse_value_msg(p, value, (const char*)msg )) == nullptr ) + cwLogError(kOpFailRC,"UI Value message parse failed."); + else + { + unsigned parentEleAppId = ele->parent == nullptr ? kInvalidId : ele->parent->appId; + + p->cbFunc( p->cbArg, wsSessId, opId, parentEleAppId, ele->uuId, ele->appId, &value ); + + } + break; + + + case kRegisterOpId: + _handle_register_msg(p, wsSessId, (const char*)msg ); + break; + + case kEndAppIdUpdateOpId: + _print_eles( p ); + cwLogInfo("App Id Update Complete."); + break; + + + case kInvalidOpId: + cwLogError(kInvalidIdRC,"The UI received a NULL op. id."); + break; + + default: + cwLogError(kInvalidIdRC,"The UI received an unknown op. id."); + break; + + } // switch opId + + } // kMessageTId break; default: @@ -325,8 +632,8 @@ namespace cw return; } - if( p->cbFunc != nullptr ) - p->cbFunc( p->cbArg, connectionId, opId, parentEleAppId, eleUuId, eleAppId, &value ); + + } } } @@ -340,10 +647,12 @@ cw::rc_t cw::ui::createUi( const char* dfltPageFn, unsigned websockTimeOutMs, unsigned rcvBufByteN, - unsigned xmtBufByteN ) + unsigned xmtBufByteN, + unsigned fmtBufByteN) { rc_t rc = kOkRC; - + ele_t* ele; + websock::protocol_t protocolA[] = { { "http", kHttpProtocolId, 0, 0 }, @@ -355,6 +664,9 @@ cw::rc_t cw::ui::createUi( if((rc = destroyUi(h)) != kOkRC ) return rc; + if( cbFunc == nullptr ) + return cwLogError(kInvalidArgRC,"The UI callback function must be a valid pointer."); + ui_t* p = mem::allocZ(); if((rc = websockSrv::create(p->wssH, _websockCb, p, physRootDir, dfltPageFn, port, protocolA, protocolN, websockTimeOutMs )) != kOkRC ) @@ -368,6 +680,15 @@ cw::rc_t cw::ui::createUi( p->eleN = 0; p->cbFunc = cbFunc; p->cbArg = cbArg; + p->buf = mem::allocZ(fmtBufByteN); + p->bufN = fmtBufByteN; + + // create the root element + if((ele = _createEle(p, nullptr, kRootEleAppId, "uiDivId" )) == nullptr || ele->uuId != kRootUuId ) + { + cwLogError(kOpFailRC,"The UI root element creation failed."); + goto errLabel; + } h.set(p); @@ -430,11 +751,12 @@ unsigned cw::ui::findElementAppId( handle_t h, unsigned parentUuId, const char* unsigned cw::ui::findElementUuId( handle_t h, unsigned parentUuId, const char* jsId ) { - ui_t* p = _handleToPtr(h); + ui_t* p = _handleToPtr(h); + ele_t* ele; + + if((ele = _parentUuId_JsId_ToEle(p, parentUuId, jsId )) != nullptr ) + return ele->uuId; - for(unsigned i=0; ieleN; ++i) - if( p->eleA[i]->parent->uuId==parentUuId && strcmp(p->eleA[i]->jsId,jsId) == 0 ) - return p->eleA[i]->uuId; return kInvalidId; } @@ -454,8 +776,15 @@ const char* cw::ui::findElementJsId( handle_t h, unsigned uuId ) return _findEleJsId(p,uuId); } +unsigned cw::ui::findElementUuId( handle_t h, const char* jsId ) +{ + ui_t* p = _handleToPtr(h); + + return _findElementUuId(p,jsId); +} -cw::rc_t cw::ui::createFromFile( handle_t h, const char* fn, unsigned parentUuId) + +cw::rc_t cw::ui::createFromFile( handle_t h, const char* fn, unsigned wsSessId, unsigned parentUuId) { ui_t* p = _handleToPtr(h); rc_t rc = kOkRC; @@ -463,13 +792,15 @@ cw::rc_t cw::ui::createFromFile( handle_t h, const char* fn, unsigned parent if((rc = objectFromFile( fn, o )) != kOkRC ) goto errLabel; - - if((rc = _createFromObj( p, o, parentUuId )) != kOkRC ) + + //o->print(); + + if((rc = _createFromObj( p, o, wsSessId, parentUuId )) != kOkRC ) goto errLabel; errLabel: if(rc != kOkRC ) - rc = cwLogError(rc,"UI from configuration the file '%s' failed.", cwStringNullGuard(fn)); + rc = cwLogError(rc,"UI instantiation from the configuration file '%s' failed.", cwStringNullGuard(fn)); if( o != nullptr ) o->free(); @@ -477,7 +808,7 @@ cw::rc_t cw::ui::createFromFile( handle_t h, const char* fn, unsigned parent return rc; } -cw::rc_t cw::ui::createFromText( handle_t h, const char* text, unsigned parentUuId) +cw::rc_t cw::ui::createFromText( handle_t h, const char* text, unsigned wsSessId, unsigned parentUuId) { ui_t* p = _handleToPtr(h); rc_t rc = kOkRC; @@ -486,12 +817,12 @@ cw::rc_t cw::ui::createFromText( handle_t h, const char* text, unsigned parentU if((rc = objectFromString( text, o )) != kOkRC ) goto errLabel; - if((rc = _createFromObj( p, o, parentUuId )) != kOkRC ) + if((rc = _createFromObj( p, o, wsSessId, parentUuId )) != kOkRC ) goto errLabel; errLabel: if(rc != kOkRC ) - rc = cwLogError(rc,"UI from configuration the string '%s' failed.", cwStringNullGuard(text)); + rc = cwLogError(rc,"UI instantiation failed from the configuration from string: '%s'.", cwStringNullGuard(text)); if( o != nullptr ) o->free(); @@ -499,34 +830,34 @@ cw::rc_t cw::ui::createFromText( handle_t h, const char* text, unsigned parentU return rc; } -cw::rc_t cw::ui::createDiv( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) -{ return _createOneEle( _handleToPtr(h), uuIdRef, "div", parentUuId, jsId, appId, clas, title ); } +cw::rc_t cw::ui::createDiv( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "div", wsSessId, parentUuId, jsId, appId, clas, title ); } -cw::rc_t cw::ui::createTitle( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) -{ return _createOneEle( _handleToPtr(h), uuIdRef, "option", parentUuId, jsId, appId, clas, title ); } +cw::rc_t cw::ui::createTitle( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "option", wsSessId, parentUuId, jsId, appId, clas, title ); } -cw::rc_t cw::ui::createButton( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) -{ return _createOneEle( _handleToPtr(h), uuIdRef, "button", parentUuId, jsId, appId, clas, title ); } +cw::rc_t cw::ui::createButton( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "button", wsSessId, parentUuId, jsId, appId, clas, title ); } -cw::rc_t cw::ui::createCheck( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, bool value ) -{ return _createOneEle( _handleToPtr(h), uuIdRef, "check", parentUuId, jsId, appId, clas, title, "value", value ); } +cw::rc_t cw::ui::createCheck( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, bool value ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "check", wsSessId, parentUuId, jsId, appId, clas, title, "value", value ); } -cw::rc_t cw::ui::createSelect( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) -{ return _createOneEle( _handleToPtr(h), uuIdRef, "select", parentUuId, jsId, appId, clas, title ); } +cw::rc_t cw::ui::createSelect( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "select", wsSessId, parentUuId, jsId, appId, clas, title ); } -cw::rc_t cw::ui::createOption( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) -{ return _createOneEle( _handleToPtr(h), uuIdRef, "option", parentUuId, jsId, appId, clas, title ); } +cw::rc_t cw::ui::createOption( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "option", wsSessId, parentUuId, jsId, appId, clas, title ); } -cw::rc_t cw::ui::createString( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, const char* value ) -{ return _createOneEle( _handleToPtr(h), uuIdRef, "string", parentUuId, jsId, appId, clas, title, "value", value ); } +cw::rc_t cw::ui::createString( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, const char* value ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "string", wsSessId, parentUuId, jsId, appId, clas, title, "value", value ); } -cw::rc_t cw::ui::createNumber( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, double value, double minValue, double maxValue, double stepValue, unsigned decpl ) -{ return _createOneEle( _handleToPtr(h), uuIdRef, "number", parentUuId, jsId, appId, clas, title, "value", value, "min", minValue, "max", maxValue, "step", stepValue, "decpl", decpl ); } +cw::rc_t cw::ui::createNumber( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, double value, double minValue, double maxValue, double stepValue, unsigned decpl ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "number", wsSessId, parentUuId, jsId, appId, clas, title, "value", value, "min", minValue, "max", maxValue, "step", stepValue, "decpl", decpl ); } -cw::rc_t cw::ui::createProgress( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, double value, double minValue, double maxValue ) -{ return _createOneEle( _handleToPtr(h), uuIdRef, "progress", parentUuId, jsId, appId, clas, title, "value", value, "min", minValue, "max", maxValue ); } +cw::rc_t cw::ui::createProgress( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, double value, double minValue, double maxValue ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "progress", wsSessId, parentUuId, jsId, appId, clas, title, "value", value, "min", minValue, "max", maxValue ); } -cw::rc_t cw::ui::createText( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +cw::rc_t cw::ui::createText( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) { rc_t rc= kOkRC; return rc; @@ -534,4 +865,19 @@ cw::rc_t cw::ui::createText( handle_t h, unsigned& uuIdRef, unsigned parentUuI +cw::rc_t cw::ui::registerAppIds( handle_t h, const appIdMap_t* map, unsigned mapN ) +{ + ui_t* p = _handleToPtr(h); + rc_t rc = kOkRC; + + for(unsigned i=0; iuiH, divUuId, kInvalidId, "myDivId", kDivId, "divClass", "My Panel" )) != kOkRC ) + if((rc = createDiv( p->uiH, divUuId, wsSessId, kInvalidId, "myDivId", kDivId, "divClass", "My Panel" )) != kOkRC ) goto errLabel; - if((rc = createButton( p->uiH, uuid, divUuId, "myBtnId", kBtnId, "btnClass", "Push Me" )) != kOkRC ) + if((rc = createButton( p->uiH, uuid, wsSessId, divUuId, "myBtnId", kBtnId, "btnClass", "Push Me" )) != kOkRC ) goto errLabel; - if((rc = createCheck( p->uiH, uuid, divUuId, "myCheckId", kCheckId, "checkClass", "Check Me", true )) != kOkRC ) + if((rc = createCheck( p->uiH, uuid, wsSessId, divUuId, "myCheckId", kCheckId, "checkClass", "Check Me", true )) != kOkRC ) goto errLabel; - if((rc = createSelect( p->uiH, selUuId, divUuId, "mySelId", kSelectId, "selClass", "Select" )) != kOkRC ) + if((rc = createSelect( p->uiH, selUuId, wsSessId, divUuId, "mySelId", kSelectId, "selClass", "Select" )) != kOkRC ) goto errLabel; - if((rc = createOption( p->uiH, uuid, selUuId, "myOpt0Id", kOption0Id, "optClass", "Option 0" )) != kOkRC ) + if((rc = createOption( p->uiH, uuid, wsSessId, selUuId, "myOpt0Id", kOption0Id, "optClass", "Option 0" )) != kOkRC ) goto errLabel; - if((rc = createOption( p->uiH, uuid, selUuId, "myOpt1Id", kOption1Id, "optClass", "Option 1" )) != kOkRC ) + if((rc = createOption( p->uiH, uuid, wsSessId, selUuId, "myOpt1Id", kOption1Id, "optClass", "Option 1" )) != kOkRC ) goto errLabel; - if((rc = createOption( p->uiH, uuid, selUuId, "myOpt2Id", kOption2Id, "optClass", "Option 2" )) != kOkRC ) + if((rc = createOption( p->uiH, uuid, wsSessId, selUuId, "myOpt2Id", kOption2Id, "optClass", "Option 2" )) != kOkRC ) goto errLabel; - if((rc = createString( p->uiH, uuid, divUuId, "myStringId", kStringId, "stringClass", "String", "a string value" )) != kOkRC ) + if((rc = createString( p->uiH, uuid, wsSessId, divUuId, "myStringId", kStringId, "stringClass", "String", "a string value" )) != kOkRC ) goto errLabel; - if((rc = createNumber( p->uiH, uuid, divUuId, "myNumberId", kNumberId, "numberClass", "Number", 10, 0, 100, 1, 0 )) != kOkRC ) + if((rc = createNumber( p->uiH, uuid, wsSessId, divUuId, "myNumberId", kNumberId, "numberClass", "Number", 10, 0, 100, 1, 0 )) != kOkRC ) goto errLabel; - if((rc = createProgress( p->uiH, uuid, divUuId, "myProgressId", kProgressId, "progressClass", "Progress", 5, 0, 10 )) != kOkRC ) + if((rc = createProgress( p->uiH, uuid, wsSessId, divUuId, "myProgressId", kProgressId, "progressClass", "Progress", 5, 0, 10 )) != kOkRC ) + goto errLabel; + + if((rc = createFromFile( p->uiH, p->uiCfgFn, wsSessId )) != kOkRC ) goto errLabel; - errLabel: return rc; } - rc_t _handleUiValueMsg( ui_test_t* p, unsigned connId, unsigned parentAppId, unsigned uuId, unsigned appId, const value_t* v ) + rc_t _handleUiValueMsg( ui_test_t* p, unsigned wsSessId, unsigned parentAppId, unsigned uuId, unsigned appId, const value_t* v ) { rc_t rc = kOkRC; @@ -103,28 +106,33 @@ namespace cw return rc; } - - rc_t _uiTestCallback( void* cbArg, unsigned connId, opId_t opId, unsigned parentAppId, unsigned uuId, unsigned appId, const value_t* v ) + + // This function is called by the websocket with messages comring from a remote UI. + rc_t _uiTestCallback( void* cbArg, unsigned wsSessId, opId_t opId, unsigned parentAppId, unsigned uuId, unsigned appId, const value_t* v ) { ui_test_t* p = (ui_test_t*)cbArg; switch( opId ) { case kConnectOpId: - cwLogInfo("Connect: connId:%i.",connId); + cwLogInfo("Connect: wsSessId:%i.",wsSessId); break; case kDisconnectOpId: - cwLogInfo("Disconnect: connId:%i.",connId); + cwLogInfo("Disconnect: wsSessId:%i.",wsSessId); break; case kInitOpId: - _uiTestCreateUi(p,connId); + _uiTestCreateUi(p,wsSessId); break; + case kRegisterOpId: + break; + + case kValueOpId: - _handleUiValueMsg( p, connId, parentAppId, uuId, appId, v ); + _handleUiValueMsg( p, wsSessId, parentAppId, uuId, appId, v ); break; case kInvalidOpId: @@ -141,7 +149,7 @@ namespace cw cw::rc_t cw::ui::test( ) { rc_t rc = kOkRC; - const char* physRootDir = "/home/kevin/src/cw_rt/html/uiTest"; + const char* physRootDir = "/home/kevin/src/cwtest/src/libcw/html/uiTest"; const char* dfltPageFn = "index.html"; int port = 5687; unsigned rcvBufByteN = 2048; @@ -150,6 +158,8 @@ cw::rc_t cw::ui::test( ) const unsigned sbufN = 31; char sbuf[ sbufN+1 ]; ui_test_t* ui = mem::allocZ(); + + ui->uiCfgFn = "/home/kevin/src/cwtest/src/libcw/html/uiTest/ui.cfg"; if((rc = createUi(ui->uiH, port, _uiTestCallback, ui, physRootDir, dfltPageFn, websockTimeOutMs, rcvBufByteN, xmtBufByteN )) != kOkRC ) return rc; diff --git a/html/uiTest/js/ui.js b/html/uiTest/js/ui.js index 9f2da47..8835f69 100644 --- a/html/uiTest/js/ui.js +++ b/html/uiTest/js/ui.js @@ -1,5 +1,6 @@ var _ws = null; -var _dfltParentId = "uiDivId"; +var _rootJsId = "uiDivId"; +var _nextEleId = 0; function set_app_title( suffix, className ) { @@ -164,8 +165,6 @@ function uiNumberSet( r ) } -function dom_id_to_ele( id ) -{ return document.getElementById(id); } function dom_child_by_id( parentEle, child_id ) { @@ -194,11 +193,6 @@ function dom_set_option_by_text( ele_id, text ) } } -function dom_set_checkbox( ele_id, fl ) -{ dom_id_to_ele(ele_id).checked = fl } - -function dom_get_checkbox( ele_id ) -{ return dom_id_to_ele(ele_id).checked } function dom_set_number( ele_id, val ) @@ -206,14 +200,53 @@ function dom_set_number( ele_id, val ) dom_id_to_ele(ele_id).value = val } + +//============================================================================== + +function dom_id_to_ele( id ) +{ return document.getElementById(id); } + +function dom_set_checkbox( ele_id, fl ) +{ dom_id_to_ele(ele_id).checked = fl } + +function dom_get_checkbox( ele_id ) +{ return dom_id_to_ele(ele_id).checked } + +function dom_create_ele( ele_type ) +{ + ele = document.createElement(ele_type); + ele.id = _nextEleId; + _nextEleId += 1; +} + +//============================================================================== + function ui_error( msg ) { console.log("Error: " + msg ) } +function ui_send_value( ele, typeId, value ) +{ + if( ele.hasOwnProperty('uuId') ) + ws_send("value " + ele.uuId + " " + typeId + " : " + value) + else + ui_error("A value msg send failed because the value had no UuId."); + +} + +function ui_send_bool_value( ele, value ) { ui_send_value(ele,'b',value); } +function ui_send_int_value( ele, value ) { ui_send_value(ele,'i',value); } +function ui_send_string_value( ele, value ) { ui_send_value(ele,'s',value); } + +function ui_send_register( ele ) +{ + ws_send("register " + ele.parentJsId + " " + ele.jsId + " " + ele.id + " " + ele.uuId + " " + ele.appId ) +} + function ui_print_children( eleId ) { - var childrenL = document.getElementById(eleId).children + var childrenL = dom_id_to_ele(eleId).children for(var i=0; i 0 ) p_ele.innerHTML = d.title @@ -296,7 +338,7 @@ function ui_create_div( parent_ele, d ) div_ele.appendChild( p_ele ) } - return div_ele + return div_ele; } function ui_create_title( parent_ele, d ) @@ -311,10 +353,10 @@ function ui_create_button( parent_ele, d ) if( ele != null ) { ele.innerHTML = d.title; - ele.onclick = function() { _ws.send("value " + this.uuId + " i : 1"); } + ele.onclick = function() { ui_send_int_value(this,1); } } - return ele + return ele; } function ui_create_check( parent_ele, d ) @@ -327,15 +369,14 @@ function ui_create_check( parent_ele, d ) dom_set_checkbox(ele.id, d.value ); - ele.onclick = function() { _ws.send("value" + this.uuId + " b : " + dom_get_checkbox(this.id)); } + ele.onclick = function() { ui_send_bool_value(this,dom_get_checkbox(this.id)); } } + return ele; } function ui_on_select( ele ) { - var s = "value " + ele.uuId + " i : " + ele.options[ ele.selectedIndex ].appId - - _ws.send( s ); + ui_send_int_value(ele,ele.options[ ele.selectedIndex ].appId); } function ui_create_select( parent_ele, d ) @@ -365,7 +406,7 @@ function ui_create_string( parent_ele, d ) if( ele != null ) { ele.value = d.value; - ele.addEventListener('keyup', function(e) { if(e.keyCode===13){ _ws.send("value" + this.uuId + " s : " + this.value + "\0");} } ); + ele.addEventListener('keyup', function(e) { if(e.keyCode===13){ ui_send_string_value(this, this.value); }} ); } } @@ -377,7 +418,9 @@ function ui_number_keyup( e ) var ele = dom_id_to_ele(e.target.id) console.log(ele.value) if( ele != null ) - _ws.send("value" + ele.uuId + " i : " + ele.value); + { + ui_send_int_value(ele,ele.value); + } } } @@ -394,7 +437,7 @@ function ui_create_number( parent_ele, d ) ele.decpl = d.decpl; ele.addEventListener('keyup', ui_number_keyup ); } - + return ele; } function ui_set_progress( ele_id, value ) @@ -415,70 +458,130 @@ function ui_create_progress( parent_ele, d ) ele.minValue = d.min; ui_set_progress( ele.id, d.value ); } + return ele } function ui_create( parentId, ele_type, d ) { var parent_ele = ui_get_parent(parentId); + var ele = null; if( parent_ele != null ) { + d.parentJsId = parentId + switch( ele_type ) { case "div": - ui_create_div( parent_ele, d ) + ele = ui_create_div( parent_ele, d ) break; case "title": - ui_create_title( parent_ele, d ) + ele = ui_create_title( parent_ele, d ) break; case "button": - ui_create_button( parent_ele, d ) + ele = ui_create_button( parent_ele, d ) break; case "check": - ui_create_check( parent_ele, d ) + ele = ui_create_check( parent_ele, d ) break; case "select": - ui_create_select( parent_ele, d ); + ele = ui_create_select( parent_ele, d ); break; case "option": - ui_create_option( parent_ele, d ); + ele = ui_create_option( parent_ele, d ); break; case "string": - ui_create_string( parent_ele, d ); + ele = ui_create_string( parent_ele, d ); break; case "number": - ui_create_number( parent_ele, d ); + ele = ui_create_number( parent_ele, d ); break; case "progress": - ui_create_progress( parent_ele, d ); + ele = ui_create_progress( parent_ele, d ); break; default: ui_error("Unknown UI element type: " + ele_type ) } + + if( d.hasOwnProperty("children") ) + { + for (const ele_type in d.children) + ui_create( d.jsId, ele_type, d.children[ele_type] ) + } } } -function ws_send( d ) +function ui_uuid_to_ele( uuId, ele=null ) +{ + if( ele == null ) + ele = dom_id_to_ele( _rootJsId ) + + if( ele.hasOwnProperty('uuId') ) + { + if( ele.uuId == uuId ) + return ele + + for(var i=0; i