15Code Listing for Updates to the Customization Package from Version 2.0

Code Listing for Updates to the Customization Package from Version 2.0

This chapter lists some of the code updates required when updating the customization package from Oracle CRM On Demand Desktop Version 2.0. The appendix includes the following topics:

XML Code Addition for the od_meta_info.xml File

Insert the following XML code inside the <root> element in the od_meta_info.xml file, as described in Step k in the topic Setting Up Books for a Customized Package from Version 2.0:

<object TypeId='BookRaw' Label='BookRaw' LabelPlural='Books' ViewMode='Sales Rep' 
IntObjName='Book' XmlElemName='Book' XmlCollectionElemName='ListOfBook' 
DeleteIsProhibited="yes">
   <direct_ws_params DataNS="urn:/crmondemand/xml/Book/Query" 
InputElemname="BookQueryPage_Input" InputNS="urn:crmondemand/ws/ecbs/book/" 
OutputElemname="BookQueryPage_Output" OutputNS="urn:crmondemand/ws/ecbs/book/" 
SOAPAction="document/urn:crmondemand/ws/ecbs/book/:BookQueryPage"  
WrapQBEVals="false" PagingSupported="true" />
   <field Name="BookName" Label="BookName" DataType="DTYPE_TEXT" CRMName="Name" />
   <field Name="BookType" Label="BookType" DataType="DTYPE_TEXT" CRMName="Book 
Type" />
   <field Name="CanContainDataFlag" Label="CanContainDataFlag" 
DataType="DTYPE_BOOL" CRMName="Can Contain Data Flag" />
   <field Name="Description" Label="Description" DataType="DTYPE_TEXT" />
   <field Name="Id" Label="Id" DataType="DTYPE_TEXT" IsPrimaryKey="yes" 
IsFilterable='no' IsHidden="yes" />
   <field Name="ModId" Label="ModId" DataType="DTYPE_INTEGER"  IsTimestamp="yes" 
IsFilterable='no' IsHidden="yes" />
   <field Name="ParentBookId" Label="ParentBookId" DataType="DTYPE_TEXT" 
IsFilterable='no' />
   <field Name="ParentBookName" Label="ParentBookName" DataType="DTYPE_TEXT" 
CRMName="Parent Book Name" />
</object>

<!-- 
This type draws its data from BookPL picklist, which in turn is based on Book type.
It was introduced because currently platform caches picklists, so we will not see 
any picklists updates while OL instance lives.
Book type could be used directly instead, but that would lead to duplicated Book 
downloads (picklist is always loaded and then we would have to execute query_changes/
query_objects/... calls for that same type).
Existence if this type allows to just take existing data from the connector picklists 
cache.
-->
<object TypeId='Book' Label='Book' LabelPlural='BookPLBased' >
   <svc_params Type="PicklistBased" Path="[Book]@[::Defaults]" />
   <field Name="Id" Label="Id" DataType="DTYPE_ID" IsCalculated="true" 
Formula=":[:(Value):]" IsPrimaryKey="yes" IsFilterable='no' IsHidden="yes"/>
   <field Name="BookId" Label="BookId" DataType="DTYPE_TEXT" IsCalculated="true" 
Formula=":[:(Value):]" IsFilterable='no'/>
   <field Name="BookName" Label="BookName" DataType="DTYPE_TEXT" 
IsCalculated="true" Formula=":[:(Label):]" IsFilterable='no'/>
   <field Name="Value" Label="Value" DataType="DTYPE_TEXT" />
   <field Name="Label" Label="Label" DataType="DTYPE_TEXT" />
   <field Name="ParentBookName" Label="ParentBookName" DataType="DTYPE_TEXT" />
   <field Name="ParentBookId" Label="ParentBookId" DataType="DTYPE_TEXT" />
   <field Name="BookType" Label="BookType" DataType="DTYPE_TEXT" />
   <field Name="CanContainDataFlag" Label="CanContainDataFlag" 
DataType="DTYPE_BOOL" />
   <field Name="OrderNumber" Label="OrderNumber" DataType="DTYPE_TEXT" />
</object>
<object TypeId='BookDefaults' Label='BookDefaults' LabelPlural='BookDefaults' >
   <svc_params Type="PicklistBased" Path="[TypeDefaultBook]@[::Defaults]" />
   <field Name="Id" Label="Id" DataType="DTYPE_ID" IsCalculated="true" 
Formula=":[:(Value):]" IsPrimaryKey="yes" IsFilterable='no' IsHidden="yes"/>
   <field Name="Value" Label="Value" DataType="DTYPE_TEXT" />
   <field Name="TypeName" Label="TypeName" DataType="DTYPE_ID" IsCalculated="true" 
Formula=":[:(Value):]" IsFilterable='no'/>
   <field Name="Label" Label="Label" DataType="DTYPE_TEXT" />
   <field Name="BookId" Label="BookId" DataType="DTYPE_TEXT" />
   <field Name="IncludeSubBooks" Label="IncludeSubBooks" DataType="DTYPE_BOOL" />
   <field Name="OrderNumber" Label="OrderNumber" DataType="DTYPE_TEXT" />
</object>
<object TypeId="Book.Type.Association" Label="Book.Type.Association" 
LabelPlural="Book.Type.Associations" >
   <svc_params Type="SettingsBased" Path="RemoteConnectorStorage/
:[:(storage_id):]/Types" />
   <field Name="BookId" Label="BookId" DataType="DTYPE_TEXT" />
   <field Name="TypeName" Label="TypeName" DataType="DTYPE_TEXT" />
   <field Name="IncludeSubbooks" Label="IncludeSubBooks" DataType="DTYPE_BOOL" />
</object>
<object TypeId='RequiredFields' Label='RequiredFields' 
LabelPlural='RequiredFields'>
   <svc_params Type="PicklistBased" Path="[RequiredFields]@[::Defaults]" />
   <field Name="Id" Label="Id" DataType="DTYPE_ID" IsCalculated="true" 
Formula=":[:(Value):]@:[:(TypeName):]" IsPrimaryKey="yes" IsFilterable='no' 
IsHidden="yes"/>
   <field Name="FieldName" Label="FieldName" DataType="DTYPE_TEXT" 
IsCalculated="true" Formula=":[:(Value):]" IsFilterable='no'/>
   <field Name="Value" Label="Value" DataType="DTYPE_TEXT" />
   <field Name="Label" Label="Label" DataType="DTYPE_TEXT" />
   <field Name="TypeName" Label="TypeName" DataType="DTYPE_TEXT" />
   <field Name="Required" Label="Required" DataType="DTYPE_BOOL" />
   <field Name="OrderNumber" Label="OrderNumber" DataType="DTYPE_TEXT" />
