Forum OpenACS Q&A: Breakthrough on bookmarks module

Collapse
Posted by Bob Fuller on
I now have a working bookmarks module, including the ability
to add and delete bookmarks, create folders, edit bookmarks (which
includes moving them from one folder to another), and open/close
folders.  The complete functionality as it exists in the original
ACS is not quite there, but it's getting close.

In order to accomplish this, I had to make several adjustments in the
original ACS code, including, but not limited to, the following:
    * bm_list_pkg becomes a simple table (not an Oracle package!)
    * some of the trigger mechanisms had to be moved from the data
      model to the edit-bookmark-2.tcl file, which is most likely OK,
      since the parent_id's of bookmarks probably only change when
      you use the "edit" part of the module, anyway
    * in place of the Oracle (proprietary) CONNECT BY construct,
      I decided to make judicious use of the "sort keys", making
      all recursive parent-child references unnecessary

I have all the fixes I've done so far ready and available, and I will
be posting them in the next day or two, as followup threads to this
posting.  Thus far, I've had to modify about 8 Tcl files, in addition
to the bookmarks.sql data model.

When I post the fixes, I'll try to be specific as to how the revised
data model works, and why I made the changes I did.

Stay tuned!
Collapse
Posted by Bob Fuller on
OK, I tried submitting this last night, but the bboard choked because it was too large (most likely). Therefore, I'll try it again, but in pieces. Here's the first one:

General Remarks

As I mentioned, I've made a breakthrough in the bookmarks module. To the best of my knowledge, the following files (at least) require adjustments in order to get the module to work:

  • doc/sql/bookmarks.sql
    (this file, with corrections applied, needs to be loaded into the data module)

  • /web/{server}/tcl/bookmarks-defs.tcl

  • in www/bookmarks:
    • create-folder-2.tcl
    • delete-bookmark-2.tcl
    • edit-bookmark-2.tcl
    • index.tcl
    • insert-one-2.tcl
    • insert-one.tcl
    • most-popular-public.tcl
    • toggle-open-close.tcl

Since this is only (so far) a partial fix, there are probably other files that need to be adjusted. However, I've got some of the major items handled, including the ability to add and delete bookmarks, create folders, edit bookmarks (which includes moving them from one folder to another), and open/close folders. I will probably be publishing a more complete fix as time permits...

Please note that some of the triggers and functions are no longer in the data model. As a workaround, I found it necessary, in some cases, to move the functionality supplied by some of the triggers and functions directly into the context of the pertinent Tcl code. The workaround(s) for the functions and triggers in question now reside mostly in edit-bookmark-2.tcl.

The bookmarks.sql file requires the following components:

  • Tables
    1. bm_urls
    2. bm_list
    3. bm_list_pkg (was originally a package in ACS)
  • Sequences
    1. bm_url_id_seq
    2. bm_bookmark_id_seq
  • Functions
    (Note that some functions/procedures are no longer in the data model!)
    1. inc_char_for_sort_key
    2. new_sort_key
    3. trig_bm_list_sort_key_i_tr
    4. trig_bm_list_sort_key_row_u_tr
  • Triggers
    (Likewise, note that some triggers are no longer in the data model!)
    1. bm_list_sort_key_i_tr
    2. bm_list_sort_key_row_u_tr

Collapse
Posted by Bob Fuller on

The revised data model, bookmarks.sql

To the best of my knowledge, then, here's what's required in the file for the data model (bookmarks.sql):
-- bookmarks.sql

create sequence bm_url_id_seq;

create table bm_urls (
    url_id                  integer primary key,
    -- url title may be null in the case of bookmarks
    -- that are merely icons ie. AIM
    url_title               varchar(500),
    -- host url is separated from complete_url for counting purposes
    host_url                varchar(100) not null,
    complete_url            varchar(500) not null,
    -- meta tags that could be looked up regularly
    meta_keywords           varchar(4000),
    meta_description        varchar(4000),
    last_checked_date       datetime,
    -- the last time the site returned a "live" status
    last_live_date          datetime
);

-- this insertion is required for the change in create-folder-2.tcl
-- (and possibly elsewhere):
insert into bm_urls (url_id, host_url, complete_url)
  values (0, ' ', ' ');
-- (folders in bm_list are joined to a "dummy" entry in bm_urls, as above)

