tag picker with autocompletion
authorAleksandr Balezin <gescheit@yandex-team.ru>
Fri, 13 Dec 2013 14:02:50 +0000 (18:02 +0400)
committerAlexey Andriyanov <alan@al-an.info>
Sat, 11 Jan 2014 23:46:06 +0000 (03:46 +0400)
This is an UI enhancement.
Instead of showing tons of tags in one page, show only a text input
with suggestion. A user can quickly find necessary tags by typing
some characters of it.

Removed 'Tag' tab from all pages. Now tag assigning is performed on
'Properties' or 'Edit' tabs.

changes:
Removed saveEntityTags(), renderNewEntityTags(), renderEntityTags(),
    renderEntityTagsPortlet() functions.
    'editRange', 'updIPv4RSP' and 'updateFile' ops switched from
    tableHandler()
       to dedicated functions.

ChangeLog
wwwroot/css/tagit.css [new file with mode: 0644]
wwwroot/inc/interface-lib.php
wwwroot/inc/interface.php
wwwroot/inc/navigation.php
wwwroot/inc/ophandlers.php
wwwroot/inc/slb-interface.php
wwwroot/inc/slb2-interface.php
wwwroot/js/tag-it-local.js [new file with mode: 0644]
wwwroot/js/tag-it.js