</object>

<object TypeId='CustomRecordType' Label='CustomRecordType' 
LabelPlural='CustomRecordTypes' XmlElemName='CustomRecordType' 
DeleteIsProhibited="yes" >
   <direct_ws_params DataNS="urn:/crmondemand/xml/customrecordtype/query" 
InputElemname="CustomRecordTypeRead_Input" InputNS="urn:crmondemand/ws/odesabs/
customrecordtype/" OutputElemname="CustomRecordTypeRead_Output" 
OutputNS="urn:crmondemand/ws/odesabs/customrecordtype/" SOAPAction="document/
urn:crmondemand/ws/odesabs/CustomRecordType/:CustomRecordTypeRead" 
WrapQBEVals="false" SuppressListOfElem="request" PagingSupported="false" URL="/
Services/cte/CustomRecordTypeService" />
   <field Name="AccessObjName" Label="AccessObjName" DataType="DTYPE_TEXT" />
   <field Name="Name" Label="Name" DataType="DTYPE_TEXT" IsPrimaryKey = "yes" />
   <field Name="IncludeAll" Label="IncludeAll" DataType="DTYPE_BOOL" 
FillOnQueryWith="true" />
</object>

<object TypeId='FieldSet' Label='FieldSet' LabelPlural='FieldSets' ViewMode='Sales 
Rep' XmlElemName='FieldSet' DeleteIsProhibited="yes" >
   <direct_ws_params DataNS="urn:/crmondemand/xml/fieldmanagement/query" 
InputElemname="FieldManagementRead_Input" InputNS="urn:crmondemand/ws/odesabs/
fieldmanagement/" OutputElemname="FieldManagementRead_Output" 
OutputNS="urn:crmondemand/ws/odesabs/fieldmanagement/" SOAPAction="document/
urn:crmondemand/ws/odesabs/FieldManagement/:FieldManagementRead" 
WrapQBEVals="false" SuppressListOfElem="request" PagingSupported="false" URL="/
Services/cte/FieldManagementService" />
   <field Name="ObjectName" Label="ObjectName" DataType="DTYPE_TEXT" IsPrimaryKey = 
"yes" />
   <field Name="IncludeAll" Label="IncludeAll" DataType="DTYPE_BOOL" />
</object>

<object TypeId='FieldSet.Fields' Label='FieldSet.Fields' 
LabelPlural='FieldSet.Fields' ViewMode='Sales Rep' XmlElemName='Field' 
XmlCollectionElemName='ListOfFields' DeleteIsProhibited="yes" >
   <field Name="TypeName" Label="TypeName" ActualFldName="ObjectName" 
ActualObjTypeId="FieldSet" IsRequired="yes" DataType="DTYPE_TEXT" IsNullable="no" 
IsFilterable="no" IsRefObjId="yes" RefObjTypeId="FieldSet" RefObjIsParent="yes" 
IsFake="yes" />
   <field Name="IncludeAll" Label="IncludeAll" ActualFldName="IncludeAll" 
ActualObjTypeId="FieldSet" DataType="DTYPE_TEXT" IsFilterable="no" IsFake="yes" />
   <field Name="Name" Label="Name" DataType="DTYPE_TEXT" IsPrimaryKey = "yes" />
   <field Name="IntegrationTag" Label="IntegrationTag" DataType="DTYPE_TEXT" 
IsPrimaryKey = "yes" />
   <field Name="GenericIntegrationTag" Label="GenericIntegrationTag" 
DataType="DTYPE_TEXT" />
   <field Name="Required" Label="Required" DataType="DTYPE_BOOL" />
</object>

<object TypeId="User.DefaultBooks" Label="User.DefaultBooks" 
LabelPlural="User.DefaultBooks" ViewMode="Sales Rep" 
XmlElemName="UserDefaultBookPerType" 
XmlCollectionElemName="ListOfUserDefaultBookPerType" DeleteIsProhibited="yes" >
   <field Name="AccessObjectId" Label="AccessObjectId" DataType="DTYPE_TEXT" />
   <field Name="AccessObjectName" Label="AccessObjectName" DataType="DTYPE_TEXT" />
   <field Name="BookDescription" Label="BookDescription" DataType="DTYPE_TEXT" />
   <field Name="BookId" Label="BookId" DataType="DTYPE_TEXT" IsRefObjId="yes" 
RefObjTypeId="BookRaw"  IsFilterable='no' />
   <field Name="BookName" Label="BookName" DataType="DTYPE_TEXT" />
   <field Name="Id" Label="Id" DataType="DTYPE_ID"  IsPrimaryKey="yes" 
IsFilterable='no' IsHidden="yes" />
   <field Name="IncludeSubBooks" Label="IncludeSubBooks" DataType="DTYPE_BOOL" />
   <field Name="ModId" Label="ModId" DataType="DTYPE_INTEGER"  IsTimestamp="yes" 
IsFilterable='no' IsHidden="yes" />
   <field Name="UserId" Label="UserId" ActualFldName="Id" ActualObjTypeId="User" 
IsRequired="yes" DataType="DTYPE_ID" IsNullable="no" IsFilterable="no" 
IsRefObjId="yes" RefObjTypeId="User" RefObjIsParent="yes" IsFake="yes" />
   <!-- inherited from parent -->
   <field Name="CurrencyCode" Label="Currency Code" DataType="DTYPE_TEXT" 
ActualFldName="CurrencyCode" ActualObjTypeId="User" IsFilterable="no" />
   <!-- <field Name="DefaultBookId" Label="DefaultBookId" 
ActualFldName="DeftBookId" ActualObjTypeId="User" DataType="DTYPE_TEXT" 
IsFilterable="no" /> -->
   <field Name="DefaultBookName" Label="DefaultBookName" 
ActualFldName="DefaultBookName" ActualObjTypeId="User" DataType="DTYPE_TEXT" 
IsFilterable="no" />
   <field Name="FullName" Label="Full Name" DataType="DTYPE_TEXT" CRMName="Full 
Name" ActualFldName="FullName" ActualObjTypeId="User"  IsFilterable="no" />
   field Name="LanguageCode" Label="Language Code" ActualFldName="LanguageCode" 
