Index: openacs-4/packages/acs-core-docs/www/xml/developers-guide/tutorial-advanced.xml =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-core-docs/www/xml/developers-guide/tutorial-advanced.xml,v diff -u -N -r1.33 -r1.34 --- openacs-4/packages/acs-core-docs/www/xml/developers-guide/tutorial-advanced.xml 21 Jul 2004 12:15:05 -0000 1.33 +++ openacs-4/packages/acs-core-docs/www/xml/developers-guide/tutorial-advanced.xml 13 Aug 2004 10:22:26 -0000 1.34 @@ -1,4 +1,4 @@ - + @@ -290,7 +290,9 @@ Categories - + + extended by Nima Mazloumi + You can associate any ACS Object with one or more categories. In this tutorial we'll show how to equip your application with user interface to take advantage of the Categories service. @@ -327,20 +329,18 @@ file: -set category_map_url [export_vars -base \ - "[site_node::get_package_url -package_key categories]cadmin/one-object" \ - { { object_id $package_id } }] + set category_map_url [export_vars -base "[site_node::get_package_url -package_key categories]cadmin/one-object" { { object_id $package_id } }] and the following snippet to your /var/lib/aolserver/$OPENACS_SERVICE_NAME/packages/myfirstpackage/www/admin/index.adp file: -<li><a href="@category_map_url@" - class="action_link">Site-Wide Categories</a> - - The link created by the above code will take the admin to the generic + <a href="@category_map_url@"<#categories.Site_wide_Categories#</a> + + The link created by the above code (category_map_url) + will take the admin to the generic admin UI where he can pick category trees that make sense for this application. The same UI also includes facilities to build and edit category trees. Notice that the only parameter in this example is @@ -364,50 +364,37 @@ switch to the ad_form command. Here's the "meat" of the note-edit.tcl page: -ad_form -name note -form { - {item_id:key} - {title:text {label Title}} -} + #extend the form to support categories + set package_id [ad_conn package_id] + + category::ad_form::add_widgets -form_name note -container_object_id $package_id -categorized_object_id [value_if_exists item_id] -set package_id [ad_conn package_id] - -set category_trees [category_tree::get_mapped_trees $package_id] - -foreach tree $category_trees { - foreach { tree_id name subtree_id } $tree {} - ad_form -extend -name note -form \ - [list [list category_id_${tree_id}:integer(category),optional \ - {label $name} \ - {html {single single}} \ - {category_tree_id $tree_id} \ - {category_subtree_id $subtree_id} \ - {category_object_id {[value_if_exists entry_id]}}]] -} - -ad_form -extend \ - -name note \ - -new_request { - permission::require_permission -object_id [ad_conn package_id] -privilege create - set page_title "Add a Note" - set context [list $page_title] -} -edit_request { - permission::require_write_permission -object_id $item_id - mfp::note::get \ - -item_id $item_id \ - -array note_array - - set title $note_array(title) - - set page_title "Edit a Note" - set context [list $page_title] -} -new_data { - mfp::note::add \ - -title $title -} -after_submit { - ad_returnredirect "." - ad_script_abort -} - This page requires a + ad_form -extend -name note -on_submit { + set category_ids [category::ad_form::get_categories -container_object_id $package_id] + } -new_data { + .... + category::map_object -remove_old -object_id $item_id $category_ids + db_dml insert_asc_named_object "insert into acs_named_objects (object_id, object_name, package_id) values ( :item_id, :title, :package_id)" + } -edit_data { + .... + db_dml update_asc_named_object "update acs_named_objects set object_name = :title, package_id = :package_id where object_id = :item_id" + category::map_object -remove_old -object_id $item_id $category_ids + } -after_submit { + ad_returnredirect "." + ad_script_abort + } + + While the category::ad_form::add_widgets proc is taking + care to extend your form with associated categories you need to ensure that your items are mapped + to the corresponding category object yourself. Also since the categories package knows nothing from + your objects you have to keep the acs_named_objects table updated with + any changes taking place. We use the items title so that they are listed in the categories browser by + title.Make sure that you also delete these entries if your item is delete. Add this to + your corresponding delete page: + + db_dml delete_named_object "delete from acs_named_objects where object_id = :item_id" + + note-edit.tcl requires a note_id to determine which record should be deleted. It also looks for a confirmation variable, which should initially be absert. If it is absent, we create a form to @@ -450,6 +437,178 @@ by adding the new files in the APM and then deleting a few samplenotes. + We will now make categories optional on package instance level and + also add a configuration page to allow the package admin to enable/disable + categories for his package. + + Go to the APM and create a number parameter with the name "EnableCategoriesP" + and the default value "0". + Add the following lines to your index.tcl: + + set return_url [ns_conn url] + set use_categories_p [parameter::get -parameter "EnableCategoriesP"] + + Change your to this: + + <a href=configure?<%=[export_url_vars return_url]%>>Configure</a> + <if @use_categories_p@> + <a href="@category_map_url@"<#categories.Site_wide_Categories#</a> + </if> + + Now create a configure page + + ad_page_contract { + This page allows an admin to change the categories usage mode. + } { + {return_url ""} + } + + set title "Configure category mode" + set context [list $title] + set use_categories_p [parameter::get -parameter "EnableCategoriesP"] + + ad_form -name categories_mode -form { + {enabled_p:text(radio) + {label "Enable Categories"} + {options {{Yes 1} {No 0}}} + {value $use_categories_p} + } + {return_url:text(hidden) {value $return_url}} + {submit:text(submit) {label "Set Mode"}} + } -on_submit { + parameter::set_value -parameter "EnableCategoriesP" -value $enabled_p + if {![empty_string_p $return_url]} { + ns_returnredirect $return_url + } + } + + and add this to its corresponding ADP page + + <master> + <property name="title">@title@</property> + <property name="context">@context@</property> + + <formtemplate id="categories_mode"></formtemplate> + + Reference this page from your admin page + + #TCL: + set return_url [ad_conn url] + + #ADP: + <a href=configure?<%=[export_url_vars return_url]%>>Configure</a> + + Change the note-edit.tcl: + + # Use Categories? + set use_categories_p [parameter::get -parameter "EnableCategoriesP" -default 0] + if { $use_categories_p == 1 } { + # YOUR NEW FORM DEFINITION + } else { + # YOUR OLD FORM DEFINITION + } + + + You can filter your notes using categories. The below example does not support multiple + filters and displays a category in a flat format.The first step is to + define the optional parameter category_id for + index.tcl: + + ad_page_contract { + YOUR TEXT + } { + YOURPARAMS + {category_id:integer,optional {}} + } + + Now you have to check whether categories are enabled or not. If this is the case and a + category id is passed you need to extend your sql select query to support filtering. One + way would be to extend the mfp::note::get proc to + support two more swiches -where_clause and + -from_clause. + + set use_categories_p [parameter::get -parameter "EnableCategoriesP" -default 0] + + if { $use_categories_p == 1 && [exists_and_not_null category_id] } { + + set from_clause "category_object_map com, acs_named_objects nam" + set_where_clause "com.object_id = qa.entry_id and + nam.package_id = :package_id and + com.object_id = nam.object_id and + com.category_id = :category_id" + + ... + + mfp::note::get \ + -item_id $item_id \ + -array note_array \ + -where_clause $where_clause \ + -from_clause $from_clause + + ... + } else { + # OLD STUFF + } + + Also you need to make sure that the user can see the corresponding categories. Add the following + snippet to the end of your index page: + + # Site-Wide Categories + if { $use_categories_p == 1} { + set package_url [ad_conn package_url] + if { ![empty_string_p $category_id] } { + set category_name [category::get_name $category_id] + if { [empty_string_p $category_name] } { + ad_return_exception_page 404 "No such category" "Site-wide \ + Category with ID $category_id doesn't exist" + return + } + # Show Category in context bar + append context_base_url /cat/$category_id + lappend context [list $context_base_url $category_name] + set type "all" + } + + # Cut the URL off the last item in the context bar + if { [llength $context] > 0 } { + set context [lreplace $context end end [lindex [lindex $context end] end]] + } + + db_multirow -unclobber -extend { category_name tree_name } categories categories { + select c.category_id as category_id, c.tree_id + from categories c, category_tree_map ctm + where ctm.tree_id = c.tree_id + and ctm.object_id = :package_id + } { + set category_name [category::get_name $category_id] + set tree_name [category_tree::get_name $tree_id] + } + } + + and to the corresponding index ADP page: + + <if @use_categories_p@> + <multiple name="categories"> + <h2>@categories.tree_name@ + <group column="tree_id"> + <a href="@package_url@cat/@categories.category_id@?@YOURPARAMS@&category_id=@categories.category_id@">@categories.category_name@ + </group> + </multiple> + <a href="@package_url@view?@YOURPARAMS@">All Items</if> + + Finally you need a an index.vuh in your + www folder to rewrite the URLs correctly, : + + set url /[ad_conn extra_url] + + if {[regexp {^/+cat/+([^/]+)/*} $url \ + ignore_whole category_id]} { + rp_form_put category_id $category_id + } + rp_internal_redirect "/packages/YOURPACKAGE/www/index" + + Now when ever the user select a category only notes that belong to this category are displayed. + @@ -1025,9 +1184,140 @@ See ad_schedule_proc for more information. + + Enabling WYSIWYG + + by Nima Mazloumi + + Most of the forms in OpenACS are created using the form builder, see . For detailed information on the + API take a look here. + The following section shows how you can modify your form to allow WYSIWYG functionalities. + Convert your page to use ad_form (some changes but worth it) + Here an examples. From: + + template::form create my_form + template::element create my_form my_form_id -label "The ID" -datatype integer -widget hidden + template::element create my_form my_input_field_1 -html { size 30 } -label "Label 1" -datatype text -optional + template::element create my_form my_input_field_2 -label "Label 2" -datatype text -help_text "Some Help" -after_html {Anchor} + + To: + + ad_form -name my_form -form { + my_form_id:key(acs_object_id_seq) + {my_input_field_1:text,optional + {label "Label 1"} + {html {size 30}}} + {my_input_field_2:text + {label "Label 2"} + {help_text "Some Help"} + {after_html + {Anchor}}} + } ... + + Convert your textarea widget to a richtext widget and enable htmlarea. + The htmlarea_p-flag can be used to prevent + WYSIWYG functionality. Defaults to true if left away. + From: + + {my_input_field_2:text + + To: + + {my_input_field_2:richtext(richtext) + {htmlarea_p "t"} + + The richtext widget presents a list with two elements: text and content type. + To learn more on existing content types search in Google for "MIME-TYPES" or + take a look at the cr_mime_types table. + Make sure that both values are passed as a list to your + ad_form or you will have problems + displaying the content or handling the data manipulation correctly. + Depending on the data model of your package you either support a content format + or don't. If you don't you can assume "text/html" or + "text/richtext" or "text/enhanced". + The relevant parts in your ad_form definition are the + switches -new_data, -edit_data, + -on_request and -on_submit. + To allow your data to display correctly you need to add an -on_request block. + If you have the format stored in the database pass this as well else use "text/html": + + set my_input_field_2 [template::util::richtext::create $my_input_field_2 "text/html"] + + Now make sure that your SQL queries that do the data manipulation retrieve the correct value. + If you simply use my_input_field_2 you will store a list. + Thus you need to add an -on_submit block: + + set my_input_field_2 [ template::util::richtext::get_property contents $my_input_field_2] + set format [ template::util::richtext::get_property format $my_input_field_2] #This is optional + + Now the correct values for my_input_field_2 and + format are passed to the -new_data and + -edit_data blocks which don't need to get touched. + To make HTMLArea optional per package intance define a string parameter + "UseWysiwygP" which defaults "0" for your + package using the APM. + In your edit page make the following changes + + # Is WYSIWYG enabled? + set use_wysiwyg_p [parameter::get -parameter "UseWysiwygP" -default "f"] + + ... + + {htmlarea_p $use_wysiwyg_p} + + The -on_request switch should set this value for your form. + + set htmlarea_p $use_wysiwyg_p + + All you need now is a configuration page where the user can change this setting. Create a + configure.tcl file: + + ad_page_contract { + This page allows a faq admin to change the UseWysiwygP setting + } { + {return_url ""} + } + set title "Should we support WYSIWYG?" + set context [list $title] + + set use_wysiwyg_p + + ad_form -name categories_mode -form { + {enabled_p:text(radio) + {label "Enable WYSIWYG"} + {options {{Yes t} {No f}}} + {value $use_wysiwyg_p} + } + {return_url:text(hidden) {value $return_url}} + {submit:text(submit) {label "Change"}} + } -on_submit { + parameter::set_value -parameter "UseWysiwygP" -value $enabled_p + if {![empty_string_p $return_url]} { + ns_returnredirect $return_url + } + } + + In the corresponding ADP file write + + <master> + <property name="title">@title@</property> + <property name="context">@context@</property> + + <formtemplate id="categories_mode"></formtemplate> + + And finally reference this page from your admin page + + #TCL: + set return_url [ad_conn url] + + #ADP: + <a href=configure?<%=[export_url_vars return_url]%>>Configure</a> + + + Future Topics