index d8b3043b1ed9e0f064bb39dc85d83566a7c645ef..93b9eb638ec569cc72f7bb09b0026fdd9f106c30 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -15,6 +15,7 @@
        update: display a datetime format hint for date attributes (#1051)
        update: display location tree in object breadcrumbs (#1125)
        new feature: add IPv4 natting for all protocols (#971) by Tim Wilkes/Mark Wilkinson
+       update: use suggest-menu for tag assignment
        update: show tag trace in tag title
 0.20.6 2013-11-29
        bugfix: "sshnokey" gateway had a bug
diff --git a/wwwroot/css/tagit.css b/wwwroot/css/tagit.css
new file mode 100644 (file)
index 0000000..3481c06
--- /dev/null
@@ -0,0 +1,171 @@
+ul.autocomplete-menu {
+       border: 1px dashed #3C78B5;
+       background-color: #FFF;
+       overflow-y: auto;
+       overflow-x: auto;
+       display: inline-block;
+       margin: 0 auto;
+       max-height: 500px;
+       margin-top: -5px;
+}
+
+ul.tagit {
+       padding: 1px 5px;
+       overflow: auto;
+       margin-left: inherit; /* usually we don't want the regular ul margins. */
+       margin-right: inherit;
+       display: table-row;
+       list-style-type: none;
+       background-color: #e8e8e8;
+}
+
+ul.tagit-vertical {
+       margin-left: 2px;
+       margin-right: 2px;
+       overflow: hidden;
+       margin-top: 0px;
+       margin-bottom: 0px;
+       padding-left: 1px;
+       background-color: #e8e8e8;
+}
+
+ul.tagit-horizontal {
+       margin-left: 2px;
+       margin-right: 2px;
+       overflow: hidden;
+       margin-top: 0px;
+       margin-bottom: 0px;
+       padding-left: 1px;
+       background-color: #e8e8e8;
+}
+
+ul.tagit-horizontal li {
+       display: inline-block;
+       margin: 2px 5px 2px 0;
+       vertical-align: middle;
+       list-style-type: none;
+       height: 100%;
+}
+
+ul.tagit li {
+       display: inline-block;
+       float: left;
+       margin: 2px 5px 2px 0;
+       vertical-align: middle;
+       list-style-type: none;
+       height: 100%;
+}
+
+ul.tagit-vertical li {
+       margin: 1px 5px 1px 0;
+       height: 100%;
+       display: block;
+       text-align: left;
+}
+
+ul.tagit li span {
+       display: inline-block;
+       vertical-align: middle;
+}
+
+ul.tagit li.tagit-choice, ul.tagit-vertical li.tagit-choice {
+       position: relative;
+       line-height: inherit;
+}
+
+ul.tagit li.tagit-choice-read-only, ul.tagit-vertical li.tagit-choice-read-only {
+       padding: .2em .5em .2em .5em;
+}
+
+ul.tagit li.tagit-choice-editable, ul.tagit-vertical li.tagit-choice-editable {
+       padding: .2em 18px .2em 0px;
+}
+
+ul.tagit li.tagit-new, ul.tagit-vertical li.tagit-new {
+       padding: .25em 4px .25em 0;
+}
+
+ul.tagit li.tagit-choice a.tagit-label, ul.tagit-vertical li.tagit-choice a.tagit-label,  ul.tagit-horizontal li.tagit-choice a.tagit-label {
+       cursor: pointer;
+       text-decoration: none;
+}
+ul.tagit li.tagit-choice .tagit-close, ul.tagit-vertical li.tagit-choice .tagit-close {
+       cursor: pointer;
+       right: .1em;
+       top: 50%;
+       margin-left: 3px;
+       line-height: 17px;
+       vertical-align: middle;
+       display: inline-block;
+}
+
+/* used for some custom themes that don't need image icons */
+ul.tagit li.tagit-choice .tagit-close .text-icon, ul.tagit-vertical li.tagit-choice .tagit-close .text-icon, ul.tagit-horizontal li.tagit-choice .tagit-close .text-icon {
+       display: none;
+}
+
+.icon-close {
+       background-image: url(?module=chrome&uri=pix/jquery-ui-1.8.22.redmond/ui-icons_469bdd_256x240.png);
+       background-position: -80px -128px;
+       height: 16px;
+       width: 16px;
+       background-repeat: no-repeat;
+       overflow: hidden;
+       text-indent: -99999px;
+       display: inline-block;
+}
+
+.icon-folder-open {
+       background-image: url(?module=chrome&uri=pix/jquery-ui-1.8.22.redmond/ui-icons_469bdd_256x240.png);
+       background-position: -16px -96px;
+       height: 16px;
+       width: 16px;
+       background-repeat: no-repeat;
+       overflow: hidden;
+       text-indent: -99999px;
+       display: inline-block;
+       cursor: pointer;
+}
+
+.disabled a {
+       cursor: default !important;
+       color: grey !important;
+}
+
+.context-menu-item {
+       font-size: 12px ! important;
+}
+
+.tagit-menu {
+       list-style: none;
+       padding: 2px;
+       margin: 0;
+       display: block;
+       float: left;
+}
+
+.tagit-menu .tagit-menu {
+       margin-top: -3px;
+}
+
+.tagit-menu .tagit-menu-item {
+       margin:0;
+       padding: 0;
+       zoom: 1;
+       float: left;
+       clear: left;
+       width: 100%;
+       color: black;
+       cursor: pointer;
+}
+
+.ui-menu .tagit-menu-item a.ui-state-hover,
+.ui-menu .tagit-menu-item a.ui-state-active {
+       font-weight:inherit !important;
+}
+
+a div {
+       font-family: inherit;
+       color: inherit;
+       text-decoration: inherit;
+}
index a5e7a314bab10bd39bea1587f412229b346fc767..a41ab4404c9bcef03100c5f423218dd62c674a2a 100644 (file)
@@ -1012,4 +1012,56 @@ function serializeFileLinks ($links, $scissors = FALSE)
        return $ret;
 }
 
+function printTagsPicker ($preselect=NULL)
+{
+       printTagsPickerInput ();
+       printTagsPickerUl ($preselect);
+       enableTagsPicker ();
+}
+
+function printTagsPickerInput ($input_name="taglist")
+{
+       # use data-attribute as identifier for tagit
+       echo "<input type='text' data-tagit-valuename='" . $input_name . "' data-tagit='yes' placeholder='new tags here...' class='ui-autocomplete-input' autocomplete='off' role='textbox' aria-autocomplete='list' aria-haspopup='true'>";
+       echo "<span title='show tag tree' class='icon-folder-open tagit_input_" . $input_name . "'></span>";
+}
+
+function printTagsPickerUl ($preselect=NULL, $input_name="taglist")
+{
+       global $target_given_tags;
+       if ($preselect === NULL)
+               $preselect = $target_given_tags;
+       foreach ($preselect as &$value) # readable time format
+               $value['time_parsed'] = formatAge ($value['time']);
+       usort ($preselect, 'cmpTags');
+       $preselect_hidden = "";
+       unset($value);
+       foreach ($preselect as $value){
+               $preselect_hidden .= "<input type=hidden name=" . $input_name . "[] value=" . $value['id'] . ">";
+       }
+       echo $preselect_hidden; # print preselected tags id that used in case javascript problems
+       echo "<ul data-tagit='yes' data-tagit-valuename='" . $input_name . "' data-tagit-preselect='" . json_encode($preselect) . "' class='tagit-vertical'></ul>";
+}
+
+function enableTagsPicker ()
+{
+       global $taglist;
+       static $taglist_inserted;
+       includeJQueryUI ();
+       addCSS ('css/tagit.css');
+       addJS ('js/tag-it.js');
+       addJS ('js/tag-it-local.js');
+       if (! $taglist_inserted)
+       {
+               $taglist_filtred = $taglist;
+               foreach ($taglist_filtred as &$tag) # remove unused fields
+               {
+                       foreach (array_keys ($tag) as $key)
+                       if (! in_array ($key, array("tag", "is_assignable", "trace")))
+                               unset($tag[$key]);
+               }
+               addJS ('var taglist = ' . json_encode ($taglist_filtred) . ';', TRUE);
+               $taglist_inserted = TRUE;
+       }
+}
 ?>
index 6e2cdb0f4f8a4fc87678c727bcfef9f8ac7a9408..740aebff3836f5f5c1031bde55fdaa311e128ce3 100644 (file)
@@ -918,8 +918,8 @@ function renderNewRackForm ($row_id)
        printOpFormIntro ('addRack', array ('got_data' => 'TRUE'));
        echo '<table border=0 align=center>';
        echo "<tr><th class=tdright>Name (required):</th><td class=tdleft><input type=text name=name tabindex=1></td>";
-       echo "<td rowspan=4>Assign tags:<br>";
-       renderNewEntityTags ('rack');
+       echo "<td rowspan=4>Tags:<br>";
+       printTagsPicker ();
        echo "</td></tr>\n";
        echo "<tr><th class=tdright>Height in units (required):</th><td class=tdleft><input type=text name=height1 tabindex=2 value='${default_height}'></td></tr>\n";
        echo "<tr><th class=tdright>Asset tag:</th><td class=tdleft><input type=text name=asset_no tabindex=4></td></tr>\n";
@@ -933,7 +933,7 @@ function renderNewRackForm ($row_id)
        echo '<table border=0 align=center>';
        echo "<tr><th class=tdright>Height in units (*):</th><td class=tdleft><input type=text name=height2 value='${default_height}'></td>";
        echo "<td rowspan=3 valign=top>Assign tags:<br>";
-       renderNewEntityTags ('rack');
+       printTagsPicker ();
        echo "</td></tr>\n";
        echo "<tr><th class=tdright>Rack names (required):</th><td class=tdleft><textarea name=names cols=40 rows=25></textarea></td></tr>\n";
        echo "<tr><td class=submit colspan=2>";
@@ -960,6 +960,9 @@ function renderEditObjectForm()
        echo "<tr><td>&nbsp;</td><th class=tdright>Common name:</th><td class=tdleft><input type=text name=object_name value='${object['name']}'></td></tr>\n";
        echo "<tr><td>&nbsp;</td><th class=tdright>Visible label:</th><td class=tdleft><input type=text name=object_label value='${object['label']}'></td></tr>\n";
        echo "<tr><td>&nbsp;</td><th class=tdright>Asset tag:</th><td class=tdleft><input type=text name=object_asset_no value='${object['asset_no']}'></td></tr>\n";
+       echo "<tr><td>&nbsp;</td><th class=tdright>Tags:</th><td class=tdleft>";
+       printTagsPicker ();
+       echo "</td></tr>\n";
        // parent selection
        if (objectTypeMayHaveParent ($object['objtype_id']))
        {
@@ -1077,6 +1080,9 @@ function renderEditRackForm ($rack_id)
        echo "<tr><td>&nbsp;</td><th class=tdright>Name (required):</th><td class=tdleft><input type=text name=name value='${rack['name']}'></td></tr>\n";
        echo "<tr><td>&nbsp;</td><th class=tdright>Height (required):</th><td class=tdleft><input type=text name=height value='${rack['height']}'></td></tr>\n";
        echo "<tr><td>&nbsp;</td><th class=tdright>Asset tag:</th><td class=tdleft><input type=text name=asset_no value='${rack['asset_no']}'></td></tr>\n";
+       echo "<tr><td>&nbsp;</td><th class=tdright>Tags:</th><td class=tdleft>";
+       printTagsPicker ();
+       echo "</td></tr>\n";
        // optional attributes
        $values = getAttrValues ($rack_id);
        $num_attrs = count($values);
@@ -2511,24 +2517,23 @@ END
        , TRUE);
 
        startPortlet ('Add new');
-       echo '<table border=0 cellpadding=10 align=center>';
        printOpFormIntro ('add');
-       // tags column
-       echo '<tr><td rowspan=5><h3>assign tags</h3>';
-       renderNewEntityTags ($realm);
-       echo '</td>';
+       echo '<table border=0 cellpadding=5 cellspacing=0 align=center>';
+
        // inputs column
        $prefix_value = empty ($_REQUEST['set-prefix']) ? '' : $_REQUEST['set-prefix'];
-       echo "<th class=tdright>prefix</th><td class=tdleft><input type=text name='range' size=36 class='live-validate' tabindex=1 value='${prefix_value}'></td>";
-       echo '<tr><th class=tdright>VLAN</th><td class=tdleft>';
+       echo "<th class=tdright>Prefix:</th><td class=tdleft><input type=text name='range' size=36 class='live-validate' tabindex=1 value='${prefix_value}'></td>";
+       echo '<tr><th class=tdright>VLAN:</th><td class=tdleft>';
        echo getOptionTree ('vlan_ck', getAllVLANOptions(), array ('select_class' => 'vertical', 'tabindex' => 2)) . '</td></tr>';
-       echo "<tr><th class=tdright>name</th><td class=tdleft><input type=text name='name' size='20' tabindex=3></td></tr>";
-       echo '<tr><td class=tdright><input type=checkbox name="is_connected" id="is_connected" tabindex=4></td>';
-       echo '<th class=tdleft><label for="is_connected">reserve subnet-router anycast address</label></th></tr>';
+       echo "<tr><th class=tdright>Name:</th><td class=tdleft><input type=text name='name' size='20' tabindex=3></td></tr>";
+       echo '<tr><th class=tdright>Tags:</th><td class="tdleft">';
+       printTagsPicker ();
+       echo '</td></tr>';
+       echo '<tr><td class=tdright><input type=checkbox name="is_connected" tabindex=4></td><th class=tdleft>reserve subnet-router anycast address</th></tr>';
        echo "<tr><td colspan=2>";
        printImageHREF ('CREATE', 'Add a new network', TRUE, 5);
        echo '</td></tr>';
-       echo "</form></table><br><br>\n";
+       echo "</table></form><br><br>\n";
        finishPortlet();
 }
 
@@ -2932,17 +2937,20 @@ function renderIPNetworkProperties ($id)
        global $pageno;
        $netdata = spotEntity ($pageno, $id);
        echo "<center><h1>${netdata['ip']}/${netdata['mask']}</h1></center>\n";
-       echo "<table border=0 cellpadding=10 cellpadding=1 align='center'>\n";
        printOpFormIntro ('editRange');
-       echo '<tr><td class=tdright><label for=nameinput>Name:</label></td>';
+       echo "<table border=0 cellpadding=5 cellspacing=0 align='center'>\n";
+       echo '<tr><th class=tdright><label for=nameinput>Name:</label></th>';
        echo "<td class=tdleft><input type=text name=name id=nameinput size=80 maxlength=255 value='";
        echo htmlspecialchars ($netdata['name'], ENT_QUOTES, 'UTF-8') . "'></tr>";
-       echo '<tr><td class=tdright><label for=commentinput>Comment:</label></td>';
+       echo "<tr><th class=tdright>Tags:</th><td class=tdleft>";
+       printTagsPicker ();
+       echo "</td></tr>\n";
+       echo '<tr><th class=tdright><label for=commentinput>Comment:</label></th>';
        echo "<td class=tdleft><textarea name=comment id=commentinput cols=80 rows=25>\n";
        echo htmlspecialchars ($netdata['comment'], ENT_QUOTES, 'UTF-8') . "</textarea></tr>";
        echo "<tr><td colspan=2 class=tdcenter>";
        printImageHREF ('SAVE', 'Save changes', TRUE);
-       echo "</td></form></tr></table>\n";
+       echo "</td></tr></table></form>\n";
 
        echo '<center>';
        if (! isIPNetworkEmpty ($netdata))
@@ -3299,7 +3307,7 @@ function renderAddMultipleObjectsForm ()
                if ($i == 0)
                {
                        echo "<td valign=top rowspan=${max}>";
-                       renderNewEntityTags ('object');
+                       printTagsPicker ();
                        echo "</td>\n";
                }
                echo "</tr>\n";
@@ -3318,7 +3326,7 @@ function renderAddMultipleObjectsForm ()
        echo "</td></tr>";
        echo "<tr><th>Tags</th></tr>";
        echo "<tr><td valign=top>";
-       renderNewEntityTags ('object');
+       printTagsPicker ();
        echo "</td></tr>";
        echo "<tr><td colspan=2><input type=submit name=got_very_fast_data value='Go!'></td></tr></table>\n";
        echo "</form>\n";
@@ -3696,10 +3704,10 @@ function renderUserListEditor ()
                startPortlet ('Add new');
                printOpFormIntro ('createUser');
                echo '<table cellspacing=0 cellpadding=5 align=center>';
-               echo '<tr><th>&nbsp;</th><th>&nbsp;</th><th>Assign tags</th></tr>';
+               echo '<tr><th>&nbsp;</th><th>&nbsp;</th><th>Tags</th></tr>';
                echo '<tr><th class=tdright>Username</th><td class=tdleft><input type=text size=64 name=username tabindex=100></td>';
                echo '<td rowspan=4>';
-               renderNewEntityTags ('user');
+               printTagsPicker ();
                echo '</td></tr>';
                echo '<tr><th class=tdright>Real name</th><td class=tdleft><input type=text size=64 name=realname tabindex=101></td></tr>';
                echo '<tr><th class=tdright>Password</th><td class=tdleft><input type=password size=64 name=password tabindex=102></td></tr>';
@@ -3945,6 +3953,9 @@ function renderEditLocationForm ($location_id)
        printSelect ($locations, array ('name' => 'parent_id'), $location['parent_id']);
        echo "</td></tr>\n";
        echo "<tr><td>&nbsp;</td><th class=tdright>Name (required):</th><td class=tdleft><input type=text name=name value='${location['name']}'></td></tr>\n";
+       echo "<tr><td>&nbsp;</td><th class=tdright>Tags:</th><td class=tdleft>";
+       printTagsPicker ();
+       echo "</td></tr>\n";
        // optional attributes
        $values = getAttrValues ($location_id);
        $num_attrs = count($values);
@@ -5277,20 +5288,6 @@ END
        finishPortlet();
 }
 