ActualObjTypeId="User" DataType="DTYPE_TEXT" IsFilterable="no" />
   <field Name="PrimaryPositionId" Label="PrimaryPositionId" DataType="DTYPE_TEXT" 
ActualFldName="PrimaryPositionId" ActualObjTypeId="User" IsFilterable="no" />
   <field Name="RoleId" Label="Role Id" ActualFldName="RoleId" 
ActualObjTypeId="User" DataType="DTYPE_TEXT" IsFilterable="no" />
   <field Name="SalesProcessId" Label="Sales Process Id" 
ActualFldName="SalesProcessId" ActualObjTypeId="User" DataType="DTYPE_TEXT" 
IsFilterable="no" />
   <field Name="SalesProcessName" Label="Sales Process Name" 
ActualFldName="SalesProcessName" ActualObjTypeId="User" DataType="DTYPE_TEXT" 
IsFilterable="no" />
   <field Name="UserSignInId" Label="User Sign In Id" DataType="DTYPE_TEXT" 
IsHidden="yes" ActualFldName="UserSignInId" ActualObjTypeId="User" />
</object>

<picklist TypeId='TypeDefaultBookPL' SrcObjectTypeId='User.DefaultBooks' 
ValueFldName='AccessObjectName' LabelFldName='AccessObjectName' Artificial="true">
   <extra_src_fldname Visible="true">BookId</extra_src_fldname>
   <extra_src_fldname Visible="true">IncludeSubBooks</extra_src_fldname>
</picklist>

<picklist TypeId='BookPL' SrcObjectTypeId='BookRaw' ValueFldName='Id' 
LabelFldName='BookName' OrderByExpression=':[:(BookName):]'>
   <extra_src_fldname Visible="true">ParentBookName</extra_src_fldname>
   <extra_src_fldname Visible="true">ParentBookId</extra_src_fldname>
   <extra_src_fldname Visible="true">BookType</extra_src_fldname>
   <extra_src_fldname Visible="true">CanContainDataFlag</extra_src_fldname>
</picklist>

<picklist TypeId='FieldsInfoPL' SrcObjectTypeId='FieldSet.Fields' 
ValueFldName='IntegrationTag' LabelFldName='IntegrationTag' 
OrderByExpression=':[:(IntegrationTag):]'>
   <extra_src_fldname Visible="true">TypeName</extra_src_fldname>
   <extra_src_fldname Visible="true">Required</extra_src_fldname>
   <extra_src_fldname Visible="false" OutVal="true">IncludeAll<
   extra_src_fldname>
</picklist>

JavaScript Code Addition for the od_helpers.js File

Insert the following JavaScript code after the first line of the file, as described in Step 8 in the topic Setting Up Books for a Customized Package from Version 2.0:

include("form_helpers.js", "form_helpers");
var allow_includesubbooks =
{
//   "Account": true
};
function book_selector(ctx, options)
{
   var session = ctx.session,
      ui = ctx.ui,
      dlg,
      tree,
      image = "",
      on_changed;
      
   var btn = options.ctrl_btn,
      edit = options.ctrl_edit,
      id_field = options.field || "BookId",
      ownership_mode = options.mode,
      context = options.context,
      in_tree = {};
   
   edit.enabled = false;
   on_changed = new helpers.signal();
   this.on_changed = on_changed;
   this.refresh = refresh;
   if(context == "inspector")
   {
      ctx.events.connect(btn, "on_click", show);
      dlg = ctx.ui.create_dialog(0, "select_book_dialog");
      tree = dlg.books_tree;
      ctx.events.connect(tree, "on_node_activated", select);
      ctx.events.connect(dlg.btn_cancel, "on_click", on_cancel);
      ctx.events.connect(dlg.btn_ok, "on_click", on_save);
      ctx.events.connect(dlg.btn_clear, "on_click", on_clear);
      dlg.btn_clear.visible = true;
      btn.enabled = ownership_mode != "owner";
      this.refresh();
   }
      
   this.set_visible = function(visible)
   {
      edit.visible = btn.visible = visible;
   }
   
   function fill_tree(parent_book_id, parent_node, seelected_book_id)
   {
      parent_book_id = parent_book_id || "";
      parent_node = parent_node || tree;
      var add_node = function(item)
      {
         var book_id = item.Value
            book_name = item.Label;
         image = "book_" + (item.CanContainDataFlag ? "can" : "cannot") + 
"_contain_data_icon";
         var node = parent_node.nodes.add(book_name, image, {"id": book_id});
         if(book_id == seelected_book_id && book_id)
            tree.selected = node;
         in_tree[book_id] = true;
         fill_tree(book_id, node, seelected_book_id);
      }
      for_each_child(parent_book_id, add_node);
      // handle orphans
      if(parent_book_id == "")   // top level
      {
         helpers.for_each(session.find_items("Book", session.empty_criteria), 
function(item)
         {
            var has_no_parent = session.find_item("Book", 
session.create_expression("Value", "eq", item.ParentBookId)) == null;
            if(!in_tree[item.Value] && has_no_parent)
               add_node(item);
         });
      }
   };
   function show()
   {
      dlg.visible = true;
      tree.nodes.remove_all();
      in_tree = {};
      fill_tree(null, null, ctx.item_ex.get_property(id_field));
      // Tree refresh workaround
      dlg.btn_clear.set_focus();
      tree.set_focus();
   }
   
   function refresh()
   {
      edit.value = id_to_label(ctx.item_ex.get_property(id_field));
   }
   
   function on_cancel(){dlg.close();}
   
   function on_save(){
      var selected_node = tree.selected;
      select(selected_node);   
   }
   

   function on_clear(){
      tree.selected = null;
   }
   
   function select(node)
   {
      edit.value = node != null ? node.name : "";
      var book_id = node != null ? node.data.id : "";
      ctx.item_ex.set_property(id_field, book_id);
      dlg.close();
      on_changed.raise(book_id);   }

   
   function for_each_child(parent_book_id, fn)
   {
      helpers.for_each(session.find_items("Book", 
session.create_expression("ParentBookId", "eq", parent_book_id)), fn);
   }
   
   function id_to_label(id)
   {
      var book = id != null ? session.find_item("Book", 
session.create_expression("Value", "eq", id)) : null;
      var label = "";
      if(book != null)
         label = book.Label;
      else
         if(id != null && id != "")
         {
            var user = ctx.session.find_item("User", 
ctx.session.create_expression("PrimaryPositionId", "eq", id));
            if(user != null)
               label = user["FullName"];
            else
               label = session.res_string("inaccessible_book");
         }
      return label;
   }
   function get_current_user_userbook(ctx)
   {
      var defaults = helpers.get_defaults(ctx.session);
      return defaults != null ? defaults.PrimaryPositionId : "";
   }}