create sequence bm_bookmark_id_seq;
create table bm_list (
    bookmark_id             integer primary key,
    -- sort keys contains 3 characters per level of depth, from
    -- 0-9, then A-Z, a-z. You can get the depth as
    -- length(parent_sort_key) / 3.
    -- the full sort key for any bookmark is
    -- parent_sort_key || local_sort_key
    parent_sort_key         varchar(99), -- parent's sort key
    local_sort_key          char(3) not null,
    owner_id                integer not null references users(user_id),
    creation_date           datetime not null,
    modification_date       datetime,
    -- url_id may be null if the bookmark is a folder
    url_id                  integer references bm_urls,
    -- a person may rename any of his bookmarks so we keep a local title
    local_title                     varchar(500),
    private_p               char(1) default 'f' check (private_p in ('t','f')),
    -- needed in addition to private_p for the case where a public bookmark
    -- is under a hidden folder
    hidden_p                char(1) default 'f' check (hidden_p in ('t','f')),
    -- this is 't' if the bookmark is a folder
    folder_p                char(1) default 'f' check (folder_p in ('t','f')),
    -- null parent_id indicates this is a top level folder/bookmark
    parent_id               integer references bm_list(bookmark_id),
    -- refers to whether a folder is open or closed
    closed_p                char(1) default 't' check (closed_p in ('t','f')),
    -- whether the bookmark is within a closed folder and therefore not shown
    in_closed_p             char(1) default 'f' check (in_closed_p in ('t','f'))
);

CREATE function inc_char_for_sort_key (char)
  returns char(2)
AS '
DECLARE
   old_char char(2);
   carry_p  integer;
   old_code INTEGER;
   new_code INTEGER;
BEGIN
   old_char := $1;
   old_code := ascii(old_char);
   IF old_code = 57 THEN
      -- skip from 9 to A
      new_code := 65;
      carry_p := 0;
   ELSE IF old_code = 90 THEN
      -- skip from Z to a
      new_code := 97;
      carry_p := 0;
   ELSE IF old_code = 122 THEN
      -- wrap around
      new_code := 48;
      carry_p := 1;
   ELSE
      new_code := old_code + 1;
      carry_p := 0;
   END IF;
   END IF;
   END IF;
   --old_char := chr(new_code);
   old_char := ichar(new_code) || cast(carry_p as char(1));
   return old_char;
END;
--END inc_char_for_sort_key;
' language 'plpgsql';

CREATE FUNCTION new_sort_key (CHAR(3)) RETURNS CHAR(3)
AS '
DECLARE
   v_old_sort_key char(3);
   v_chr_1 char(2);
   v_chr_2 char(2);
   v_chr_3 char(2);
   v_carry INTEGER;