-// Dump all tags in a single SELECT element.
-function renderNewEntityTags ($for_realm = '')
-{
-       global $taglist, $tagtree;
-       if (!count ($taglist))
-       {
-               echo "No tags defined";
-               return;
-       }
-       echo '<div class=tagselector><table border=0 align=center cellspacing=0 class="tagtree">';
-       printTagCheckboxTable ('taglist', array(), array(), $tagtree, $for_realm);
-       echo '</table></div>';
-}
-
 function renderTagRollerForRow ($row_id)
 {
        $a = rand (1, 20);
@@ -5302,7 +5299,7 @@ function renderTagRollerForRow ($row_id)
        echo "rack row.<br>The tag(s) selected below will be ";
        echo "appended to already assigned tag(s) of each particular entity. </td></tr>";
        echo "<tr><th>Tags</th><td>";
-       renderNewEntityTags();
+       printTagsPicker ();
        echo "</td></tr>";
        echo "<tr><th>Control question: the sum of ${a} and ${b}</th><td><input type=text name=sum></td></tr>";
        echo "<tr><td colspan=2 align=center><input type=submit value='Go!'></td></tr>";
@@ -5584,10 +5581,13 @@ function renderFileDownloader ($file_id)
 function renderFileProperties ($file_id)
 {
        $file = spotEntity ('file', $file_id);
-       echo '<table border=0 align=center>';
        printOpFormIntro ('updateFile');
+       echo '<table border=0 align=center>';
        echo "<tr><th class=tdright>MIME-type:</th><td class=tdleft><input tabindex=101 type=text name=file_type value='";
        echo htmlspecialchars ($file['type']) . "'></td></tr>";
+       echo "<tr><th class=tdright>Tags:</th><td class=tdleft>";
+       printTagsPicker ();
+       echo "</td></tr>\n";
        echo "<tr><th class=tdright>Filename:</th><td class=tdleft><input tabindex=102 type=text name=file_name value='";
        echo htmlspecialchars ($file['name']) . "'></td></tr>\n";
        echo "<tr><th class=tdright>Comment:</th><td class=tdleft><textarea tabindex=103 name=file_comment rows=10 cols=80>\n";
@@ -5597,7 +5597,7 @@ function renderFileProperties ($file_id)
        echo '</td></tr>';
        echo "<tr><th class=submit colspan=2>";
        printImageHREF ('SAVE', 'Save changes', TRUE, 102);
-       echo '</th></tr></form></table>';
+       echo '</th></tr></table></form>';
 }
 
 function renderFileBrowser ()
@@ -5614,12 +5614,12 @@ function renderFileManager ()
                startPortlet ('Upload new');
                printOpFormIntro ('addFile', array (), TRUE);
                echo "<table border=0 cellspacing=0 cellpadding='5' align='center'>";
-               echo '<tr><th colspan=2>Comment</th><th>Assign tags</th></tr>';
-               echo '<tr><td valign=top colspan=2><textarea tabindex=101 name=comment rows=10 cols=80></textarea></td>';
-               echo '<td rowspan=2>';
-               renderNewEntityTags ('file');
+               echo '<tr><th class=tdright>Comment:</th><td class=tdleft><textarea tabindex=101 name=comment rows=10 cols=80></textarea></td></tr>';
+               echo '<tr><th class=tdright>Tags:</td><td class=tdleft>';
+               printTagsPicker ();
                echo '</td></tr>';
-               echo "<tr><td class=tdleft><label>File: <input type='file' size='10' name='file' tabindex=100></label></td><td class=tdcenter>";
+               echo "<tr><th class=tdright>File:</th><td class=tdleft><input type='file' size='10' name='file' tabindex=100></td></td>";
+               echo "<tr><td colspan=2>";
                printImageHREF ('CREATE', 'Upload file', TRUE, 102);
                echo '</td></tr>';
                echo "</table></form><br>";
@@ -7922,6 +7922,9 @@ function renderVSTRulesEditor ($vst_id)
        }
        printOpFormIntro ('upd');
        echo '<table cellspacing=0 cellpadding=5 align=center class="widetable template-rules">';
+       echo "<tr><th class=tdright>Tags:</th><td class=tdleft style='border-top: none;'>";
+       printTagsPicker ();
+       echo "</td></tr>";
        echo '<tr><th></th><th>sequence</th><th>regexp</th><th>role</th>';
        echo '<th>VLAN IDs</th><th>comment</th><th><a href="#" class="vst-add-rule initial">' . getImageHREF ('add', 'Add rule') . '</a></th></tr>';
        global $port_role_options;
@@ -9628,4 +9631,16 @@ function renderDataIntegrityReport ()
                echo '<h2>No integrity violations found</h2>';
 }
 
+function renderUserProperties ($user_id)
+{
+       printOpFormIntro ('edit');
+       echo '<table border=0 align=center>';
+       echo "<tr><th class=tdright>Tags:</th><td class=tdleft>";
+       printTagsPicker ();
+       echo "</td></tr>\n";
+       echo "<tr><th class=submit colspan=2>";
+       printImageHREF ('SAVE', 'Save changes', TRUE, 102);
+       echo '</th></tr></table></form>';
+}
+
 ?>
index c32649a8df4406eb4eab6ccc1728752bd61d9ef1..a8bc70c125b0ff51de2dcc290a1a3761dcfd9879 100644 (file)
@@ -308,7 +308,7 @@ $tabhandler['ipv4net']['files'] = 'renderFilesForEntity';
 $tabhandler['ipv4net']['8021q'] = 'renderVLANIPLinks';
 $trigger['ipv4net']['tags'] = 'trigger_tags';
 $trigger['ipv4net']['8021q'] = 'trigger_ipv4net_vlanconfig';
-$ophandler['ipv4net']['properties']['editRange'] = 'tableHandler';
+$ophandler['ipv4net']['properties']['editRange'] = 'editIPv4Net';
 $ophandler['ipv4net']['properties']['del'] = 'delIPv4Prefix';
 $ophandler['ipv4net']['liveptr']['importPTRData'] = 'importPTRData';
 $ophandler['ipv4net']['tags']['saveTags'] = 'saveEntityTags';
@@ -333,7 +333,7 @@ $tabhandler['ipv6net']['files'] = 'renderFilesForEntity';
 $tabhandler['ipv6net']['8021q'] = 'renderVLANIPLinks';
 $trigger['ipv6net']['tags'] = 'trigger_tags';
 $trigger['ipv6net']['8021q'] = 'trigger_ipv6net_vlanconfig';
-$ophandler['ipv6net']['properties']['editRange'] = 'tableHandler';
+$ophandler['ipv6net']['properties']['editRange'] = 'editIPv6Net';
 $ophandler['ipv6net']['properties']['del'] = 'delIPv6Prefix';
 $ophandler['ipv6net']['tags']['saveTags'] = 'saveEntityTags';
 $ophandler['ipv6net']['files']['addFile'] = 'addFileToEntity';
@@ -461,7 +461,7 @@ $tabhandler['ipv4rspool']['editvslinks'] = 'renderTripletForm';
 $tabhandler['ipv4rspool']['editlblist'] = 'renderSLBEditTab';
 $tabhandler['ipv4rspool']['tags'] = 'renderEntityTags';
 $tabhandler['ipv4rspool']['files'] = 'renderFilesForEntity';
-$ophandler['ipv4rspool']['edit']['updIPv4RSP'] = 'tableHandler';
+$ophandler['ipv4rspool']['edit']['updIPv4RSP'] = 'updIPv4RSP';
 $ophandler['ipv4rspool']['edit']['cloneIPv4RSP'] = 'cloneRSPool';
 $ophandler['ipv4rspool']['edit']['del'] = 'deleteRSPool';
 $ophandler['ipv4rspool']['editrslist']['addRS'] = 'addRealServer';
@@ -501,11 +501,14 @@ $page['user']['parent'] = 'userlist';
 $page['user']['bypass'] = 'user_id';
 $page['user']['bypass_type'] = 'uint';
 $tab['user']['default'] = 'View';
+$tab['user']['properties'] = 'Properties';
 $tab['user']['tags'] = 'Tags';
 $tab['user']['files'] = 'Files';
 $tabhandler['user']['default'] = 'renderUser';
 $tabhandler['user']['tags'] = 'renderEntityTags';
 $tabhandler['user']['files'] = 'renderFilesForEntity';
+$tabhandler['user']['properties'] = 'renderUserProperties';
+$ophandler['user']['properties']['edit'] = 'editUserProperties';
 $ophandler['user']['tags']['saveTags'] = 'saveEntityTags';
 $ophandler['user']['files']['addFile'] = 'addFileToEntity';
 $ophandler['user']['files']['linkFile'] = 'linkFileToEntity';
@@ -691,7 +694,7 @@ $tabhandler['file']['tags'] = 'renderEntityTags';
 $tabhandler['file']['editText'] = 'renderTextEditor';
 $tabhandler['file']['replaceData'] = 'renderFileReuploader';
 $tabhandler['file']['download'] = 'renderFileDownloader';