function book_can_contain_data(ctx, id)

{
   var book = ctx.session.find_item("Book", ctx.session.create_expression("Value", 
"eq", id)),
      result = true;
   if(book != null)
      result = book.CanContainDataFlag;
   return result;
}
function book_is_userbook(ctx, id)
{
   var user = ctx.session.find_item("User", 
ctx.session.create_expression("PrimaryPositionId", "eq", id));
   return user != null;
}
function get_ownership_mode(ctx, type)
{
   if(type == "Event" || type == "Task" || type == "Mail")
      type = "Activity";
   var mode = "owner";
   var books_enabled = ctx.session.find_item("Book", 
ctx.session.create_criteria("or")) != null;
   if(books_enabled)
   {
      var owner_filter = ctx.session.create_criteria("and");
      owner_filter.add(ctx.session.create_expression("TypeName", "eq", type));
      owner_filter.add(ctx.session.create_expression("Required", "eq", true));
      owner_filter.add(ctx.session.create_expression("Value", "eq", "Owner"));
      var owner_required = ctx.session.find_item("RequiredFields", owner_filter) != 
null;
      if(!owner_required)      {
         var book_filter = ctx.session.create_criteria("and");
         book_filter.add(ctx.session.create_expression("TypeName", "eq", type));
         book_filter.add(ctx.session.create_expression("Value", "eq", 
"BookName"));
         var oField = ctx.session.find_item("RequiredFields", book_filter);
         var book_required = oField != null ? oField.Required : null;
         if (book_required)
            mode = "book";
         else
            mode = "mixed";
      }
   }
   return mode;
}
function cp_book_selector(ctx, options)
{
   var dlg;
   var tree;
   var image = "";
   var btn = options.ctrl_btn;
   var edit = options.ctrl_edit;
   var books = options.books;
   var allow_include_subbooks = options.allow_include_subbooks;
   var selected_book;
   var on_changed = new helpers.signal();
   this.on_changed = on_changed;
   
   edit.enabled = false;
   ctx.events.connect(btn, "on_click", show);
   dlg = ctx.ui.create_dialog(0, "select_book_dialog");
   dlg.include_subitems.visible = true;
   dlg.include_subitems.enabled = allow_include_subbooks;
   tree = dlg.books_tree;
   ctx.events.connect(tree, "on_node_activated", select);
   ctx.events.connect(tree, "on_node_selected", uncheck_include_subitems);
   ctx.events.connect(dlg.btn_cancel, "on_click", on_cancel);
   ctx.events.connect(dlg.btn_ok, "on_click", on_save);
      
   this.set_visible = function(visible) {
      edit.visible = btn.visible = visible;
   };
   
   this.enable = function(enabled) {
      btn.enabled = enabled;
   }
      this.update_options = function (new_options) {
      btn = new_options.ctrl_btn || btn;
      edit = new_options.ctrl_edit || edit;
      books = new_options.books || books;
   };
   function show() {
      dlg.visible = true;
      tree.nodes.remove_all();
      fill_tree();
      // Tree refresh workaround
      dlg.include_subitems.set_focus();
      tree.set_focus();
   }
   
   function on_cancel() { dlg.close(); }
   
   function on_save() {
      select(tree.selected);   
   }
   
   function select(node) {
      var selected_book_id = node != null ? node.data.id : null;
      var selected_include_subitems = node != null ? dlg.include_subitems.checked : 
null;
      if (selected_book_id) {
         if (selected_include_subitems || 
books[selected_book_id].CanContainDataFlag) {
            set_book_selected({BookId: selected_book_id, IncludeSubBooks: 
selected_include_subitems});
            uncheck_include_subitems();
            dlg.close();
         } else {
            var message = (allow_include_subbooks ? 
"msg_subitems_required_for_book" : "msg_cannot_select_book_subitems_disallowed");
            ctx.ui.message_box(0, ctx.session.res_string(message), 
ctx.session.res_string("msg_validation_failed"), 0x30);
         }
      } else {
         dlg.close();
      }
   }
   
   var set_book_selected = function (book) {
      selected_book = book;
      refresh(book);
      on_changed.raise(book);
   }
   this.set_book_selected = set_book_selected;
   
   function uncheck_include_subitems() { dlg.include_subitems.checked = false; }
   
   function fill_tree(parent_book_id, parent_node) {
      parent_book_id = parent_book_id || "";
      parent_node = parent_node || tree;
      for_each_child(parent_book_id, add_node);
      // handle orphans
      if (parent_book_id == "") {
         helpers.for_each2(books, function(item) {
            var parent_inaccessible = books[item.ParentBookId] == null;
            if (item.ParentBookId != "" && parent_inaccessible)
               add_node(item);
         });
      }
      function add_node(item) {
         var book_id = item.Value,
            book_name = item.Label;
         image = "book_" + (item.CanContainDataFlag ? "can" : "cannot") + 
"_contain_data_icon";
         var node = parent_node.nodes.add(book_name, image, {"id": book_id});
         if(selected_book && book_id && selected_book.BookId == book_id) {
            tree.selected = node;
            dlg.include_subitems.checked = dlg.include_subitems.visible && 
!!selected_book.IncludeSubBooks;
         }
         fill_tree(book_id, node);
      }
      function for_each_child(parent_book_id, fn) {
         var child_books = [];
         helpers.for_each2(books, function (item) {
            if (item.ParentBookId == parent_book_id)
               child_books.push(item);
         });
         helpers.for_each2(child_books, fn);
      }
   };   
   function refresh(book) {
      // Update book label
      var book_id = book != null ? book.BookId : null;
      var label = "";
      if (book_id != null && book_id != "")
         label = books[book_id] != null
            ? (books[book_id].Label + (book.IncludeSubBooks ? "+" : ""))
            : ctx.session.res_string("inaccessible_book");
      edit.value = label;
   }
}
function record_set_page(ctx)
{
   var parent_form, form = ctx.form;
   var cached_bs_controllers = {};
   var saved_item_snapshot, current_item;
   var record_set_types = [];
   var book_processor, predefined_options = {};
      ctx.events.connect(form.btn_save1, "on_click", on_save);

   ctx.events.connect(form, "on_action_requested", on_action_requested);
   
   function saved_books_retrieved(retrieved_books) {
      current_item = retrieved_books;
      rebuild_saved_item_snapshot();
      
      // Switch back to active page UI from on_busy
      switch_back_from_daisy();
      form.head_record_type.visible = form.head_visibility_model.visible = 
form.head_book_name.visible = true;
      
      // Get list of supported types
      record_set_types = book_processor.get_types_list();
      
      // Clear previous records set controls table
      var i = 0;
      helpers.for_each2(record_set_types, function(){
         var img_type = form["img_type" + i],
            lbl_type = form["lbl_type" + i],
            cbx_mode = form["cbx_mode" + i],
            edit_book = form["edit_book" + i],
            btn_book_select = form["btn_book_select" + i];
         
         img_type.image = null;
         lbl_type.caption = "";
         cbx_mode.value = null;
         img_type.visible = lbl_type.visible = cbx_mode.visible = edit_book.visible 
= btn_book_select.visible = false;
         i++;
      });
         
      build_table();
   }
      function build_table() {
      var row_num = 0;
      // Disable "Save" button initially
      validate_changes();
      // Build table
      helpers.for_each2(record_set_types, function(rs_type){
         var img_type = form["img_type" + row_num],
            lbl_type = form["lbl_type" + row_num],
            cbx_mode = form["cbx_mode" + row_num],
            edit_book = form["edit_book" + row_num],
            btn_book_select = form["btn_book_select" + row_num];
         var book_available = rs_type.books_enabled && 
book_processor.user_has_books();
         
         // Make type image, caption and mode selector visible
         var caption_lbl = rs_type.type_id;
         try { img_type.image = "type_image:"+rs_type.type_id+":24"; } catch(e){}
         try { caption_lbl = 
ctx.session.res_string("obj_"+rs_type.type_id.toLowerCase()); } catch(e){}
         lbl_type.caption = caption_lbl;
         img_type.visible = lbl_type.visible = cbx_mode.visible = true;
         cbx_mode.enabled = edit_book.visible = btn_book_select.visible = 
book_available;
         var current_type_settings;
         book_processor.apply_to_type(rs_type.type_id, 
function(item){current_type_settings = item}, current_item);   // set 
current_type_settings            
         // Build mode selector options
         cbx_mode.items.clear();
         cbx_mode.items.start_adding();
         cbx_mode.items.add("team", 
ctx.session.res_string("cbx_record_set_team"));
         if (book_available || current_type_settings.BookId != null)
            cbx_mode.items.add("book", 
ctx.session.res_string("cbx_record_set_book"));
         if (book_available && book_processor.default_book_exists(rs_type) || 
is_default_book_selected(current_type_settings, rs_type.default_book))
            cbx_mode.items.add("default_book", 
ctx.session.res_string("cbx_record_set_default_book"));
         cbx_mode.items.finish_adding();
         
         // Retrieve cached book selector (or create a new one if not found)
         var books = book_processor.get_available_books();
         var bs = cached_bs_controllers[rs_type.type_id];
         if (bs) {
            bs.update_options({type: rs_type, books: books});
         } else {
                  var bs_options =
               {
               ctrl_edit: edit_book,
               ctrl_btn: btn_book_select,
               type: rs_type,
               books: books,
               allow_include_subbooks: 
book_processor.allow_include_subbooks(rs_type.type_id)
            };
            bs = new cp_book_selector(ctx, bs_options);
            ctx.events.connect(bs, "on_changed", set_option_selected);
            cached_bs_controllers[rs_type.type_id] = bs;
            ctx.events.connect(cbx_mode, "changed", 
function(){on_mode_changed(cbx_mode.value)});
         }
         
         // Fill controls with initial values
         prefill_type(current_type_settings);
         function set_option_selected(book) {
            book_processor.apply_to_type(rs_type.type_id, function(item){
               item.BookId = book.BookId;
               item.IncludeSubBooks = book.IncludeSubBooks;
            }, current_item);
            validate_changes();
         }
         
         function on_mode_changed(mode) {
            bs.set_visible(mode !== "team");
            switch (mode) {
               case "team":
                  set_option_selected(predefined_options.team);
               break;
               case "book":
                  bs.enable(true);
                  if (book_processor.default_book_exists(rs_type))
                     bs.set_book_selected(rs_type.default_book);
                  else
                     bs.set_book_selected(predefined_options.no_book_selected);
               break;
               case "default_book":
                  bs.enable(false);
                  bs.set_book_selected(rs_type.default_book);
               break;
            }
         }
         
         /**
         Prefills mode selector and appropriate controls with passed item values.
         @param item Item of type Book.Type.Association
         @type Function
         */
         function prefill_type(item) {
            var book_id = item.BookId;
            var include_subbooks = item.IncludeSubBooks;
            var mode = "team";
            if (book_id !== predefined_options.team.BookId)
               if (is_default_book_selected(item, rs_type.default_book))
                  mode = "default_book";
               else
                  mode = "book";
            cbx_mode.value = mode;
            if(mode != "team")
               cbx_mode.enabled = true;   // user should be able to switch to "team" 
mode in any case
            on_mode_changed(mode);
            if (mode == "book")
               bs.set_book_selected({"BookId": book_id, "IncludeSubBooks": 
include_subbooks});
         }
         
         function is_default_book_selected(type_item, default_book_item) {
            return type_item.BookId == default_book_item.BookId && 
type_item.IncludeSubBooks == default_book_item.IncludeSubBooks
         }
         
         row_num++;
      });
   }
   
   function on_save() {
      book_processor.save(current_item, on_saved);
      function on_saved(){
         rebuild_saved_item_snapshot();
         validate_changes();
      }
   }
   function on_action_requested(ar) {
      switch (ar.action) {
         case "set_parent":
            parent_form = ar.parent_form;
         break;
         case "page_select":
            on_page_select();
         break;
         case "query_close":   // CP closing
            if (form.btn_save1.enabled)
            {
               var res = ctx.application.ui.message_box(parent_form, 
ctx.session.res_string("msg_record_set_query_save_record_set"), 
ctx.session.res_string("lbl_record_set_caption"), 35);
               ar.cancel = res == 2;
               if (res == 6 && book_processor != null)
                  on_save();
            }
         break;
         case "close":   // CP closed
            on_close();
         break;
      }
   }
   
   function on_page_select() {
      var bp_options = {
         "connector": book_processor ? book_processor.get_connector() : null
      };
      book_processor = new book_of_business_processor(ctx, bp_options);
      predefined_options = book_processor.get_predefined_options();
      parent_form.infobar.caption = 
ctx.session.res_string("record_set_tab_description");
      parent_form.infobar.visible = parent_form.toolbar.visible = false;
      parent_form.cp_pages.active_page = parent_form.cp_pages.pages.cp_busy_page;
      book_processor.save_default_books_if_empty(saved_books_retrieved, 
on_connector_failed);
   }
   
   function on_close() {
      if (book_processor)
         book_processor.abort();
   }
      function on_connector_failed() {
      // close Control Panel
      parent_form.close();
   }
   
   /**
   Switches back to active page UI from on_busy page with daisy.
   @type Function
   */
   function switch_back_from_daisy() {
      parent_form.cp_pages.active_page = 
parent_form.cp_pages.pages.cp_record_set_page;
      parent_form.infobar.visible = parent_form.toolbar.visible = true;
   }
    /**
   Enables/disables "Save" button based on detected changes and performed 
validations.
   @type Function
   */
   function validate_changes() {
      var enable_save_btn = true;
      var changes_found = false;
      // Detect changes
      if (saved_item_snapshot.length != current_item.length) {
         changes_found = true;
      } else {
         for (var i in current_item) {
            var current_item_options = current_item[i];
            var saved_item_snapshot_options = saved_item_snapshot[i];
            if (saved_item_snapshot_options.BookId != current_item_options.BookId 
|| saved_item_snapshot_options.IncludeSubBooks != 
current_item_options.IncludeSubBooks) {
               changes_found = true;
               break;
            }
         }
      }
      
      if (!changes_found) {
         enable_save_btn = false;
      } else {
         // Run validations
         for (var i in current_item) {
            if (current_item[i].BookId === 
predefined_options.no_book_selected.BookId) {
               enable_save_btn = false;
               break;
            }
         }
      }
      // Enable/disable "Save" button
      form.btn_save1.enabled = enable_save_btn;
   }
   /**
   Builds a snapshot of current_item, which can later be used as saved item snapshot 
for detecting changes.
   @type Function
   */
   function rebuild_saved_item_snapshot() {
      var data_source = book_processor.get_data_source();
      var columns = data_source.columns.toArray();
      saved_item_snapshot = [];
      for (var key in current_item) {
         var snapshot_item = {};
         for (var i in columns)
            snapshot_item[columns[i]] = current_item[key][columns[i]];
         saved_item_snapshot.push(snapshot_item);
      }
   }
}