BEGIN
   v_old_sort_key := $1;
   IF v_old_sort_key IS null or v_old_sort_key = '''' THEN
      RETURN ''000'';
   END IF;

   v_chr_1 := substr(v_old_sort_key, 1, 1);
   v_chr_2 := substr(v_old_sort_key, 2, 1);
   v_chr_3 := substr(v_old_sort_key, 3, 1);

   v_chr_3 := inc_char_for_sort_key(v_chr_3);
   v_carry := cast(substr(v_chr_3, 2, 1) as integer);
   v_chr_3 := substr(v_chr_3, 1, 1);
   IF v_carry = 1 THEN
      v_chr_2 := inc_char_for_sort_key(v_chr_2);
      v_carry := cast(substr(v_chr_2, 2, 1) as integer);
      v_chr_2 := substr(v_chr_2, 1, 1);
      IF v_carry = 1 THEN
         v_chr_1 := inc_char_for_sort_key(v_chr_1);
         v_carry := cast(substr(v_chr_1, 2, 1) as integer);
         v_chr_1 := substr(v_chr_1, 1, 1);
      END IF;
   END IF;

   RETURN trim(v_chr_1) || trim(v_chr_2) || trim(v_chr_3);
END;
--END new_sort_key;
' language 'plpgsql';

create function trig_bm_list_sort_key_i_tr() returns opaque
as '
DECLARE
  v_last_sort_key bm_list.local_sort_key%TYPE;
  v_parent_sort_key bm_list.parent_sort_key%TYPE;
BEGIN
   IF NEW.parent_id IS NULL THEN
      SELECT max(local_sort_key) INTO v_last_sort_key
        FROM bm_list
        WHERE parent_id IS NULL;
      v_parent_sort_key := null;
   ELSE
      SELECT max(local_sort_key) INTO v_last_sort_key
        FROM bm_list
        WHERE parent_id = NEW.parent_id;
      SELECT parent_sort_key || cast(local_sort_key as varchar(3))
        INTO v_parent_sort_key
        FROM bm_list WHERE bookmark_id = NEW.parent_id;
   END IF;

   NEW.local_sort_key := new_sort_key(v_last_sort_key);
   NEW.parent_sort_key := v_parent_sort_key;
    RETURN NEW;
END;
' language 'plpgsql';

CREATE trigger bm_list_sort_key_i_tr
before INSERT ON bm_list
FOR each row
execute procedure trig_bm_list_sort_key_i_tr();

create table bm_list_pkg (v_updated_ids integer, v_num_entries integer);
insert into bm_list_pkg (v_num_entries) values (0);

create function trig_bm_list_sort_key_row_u_tr() returns opaque
as '
DECLARE
  old_parent_id integer;
  new_parent_id integer;
  f_bookmark_id integer;
  f_num_entries integer;
BEGIN
  old_parent_id := OLD.parent_id;
  new_parent_id := NEW.parent_id;
  IF old_parent_id is null THEN
    old_parent_id := 0;
  END IF;
  IF new_parent_id is null THEN
    new_parent_id := 0;
  END IF;
  IF old_parent_id != new_parent_id THEN
    f_bookmark_id := NEW.bookmark_id;
    select into f_num_entries max(v_num_entries)+1 from bm_list_pkg;
    insert into bm_list_pkg (v_updated_ids, v_num_entries)
      values (f_bookmark_id, f_num_entries);
  END IF;
  return NEW;
END;
' language 'plpgsql';

CREATE trigger bm_list_sort_key_row_u_tr
  before UPDATE ON bm_list
  FOR each row
execute procedure trig_bm_list_sort_key_row_u_tr();

-- the rest of the data model (functions and triggers) has moved,
-- for the time being, primarily, to edit-bookmark-2.tcl!

-- need two indices to support CONNECT BY

create index bm_list_idx1 on bm_list(bookmark_id, parent_id);
create index bm_list_idx2 on bm_list(parent_id, bookmark_id);

-- and the jury's still out on these two indexes, since our data
-- model has changed substantially (and does not use CONNECT BY)!


Collapse
Posted by Bob Fuller on

New proc in bookmarks-defs.tcl

The bookmarks-defs.tcl file, to the best of my recollection, just has the following proc added:

proc_doc bm_set_one_in_closed_p {db owner_id bookmark_id closed_p}
    {This procedure insures that the 'in_closed_p' column in the 'bm_list'
     table is consistent with the open/closed of the folder structure
     (by setting all contents of a single folder in_closed_p=t)} {

    set sort_key [database_to_tcl_string $db "
      select case when parent_sort_key is null then local_sort_key
        else parent_sort_key || cast(local_sort_key as varchar(3)) end
          as sort_key
    from bm_list
    where bookmark_id = $bookmark_id and folder_p = 't'
      and closed_p = '$closed_p' and owner_id = $owner_id"]

    if { $sort_key != "" } {

        ns_db dml $db "begin transaction"

        # ### This can't be done in postgresql, so we've rewritten it
        # Set as in_closed_p those bookmarks which have any parent as closed.
    #     ns_db dml $db "update bm_list set in_closed_p = 't'
    #  where bookmark_id in (select bookmark_id from bm_list
    #  where owner_id = $owner_id
    #  connect by prior bookmark_id = parent_id
    #  start with parent_id in (select bookmark_id from bm_list where owner_id = $owner_id and folder_p = 't' and closed_p = 't'))"

        ns_db dml $db "update bm_list set in_closed_p = '$closed_p'
                       where owner_id = $owner_id and parent_sort_key like '$sort_key%'"

        ns_db dml $db "end transaction"
    }
}

It should be noted that my logic for working with the closed_p and in_closed_p fields was as follows:

  • If the action taken is "open_all", then just set both closed_p and in_closed_p to 'f' where owner_id = $user_id
  • If the action taken is "close_all", then just set closed_p = 't' where owner_id = $user_id, and do the same for in_closed_p (parent_id not null)
  • This leaves two final scenarios:
    1. Open one folder
    2. Close one folder

    In both of these remaining scenarios, all you need to do is update closed_p as appropriate, and then update in_closed_p similarly for all the children of the folder in question. That's what the new proc accomplishes.

Collapse
Posted by Bob Fuller on

Changes in create-folder-2.tcl

Numbers 1 and 2 below reflect the change in the treatment of folders, which now have a dummy entry in bm_urls (as described in the data model).

  1. Around line 39:
    (bookmark_id, owner_id, local_title, parent_id, creation_date, folder_p, closed_p, url_id)
    

    instead of

    (bookmark_id, owner_id, local_title, parent_id, creation_date, folder_p, closed_p)
    
  2. Around line 41:
    ($bookmark_id, $user_id, '$local_title', [ns_dbquotevalue $parent_id], sysdate(), 't', 'f', 0)
    

    instead of

    ($bookmark_id, $user_id, [db_postgres_doubleapos_null_sql $local_title]', [ns_dbquotevalue $parent_id], sysdate(), 't', 'f')
    
  3. Around line 57:
    # bm_set_in_closed_p $db $user_id
    

    instead of

    bm_set_in_closed_p $db $user_id
    

Collapse
Posted by Bob Fuller on
<h2>Changes in delete-bookmark-2.tcl</h2>
<P>
The following changes were made to the <B>delete-bookmark-2.tcl</B> file:
<P>
<OL>
<LI>The following gets commented out:
<BLOCKQUOTE>
<PRE>
# set sql_child_delete "
#    delete from bm_list
#    where bookmark_id in (select      bookmark_id
#                          from        bm_list
#                      connect by  prior bookmark_id = parent_id
#                      start with  parent_id = $bookmark_id)
#    or bookmark_id = $bookmark_id"
</PRE>
</BLOCKQUOTE>
<LI>... and replaced by:
<BLOCKQUOTE>
<PRE>
set bookmark_sort_key [database_to_tcl_string $db "
  select case when parent_sort_key is null then local_sort_key
        else parent_sort_key || cast(local_sort_key as varchar(3)) end
  from bm_list where bookmark_id = $bookmark_id"]

set sql_child_delete "
  delete from bm_list
  where parent_sort_key like '$bookmark_sort_key%'
    or bookmark_id = $bookmark_id"`
</PRE>
</BLOCKQUOTE>
</OL>
<P>
(The theory here, in case anyone is interested, is that we avoid having
to use the Oracle proprietary CONNECT BY construct by very cleverly
leveraging the "sort keys" to produce the identical result!) I'd be happy
to explain this in more detail, if anyone wants to hear about it, since
my breakthrough in the bookmarks module hinges <I>precisely</I> on judicious
use of the sort keys, allowing us to avoid both the CONNECT BY construct
<B>and</B> the use of recursive function (or procedure) calls.
<P>
<HR>

Collapse
Posted by Bob Fuller on

The revised edit-bookmark-2.tcl file

Here's the complete revised edit-bookmark-2.tcl file:

# edit-bookmark-2.tcl,v 3.0 2000/02/06 03:35:21 ron Exp
# edit-bookmark-2.tcl
#
# edit a bookmark in your bookmark list
#
# by aure@arsdigita.com and dh@arsdigita.com

set_the_usual_form_variables

# local_title, complete_url, bookmark_id, parent_id, return_url

validate_integer bookmark_id $bookmark_id

set user_id [ad_verify_and_get_user_id]
ad_maybe_redirect_for_registration

set db [ns_db gethandle]

# start error-checking
set exception_text ""
set exception_count 0

if {(![info exists bookmark_id])||([empty_string_p $bookmark_id])} {
    incr exception_count
    append exception_text "<li>No bookmark was specified"
}

# make sure that the user owns the bookmark
set  ownership_query "
        select count(*)
        from   bm_list
        where  owner_id=$user_id
        and bookmark_id=$bookmark_id"
set ownership_test [database_to_tcl_string $db $ownership_query]

if {$ownership_test==0} {
    incr exception_count
    append exception_text "<li>You can not edit this bookmark"
}

# return errors
if { $exception_count> 0 } {
    ad_return_complaint $exception_count $exception_text
    return 0
}

if { ![info exists parent_id] || [empty_string_p $parent_id] } {
    set parent_id "null"
}
ns_db dml $db "begin transaction"

# ### not sure why I had to change this section, but it didn't work ###
# if the bookmark to edit is a folder, complete_url won't be defined
# if { ![info exists complete_url] } {
# if { [empty_string_p $complete_url]||![info exists complete_url] } {

    # this is a folder so edit its name
#     set sql_update "
#          update  bm_list
#          set     local_title = '[DoubleApos $local_title]',
#                  private_p = '$private_p',
#                  parent_id = $parent_id
#          where   owner_id = $user_id
#          and     bookmark_id = $bookmark_id"
#    set sql_update "
#         update  bm_list
#         set     local_title = '$local_title',
#                 private_p = '$private_p',
#                 parent_id = $parent_id
#         where   owner_id = $user_id
#         and     bookmark_id = $bookmark_id"
#    ns_db dml $db $sql_update

# } else {
if { [info exists complete_url] } {

    # entry is a bookmark - need to update both name and url

    set host_url [bm_host_url $complete_url]

    # check to see if we already have the url in our database
    set url_query "select url_id
                   from   bm_urls
                   where  complete_url = '[DoubleApos $complete_url]'"
    set url_id [database_to_tcl_string_or_null  $db $url_query]

    # if we don't have the url, then insert the url into the database
    if {[empty_string_p $url_id]} {
        set url_id [database_to_tcl_string $db "select bm_url_id_seq.nextval from dual"]
        ns_db dml $db "
        insert into bm_urls
        (url_id, host_url, complete_url)
        values
        ($url_id, '[DoubleApos $host_url]', '[DoubleApos $complete_url]')"
    }
# have added the url if needed - now just update the name

    set sql_update "
        update  bm_list
        set     local_title = '[DoubleApos $local_title]',
                url_id = $url_id,
                private_p = '$private_p',
                parent_id = $parent_id
        where   bookmark_id = $bookmark_id"

    ns_db dml $db $sql_update
} else {
    # this is a folder so edit its name
    set sql_update "
         update  bm_list
         set     local_title = '[DoubleApos $local_title]',
                 private_p = '$private_p',
                 parent_id = $parent_id
         where   owner_id = $user_id
         and     bookmark_id = $bookmark_id"
   ns_db dml $db $sql_update

}

# the bookmark_id's are tagged by a "before update" trigger on bm_list
# -- and they end up as v_updated_ids in bm_list_pkg

# just checking to make sure we have some updates to do...
set id_count [database_to_tcl_string $db "
   select count(*)
   from bm_list_pkg
   where v_num_entries > 0
   and v_updated_ids is not null"]

set i 0

# ### for some reason, I had to do the other selects in a different loop ###
# ... so, in this loop, I collect everything into arrays
if {$id_count > 0} {

  set selection [ns_db select $db "
     select distinct v_updated_ids, coalesce(parent_id, -1) as parent_id,
       case when parent_sort_key is null then local_sort_key
            else parent_sort_key || cast(local_sort_key as varchar(3)) end
         as parent_sort_key,
       folder_p
     from bm_list_pkg, bm_list
     where v_num_entries > 0
     and v_updated_ids = bookmark_id"]

  while {[ns_db getrow $db $selection]} {
    set_variables_after_query
    set v_upd_ids($i) $v_updated_ids
    set new_parent_id($i) $parent_id
    set old_parent_sort_key($i) $parent_sort_key
    set is_folder($i) $folder_p

    incr i
  }
  while {$i > 0} {
    incr i -1

    if {$new_parent_id($i) > -1} {
      set v_parent_sort_key [database_to_tcl_string $db "
        select case when parent_sort_key is null
          then local_sort_key
          else parent_sort_key || cast(local_sort_key as varchar(3)) end
        from bm_list where bookmark_id = $new_parent_id($i)"]

      set v_last_sort_key [database_to_tcl_string $db "
        select max(local_sort_key) from bm_list
        where parent_id = $new_parent_id($i)
        and bookmark_id != $v_upd_ids($i)"]

      ns_db dml $db "update bm_list set parent_sort_key = '$v_parent_sort_key',
        local_sort_key = new_sort_key('$v_last_sort_key')
        where bookmark_id = $v_upd_ids($i)"

    } else {

      set v_last_sort_key [database_to_tcl_string $db "
        select max(local_sort_key) from bm_list where parent_id is null
        and bookmark_id != $v_upd_ids($i)"]

      ns_db dml $db "update bm_list set parent_sort_key = null,
        local_sort_key = new_sort_key('$v_last_sort_key')
        where bookmark_id = $v_upd_ids($i) and parent_id is null"

    }
    set v_new_sort_key [database_to_tcl_string $db "
      select case when parent_sort_key is null then local_sort_key
        else parent_sort_key || cast(local_sort_key as varchar(3)) end
      from bm_list where bookmark_id = $v_upd_ids($i)"]
    set len_old_sort_key [string length $old_parent_sort_key($i)]
    ns_db dml $db "update bm_list
      set parent_sort_key = '$v_new_sort_key' ||
        substr(parent_sort_key, $len_old_sort_key+1,
          length(parent_sort_key)-$len_old_sort_key)
      where parent_sort_key like '$old_parent_sort_key($i)%'"
  }
}

# ### need to delete all the entries "tagged" by the update trigger
# ### now that we're done updating sort keys
ns_db dml $db "delete from bm_list_pkg where v_num_entries > 0"

# ### I tried to do this as a Tcl proc in tcl/bookmarks-defs.tcl, ###
# ### but no luck on that (so far)...
# bm_recalc_sort_keys $db $user_id

# ### this may need to be changed, at some point...
bm_set_hidden_p $db $user_id

# ### the first of these is my modification of bm_set_in_closed_p, ###
# ### which, as you can tell, does only ONE folder
# ### forget why I had to comment it out, but it's NOT relevant to
# ### this part of the bookmark module
# ### (bm_set_in_closed_p is commented out because it didn't work)
# bm_set_one_in_closed_p $db $user_id $bookmark_id
# bm_set_in_closed_p $db $user_id

ns_db dml $db "end transaction"

# send the user back to where they came from before editing began
ns_returnredirect $return_url



Collapse
Posted by Bob Fuller on

Query change in index.tcl

In index.tcl, comment out the following query:

#set bookmark_query " # select bookmark_id, # bm_list.url_id, # coalesce(local_title, url_title) as bookmark_title, # hidden_p, # complete_url, # last_live_date, # last_checked_date, # folder_p, # closed_p, # length(parent_sort_key)*8 as indent_width # from bm_list, bm_urls # where owner_id = $user_id # and in_closed_p = 'f' # and bm_list.url_id = bm_urls.url_id(+) # order by parent_sort_key || local_sort_key"
and do the following query instead (to replace the outer join, plus a couple of other fixes):
set bookmark_query " select bookmark_id, bm_list.url_id, coalesce(local_title, url_title) as bookmark_title, hidden_p, complete_url, last_live_date, last_checked_date, folder_p, closed_p, case when parent_sort_key is null then 0 else length(parent_sort_key)*8 end as indent_width from bm_list, bm_urls where owner_id = $user_id and in_closed_p = 'f' and bm_list.url_id = bm_urls.url_id union select bookmark_id, '' as url_id, local_title as bookmark_title, hidden_p, '' as complete_url, NULL as last_live_date, NULL as last_checked_date, folder_p, closed_p, case when parent_sort_key is null then 0 else length(parent_sort_key)*8 end as indent_width from bm_list where owner_id = $user_id and in_closed_p = 'f' and not exists (select 1 from bm_urls where url_id = bm_list.url_id) order by case when parent_sort_key is null then '!' else '!' || parent_sort_key end || cast(local_sort_key as varchar(3))"

Collapse
Posted by Bob Fuller on

Changes in insert-one-2.tcl and insert-one.tcl

In insert-one-2.tcl, it looks like I had to replace this line:
($bookmark_id, $user_id, $url_id,'[db_postgres_doubleapos_null_sql $local_title]', [ns_dbquotevalue $parent_id], sysdate())"
with the following one:
($bookmark_id, $user_id, $url_id,'$local_title', $parent_id, sysdate())"

In insert-one.tcl, looks like for some reason I had to omit an extra < /ul> tag, so that the following:

If this is correct, choose which folder to place the bookmark in: <ul> <table> <tr> <td> [bm_folder_selection $db $user_id $bookmark_id] <p> <input type=submit value=Submit> </td> </tr> </table> </form> </ul>"
had to be changed to:
If this is correct, choose which folder to place the bookmark in: <ul> <table> <tr> <td> [bm_folder_selection $db $user_id $bookmark_id] <p> <input type=submit value=Submit> </td> </tr> </table> </form>"

Changes in most-popular-public.tcl and toggle-open-close.tcl

In most-popular-public.tcl, I had to change two queries to reflect the presence of the "dummy" entry (for folders) that I added to bm_urls. The two queries now read as follows:
# -- get the most popular hosts ----------- set selection [ns_db select $db "select host_url, count(*) as n_bookmarks from bm_urls, bm_list where bm_urls.url_id = bm_list.url_id and bm_list.private_p <> 't' and bm_urls.url_id > 0 group by host_url order by n_bookmarks desc"]
and:
set selection [ns_db select $db "select complete_url, url_title, count(*) as n_bookmarks from bm_urls, bm_list where bm_urls.url_id = bm_list.url_id and bm_list.private_p <> 't' and bm_urls.url_id > 0 group by complete_url, url_title order by n_bookmarks desc"]

And, finally, for toggle-open-close.tcl, the part I changed was after the "begin transaction". Here's the corrected version (note that the final line needs to be commented out -- it's uncommented in the original):

if { [string compare $action "open_all"] == 0 } { # we're assuming if all are open, no bookmarks are in closed folders! ns_db dml $db "update bm_list set closed_p = 'f', in_closed_p = 'f' where owner_id = $user_id" } elseif { [string compare $action "close_all"] == 0 } { # ... and if all folders are closed, all bookmarks are in closed folders! ns_db dml $db "update bm_list set closed_p = 't' where owner_id = $user_id" ns_db dml $db "update bm_list set in_closed_p = 't' where owner_id = $user_id and parent_id is not null" } else { validate_integer bookmark_id $bookmark_id # determine current state of folder (closed/open) set closed_p [database_to_tcl_string $db " select closed_p from bm_list where bookmark_id = $bookmark_id"] if { $closed_p == "t" } { ns_db dml $db " update bm_list set closed_p = 'f' where bookmark_id = $bookmark_id and owner_id = $user_id" set closed_p "f" } else { ns_db dml $db " update bm_list set closed_p = 't' where bookmark_id = $bookmark_id and owner_id = $user_id" set closed_p "t" } bm_set_one_in_closed_p $db $user_id $bookmark_id $closed_p } # bm_set_in_closed_p $db $user_id

Hopefully this covers everything. Please let me know if there are any questions or problems.


Final observations

If I've communicated everything correctly, these changes should result in a working bookmarks module. However, although the basic functionality (as detailed in the "Original question") should now be available, there are definitely certain items still missing, such as import/export. However, they should be much easier to track down and fix, now that the basic module is (or should be) working, once these fixes have been applied.
Collapse
Posted by Erik Rogneby on
Thanks Bob!

I'll be trying your code out on our company intranet in the next couple of days.  I'll post how it goes.

Collapse
Posted by Alex Sokoloff on
Arrghhhh! I created a 100% functional port of the bookmarks modules a couple of months ago. I mentioned it in passing in one of the threads, but it wasn't really formally announced. Obviously, I should have made sure word got out. I think Don Baccus commited my port to CVS. I'm still CVS-impaired at this point, but if you can't find the port there, email me and I'll send it to you. Hope y'all didn't spend too much time on a duplicate effort, or if you did that you enjoyed yourself all the same.

-Alex

Collapse
Posted by Ben Adida on
Yes, duplication here is a sad result of the monolithic aspect of the
ACS as it stands. Thankfully, we haven't had *too* much of it. Look at
the bright side of things: there is enough interest out there in the
community that two separate efforts have ported the bookmarks module!
Collapse
Posted by Bob Fuller on
Hey, no problem!  I've learned a hell of a lot in my efforts, and, even if my code wasn't completely 100% on the mark, I feel like I now have much more in the way of ability to help with other ports.  So it's not a wasted effort, from my perspective.  In terms of CVS, I'm also "CVS-impaired", to date...
Collapse
Posted by Don Baccus on
Hmmm...I know I did post that I'd updated CVS with Alex's bookmarks port after I committed it, but apparently it wasn't obvious.

I guess we should've posted it in News, too.

Ben's right that OpenACS 4.1 will help a lot - modules will be individually downloadable, and those that aren't ported simply won't exist in the release tree.