-$ophandler['file']['edit']['updateFile'] = 'tableHandler';
+$ophandler['file']['edit']['updateFile'] = 'updateFile';
 $ophandler['file']['tags']['saveTags'] = 'saveEntityTags';
 $ophandler['file']['editText']['updateFileText'] = 'updateFileText';
 $ophandler['file']['replaceData']['replaceFile'] = 'replaceFile';
index 01858394db9c9b37aa87e5cdeaaf51c505128d08..7f43c2c83581332b1b0a78b831bc6a1989433be3 100644 (file)
@@ -203,34 +203,6 @@ $opspec_list['object-munin-del'] = array
                array ('url_argname' => 'graph', 'assertion' => 'string'),
        ),
 );
-$opspec_list['ipv4net-properties-editRange'] = array
-(
-       'table' => 'IPv4Network',
-       'action' => 'UPDATE',
-       'set_arglist' => array
-       (
-               array ('url_argname' => 'name', 'assertion' => 'string0'),
-               array ('url_argname' => 'comment', 'assertion' => 'string0'),
-       ),
-       'where_arglist' => array
-       (
-               array ('url_argname' => 'id', 'assertion' => 'uint')
-       ),
-);
-$opspec_list['ipv6net-properties-editRange'] = array
-(
-       'table' => 'IPv6Network',
-       'action' => 'UPDATE',
-       'set_arglist' => array
-       (
-               array ('url_argname' => 'name', 'assertion' => 'string0'),
-               array ('url_argname' => 'comment', 'assertion' => 'string0'),
-       ),
-       'where_arglist' => array
-       (
-               array ('url_argname' => 'id', 'assertion' => 'uint')
-       ),
-);
 $opspec_list['ipv4rspool-editrslist-delRS'] = array
 (
        'table' => 'IPv4RS',
@@ -240,36 +212,6 @@ $opspec_list['ipv4rspool-editrslist-delRS'] = array
                array ('url_argname' => 'id', 'assertion' => 'uint'),
        ),
 );
-$opspec_list['ipv4rspool-edit-updIPv4RSP'] = array
-(
-       'table' => 'IPv4RSPool',
-       'action' => 'UPDATE',
-       'set_arglist' => array
-       (
-               array ('url_argname' => 'name', 'assertion' => 'string0', 'if_empty' => 'NULL'),
-               array ('url_argname' => 'vsconfig', 'assertion' => 'string0', 'if_empty' => 'NULL'),
-               array ('url_argname' => 'rsconfig', 'assertion' => 'string0', 'if_empty' => 'NULL'),
-       ),
-       'where_arglist' => array
-       (
-               array ('url_argname' => 'pool_id', 'table_colname' => 'id', 'assertion' => 'uint')
-       ),
-);
-$opspec_list['file-edit-updateFile'] = array
-(
-       'table' => 'File',
-       'action' => 'UPDATE',
-       'set_arglist' => array
-       (
-               array ('url_argname' => 'file_name', 'table_colname' => 'name', 'assertion' => 'string'),
-               array ('url_argname' => 'file_type', 'table_colname' => 'type', 'assertion' => 'string'),
-               array ('url_argname' => 'file_comment', 'table_colname' => 'comment', 'assertion' => 'string0', 'if_empty' => 'NULL'),
-       ),
-       'where_arglist' => array
-       (
-               array ('url_argname' => 'file_id', 'table_colname' => 'id', 'assertion' => 'uint')
-       ),
-);
 $opspec_list['parentmap-edit-add'] = array
 (
        'table' => 'ObjectParentCompat',
@@ -907,8 +849,7 @@ function addIPv4Prefix ()
 {
        assertStringArg ('range');
        assertStringArg ('name', TRUE);
-
-       $taglist = isset ($_REQUEST['taglist']) ? $_REQUEST['taglist'] : array();
+       $taglist = genericAssertion ('taglist', 'array0');
        global $sic;
        $vlan_ck = empty ($sic['vlan_ck']) ? NULL : $sic['vlan_ck'];
        $net_id = createIPv4Prefix ($_REQUEST['range'], $sic['name'], isCheckSet ('is_connected'), $taglist, $vlan_ck);
@@ -919,8 +860,7 @@ function addIPv6Prefix ()
 {
        assertStringArg ('range');
        assertStringArg ('name', TRUE);
-
-       $taglist = isset ($_REQUEST['taglist']) ? $_REQUEST['taglist'] : array();
+       $taglist = genericAssertion ('taglist', 'array0');
        global $sic;
        $vlan_ck = empty ($sic['vlan_ck']) ? NULL : $sic['vlan_ck'];
        $net_id = createIPv6Prefix ($_REQUEST['range'], $sic['name'], isCheckSet ('is_connected'), $taglist, $vlan_ck);
@@ -1136,6 +1076,7 @@ function updateObjectAllocation ()
 $msgcode['updateObject']['OK'] = 51;
 function updateObject ()
 {
+       $taglist = genericAssertion ('taglist', 'array0');
        genericAssertion ('num_attrs', 'uint0');
        genericAssertion ('object_name', 'string0');
        genericAssertion ('object_label', 'string0');
@@ -1167,6 +1108,7 @@ function updateObject ()
        foreach (getResidentRacksData ($object_id, FALSE) as $rack_id)
                usePreparedDeleteBlade ('RackThumbnail', array ('rack_id' => $rack_id));
        $dbxlink->commit();
+       rebuildTagChainForEntity ('object', $object_id, buildTagChainFromIds ($taglist), TRUE);
        showFuncMessage (__FUNCTION__, 'OK');
 }
 
@@ -1232,7 +1174,7 @@ function updateObjectAttributes ($object_id)
 
 function addMultipleObjects()
 {
-       $taglist = isset ($_REQUEST['taglist']) ? $_REQUEST['taglist'] : array();
+       $taglist = genericAssertion ('taglist', 'array0');
        $max = getConfigVar ('MASSCOUNT');
        for ($i = 0; $i < $max; $i++)
        {
@@ -1275,7 +1217,7 @@ function addMultipleObjects()
 
 function addLotOfObjects()
 {
-       $taglist = isset ($_REQUEST['taglist']) ? $_REQUEST['taglist'] : array();
+       $taglist = genericAssertion ('taglist', 'array0');
        assertUIntArg ('global_type_id', TRUE);
        assertStringArg ('namelist', TRUE);
        $global_type_id = $_REQUEST['global_type_id'];
@@ -1674,6 +1616,7 @@ function updateVService ()
 {
        global $sic;
        assertUIntArg ('vs_id');
+       $taglist = genericAssertion ('taglist', 'array0');
        $vip_bin = assertIPArg ('vip');
        genericAssertion ('proto', 'enum/ipproto');
        if ($_REQUEST['proto'] == 'MARK')
@@ -1692,17 +1635,20 @@ function updateVService ()
                $sic['vsconfig'],
                $sic['rsconfig']
        );
+       rebuildTagChainForEntity ('ipvs', $_REQUEST['vs_id'], buildTagChainFromIds ($taglist), TRUE);
        showFuncMessage (__FUNCTION__, 'OK');
 }
 
 function updateVS ()
 {
+       $taglist = genericAssertion ('taglist', 'array0');
        $vs_id = assertUIntArg ('vs_id');
        $name = assertStringArg ('name');
        $vsconfig = nullEmptyStr (assertStringArg ('vsconfig', TRUE));
        $rsconfig = nullEmptyStr (assertStringArg ('rsconfig', TRUE));
 
        usePreparedUpdateBlade ('VS', array ('name' => $name, 'vsconfig' => $vsconfig, 'rsconfig' => $rsconfig), array ('id' => $vs_id));
+       rebuildTagChainForEntity ('ipvs', $vs_id, buildTagChainFromIds ($taglist), TRUE);
        showSuccess ("Service updated successfully");
 }
 
@@ -2190,10 +2136,12 @@ function updateLocation ()
 
        if ($pageno == 'location')
        {
+               $taglist = genericAssertion ('taglist', 'array0');
                $has_problems = (isset ($_REQUEST['has_problems']) and $_REQUEST['has_problems'] == 'on') ? 'yes' : 'no';
                assertStringArg ('comment', TRUE);
                commitUpdateObject ($_REQUEST['location_id'], $_REQUEST['name'], NULL, $has_problems, NULL, $_REQUEST['comment']);
                updateObjectAttributes ($_REQUEST['location_id']);
+               rebuildTagChainForEntity ('location', $_REQUEST['location_id'], buildTagChainFromIds ($taglist), TRUE);
        }
        else
                commitRenameObject ($_REQUEST['location_id'], $_REQUEST['name']);
@@ -2299,7 +2247,7 @@ function deleteRow ()
 $msgcode['addRack']['ERR2'] = 172;
 function addRack ()
 {
-       $taglist = isset ($_REQUEST['taglist']) && is_array ($_REQUEST['taglist']) ? $_REQUEST['taglist'] : array();
+       $taglist = genericAssertion ('taglist', 'array0');
 
        // The new rack(s) should be placed on the bottom of the list, sort-wise
        $rowInfo = getRowInfo($_REQUEST['row_id']);
@@ -2364,7 +2312,7 @@ function updateRack ()
        assertUIntArg ('height');
        assertStringArg ('asset_no', TRUE);
        assertStringArg ('comment', TRUE);
-
+       $taglist = genericAssertion ('taglist', 'array0');
        $rack_id = getBypassValue();
        usePreparedDeleteBlade ('RackThumbnail', array ('rack_id' => $rack_id));
        commitUpdateRack
@@ -2378,6 +2326,7 @@ function updateRack ()
                $_REQUEST['comment']
        );
        updateObjectAttributes ($rack_id);
+       rebuildTagChainForEntity ('rack', $rack_id, buildTagChainFromIds ($taglist), TRUE);
        showFuncMessage (__FUNCTION__, 'OK', array ($_REQUEST['name']));
 }
 
@@ -3000,6 +2949,7 @@ function updVSTRule()
        }
 
        global $port_role_options, $sic;
+       $taglist = genericAssertion ('taglist', 'array0');
        assertUIntArg ('mutex_rev', TRUE);
        $data = genericAssertion ('template_json', 'json');
        $rule_no = 0;
@@ -3033,6 +2983,7 @@ function updVSTRule()
                }
                throw $e;
        }
+       rebuildTagChainForEntity ('vst', $_REQUEST['vst_id'], buildTagChainFromIds ($taglist), TRUE);
        showFuncMessage (__FUNCTION__, 'OK');
 }
 