function book_of_business_processor(ctx, options) {
   options = options || {};
      var available_books, user_default_book, user_has_books;
   var record_set_types = [];
   var default_books = [];
   var data_source, connector = options.connector || null;
   var disconnectable = {};
   var book_predefined_options =
   {
      "team": {BookId: "", IncludeSubBooks: false},
      "no_book_selected": {BookId: null, IncludeSubBooks: null}
   };
   
   this.save_default_books_if_empty = function(on_after_saved_fn, 
on_save_failed_fn)

{
      var on_books_retrieved = function (retrieved_books) {
         save(retrieved_books, on_after_saved_fn);
      };
      this.retrieve_saved_or_default_books(on_books_retrieved, on_save_failed_fn);
   };
   
   this.retrieve_saved_or_default_books = function(on_books_retrieved_fn, 
on_err_fn)
{
      // Saving default visibility modes
      if (!connector)
         ctx.application.connector.async_get_remote_connector(0, 
form_helpers.disconnectable_handler(on_connector_ready, disconnectable));
      else
         on_connector_ready(connector);
         
      function on_connector_ready(c, err) {
         if (err) {
            ctx.ui.message_box(ctx.ui.app_window, err.message, 
ctx.session.res_string("books_retrieve_failed_title"), 0x10); // MB_OK | 
MB_ICONERROR
            if (on_err_fn)
               on_err_fn(err);
            return;
         }
         
         // Reset variables
         var saved_item = [];
         var default_prefilling = [];
         record_set_types = [];
         default_books = [];
         available_books = [];
         user_default_book = null;
         data_source = null;
         
         // Cache connector
         connector = c;
         // Retrieve remotely available books list and default books per type for 
current user
         create_data_source("Book", function(book_ds) {
            available_books = retrieve_books_picklist_from_datasource(book_ds);
            var books_count = 0;
            for (var _ in available_books) { books_count++; }
            user_has_books = books_count > 0;
            create_data_source("BookDefaults", function(BookDefaults_ds) {
               default_books = 
retrieve_books_picklist_from_datasource(BookDefaults_ds);
               on_books_ready();
            });
         });
         
         function on_books_ready()
         {
            // Configure list of supported types
            for (var type_id in default_books)
               record_set_types.push({type_id: type_id, sort_order: 
parseInt(default_books[type_id]["OrderNumber"])});
            record_set_types.sort(function(a, b){ return a.sort_order - 
b.sort_order; });

            // Get global default book
            create_data_source("::Defaults", function(defaults_ds) {
               // If no items found, proceed
               if (defaults_ds.record_count == 0) {
                  configure_default_books();
               } else {
                  var ds_item = defaults_ds.get_item_id(0);
                  connector.async_get_item("::Defaults", ds_item, function(){}, 
form_helpers.disconnectable_handler(function(defaults, err) {
                     if (err == null && defaults.DefaultBookName != "" && 
defaults.DefaultBookName != "All+") {
                        for (var id in available_books) {
                           if (available_books[id].Label == 
defaults.DefaultBookName) {
                              user_default_book = id;
                              break;
                           }
                        }
                     }
                     configure_default_books();
                  }, disconnectable));
               }
               
               function configure_default_books() {
                  helpers.for_each2(record_set_types, function(rs_type){
                     // If default book per type is not available set to global 
default book if available
                     if (default_books[rs_type.type_id].BookId == null && 
user_default_book != null) {
                        default_books[rs_type.type_id].BookId = user_default_book;
                        default_books[rs_type.type_id].IncludeSubBooks = 
!available_books[user_default_book].CanContainDataFlag && 
allow_include_subbooks(rs_type.type_id);
                     }
                     // Check if books of business are enabled
                     rs_type.books_enabled = default_books[rs_type.type_id] != 
null;
                     // Lookup default book for type if books enabled
                     if (rs_type.books_enabled)
                        rs_type.default_book = default_books[rs_type.type_id];
                     // Default prefilling
                     var book_available = rs_type.books_enabled && user_has_books;
                     var def_book_exists = default_book_exists(rs_type);
                     var prefill_default_book = book_available && def_book_exists;
                     apply_to_type(rs_type.type_id, function(item){
                        item.BookId = prefill_default_book ? 
rs_type.default_book.BookId : book_predefined_options.team.BookId;
                        item.IncludeSubBooks = prefill_default_book ? 
rs_type.default_book.IncludeSubBooks : 
book_predefined_options.team.IncludeSubBooks;
                     }, default_prefilling);
                  });
                  // Get saved item if found
                  create_data_source("Book.Type.Association", function(ds) {
                     // Make datasource global for future use
                     data_source = ds;
                     // If no saved items found, consider this is a first run and 
proceed next
                     if (data_source.record_count > 0) {
                        // Process found items
                        var items_processed = 0;
                        for (var i=0; i<data_source.record_count; i++) {
                           var ds_item = data_source.get_item_id(i);
                           connector.async_get_item("Book.Type.Association", 
ds_item, function(){}, 
form_helpers.disconnectable_handler(function(retrieved_item, err) {
                              if (err == null)
                                 saved_item.push(retrieved_item);
                              items_processed++;
                              // If all items retrieved
                              if (items_processed == data_source.record_count)
                                 configure_default_item();
                           }, disconnectable));
                        }
                     } else {
                        configure_default_item();
                     }
                     
                     // Compiles default visibility modes list
                     function configure_default_item() {
                        helpers.for_each2(record_set_types, function(rs_type){
                           // Trying to find saved mode
                           var item_found = false;
                           for (var key in saved_item) {
                              if (saved_item[key].TypeName == rs_type.type_id) {
                                 item_found = true;
                                 break;
                              }
                           }
                           // set to default mode if not found
                           if (!item_found)
                              apply_to_type(rs_type.type_id, function(item){
                                 for (var key in default_prefilling) {
                                    if (default_prefilling[key].TypeName == 
item.TypeName) {
                                       item.BookId = 
default_prefilling[key].BookId;
                                       item.IncludeSubBooks = 
default_prefilling[key].IncludeSubBooks;
                                       break;
                                    }
                                 }
                              }, saved_item);
                        });
                        
                        on_books_retrieved_fn(saved_item);
                     }
                  });
               }
            });
         }
            
         function retrieve_books_picklist_from_datasource(ds) {
            var result = {};
            var count = ds.record_count;
            var _columns = ds.columns.toArray();
            var columns = {};
            for (var i=0; i < _columns.length; i++) {
               columns[_columns[i]] = i;
            }
            for (var i=0; i < count; i++) {
               var row = ds.get_item_fields(i).toArray();
               result[get_value(row)] = get_item(row);
            }
            
            function get_value(row)
            {
               return row[columns["Value"]];
            }
            function get_item(row)
            {
               var item = {};
               helpers.for_each2(_columns, function(fld_name){
                  item[fld_name] = row[columns[fld_name]];
               });
               return item;
            }
            return result;
         }
         
         function create_data_source(type_id, fn) {
            var ds = connector.create_data_source(type_id);
            ds.on_changed.connect(form_helpers.disconnectable_handler(function() { 
fn(ds); }, disconnectable));
            ds.enabled = true;
            ds.refresh(100, 600000);
            ds.enabled = false;
         }
      }
   }

   function save(item, on_after_saved_fn) {
      var saved_items_count = 0;
      for (var key in item) {
         connector.async_put_item(item[key], on_progress, 
form_helpers.disconnectable_handler(on_put_item_complete, disconnectable));
      }
      if (item.length == 0)
         on_after_saved_fn(item);
      
      function on_progress() {}
      function on_put_item_complete(saved_book) {
         // Synchronize new selected value with local storage
         var user_selected_book = get_ol_storage_rs_book(ctx, saved_book.TypeName) 
|| ctx.session.create_item("Book.Type.Association");
         user_selected_book.TypeName = saved_book.TypeName;
         user_selected_book.BookId = saved_book.BookId;
         user_selected_book.IncludeSubBooks = saved_book.IncludeSubBooks;
         user_selected_book.save();
         
         saved_items_count++;
         if (saved_items_count == item.length)
            on_after_saved_fn(item);
      }
   }
   this.save = save;
   
   this.get_types_list = function () {
      return record_set_types;
   }
   this.get_data_source = function () {
      return data_source;
   }
   
   this.get_connector = function () {
      return connector;
   };
   
   this.get_available_books = function () {
      return available_books;
   };
   
   this.user_has_books = function () {
      return user_has_books;
   }
   
   this.get_predefined_options = function () {
      return book_predefined_options;
   }
   
   this.abort = function() {
      disconnectable.disabled = true;
   }
   
   function allow_include_subbooks(type) {
      return allow_includesubbooks[type] || false;
   }
   this.allow_include_subbooks = allow_include_subbooks;
   
   /**
   Checks whether default book is defined for a type.
   @param type Type name to check default book for
   @returns {Boolean} Does default book exist for a type
   @type Function
   */
   function default_book_exists(type) {
      // Books are disabled
      if (!type.books_enabled)
         return false;
      // No default book
      var book_id = type.default_book.BookId;
      if (book_id == null)
         return false;
      // accessible book && the book that can contain data or with "Include Sub-
Items" enabled by administrator
      var book = available_books[book_id];
      if (book != null && (book.CanContainDataFlag || 
type.default_book.IncludeSubBooks))
         return true;
      // The book that cannot contain data and "Include Sub-Items" for it is disabled 
by administrator
      return false;
   }
   this.default_book_exists = default_book_exists;
   
   /**
   Applies a function to a specific type records set option in destination item. if 
not yet exists, creates "team" records set option for a type (default option).
   @param type_id Records set option type name
   @param {Function} f Function to call over an item
   @param dest Destination object to lookup an item in.
   @type Function
   */
   function apply_to_type(type_id, f, dest) {
      // Try to find item in dest
      var item = null;
      for (var i in dest) {
         if (dest[i].TypeName == type_id) {
            item = dest[i];
            break;
         }
      }
      // Create item if not found
      if (item == null) {
         item = connector.create_item("Book.Type.Association");
         item.TypeName = type_id;
         item.BookId = book_predefined_options.team.BookId;
         item.IncludeSubBooks = book_predefined_options.team.IncludeSubBooks;
         dest.push(item);
      }
      // Apply function
      f(item);
   }
   this.apply_to_type = apply_to_type;
}

