/** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2018 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ SubPanel implementation: Summary: DetailView pages now utilize the SubPanelTiles php class to display Subpanels of Related Objects. Subpanels are generated asynchronously and on demand, and utilize an improved ListView class. ------------------------------------------------------------------------------- Class Heirarchy: DetailView -> SubPanelTiles ->(remotely calls) -> SubPanelViewer -> SubPanel -> ListView -> (calls template) --> SubPanelDynamic.html ------------------------------------------------------------------------------- SubPanelTiles: The SubPanelTiles class creates the subpanel
s, outputs the javascript to manage loading/displaying panels, and displays tabs. - Each Subpanel is outputted as empty - When a tab is selected, or a subpanel to be maximized, the showSubPanel javascript function is called. - an asynchronous XMLHTTPRequest object is used to remotely fetch snippets of the SubPanel HTML. When the call returns, the contents of the response from the server will be assigned to the innerHTML of the target
. - The XMLHTTPRequest calls the subpanel snippets with your normal Sugar URL with module/action/record - The header/menu/footer panels are not displayed by adding to the query string: "&sugar_body_only=1&inline=1" - the "action" is SubPanelViewer, which of course causes Sugar to execute: modules/$_REQUEST['module']/SubPanelViewer.php - SubPanelViewer.php files in every module directory usually just call include/SubPanel/SubPanelViewer.php and return. - SubPanelViewer acts as a wrapper around the SubPanel when it is called remotely. - The Top "Create" and "Select" buttons are generated inside the hide/display
, but outside of the div that holds the innerHTML of the Subpanel listview. - the presence of, order of, and SugarWidget class of these buttons are controlled in the subpanel definitions. - (optional) The tabs are displayed using the SugarWidgetTabs class, which outputs the javascript/html. SugarWidgetTabs is generated by javascript, and tab selection is done by dynamically changing CSS attributes of the tabs. Someone using this class would pass it a php array of the tab definition. It is converted to javascript data object definitions and outputted as javascript to the browser. The other input would be the name of the javascript callback function that should be called when a tab is clicked. ------------------------------------------------------------------------------- SubPanel Snippets: SubPanel.php acts as a wrapper around ListView.php: - instantiates parent and related bean - gets subpanel definitions using a search path (custom, then module-specific, then global) - ListView manages sorting, paging and displaying the header and rows - SubPanel sets a flag in ListView that it knows to run dynamically - passes ListView the javascript link wrappers so that paging and sorting links will work correctly. The links will call a javascript function that will return the HTML to the subpanel
, and not refresh the whole page - calls $ListView->loadListFieldDefs($list_fields,$child_focus); so that it can iterate through the fields and display the appropriate widget and data ------------------------------------------------------------------------------- ListView and SugarWidgets - To generate the header and data cells, dynamic ListViews use SugarWidget singleton display functions. - as ListView prepares to display a cell, it looks at its corresponding field definition to determine which SugarWidget to display. If there is no definition for the widget_class, then it will default to displaying SugarWidgetField snippet: list_field_defs as $list_field) { $list_field['fields'] = $fields; $list_field['start_link_wrapper'] = $this->start_link_wrapper; $list_field['end_link_wrapper'] = $this->end_link_wrapper; $widget_contents = $layout_manager->widgetDisplay($list_field); $this->xTemplate->assign('CELL', $widget_contents); $this->xTemplate->parse($xtemplateSection.".row.cell"); } ?> - a list_field definition example (from layout_defs.php) 'list_fields'=> array( array ( // use the 'ENCODED_NAME' key to get the data 'varname'=>'ENCODED_NAME', // the key into the vardef.php definition // also used as the sort order.. split that out 'name'=>'last_name', // use this custom label in the header 'vname'=>'LBL_LIST_NAME', // link this field 'linked'=>true, // use this module because we are linking this field 'module'=>'Contacts', ), ), - SugarWidgetField will call a different displayXXX() function depending on the context of the container. - if in the "HeaderCell" context, it calls $this->displayHeaderCell($args), which returns the HTML content of the Header Cell - if in the "List" context, it calls $this->displayList($args), which returns the HTML content of a data cell of the ListView. - Before ListView displays the widget for a cell, it attaches a pointer to the data for that SugarBean instance. - if the field definition contains a link = true and a module attribute, then it will create a link to the DetailView of the target SugarBean instance - The edit and remove buttons in the row are also generated like the other cells except it uses SugarWidgetSubPanelEditButton to display. The buttons are defined in the subpanel definition just like the fields that are being displayed Selecting and deleting relationships: - When either a relationship is added via a popup, or the remove button is clicked, an async XMLHTTPRequest is called. - The contents of the request is a POST or GET that is constructed and send to the server. The return_url is set so that the server returns the Subpanel snippet, which is then added to the innerHTML of the calling subpanel ------------------------------------------------------------------------------ Back end methods: - When being used for Subpanels, ListView uses the get_related_list() method of the SugarBean Class to retrieve related objects. - in $sugarbean->get_related_list(): // load relationship $this->load_relationship($related_field_name); // now get the query that fetches related records: $query_array = $this->$related_field_name->getQuery(true); - adding and deleting relationships are done through the include/generic/Save2.php and include/generic/DeleteRelationship.php - Link::add() and Link::delete() methods are the core functions that do the database work. ------------------------------------------------------------------------------- FILES: New: include/SubPanel/SubPanelDynamic.html - the xtemplate file that provides the skeleton of the dynamic ListView output include/SubPanel/SubPanel.php - prepares ListView to output related objects, and within a
include/SubPanel/SubPanelTiles.php - includes tabs and each subpanel, and manages the show/hide and UI. include/SubPanel/SubPanelViewer.php - wrapper for SubPanel.php when called remotely include/generic/DeleteRelationship.php - generically deletes a relationship based on a parent focus, the linked_field, and the linked id; include/generic/LayoutManager.php - Factory class to provide singleton access and repository for SugarWidget - provides global data registry that SugarWidgets can access include/generic/Save2.php - generically adds a relationship using EditView form variables, but is actually called via XMLHTTPRequest, and only tested with relationship fields. include/generic/SugarWidgets/SugarWidget.php - base class, defines display(), and holds a reference to the LayoutManager; include/generic/SugarWidgets/SugarWidgetField.php - base class to represent database columns as UI widgets include/generic/SugarWidgets/SugarWidgetSubPanelTopButtons.php - they are the "Create" and "Select" buttons include/generic/SugarWidgets/registered_widgets.php - add new SugarWidget classes here include/generic/SugarWidgets/SugarWidgetDynamicTabs.php - javascript generated tabs include/generic/SugarWidgets/SugarWidgetSubPanelEditButton.php - holds both SugarWidgetSubPanelEditButton and SugarWidgetSubPanelRemoveButton - defines 'edit' and 'rem' buttons within the row on the right each module: modules/$module_name/Save2.php - redirects to: include/generic/Save2.php modules/$module_name/layout_defs.php - defines subpanels for each bean within this module directory modules/$module_name/SubPanelViewer.php - redirects to: include/generic/SubPanelViewer.php modules/$module_name/SaveRelationship.php - redirects to: include/generic/SaveRelationship.php CHANGED: modules/$module_name/DetailView.php - contains SubPanelTiles include/ListView/ListView.php - added: loadListFieldDefs() processHeaderDynamic() getLayoutManager() is_dynamic - changed: processListRows() processListViewTwo() data/SugarBean.php - added: get_related_list() ------------------------------------------------------------------------------- TODO: - Within the SugarWidgetField display() function, based on the field data type being displayed, call another SugarWidget that can output it correctly - this includes checkboxes, dropdowns, relationships, dates, etc.. - do it throughout the rest of the modules - might need to change the how "create" button passes form data. - make it more configurable so that it can pass custom form data from focus bean - change how tabs are displayed/hidden. - right now it uses the linked_fields directly from the sugarbean - use the visible_subpanels definition in the layout_def.php, or just use the order in subpanel_defines itself. if you do you have to add an order_index to each field and sort on that. maybe have a visible/hidden attribute - define custom titles in visible_subpanels, else fall back to subpanel string. - where do you put union subpanel definitions? change function that returns the definitions to another search path. - create special union subpanel type. - not just one focus, but an array of them, and some may have be the same class - make layout editor work with subpanel listviews ------------------------------------------------------------------------------- PROBLEMS: - using firefox, when the "select" button is within the div that gets it's innerHTML replaced, things break the second time you select a new relationship. it fails on the window.open that is called with the popup_helper. some internal mozilla error. this is why the top buttons are in SubPanelTiles.php and not SubPanel.php this needs to be resolve or else we will not be able to have popups that cause refreshes. sample layout_def: ------------------------------------------------------------------------------- $layout_defs['account'] = array( 'subpanel_setup' => array ( // sets up which panels to show, in which order, and with what // linked_fields 'show_subpanels' => array ( array( /// which subpanel to show 'subpanel_key'=>'opportunities', /// custom string for the title of this subpanel 'string_key'=>'LBL_OPPORTUNITIES', ), ), ), // defines the default subpanel definition for this SugarBean 'default_subpanel_define' => array ( 'class_name'=>array('Opportunity'), // define top left buttons 'top_buttons'=>array( array ( 'widget_class'=>'SubPanelTopCreateButton', ), array ( 'widget_class'=>'SubPanelTopSelectButton', ), ), // define list fields // yea, i know this will only show the Name 'list_fields'=> array( array ( 'name'=>'name', 'module'=>'Accounts', 'linked'=>true, ), ), ), // defines custom subpanel definitions for this SugarBean 'subpanel_defines' => array ( 'opportunities' => array ( // define top left buttons 'top_buttons'=>array( array ( 'widget_class'=>'SubPanelTopCreateButton', ), array ( 'widget_class'=>'SubPanelTopSelectButton', ), ), 'class_name'=>array('Opportunity'), // define list fields // this one displays the same fields as the original 'list_fields'=> array( array ( 'name'=>'name', 'module'=>'Opportunities', 'linked'=>true, ), array ( 'varname'=>'account_name', 'vname'=>'LBL_LIST_ACCOUNT_NAME', ), array ( 'name'=>'date_closed', ), array ( 'widget_class'=>'SubPanelEditButton', 'name'=>'edit_button', ), ), ), 'contact' => array ( 'class_name'=>array('Opportunity'), 'top_buttons'=>array( array ( 'widget_class'=>'SubPanelTopCreateButton', ), array ( 'widget_class'=>'SubPanelTopSelectButton', ), ), // this one also displays the same fields as the original 'list_fields'=> array( array ( 'varname'=>'ENCODED_NAME', 'name'=>'last_name', 'vname'=>'LBL_LIST_NAME', 'module'=>'Contacts', 'linked'=>true, ), array ( 'varname'=>'ACCOUNT_NAME', 'vname'=>'LBL_LIST_ACCOUNT_NAME', ), array ( 'name'=>'email1', ), array ( 'vname'=>'LBL_LIST_PHONE', 'name'=>'phone_home', ), array ( 'widget_class'=>'SubPanelEditButton', 'name'=>'edit_button', ), array ( 'widget_class'=>'SubPanelRemoveButton', 'name'=>'remove_button', ), ), ) ) );