@@ -3473,5 +3424,88 @@ function tableHandler()
        }
        showOneLiner ($retcode);
 }
+$msgcode['updateFile']['OK'] = 6;
+function updateFile ()
+{
+       $file_id = getBypassValue();
+       $file_name = genericAssertion ('file_name', 'string');
+       $file_type = genericAssertion ('file_type', 'string');
+       $file_comment = genericAssertion ('file_comment', 'string0');
+       $taglist = genericAssertion ('taglist', 'array0');
+       usePreparedUpdateBlade
+       (
+               'File',
+               array ('name' => $file_name, 'type' => $file_type, 'comment' => $file_comment),
+               array ('id' => $file_id),
+               array_key_exists ('conjunction', $opspec) ? $opspec['conjunction'] : 'AND'
+       );
+       rebuildTagChainForEntity ('file', $file_id, buildTagChainFromIds ($taglist), TRUE);
+       showFuncMessage (__FUNCTION__, 'OK', array ($file_name));
+}
+
+$msgcode['editIPv4Net']['OK'] = 6;
+function editIPv4Net ()
+{
+       $net_id = getBypassValue();
+       $name = genericAssertion ('name', 'string0');
+       $comment = genericAssertion ('comment', 'string0');
+       $taglist = genericAssertion ('taglist', 'array0');
+       usePreparedUpdateBlade
+       (
+               'IPv4Network',
+               array ('name' => $name, 'comment' => $comment),
+               array ('id' => $net_id)
+       );
+       rebuildTagChainForEntity ('ipv4net', $net_id, buildTagChainFromIds ($taglist), TRUE);
+       $netdata = spotEntity ('ipv4net', $net_id);
+       showFuncMessage (__FUNCTION__, 'OK', array ("${netdata['ip']}/${netdata['mask']}"));
+}
+
+$msgcode['editIPv6Net']['OK'] = 6;
+function editIPv6Net ()
+{
+       $net_id = getBypassValue();
+       $name = genericAssertion ('name', 'string0');
+       $comment = genericAssertion ('comment', 'string0');
+       $taglist = genericAssertion ('taglist', 'array0');
+       usePreparedUpdateBlade
+       (
+               'IPv6Network',
+               array ('name' => $name, 'comment' => $comment),
+               array ('id' => $net_id)
+       );
+       rebuildTagChainForEntity ('ipv6net', $net_id, buildTagChainFromIds ($taglist), TRUE);
+       $netdata = spotEntity ('ipv6net', $net_id);
+       showFuncMessage (__FUNCTION__, 'OK', array ("${netdata['ip']}/${netdata['mask']}"));
+}
+
+$msgcode['updIPv4RSP']['OK'] = 6;
+function updIPv4RSP ()
+{
+       $rspool_id = getBypassValue();
+       $name = genericAssertion ('name', 'string0');
+       $vsconfig = genericAssertion ('vsconfig', 'string0');
+       $rsconfig = genericAssertion ('rsconfig', 'string0');
+       $taglist = genericAssertion ('taglist', 'array0');
+       usePreparedUpdateBlade
+       (
+               "IPv4RSPool",
+               array ('name' => $name, 'vsconfig' => $comment, 'rsconfig' => $vsconfig),
+               array ('id' => $rspool_id)
+       );
+       rebuildTagChainForEntity ('ipv4rspool', $rspool_id, buildTagChainFromIds ($taglist), TRUE);
+       showFuncMessage (__FUNCTION__, 'OK', array($_REQUEST['name']));
+}
+
+$msgcode['editUserProperties']['OK'] = 6;
+function editUserProperties ()
+{
+       $taglist = genericAssertion ('taglist', 'array0');
+       $user_id = getBypassValue();
+       rebuildTagChainForEntity ('user', $user_id, buildTagChainFromIds ($taglist), TRUE);
+       $user = spotEntity ('user', $user_id);
+       print_r($user);
+       showFuncMessage (__FUNCTION__, 'OK', array($user['user_name']));
+}
 
 ?>
index 08081f5208501b1465615f0daed9b11564fac7ca..1cd9028ef1cd023ec1a0fe34bdf3c49c9599875b 100644 (file)
@@ -486,17 +486,18 @@ function renderNewRSPoolForm ()
 {
        startPortlet ('Add new RS pool');
        printOpFormIntro ('add');
-       echo "<table border=0 cellpadding=10 cellspacing=0 align=center>";
-       echo "<tr><th class=tdright>Name</th>";
+       echo "<table border=0 cellpadding=5 cellspacing=0 align=center>\n";
+       echo "<tr><th class=tdright>Name:</th>";
        echo "<td class=tdleft><input type=text name=name tabindex=101></td><td>";
+       echo "</td></tr><th class=tdright>Tags:</th><td class='tdleft'>";
+       printTagsPicker ();
+       echo "</td></tr>";
+       echo "<tr><th class=tdright>VS config:</th><td colspan=2><textarea name=vsconfig rows=10 cols=80 tabindex=102></textarea></td></tr>\n";
+       echo "<tr><th class=tdright>RS config:</th><td colspan=2><textarea name=rsconfig rows=10 cols=80 tabindex=103></textarea></td></tr>\n";
+       echo "<tr><td colspan=2>";
        printImageHREF ('CREATE', 'create real server pool', TRUE, 104);
-       echo "</td><th>Assign tags</th></tr>";
-       echo "<tr><th class=tdright>VS config</th><td colspan=2><textarea name=vsconfig rows=10 cols=80 tabindex=102></textarea></td>";
-       echo "<td rowspan=2>";
-       renderNewEntityTags ('ipv4rspool');
        echo "</td></tr>";
-       echo "<tr><th class=tdright>RS config</th><td colspan=2><textarea name=rsconfig rows=10 cols=80 tabindex=103></textarea></td></tr>";
-       echo "</table></form>";
+       echo "</table></form>\n";
        finishPortlet();
 }
 
@@ -536,23 +537,26 @@ function renderNewVSForm ()
 {
        startPortlet ('Add new virtual service');
        printOpFormIntro ('add');
-       echo "<table border=0 cellpadding=10 cellspacing=0 align=center>\n";
-       echo "<tr valign=bottom><td>&nbsp;</td><th>VIP</th><th>port</th><th>proto</th><th>name</th><th>&nbsp;</th><th>Assign tags</th></tr>";
-       echo '<tr valign=top><td>&nbsp;</td>';
-       echo "<td><input type=text name=vip tabindex=101></td>";
        $default_port = getConfigVar ('DEFAULT_SLB_VS_PORT');
+       global $vs_proto;
        if ($default_port == 0)
                $default_port = '';
-       echo "<td><input type=text name=vport size=5 value='${default_port}' tabindex=102></td><td>";
-       global $vs_proto;
-       $vs_keys = array_keys ($vs_proto);
-       printSelect ($vs_proto, array ('name' => 'proto'), array_shift ($vs_keys));
-       echo '</td><td><input type=text name=name tabindex=104></td><td>';
+       echo "<table border=0 cellpadding=5 cellspacing=0 align=center>\n";
+       echo "<tr><th class=tdright>VIP:</th><td class=tdleft><input type=text name=vip tabindex=101></td>";
+       echo "<tr><th class=tdright>Port:</th><td class=tdleft>";
+       echo "<input type=text name=vport size=5 value='${default_port}' tabindex=102></td></tr>";
+       echo "<tr><th class=tdright>Proto:</th><td class=tdleft>";
+       printSelect ($vs_proto, array ('name' => 'proto'), array_shift (array_keys ($vs_proto)));
+       echo "</td></tr>";
+       echo "<tr><th class=tdright>Name:</th><td class=tdleft><input type=text name=name tabindex=104></td><td>";
+       echo "<tr><th class=tdright>Tags:</th><td class=tdleft>";
+       printTagsPicker ();
+       echo "</td></tr>";
+       echo "<tr><th class=tdrigh>VS configuration:</th><td class=tdleft><textarea name=vsconfig rows=10 cols=80></textarea></td></tr>";
+       echo "<tr><th class=tdrigh>RS configuration:</th><td class=tdleft><textarea name=rsconfig rows=10 cols=80></textarea></td></tr>";
+       echo "<tr><td colspan=2>";
        printImageHREF ('CREATE', 'create virtual service', TRUE, 105);
-       echo "</td><td rowspan=3>";
-       renderNewEntityTags ('ipv4vs');
-       echo "</td></tr><tr><th>VS configuration</th><td colspan=5 class=tdleft><textarea name=vsconfig rows=10 cols=80></textarea></td>";
-       echo "<tr><th>RS configuration</th><td colspan=5 class=tdleft><textarea name=rsconfig rows=10 cols=80></textarea></td></tr>";
+       echo "</td></tr>";
        echo '</table></form>';
        finishPortlet();
 }