function get_ol_storage_rs_book(ctx, type_id) {
   return ctx.session.find_item("Book.Type.Association", 
ctx.session.create_expression("TypeName", "eq", type_id));
}

XML Code Addition for the od_basic_mapping.xml File

Add new types by inserting the following lines inside the <database> <types> element in the od_basic_mapping.xml as described in Step c in the topic Setting Up Books for a Customized Package from Version 2.0:

<type id = "Book" icon = "type_image:Generic:16">
         <field id = "Label">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "Value">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "SortOrder">
            <type>
               <simple type = "integer"/>
            </type>
         </field>
         <field id = "IsDefault">
            <type>
               <simple type = "boolean"/>
            </type>
         </field>
         <field id = "ParentCode">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "ParentBookName">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "ParentBookId">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "BookType">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "CanContainDataFlag">
            <type>
               <simple type = "boolean"/>
            </type>
         </field>
   </type>
<type id = "BookDefaults" icon = "type_image:Generic:16">
         <field id = "Label">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "Value">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "SortOrder">
            <type>
               <simple type = "integer"/>
            </type>
         </field>
         <field id = "IsDefault">
            <type>
               <simple type = "boolean"/>
            </type>
         </field>
         <field id = "ParentCode">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "BookId">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "IncludeSubBooks">
            <type>
               <simple type = "boolean"/>
            </type>
         </field>
