xref: /webtrees/resources/js/treeview.js (revision b53fbe6ab1db25a13a22a83eb9b80fa9efac8927)
12ebb07b4SGreg Roach/**
22ebb07b4SGreg Roach * webtrees: online genealogy
3d11be702SGreg Roach * Copyright (C) 2023 webtrees development team
42ebb07b4SGreg Roach * This program is free software: you can redistribute it and/or modify
52ebb07b4SGreg Roach * it under the terms of the GNU General Public License as published by
62ebb07b4SGreg Roach * the Free Software Foundation, either version 3 of the License, or
72ebb07b4SGreg Roach * (at your option) any later version.
82ebb07b4SGreg Roach * This program is distributed in the hope that it will be useful,
92ebb07b4SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
102ebb07b4SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
112ebb07b4SGreg Roach * GNU General Public License for more details.
122ebb07b4SGreg Roach * You should have received a copy of the GNU General Public License
132ebb07b4SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
142ebb07b4SGreg Roach */
152ebb07b4SGreg Roach
162ebb07b4SGreg Roachfunction TreeViewHandler (treeview_instance, ged) {
172ebb07b4SGreg Roach  var tv = this; // Store "this" for usage within jQuery functions where "this" is not this ;-)
182ebb07b4SGreg Roach
19efd89170SGreg Roach  this.treeview = $('#' + treeview_instance + '_in');
20efd89170SGreg Roach  this.loadingImage = $('#' + treeview_instance + '_loading');
21efd89170SGreg Roach  this.toolbox = $('#tv_tools');
22efd89170SGreg Roach  this.buttons = $('.tv_button:first', this.toolbox);
232ebb07b4SGreg Roach  this.zoom = 100; // in percent
242ebb07b4SGreg Roach  this.boxWidth = 180; // default family box width
252ebb07b4SGreg Roach  this.boxExpandedWidth = 250; // default expanded family box width
262ebb07b4SGreg Roach  this.cookieDays = 3; // lifetime of preferences memory, in days
27efd89170SGreg Roach  this.ajaxDetails = document.getElementById(treeview_instance + '_out').dataset.urlDetails + '&instance=' + encodeURIComponent(treeview_instance);
28efd89170SGreg Roach  this.ajaxPersons = document.getElementById(treeview_instance + '_out').dataset.urlIndividuals + '&instance=' + encodeURIComponent(treeview_instance);
292ebb07b4SGreg Roach
302ebb07b4SGreg Roach  this.container = this.treeview.parent(); // Store the container element ("#" + treeview_instance + "_out")
312ebb07b4SGreg Roach  this.auto_box_width = false;
322ebb07b4SGreg Roach  this.updating = false;
332ebb07b4SGreg Roach
342ebb07b4SGreg Roach  // Restore user preferences
35efd89170SGreg Roach  if (readCookie('compact') === 'true') {
362ebb07b4SGreg Roach    tv.compact();
372ebb07b4SGreg Roach  }
382ebb07b4SGreg Roach
3942584a2bSGreg Roach  // Drag handlers for the treeview canvas
4042584a2bSGreg Roach  (function () {
4142584a2bSGreg Roach    let dragging = false;
42*b53fbe6aSFranz Frese    let isDown = false;
4342584a2bSGreg Roach    let drag_start_x;
4442584a2bSGreg Roach    let drag_start_y;
452ebb07b4SGreg Roach
4642584a2bSGreg Roach    tv.treeview.on('mousedown touchstart', function (event) {
472ebb07b4SGreg Roach
4842584a2bSGreg Roach      let pageX = (event.type === 'touchstart') ? event.touches[0].pageX : event.pageX;
4942584a2bSGreg Roach      let pageY = (event.type === 'touchstart') ? event.touches[0].pageY : event.pageY;
502ebb07b4SGreg Roach
5142584a2bSGreg Roach      drag_start_x = tv.treeview.offset().left - pageX;
5242584a2bSGreg Roach      drag_start_y = tv.treeview.offset().top - pageY;
53*b53fbe6aSFranz Frese      isDown = true;
542ebb07b4SGreg Roach    });
5542584a2bSGreg Roach
5642584a2bSGreg Roach    $(document).on('mousemove touchmove', function (event) {
57*b53fbe6aSFranz Frese      if (isDown) {
5842584a2bSGreg Roach        event.preventDefault();
59*b53fbe6aSFranz Frese        dragging = true;
6042584a2bSGreg Roach
6142584a2bSGreg Roach        let pageX = (event.type === 'touchmove') ? event.touches[0].pageX : event.pageX;
6242584a2bSGreg Roach        let pageY = (event.type === 'touchmove') ? event.touches[0].pageY : event.pageY;
6342584a2bSGreg Roach
6442584a2bSGreg Roach        tv.treeview.offset({
6542584a2bSGreg Roach          left: pageX + drag_start_x,
6642584a2bSGreg Roach          top: pageY + drag_start_y,
6742584a2bSGreg Roach        });
6842584a2bSGreg Roach      }
6942584a2bSGreg Roach    });
7042584a2bSGreg Roach
7142584a2bSGreg Roach    $(document).on('mouseup touchend', function (event) {
72*b53fbe6aSFranz Frese      isDown = false;
7342584a2bSGreg Roach      if (dragging) {
7442584a2bSGreg Roach        event.preventDefault();
7542584a2bSGreg Roach        dragging = false;
762ebb07b4SGreg Roach        tv.updateTree();
7742584a2bSGreg Roach      }
782ebb07b4SGreg Roach    });
7942584a2bSGreg Roach  })();
802ebb07b4SGreg Roach
812ebb07b4SGreg Roach  // Add click handlers to buttons
82efd89170SGreg Roach  tv.toolbox.find('#tvbCompact').each(function (index, tvCompact) {
832ebb07b4SGreg Roach    tvCompact.onclick = function () {
842ebb07b4SGreg Roach      tv.compact();
852ebb07b4SGreg Roach    };
862ebb07b4SGreg Roach  });
872ebb07b4SGreg Roach  // If we click the "hide/show all partners" button, toggle the setting before reloading the page
88efd89170SGreg Roach  tv.toolbox.find('#tvbAllPartners').each(function (index, tvAllPartners) {
892ebb07b4SGreg Roach    tvAllPartners.onclick = function () {
90efd89170SGreg Roach      createCookie('allPartners', readCookie('allPartners') === 'true' ? 'false' : 'true', tv.cookieDays);
912ebb07b4SGreg Roach      document.location = document.location;
922ebb07b4SGreg Roach    };
932ebb07b4SGreg Roach  });
94efd89170SGreg Roach  tv.toolbox.find('#tvbOpen').each(function (index, tvbOpen) {
952ebb07b4SGreg Roach    var b = $(tvbOpen, tv.toolbox);
962ebb07b4SGreg Roach    tvbOpen.onclick = function () {
97efd89170SGreg Roach      b.addClass('tvPressed');
982ebb07b4SGreg Roach      tv.setLoading();
99efd89170SGreg Roach      var e = jQuery.Event('click');
100efd89170SGreg Roach      tv.treeview.find('.tv_box:not(.boxExpanded)').each(function (index, box) {
1012ebb07b4SGreg Roach        var pos = $(box, tv.treeview).offset();
1022ebb07b4SGreg Roach        if (pos.left >= tv.leftMin && pos.left <= tv.leftMax && pos.top >= tv.topMin && pos.top <= tv.topMax) {
1032ebb07b4SGreg Roach          tv.expandBox(box, e);
1042ebb07b4SGreg Roach        }
1052ebb07b4SGreg Roach      });
106efd89170SGreg Roach      b.removeClass('tvPressed');
1072ebb07b4SGreg Roach      tv.setComplete();
1082ebb07b4SGreg Roach    };
1092ebb07b4SGreg Roach  });
110efd89170SGreg Roach  tv.toolbox.find('#tvbClose').each(function (index, tvbClose) {
1112ebb07b4SGreg Roach    var b = $(tvbClose, tv.toolbox);
1122ebb07b4SGreg Roach    tvbClose.onclick = function () {
113efd89170SGreg Roach      b.addClass('tvPressed');
1142ebb07b4SGreg Roach      tv.setLoading();
115efd89170SGreg Roach      tv.treeview.find('.tv_box.boxExpanded').each(function (index, box) {
116efd89170SGreg Roach        $(box).css('display', 'none').removeClass('boxExpanded').parent().find('.tv_box.collapsedContent').css('display', 'block');
1172ebb07b4SGreg Roach      });
118efd89170SGreg Roach      b.removeClass('tvPressed');
1192ebb07b4SGreg Roach      tv.setComplete();
1202ebb07b4SGreg Roach    };
1212ebb07b4SGreg Roach  });
1222ebb07b4SGreg Roach
1232ebb07b4SGreg Roach  tv.centerOnRoot(); // fire ajax update if needed, which call setComplete() when all is loaded
1242ebb07b4SGreg Roach}
1252ebb07b4SGreg Roach/**
1262ebb07b4SGreg Roach * Class TreeView setLoading method
1272ebb07b4SGreg Roach */
1282ebb07b4SGreg RoachTreeViewHandler.prototype.setLoading = function () {
129efd89170SGreg Roach  this.treeview.css('cursor', 'wait');
130efd89170SGreg Roach  this.loadingImage.css('display', 'block');
1312ebb07b4SGreg Roach};
1322ebb07b4SGreg Roach/**
1332ebb07b4SGreg Roach * Class TreeView setComplete  method
1342ebb07b4SGreg Roach */
1352ebb07b4SGreg RoachTreeViewHandler.prototype.setComplete = function () {
136efd89170SGreg Roach  this.treeview.css('cursor', 'move');
137efd89170SGreg Roach  this.loadingImage.css('display', 'none');
1382ebb07b4SGreg Roach};
1392ebb07b4SGreg Roach
1402ebb07b4SGreg Roach/**
1412ebb07b4SGreg Roach * Class TreeView getSize  method
1422ebb07b4SGreg Roach * Store the viewport current size
1432ebb07b4SGreg Roach */
1442ebb07b4SGreg RoachTreeViewHandler.prototype.getSize = function () {
1452ebb07b4SGreg Roach  var tv = this;
1462ebb07b4SGreg Roach  // retrieve the current container bounding box
1472ebb07b4SGreg Roach  var container = tv.container.parent();
1482ebb07b4SGreg Roach  var offset = container.offset();
1492ebb07b4SGreg Roach  tv.leftMin = offset.left;
1502ebb07b4SGreg Roach  tv.leftMax = tv.leftMin + container.innerWidth();
1512ebb07b4SGreg Roach  tv.topMin = offset.top;
1522ebb07b4SGreg Roach  tv.topMax = tv.topMin + container.innerHeight();
1532ebb07b4SGreg Roach  /*
1542ebb07b4SGreg Roach	 var frm = $("#tvTreeBorder");
1552ebb07b4SGreg Roach	 tv.treeview.css("width", frm.width());
1562ebb07b4SGreg Roach	 tv.treeview.css("height", frm.height()); */
1572ebb07b4SGreg Roach};
1582ebb07b4SGreg Roach
1592ebb07b4SGreg Roach/**
1602ebb07b4SGreg Roach * Class TreeView updateTree  method
1612ebb07b4SGreg Roach * Perform ajax requests to complete the tree after drag
1622ebb07b4SGreg Roach * param boolean @center center on root person when done
1632ebb07b4SGreg Roach */
1642ebb07b4SGreg RoachTreeViewHandler.prototype.updateTree = function (center, button) {
1652ebb07b4SGreg Roach  var tv = this; // Store "this" for usage within jQuery functions where "this" is not this ;-)
1662ebb07b4SGreg Roach  var to_load = [];
1672ebb07b4SGreg Roach  var elts = [];
1682ebb07b4SGreg Roach  this.getSize();
1692ebb07b4SGreg Roach
1702ebb07b4SGreg Roach  // check which td with datafld attribute are within the container bounding box
1712ebb07b4SGreg Roach  // and therefore need to be dynamically loaded
172efd89170SGreg Roach  tv.treeview.find('td[abbr]').each(function (index, el) {
1732ebb07b4SGreg Roach    el = $(el, tv.treeview);
1742ebb07b4SGreg Roach    var pos = el.offset();
1752ebb07b4SGreg Roach    if (pos.left >= tv.leftMin && pos.left <= tv.leftMax && pos.top >= tv.topMin && pos.top <= tv.topMax) {
176efd89170SGreg Roach      to_load.push(el.attr('abbr'));
1772ebb07b4SGreg Roach      elts.push(el);
1782ebb07b4SGreg Roach    }
1792ebb07b4SGreg Roach  });
1802ebb07b4SGreg Roach  // if some boxes need update, we perform an ajax request
1812ebb07b4SGreg Roach  if (to_load.length > 0) {
1822ebb07b4SGreg Roach    tv.updating = true;
1832ebb07b4SGreg Roach    tv.setLoading();
1842ebb07b4SGreg Roach    jQuery.ajax({
18506f42609SGreg Roach      url: tv.ajaxPersons,
186efd89170SGreg Roach      dataType: 'json',
187efd89170SGreg Roach      data: 'q=' + to_load.join(';'),
1882ebb07b4SGreg Roach      success: function (ret) {
1892ebb07b4SGreg Roach        var nb = elts.length;
190efd89170SGreg Roach        var root_element = $('.rootPerson', this.treeview);
1912ebb07b4SGreg Roach        var l = root_element.offset().left;
1922ebb07b4SGreg Roach        for (var i = 0; i < nb; i++) {
193efd89170SGreg Roach          elts[i].removeAttr('abbr').html(ret[i]);
1942ebb07b4SGreg Roach        }
195a2c8afeaSAlejandro Criado-Pérez        // we now adjust the draggable treeview size to its content size
1962ebb07b4SGreg Roach        tv.getSize();
1972ebb07b4SGreg Roach      },
1982ebb07b4SGreg Roach      complete: function () {
199efd89170SGreg Roach        if (tv.treeview.find('td[abbr]').length) {
2002ebb07b4SGreg Roach          tv.updateTree(center, button); // recursive call
2012ebb07b4SGreg Roach        }
2022ebb07b4SGreg Roach        // the added boxes need that in mode compact boxes
2032ebb07b4SGreg Roach        if (tv.auto_box_width) {
204efd89170SGreg Roach          tv.treeview.find('.tv_box').css('width', 'auto');
2052ebb07b4SGreg Roach        }
2062ebb07b4SGreg Roach        tv.updating = true; // avoid an unuseful recursive call when all requested persons are loaded
2072ebb07b4SGreg Roach        if (center) {
2082ebb07b4SGreg Roach          tv.centerOnRoot();
2092ebb07b4SGreg Roach        }
2102ebb07b4SGreg Roach        if (button) {
211efd89170SGreg Roach          button.removeClass('tvPressed');
2122ebb07b4SGreg Roach        }
2132ebb07b4SGreg Roach        tv.setComplete();
2142ebb07b4SGreg Roach        tv.updating = false;
2152ebb07b4SGreg Roach      },
2162ebb07b4SGreg Roach      timeout: function () {
2172ebb07b4SGreg Roach        if (button) {
218efd89170SGreg Roach          button.removeClass('tvPressed');
2192ebb07b4SGreg Roach        }
2202ebb07b4SGreg Roach        tv.updating = false;
2212ebb07b4SGreg Roach        tv.setComplete();
2222ebb07b4SGreg Roach      }
2232ebb07b4SGreg Roach    });
2242ebb07b4SGreg Roach  } else {
2252ebb07b4SGreg Roach    if (button) {
226efd89170SGreg Roach      button.removeClass('tvPressed');
2272ebb07b4SGreg Roach    }
2282ebb07b4SGreg Roach    tv.setComplete();
2292ebb07b4SGreg Roach  }
2302ebb07b4SGreg Roach  return false;
2312ebb07b4SGreg Roach};
2322ebb07b4SGreg Roach
2332ebb07b4SGreg Roach/**
2342ebb07b4SGreg Roach * Class TreeView compact method
2352ebb07b4SGreg Roach */
2362ebb07b4SGreg RoachTreeViewHandler.prototype.compact = function () {
2372ebb07b4SGreg Roach  var tv = this;
238efd89170SGreg Roach  var b = $('#tvbCompact', tv.toolbox);
2392ebb07b4SGreg Roach  tv.setLoading();
2402ebb07b4SGreg Roach  if (tv.auto_box_width) {
241efd89170SGreg Roach    var w = tv.boxWidth * (tv.zoom / 100) + 'px';
242efd89170SGreg Roach    var ew = tv.boxExpandedWidth * (tv.zoom / 100) + 'px';
243efd89170SGreg Roach    tv.treeview.find('.tv_box:not(boxExpanded)', tv.treeview).css('width', w);
244efd89170SGreg Roach    tv.treeview.find('.boxExpanded', tv.treeview).css('width', ew);
2452ebb07b4SGreg Roach    tv.auto_box_width = false;
246efd89170SGreg Roach    if (readCookie('compact')) {
247efd89170SGreg Roach      createCookie('compact', false, tv.cookieDays);
2482ebb07b4SGreg Roach    }
249efd89170SGreg Roach    b.removeClass('tvPressed');
2502ebb07b4SGreg Roach  } else {
251efd89170SGreg Roach    tv.treeview.find('.tv_box').css('width', 'auto');
2522ebb07b4SGreg Roach    tv.auto_box_width = true;
253efd89170SGreg Roach    if (!readCookie('compact')) {
254efd89170SGreg Roach      createCookie('compact', true, tv.cookieDays);
2552ebb07b4SGreg Roach    }
2562ebb07b4SGreg Roach    if (!tv.updating) {
2572ebb07b4SGreg Roach      tv.updateTree();
2582ebb07b4SGreg Roach    }
259efd89170SGreg Roach    b.addClass('tvPressed');
2602ebb07b4SGreg Roach  }
2612ebb07b4SGreg Roach  tv.setComplete();
2622ebb07b4SGreg Roach  return false;
2632ebb07b4SGreg Roach};
2642ebb07b4SGreg Roach
2652ebb07b4SGreg Roach/**
2662ebb07b4SGreg Roach * Class TreeView centerOnRoot method
2672ebb07b4SGreg Roach */
2682ebb07b4SGreg RoachTreeViewHandler.prototype.centerOnRoot = function () {
269efd89170SGreg Roach  this.loadingImage.css('display', 'block');
2702ebb07b4SGreg Roach  var tv = this;
2712ebb07b4SGreg Roach  var tvc = this.container;
2722ebb07b4SGreg Roach  var tvc_width = tvc.innerWidth() / 2;
27380d699d6SGreg Roach  if (Number.isNaN(tvc_width)) {
2742ebb07b4SGreg Roach    return false;
2752ebb07b4SGreg Roach  }
2762ebb07b4SGreg Roach  var tvc_height = tvc.innerHeight() / 2;
277efd89170SGreg Roach  var root_person = $('.rootPerson', this.treeview);
2782ebb07b4SGreg Roach
2792ebb07b4SGreg Roach  if (!this.updating) {
2802ebb07b4SGreg Roach    tv.setComplete();
2812ebb07b4SGreg Roach  }
2822ebb07b4SGreg Roach  return false;
2832ebb07b4SGreg Roach};
2842ebb07b4SGreg Roach
2852ebb07b4SGreg Roach/**
2862ebb07b4SGreg Roach * Class TreeView expandBox method
287220f62c2SGreg Roach * Called ONLY for elements which have NOT the class tv_link to avoid un-useful requests to the server
288220f62c2SGreg Roach * @param {string} box   - the person box element
289220f62c2SGreg Roach * @param {string} event - the call event
2902ebb07b4SGreg Roach */
2912ebb07b4SGreg RoachTreeViewHandler.prototype.expandBox = function (box, event) {
2922ebb07b4SGreg Roach  var t = $(event.target);
293efd89170SGreg Roach  if (t.hasClass('tv_link')) {
2942ebb07b4SGreg Roach    return false;
2952ebb07b4SGreg Roach  }
2962ebb07b4SGreg Roach
2972ebb07b4SGreg Roach  var box = $(box, this.treeview);
2982ebb07b4SGreg Roach  var bc = box.parent(); // bc is Box Container
299efd89170SGreg Roach  var pid = box.attr('abbr');
3002ebb07b4SGreg Roach  var tv = this; // Store "this" for usage within jQuery functions where "this" is not this ;-)
3012ebb07b4SGreg Roach  var expanded;
3022ebb07b4SGreg Roach  var collapsed;
3032ebb07b4SGreg Roach
304efd89170SGreg Roach  if (bc.hasClass('detailsLoaded')) {
305efd89170SGreg Roach    collapsed = bc.find('.collapsedContent');
306efd89170SGreg Roach    expanded = bc.find('.tv_box:not(.collapsedContent)');
3072ebb07b4SGreg Roach  } else {
3082ebb07b4SGreg Roach    // Cache the box content as an hidden person's box in the box's parent element
3092ebb07b4SGreg Roach    expanded = box;
3102ebb07b4SGreg Roach    collapsed = box.clone();
311efd89170SGreg Roach    bc.append(collapsed.addClass('collapsedContent').css('display', 'none'));
3122ebb07b4SGreg Roach    // we add a waiting image at the right side of the box
313efd89170SGreg Roach    var loading_image = this.loadingImage.find('img').clone().addClass('tv_box_loading').css('display', 'block');
3142ebb07b4SGreg Roach    box.prepend(loading_image);
3152ebb07b4SGreg Roach    tv.updating = true;
3162ebb07b4SGreg Roach    tv.setLoading();
3172ebb07b4SGreg Roach    // perform the Ajax request and load the result in the box
318efd89170SGreg Roach    box.load(tv.ajaxDetails + '&pid=' + encodeURIComponent(pid), function () {
3192ebb07b4SGreg Roach      // If Lightbox module is active, we reinitialize it for the new links
320efd89170SGreg Roach      if (typeof CB_Init === 'function') {
3212ebb07b4SGreg Roach        CB_Init();
3222ebb07b4SGreg Roach      }
323efd89170SGreg Roach      box.css('width', tv.boxExpandedWidth * (tv.zoom / 100) + 'px');
3242ebb07b4SGreg Roach      loading_image.remove();
325efd89170SGreg Roach      bc.addClass('detailsLoaded');
3262ebb07b4SGreg Roach      tv.setComplete();
3272ebb07b4SGreg Roach      tv.updating = false;
3282ebb07b4SGreg Roach    });
3292ebb07b4SGreg Roach  }
330efd89170SGreg Roach  if (box.hasClass('boxExpanded')) {
331efd89170SGreg Roach    expanded.css('display', 'none');
332efd89170SGreg Roach    collapsed.css('display', 'block');
333efd89170SGreg Roach    box.removeClass('boxExpanded');
3342ebb07b4SGreg Roach  } else {
335efd89170SGreg Roach    expanded.css('display', 'block');
336efd89170SGreg Roach    collapsed.css('display', 'none');
337efd89170SGreg Roach    expanded.addClass('boxExpanded');
3382ebb07b4SGreg Roach  }
339a2c8afeaSAlejandro Criado-Pérez  // we must adjust the draggable treeview size to its content size
3402ebb07b4SGreg Roach  this.getSize();
3412ebb07b4SGreg Roach  return false;
3422ebb07b4SGreg Roach};
3432ebb07b4SGreg Roach
344220f62c2SGreg Roach/**
345220f62c2SGreg Roach * @param {string} name
346220f62c2SGreg Roach * @param {string} value
347220f62c2SGreg Roach * @param {number} days
348220f62c2SGreg Roach */
3492ebb07b4SGreg Roachfunction createCookie (name, value, days) {
3502ebb07b4SGreg Roach  if (days) {
3512ebb07b4SGreg Roach    var date = new Date();
3522ebb07b4SGreg Roach    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
353efd89170SGreg Roach    document.cookie = name + '=' + value + '; expires=' + date.toGMTString() + '; path=/';
3542ebb07b4SGreg Roach  } else {
355efd89170SGreg Roach    document.cookie = name + '=' + value + '; path=/';
3562ebb07b4SGreg Roach  }
3572ebb07b4SGreg Roach}
3582ebb07b4SGreg Roach
359220f62c2SGreg Roach/**
360220f62c2SGreg Roach * @param   {string} name
361220f62c2SGreg Roach * @returns {string|null}
362220f62c2SGreg Roach */
3632ebb07b4SGreg Roachfunction readCookie (name) {
364efd89170SGreg Roach  var name_equals = name + '=';
3652ebb07b4SGreg Roach  var ca = document.cookie.split(';');
3662ebb07b4SGreg Roach  for (var i = 0; i < ca.length; i++) {
3672ebb07b4SGreg Roach    var c = ca[i];
3682ebb07b4SGreg Roach    while (c.charAt(0) === ' ') {
3692ebb07b4SGreg Roach      c = c.substring(1, c.length);
3702ebb07b4SGreg Roach    }
3712ebb07b4SGreg Roach    if (c.indexOf(name_equals) === 0) {
3722ebb07b4SGreg Roach      return c.substring(name_equals.length, c.length);
3732ebb07b4SGreg Roach    }
3742ebb07b4SGreg Roach  }
3752ebb07b4SGreg Roach  return null;
3762ebb07b4SGreg Roach}
377