@@ -562,7 +566,10 @@ function renderEditRSPool ($pool_id)
        $poolinfo = spotEntity ('ipv4rspool', $pool_id);
        printOpFormIntro ('updIPv4RSP');
        echo '<table border=0 align=center>';
-       echo "<tr><th class=tdright>name:</th><td class=tdleft><input type=text name=name value='${poolinfo['name']}'></td></tr>\n";
+       echo "<tr><th class=tdright>Name:</th><td class=tdleft><input type=text name=name value='${poolinfo['name']}'></td></tr>\n";
+       echo "<tr><th class=tdright>Tags:</th><td class=tdleft>";
+       printTagsPicker ();
+       echo "</td></tr>\n";
        echo "<tr><th class=tdright>VS config:</th><td class=tdleft><textarea name=vsconfig rows=20 cols=80>${poolinfo['vsconfig']}</textarea></td></tr>\n";
        echo "<tr><th class=tdright>RS config:</th><td class=tdleft><textarea name=rsconfig rows=20 cols=80>${poolinfo['rsconfig']}</textarea></td></tr>\n";
        echo "<tr><th class=submit colspan=2>";
@@ -590,12 +597,15 @@ function renderEditVService ($vsid)
        printOpFormIntro ('updIPv4VS');
        echo '<table border=0 align=center>';
        echo "<tr><th class=tdright>VIP:</th><td class=tdleft><input tabindex=1 type=text name=vip value='${vsinfo['vip']}'></td></tr>\n";
-       echo "<tr><th class=tdright>port:</th><td class=tdleft><input tabindex=2 type=text name=vport value='${vsinfo['vport']}'></td></tr>\n";
-       echo "<tr><th class=tdright>proto:</th><td class=tdleft>";
+       echo "<tr><th class=tdright>Port:</th><td class=tdleft><input tabindex=2 type=text name=vport value='${vsinfo['vport']}'></td></tr>\n";
+       echo "<tr><th class=tdright>Proto:</th><td class=tdleft>";
        global $vs_proto;
        printSelect ($vs_proto, array ('name' => 'proto'), $vsinfo['proto']);
        echo "</td></tr>\n";
-       echo "<tr><th class=tdright>name:</th><td class=tdleft><input tabindex=4 type=text name=name value='${vsinfo['name']}'></td></tr>\n";
+       echo "<tr><th class=tdright>Name:</th><td class=tdleft><input tabindex=4 type=text name=name value='${vsinfo['name']}'></td></tr>\n";
+       echo "<tr><th class=tdright>Tags:</th><td class=tdleft>";
+       printTagsPicker ();
+       echo "</td></tr>\n";
        echo "<tr><th class=tdright>VS config:</th><td class=tdleft><textarea tabindex=5 name=vsconfig rows=20 cols=80>${vsinfo['vsconfig']}</textarea></td></tr>\n";
        echo "<tr><th class=tdright>RS config:</th><td class=tdleft><textarea tabindex=6 name=rsconfig rows=20 cols=80>${vsinfo['rsconfig']}</textarea></td></tr>\n";
        echo "<tr><th class=submit colspan=2>";
index 20a7b0bc06e417d5d47e74a68ceae770841f758c..44052c5cb8492e0366bb136f385691284f7cb481 100644 (file)
@@ -142,6 +142,9 @@ function renderEditVS ($vs_id)
        printOpFormIntro ('updVS');
        echo '<table border=0 align=center>';
        echo '<tr><th class=tdright>Name:</th><td class=tdleft><input type=text name=name value="' . htmlspecialchars ($vsinfo['name'], ENT_QUOTES) . '"></td></tr>';
+       echo "<tr><th class=tdright>Tags:</th><td class=tdleft>";
+       printTagsPicker ();
+       echo "</td></tr>\n";
        echo '<tr><th class=tdright>VS config:</th><td class=tdleft><textarea name=vsconfig rows=3 cols=80>' . htmlspecialchars ($vsinfo['vsconfig']) . '</textarea></td></tr>';
        echo '<tr><th class=tdright>RS config:</th><td class=tdleft><textarea name=rsconfig rows=3 cols=80>' . htmlspecialchars ($vsinfo['rsconfig']) . '</textarea></td></tr>';
        echo '<tr><th></th><th>';
@@ -645,12 +648,14 @@ function renderNewVSGForm ()
 {
        startPortlet ('Add new VS group');
        printOpFormIntro ('add');
-       echo '<table border=0 cellpadding=10 cellspacing=0 align=center>';
-       echo '<tr valign=bottom><th>name</th><th>Assign tags</th></tr>';
-       echo '<tr valign=top><td><input type=text name=name><p>';
+       echo '<table border=0 cellpadding=5 cellspacing=0 align=center>';
+       echo '<tr valign=bottom><th>Name:</th><td class="tdleft">';
+       echo '<input type=text name=name></td></tr>';
+       echo '<tr><th>Tags:</th><td class="tdleft">';
+       printTagsPicker ();
+       echo '</td></tr>';
+       echo '</table>';
        printImageHREF ('CREATE', 'create virtual service', TRUE);
-       echo '</p></td><td>';
-       renderNewEntityTags ('ipvs');
-       echo '</td></tr></table></form>';
+       echo '</form>';
        finishPortlet();
 }