</type>
<type id = "RequiredFields" icon = "type_image:Generic:16">
         <field id = "Label">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "Value">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "SortOrder">
            <type>
               <simple type = "integer"/>
            </type>
         </field>
         <field id = "IsDefault">
            <type>
               <simple type = "boolean"/>
            </type>
         </field>
         <field id = "ParentCode">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "TypeName">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "Required">
            <type>
               <simple type = "boolean"/>
            </type>
         </field>
         <field id = "SalesProcLang">
            <type>
               <simple type = "string"/>
            </type>
         </field>
</type>
<type id = "Book.Type.Association">
         <field id = "TypeName">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "BookId">
            <type>
               <simple type = "string"/>
            </type>
         </field>
         <field id = "IncludeSubBooks">
            <type>
               <simple type = "boolean"/>
            </type>
         </field>
</type>

JavaScript Code Addition for the forms.js File

Insert the register_book_control(ctx, owner_ctrl) function at the end of the file, as described in Step a in the topic Setting Up Books for a Customized Package from Version 2.0:

function register_book_control(ctx, owner_ctrl)
{
   var ownership_mode = ctx.security_descriptor.ownership_mode(),
      form = ctx.form,
      type = form.item.type_id,
      current_user_link = { "item_ex": ctx.item_ex, "with_id": 
helpers.get_current_user_id(ctx.session), "tag": "direct" },
      current_user_link_mvg = helpers.merge_contexts(current_user_link, { "tag": 
"mvg", "params": {"is_manual_operation": false} });
   if(!is_activity_subform(type))
   {
      ctx.validator.add_custom(util_validate_book_fab(ctx, ownership_mode), 
["book_of_business"], "msg_book_is_required");
      if(owner_ctrl)
      {
         ctx.validator.add_custom(util_validate_owner_fab(ownership_mode), 
[owner_ctrl], "msg_owner_must_be_empty");
         ctx.validator.add_custom(util_validate_owner_and_book_fab(ctx, 
ownership_mode), [owner_ctrl, "book_of_business"], 
"msg_Specifying_both_Book_and_Owner_is_prohibited");
      }
   }
   var bs = new od_helpers.book_selector(ctx, {ctrl_edit: form.book_of_business, 
ctrl_btn: form.btn_book_select, mode: ownership_mode, context: "inspector"});   
   if(owner_ctrl)
      ctx.triggers.add_simple_trigger(on_owner_changed, ctx.item_ex.get_type(), 
"User", "direct", "created");   
   ctx.events.connect(form, "on_saving", function() {
      // clean-up fields
      switch(ownership_mode)
      {
         case "owner":
            if(ctx.item_ex.get_property("OwnerId") == null)
               ctx.linker.link(current_user_link);
            ctx.linker.link(current_user_link_mvg);
            book_clean_up();
         break;
         case "book":
            ctx.item_ex.set_property("OwnerId", null);
//            ctx.linker.unlink(current_user_link_mvg);
         break;      
      }
   });   
   ctx.events.connect(bs, "on_changed", function(book_id) {
      // populate/clean-up Owner
      if(ownership_mode == "mixed")
         if(book_id != "")
         {
            ctx.item_ex.set_property("OwnerId", null);
            ctx.linker.unlink(current_user_link_mvg);
         }
         else
         {
            if(!owner_ctrl)   // no link to User
               ctx.item_ex.set_property("OwnerId", current_user_link.with_id);
            else
            {
               ctx.linker.link(current_user_link);
               ctx.linker.link(current_user_link_mvg);
            }
         }
   });   
   function is_activity_subform(type)
   {
      return type == "Event" || type == "Task" || type == "Mail";
   }   
   function on_owner_changed(ctx, action_ctx)
   {
      var owner_id = action_ctx.with_id;
      if(ownership_mode == "mixed" && owner_id != null)
         book_clean_up();
   }   
   function book_clean_up()
   {
      ctx.item_ex.set_property("BookId", "");
      bs.refresh();
   }
   function util_validate_book_fab(ctx, ownership_mode)
   {
      return function(validation_ctx)
      {
         var book = validation_ctx.snapshot()["BookId"];
         if(ownership_mode == "book" && (book == null || book == ""))
         {
            return false;
         } 
         else if(ownership_mode != "owner" && 
!od_helpers.book_can_contain_data(ctx, book))
         {
            validation_ctx.set_message("msg_book_cannot_contain_data", true);
            return false;
         } 
         else if(ownership_mode == "book" && od_helpers.book_is_userbook(ctx, 
book))
         {
            validation_ctx.set_message("msg_book_must_be_custom", true);
            return false;
         }
         else
            return true;
      }
   }
   function util_validate_owner_fab(ownership_mode)
   {
      return function(validation_ctx)
      {
         var owner = validation_ctx.snapshot()["OwnerId"];
         return !(ownership_mode == "book" && owner != null)
      }
   }
   function util_validate_owner_and_book_fab(ctx, ownership_mode)
   {
      return function(validation_ctx)
      {
         var owner = validation_ctx.snapshot()["OwnerId"];
         if(ownership_mode == "mixed")
         {
            var book = validation_ctx.snapshot()["BookId"];
            if(owner != null && book != "" && book != null && 
!od_helpers.book_is_userbook(ctx, book))
               return false;
         }
         return true;
      }
   }}