diff --git a/wwwroot/js/tag-it-local.js b/wwwroot/js/tag-it-local.js
new file mode 100644 (file)
index 0000000..e1c8eae
--- /dev/null
@@ -0,0 +1,430 @@
+suggest_size = 10;
+window.onerror = function () {
+          alert("An error occurred.");
+       };
+
+$(document).ready(function() {
+       $("input[data-tagit=yes]").each(function (index, value) {
+               var tag_input = value;
+               var form = $(tag_input).closest("form");
+               var value_name = $(tag_input).data("tagit-valuename");
+               var ul = $(form).find("ul[data-tagit=yes][data-tagit-valuename=" + value_name + "]")[0];
+               var tag_limit = $(tag_input).data("tagit-limit") || 0;
+               var pass_value = $(tag_input).data("tagit-pass-value") || false;
+               var specific_taglist = $(tag_input).data("tagit-values");
+               var preselect = $(tag_input).data("tagit-preselect") || $(ul).data("tagit-preselect")  ||[];
+               var use_taglist = specific_taglist || taglist;
+               var expand_all_btn = form.find("span.icon-folder-open.tagit_input_" + value_name)[0];
+       
+               generateTagList(tag_input, ul, use_taglist, preselect, value_name, tag_limit, expand_all_btn, pass_value);
+       });
+});
+
+function getTagChild(tag, tags_trace, tags_name_to_id) {
+       var result = [];
+       if (tags_trace.length > 0) {
+               if (typeof tag == "undefined" || tag.length == 0) { // empty tag
+                       for(var k in tags_trace) {
+                               if (tags_trace[k].length == 0)
+                                       result.push(k);
+                       };
+               }
+               else {
+                       tag_id = tags_name_to_id[tag];
+                       for(var k in tags_trace) {
+                               if (tags_trace[k].slice(-1)[0] == tag_id)
+                                       result.push(k);
+                       };
+                       // if (result.length == 0)
+                       // result.push("no child");
+               }
+       }
+       return result;
+}
+
+function finder (source, req_term) {
+       var term = $.ui.autocomplete.escapeRegex(req_term);
+       var startsWithMatcher = new RegExp("^" + term, "i");
+       var startsWith = $.grep(source, function(value) {
+               return startsWithMatcher.test(value.label || value.value || value);
+       });
+       var containsMatcher = new RegExp(term, "i");
+       var contains = $.grep(source, function (value) {
+               return $.inArray(value, startsWith) < 0 && containsMatcher.test(value.label || value.value || value);
+       });
+       return startsWith.concat(contains) || [];
+}
+
+function suggest_search (term) {
+       var results = [];
+       if (term == "*") {
+               if (typeof(this.options.tags_trace) != 'undefined' && this.options.tags_trace.length > 0) {
+                       var roots = [];
+                       var tree_styles = {};
+                       tree_styles[0] = [];
+                       for (tag in this.options.tags_trace) // root tags
+                               if (this.options.tags_trace[tag].length == 0) {
+                                       roots.push(tag);
+                                       tree_styles[0].push(this.options.tags_name_to_id[tag]);
+                               }
+                       var changes = 1;
+                       var i = 0;
+                       var taglist_tmp = $.extend(1, this.options.taglist);
+                       $(this.options.dynamic_style).appendTo('head');
+                       while (changes) {
+                               i++;
+                               tree_styles[i] = [];
+                               changes = 0;
+                               for (tag_id in taglist_tmp)
+                               {
+                                       if (taglist_tmp[tag_id]['trace'].length == i)
+                                       {
+                                               var tag = this.options.tags_to_name[tag_id];
+                                               tag_parent_id = this.options.taglist[tag_id]['trace'].slice(-1)[0];
+                                               tag_parent = this.options.tags_to_name[tag_parent_id];
+                                               index = roots.indexOf(tag_parent);
+                                               roots.splice(index + 1, 0, tag);
+                                               delete taglist_tmp[tag_id];
+                                               tree_styles[i].push(tag_id);
+                                               changes = 1;
+                                       }
+                               }
+                       }
+                       // generate style with indents depended from depth
+                       for (level in tree_styles) {
+                               if (tree_styles[level].length == 0)
+                                       continue;
+                               width = 1 + level * 15;
+                               class_str = "";
+                               for (index in tree_styles[level])
+                                       class_str += ".etag-" + tree_styles[level][index] + " a, ";
+                               this.options.dynamic_style.append(class_str.slice(0,-2) + " {padding-left:" + width + "px !important;}");
+                               }
+                       results = roots;
+               }
+               else {
+                       $.each(this.options.all_tags, function(i, v) {results.push(v)});
+                       results.sort();
+               }
+       }
+       else if (term.slice(-1) == "/" || term.slice(-1) == "\\") {
+               var term_cutted = term.slice(0, -1);
+               if (term_cutted.length == 0)
+                       results = getTagChild(undefined, this.options.tags_trace, this.options.tags_name_to_id);
+               else {
+                       var found_tags = finder(this.options.available_tags, term_cutted);
+                       if (found_tags.length == 0) {
+                               found_tags = finder(this.options.all_tags, term_cutted);
+                       }
+                       results = getTagChild(found_tags[0], this.options.tags_trace, this.options.tags_name_to_id);
+               }
+               results.sort();
+       }
+       else {
+               results = finder(this.options.available_tags, term);
+               if (results.length == 0) {
+                       results = finder(this.options.all_tags, term);
+               }
+               results = results.slice(0, suggest_size); // cutting
+               results.sort();
+       }
+       
+       return results;
+};
+
+function suggest (request, response) {
+       var results = suggest_search.call(this, request.term);
+       return results;
+};
+
+function generateTagList(input, ul, taglist, preselect, value_name, tag_limit, expand_all_btn, pass_value) {
+       var tags_name_to_id = [];
+       var tags_trace = [];
+       var tags_to_name = [];
+       var all_tags = [];
+       var available_tags = [];
+       var selected = [];
+       var dynamic_style = $('<style>');
+
+       function getHelp(tag) {
+               s = [];
+               var tag_trace = tags_trace[tag];
+               for (var k in tag_trace)
+               {
+                       s.push(tags_to_name[tag_trace[k]]);
+               }
+               var result = s.join(" \u2192 "); // right arrow
+               return result;
+       }
+
+       function generateClass(tag) {
+               var result = [];
+               result.push("tag-" + tags_name_to_id[tag]);
+               result.push("etag-" + tags_name_to_id[tag]);
+               if (typeof tags_trace[tag] != 'undefined')
+               {
+                       tag_trace = tags_trace[tag];
+               }
+               else
+                       tag_trace = [];
+               $.each(tag_trace, function (index, value) {result.push("tag-" + value);});
+               return result;
+       }
+       
+       function beforeTagAdded(event, ui) {
+               if (jQuery.inArray(ui.tagLabel, available_tags) < 0) { // skip not available tags
+                       return false;
+               }
+       }
+
+       function afterTagRemoved(event, ui) {
+               tag_id = ui.tagLabel;
+               tag = tags_to_name[tag_id];
+               selected.splice($.inArray(tag, selected), 1);
+               available_tags.push(tag);
+               available_tags.sort();
+               if (tag_limit != 0 && $(ul).tagit("assignedTags").length < tag_limit) {
+                       $(ul).tagit("setStatus", true);
+                       if (typeof(expand_all_btn) != 'undefined')
+                               $(expand_all_btn).attr('disabled', '');
+               }
+       }
+
+       function afterTagAdded(event, ui) {
+               tag = ui.tagLabel;
+               tag_id = tags_name_to_id[tag];
+               li = ui.tag[0];
+               hidded_value = $(li).find("input[value='" + tag + "']")[0]; // replace tag by tag id
+               hidded_value.value = tag_id;
+               tag_label = $(ui.tag[0]).find(".tagit-label");
+               tag_classes = generateClass(tag);
+               $.each(tag_classes, function (index, value) {tag_label.addClass(value);});
+               // generate title
+               r = [];
+               r.push(tag_label.attr('title'));
+               r.push(getHelp(tag));
+               help_text = $.map(r, function (value, index) {if (value.length > 0) {return value;}});
+               help_text = help_text.join("\n");
+               tag_label.attr("title", help_text);
+               // remove tag from available_tags
+               available_tags.splice($.inArray(tag, available_tags), 1);
+               selected.push(tag);
+               if (tag_limit != 0 && $(ul).tagit("assignedTags").length >= tag_limit) {
+                       $(ul).tagit("setStatus", false);
+                       if (typeof(expand_all_btn) != 'undefined')
+                               $(expand_all_btn).attr('disabled', 'disabled');
+               }
+       }
+
+       $.each(taglist, function (index, value) {tags_name_to_id[value['tag']] = index;});
+       $.each(taglist, function (index, value) {
+               if (typeof(value['trace']) != 'undefined')
+                       tags_trace[value['tag']] = value['trace'];
+       });
+       $.each(taglist, function (index, value) {tags_to_name[index] = value['tag'];});
+       $.each(taglist, function (index, value) {all_tags.push(value['tag']);});
+
+       var oldresizeMenu = $.ui.autocomplete.prototype._resizeMenu;
+       $.ui.autocomplete.prototype._resizeMenu = function() {
+               //oldresizeMenu.call(this);
+               var ul = this.menu.element;
+               ul.children("li:(.ui-menu-item)").addClass("tagit-menu-item");
+               ul.removeClass("ui-widget-content");
+       };
+
+       function renderItem (ul, item) {
+               var li = $('<li>');
+               var a = $('<a>');
+               var tag = item.label;
+               var tag_classes = generateClass(tag);
+               var help_text = getHelp(tag);
+               var m_div = $('<div style="display: inline-block; vertical-align: middle;"></div>');
+               a.prepend(m_div);
+               m_div.prepend($('<div style="float: left;">' + tag + '</div>'));
+               if (jQuery.inArray(tag, available_tags) < 0) {
+                       if (jQuery.inArray(tag, selected) >= 0)
+                               help_text = "already assigned";
+                       else
+                               help_text = "is not assignable";
+                       tag_classes.push("disabled");
+               }
+               // add title to item
+               a.attr("title", help_text);
+               // add classes to item
+               $.each(tag_classes, function (index, value) {
+                       li.addClass(value);
+               });
+               // add icons to item
+               if (jQuery.inArray("disabled", tag_classes) > 0)
+                       m_div.append('<span style="float: left;" class="ui-icon ui-icon-locked"></span>');
+               child_count = getTagChild(tag, tags_trace, tags_name_to_id).length;
+               if(child_count > 0)
+                       m_div.append('<span style="float: left;" class="ui-icon ui-icon-arrowthick-1-se"></span>');
+               return li.data('item.autocomplete', item).append(a).appendTo(ul);
+       };
+       
+       function afetrRenderMenu (ul, items) {
+               if (ul.find("li").length <= suggest_size) {
+                       ul.append(ul.find("li.disabled")); // move to down disabled tags
+               }
+       }
+
+       function suggest_wrapper (request, response) {
+               var results = suggest.call(this, request, response);
+               response.call(this, results);
+               return results;
+       };
+       
+       // click handler for tag expand
+       if (typeof(expand_all_btn) != 'undefined')
+       {
+               $(expand_all_btn).click(function() {
+                       if ($(this).attr('disabled') != "disabled") {
+                               e = jQuery.Event('keydown');
+                               if ($(input)[0].value == "*") {
+                                       e.keyCode = e.which = $.ui.keyCode.ESCAPE;
+                                       $(input).trigger(e);
+                                       $(input).val("");
+                               }
+                               else {
+                                       $(input).val("*");
+                                       $(input).focus();
+                                       $(input).autocomplete("search", "*");
+                               }
+                       }
+               });
+       }
+       $.each(taglist, function (index, value) {
+               if (typeof value["is_assignable"] == "undefined" || value["is_assignable"] == "yes") 
+                       available_tags.push(value['tag']);
+       });
+       
+
+       function autocomplete_open(event, ui) {
+                       $(this).data("autocomplete").menu.element.addClass("autocomplete-menu");
+                       var menu_obj = $(this);
+                       // escape handler
+                       $(document).bind("keydown", function(event, arg) {
+                               menu_obj.data("autocomplete").close(event, arg);
+                       });
+                       $(this).data("autocomplete").close = function (e, ar) {
+                               if (typeof(e) == 'undefined' || (e.type == "menuselected" || e.keyCode == $.ui.keyCode.ESCAPE || e.keyCode == $.ui.keyCode.BACKSPACE))
+                                       clearTimeout(this.closing), this.menu.element.is(":visible") && (this.menu.element.hide(), this.menu.deactivate(), this._trigger("close", e));
+                               else
+                                       return false;
+                       };
+       }
+       function change_value(input, value) {
+               if (pass_value)
+                       var tmp_value = value;
+               else
+                       var tmp_value = tags_name_to_id[value] || "";
+               input.attr("value", tmp_value);
+       }
+
+       var oldRenderMenu = $.ui.autocomplete.prototype._renderMenu;
+       if (ul) {
+               $(ul).tagit({
+                       fieldName: value_name + "[]",
+                       tagInput: $(input),
+                       showAutocompleteOnFocus: false,
+                       removeConfirmation: true,
+                       caseSensitive: false,
+                       allowDuplicates: false,
+                       allowSpaces: true,
+                       animate: false,
+                       readOnly: false,
+                       tagLimit: tag_limit,
+                       singleField: false,
+                       singleFieldDelimiter: ",",
+                       singleFieldNode: null,
+                       backspaceRemove: false,
+                       tabIndex: null,
+                       insertPosition: 'left',
+                       autocomplete: {
+                                                               autoFocus: true,
+                                                               source: suggest_wrapper,
+                                                               search: function(event, ui) {$(dynamic_style).remove();},
+                                                               open: autocomplete_open,
+                                                               create: function(event, ui){
+                                                                       $(this).data("autocomplete")._renderItem = function(ul, item) {
+                                                                               renderItem.call(this, ul, item);
+                                                                       };
+                                                                       $(this).data("autocomplete")._renderMenu = function(ul, items) {
+                                                                               oldRenderMenu.call(this, ul, items);
+                                                                               afetrRenderMenu.call(this, ul, items);
+                                                                       };
+                                                               },
+                       },
+                       // Events
+                       beforeTagAdded: beforeTagAdded,
+                       afterTagAdded: afterTagAdded,
+                       afterTagRemoved: afterTagRemoved,
+                       available_tags: available_tags,
+                       all_tags: all_tags,
+                       tags_trace: tags_trace,
+                       tags_to_name: tags_to_name,
+                       tags_name_to_id: tags_name_to_id,
+                       dynamic_style: dynamic_style,
+                       taglist: taglist,
+               });
+               $(ul).closest("form").find("input:hidden[name='" + value_name + "[]" + "']").remove(); // remove extra data
+               $.each(preselect, function(index, value) {
+                       var title = "";
+                       if (typeof value['user'] != 'undefined' && value['user'] != null)
+                               title = value['user'] + ", " + value['time_parsed'];
+                       else
+                               title = "";
+                       $(ul).tagit("createTag", value['tag'], false, false, title);
+               });
+       }
+       else {
+               if (typeof(preselect) != 'undefined') {
+                       if (typeof (preselect['tag']) != 'undefined') {
+                               var selected_tag = preselect['tag'];
+                       }
+                       else if (typeof (preselect['id']) != 'undefined') {
+                               var selected_tag = tags_to_name[preselect['id']];
+                       }
+                       if (typeof (selected_tag) != 'undefined') {
+                               $(input).attr("value", selected_tag);
+                               available_tags.splice($.inArray(selected_tag, available_tags), 1);
+                               selected.push(selected_tag);
+                       }
+               }
+               var form = $(input).closest("form");
+               var value_holder = $('<input type=hidden name="' + value_name + '" value="">');
+               $(form).find("input:hidden[name='" + value_name + "']").remove();
+               $(form).append(value_holder);
+
+               $(input).autocomplete({
+                       source: suggest_wrapper,
+                       open: autocomplete_open,
+                       available_tags: available_tags,
+                       all_tags: all_tags,
+                       create: function(event, ui){
+                               $(this).data("autocomplete")._renderItem = function(event, ui) {
+                                       renderItem.call(this, event, ui);
+                               };
+                               $(this).data("autocomplete")._renderMenu = function(ul, items) {
+                                       oldRenderMenu.call(this, ul, items);
+                                       afetrRenderMenu.call(this, ul, items);
+                               };
+                       },
+                       select: function(event, ui) {
+                               change_value(value_holder, ui.item.value);
+                       },
+               });
+               $(input).click(function(){
+                   $(this).select();
+                   $(this).autocomplete('close');
+               });
+               $(input).bind('input', function() {
+                       change_value(value_holder, this.value);
+               });
+               // for old browsers
+               $(input).keyup(function() {
+                       change_value(value_holder, this.value);
+               });
+       }
+}
index 3adcf4c29bf4728ec54a94ab9168f139192a60ea..47b998251fbf2dcc67efbbbab6ddbbcecdfde713 100644 (file)
@@ -35,6 +35,7 @@
             readOnly          : false,  // Disables editing.
             removeConfirmation: false,  // Require confirmation to remove tags.
             tagLimit          : null,   // Max number of tags allowed (null for unlimited).
+            insertPosition    : 'right',
 
             // Used for autocomplete, unless you override `autocomplete.source`.
             availableTags     : [],
                 this.tagList = $('<ul></ul>').insertAfter(this.element);
                 this.options.singleField = true;
                 this.options.singleFieldNode = this.element;
-                this.element.addClass('tagit-hidden-field');
+                this.element.css('display', 'none');
             } else {
                 this.tagList = this.element.find('ul, ol').andSelf().last();
             }
-
-            this.tagInput = $('<input type="text" />').addClass('ui-widget-content');
+            if (this.options.tagInput)
+                this.tagInput = this.options.tagInput;
+            else
+                this.tagInput = $('<input type="text" />');//.addClass('ui-widget-content');
 
             if (this.options.readOnly) this.tagInput.attr('disabled', 'disabled');
 
             }
 
             this.tagList
-                .addClass('tagit')
-                .addClass('ui-widget ui-widget-content ui-corner-all')
+                //.addClass('tagit')
+                //.addClass('ui-widget ui-widget-content ui-corner-all')
                 // Create the input field.
-                .append($('<li class="tagit-new"></li>').append(this.tagInput))
+                //.append($('<li class="tagit-new"></li>'))
                 .click(function(e) {
                     var target = $(e.target);
                     if (target.hasClass('tagit-label')) {
             this.tagInput
                 .keydown(function(event) {
                     // Backspace is not detected within a keypress, so it must use keydown.
-                    if (event.which == $.ui.keyCode.BACKSPACE && that.tagInput.val() === '') {
+                    if (that.options.backspaceRemove && event.which == $.ui.keyCode.BACKSPACE && that.tagInput.val() === '') {
                         var tag = that._lastTag();
                         if (!that.options.removeConfirmation || tag.hasClass('remove')) {
                             // When backspace is pressed, the last tag is deleted.
                             that.removeTag(tag);
-                        } else if (that.options.removeConfirmation) {
-                            tag.addClass('remove ui-state-highlight');
+                        //} else if (that.options.removeConfirmation) {
+                        //    tag.addClass('remove ui-state-highlight');
                         }
-                    } else if (that.options.removeConfirmation) {
-                        that._lastTag().removeClass('remove ui-state-highlight');
-                    }
+                    }// else if (that.options.removeConfirmation) {
+                     //   that._lastTag().removeClass('remove ui-state-highlight');
+                    //}
 
                     // Comma/Space/Enter are all valid delimiters for new tags,
                     // except when there is an open quote or if setting allowSpaces = true.
                     // Tab will also create a tag, unless the tag input is empty,
                     // in which case it isn't caught.
                     if (
-                        (event.which === $.ui.keyCode.COMMA && event.shiftKey === false) ||
+                        event.which === $.ui.keyCode.COMMA ||
                         event.which === $.ui.keyCode.ENTER ||
                         (
                             event.which == $.ui.keyCode.TAB &&
                         }
 
                         // Autocomplete will create its own tag from a selection and close automatically.
-                        if (!(that.options.autocomplete.autoFocus && that.tagInput.data('autocomplete-open'))) {
-                            that.tagInput.autocomplete('close');
+                        if (!that.tagInput.data('autocomplete-open')) {
                             that.createTag(that._cleanedInput());
                         }
                     }
             return Boolean($.effects && ($.effects[name] || ($.effects.effect && $.effects.effect[name])));
         },
 
-        createTag: function(value, additionalClass, duringInitialization) {
+        createTag: function(value, additionalClass, duringInitialization, additionalTitle) {
             var that = this;
-
             value = $.trim(value);
 
             if(this.options.preprocessTag) {
             }
 
             var label = $(this.options.onTagClicked ? '<a class="tagit-label"></a>' : '<span class="tagit-label"></span>').text(value);
-
+            label.attr('title', additionalTitle);
             // Create tag.
             var tag = $('<li></li>')
-                .addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all')
+                //.addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all')
+                .addClass('tagit-choice')
                 .addClass(additionalClass)
                 .append(label);
-
             if (this.options.readOnly){
                 tag.addClass('tagit-choice-read-only');
             } else {
                 tag.addClass('tagit-choice-editable');
                 // Button for removing the tag.
                 var removeTagIcon = $('<span></span>')
-                    .addClass('ui-icon ui-icon-close');
+                    .addClass('icon-close');
                 var removeTag = $('<a><span class="text-icon">\xd7</span></a>') // \xd7 is an X
                     .addClass('tagit-close')
                     .append(removeTagIcon)
             // Unless options.singleField is set, each tag has a hidden input field inline.
             if (!this.options.singleField) {
                 var escapedValue = label.html();
-                tag.append('<input type="hidden" value="' + escapedValue + '" name="' + this.options.fieldName + '" class="tagit-hidden-field" />');
+                tag.append('<input type="hidden" style="display:none;" value="' + escapedValue + '" name="' + this.options.fieldName + '" />');
             }
 
             if (this._trigger('beforeTagAdded', null, {
             this.tagInput.val('');
 
             // Insert tag.
-            this.tagInput.parent().before(tag);
+            if (this.insertPosition == "right")
+                this.tagList.apend(tag);
+            else
+                this.tagList.prepend(tag);
 
             this._trigger('afterTagAdded', null, {
                 tag: tag,