Edgewall Software
Modify

Opened 15 years ago

Last modified 11 years ago

#9310 new enhancement

Allow cookies be set for textarea heights upon resize and restore upon document load

Reported by: Carsten Klein <carsten.klein@…> Owned by:
Priority: normal Milestone: next-major-releases
Component: general Version: 0.12dev
Severity: trivial Keywords:
Cc: Jun Omae Branch:
Release Notes:
API Changes:
Internal Changes:

Description

This basically involves incorporating the jquery plugin for handling cookies.

The patch is rather simple. See the attached jquery_cookies.js which must be included with trac/htdocs/js/.

Here is the original project page for reference:

http://plugins.jquery.com/project/cookie

  • htdocs/js/resizer.js

     
    55    var textarea = $(this);
    66    var offset = null;
    77   
     8    function getCookieName(textarea) {
     9      if (textarea.attr("id")) {
     10        return "trac." + textarea.attr("id");
     11      }
     12      else if (textarea.attr("name")) {
     13        return "trac." + textarea.attr("name");
     14      }
     15    }
     16   
    817    function beginDrag(e) {
    918      offset = textarea.height() - e.pageY;
    1019      textarea.blur();
     
    1827    }
    1928   
    2029    function endDrag(e) {
     30      // set cookie for permanently storing information
     31      // on the height of the resizable, if possible
     32      name = getCookieName(textarea);
     33      if (name) {
     34        $.cookie(name, textarea.height(), ( new Date() ).getTime() + 1000000);         
     35      }
    2136      textarea.focus();
    2237      $(document).unbind('mousemove', dragging).unbind('mouseup', endDrag);
    2338    }
     
    2742            .parent().append(grip);
    2843    grip.style.marginLeft = (this.offsetLeft - grip.offsetLeft) + 'px';
    2944    grip.style.marginRight = (grip.offsetWidth - this.offsetWidth) +'px';
     45   
     46    // restore height from cookie, if avail
     47    var height = 0;
     48    var name = getCookieName(textarea);
     49    if (name)
     50    {
     51      height = $.cookie(name)
     52    }
     53    textarea.height(Math.max(32, height));
    3054  });
    3155});
     56
  • web/chrome.py

     
    522522        add_link(req, 'help', req.href.wiki('TracGuide'))
    523523        add_stylesheet(req, 'common/css/trac.css')
    524524        add_script(req, 'common/js/jquery.js')
     525        add_script(req, 'common/js/jquery_cookies.js')
    525526        # Only activate noConflict mode if requested to by the handler
    526527        if handler is not None and \
    527528           getattr(handler.__class__, 'jquery_noconflict', False):

BTW Thanks for incorporating this, it makes using trac a lot more easier.

Attachments (1)

jquery_cookies.js (4.1 KB ) - added by Carsten Klein <carsten.klein@…> 15 years ago.
The jquery cookies plugin

Download all attachments as: .zip

Change History (27)

by Carsten Klein <carsten.klein@…>, 15 years ago

Attachment: jquery_cookies.js added

The jquery cookies plugin

comment:1 by Carsten Klein <carsten.klein@…>, 15 years ago

Issues might be raised on non secure cookies set by the above provided patch. So additional tests must be run in order to make it work under all conditions, e.g. ie8+ requires secure cookie domains and so on.

comment:2 by Carsten Klein <carsten.klein@…>, 15 years ago

*Sigh*, the patch is missing a semicolon in line 51 on height = $.cookie(name). However, since it is the sole statement in that block it eventually works just fine. Feel free to add missing punctuation at your own expense ;)

comment:3 by Remy Blank, 15 years ago

Milestone: 0.12.1
Owner: set to Remy Blank

Good idea! It would be great if you could complete and test the implementation according to your concerns in comment:1.

Also, you don't really need getCookieName(). Just calculate the name once at the top level and store the result in a local, the same as textarea. And the expires argument to $.cookie() can be a number of days, so you don't have to make the calculation yourself. Finally, I would add the jquery_cookies.js script in Chrome.add_textarea_grips() instead of including it unconditionally.

comment:4 by Carsten Klein <carsten.klein@…>, 15 years ago

Nice of you. I will look further into this. As of now, all cookies established by the above patch are still session bound, so that the setting will be lost on user agent exit.

I will revise the patch according to your input.

Apart from that, I would rather see the cookies plugin be available on a general basis, and not just on add_text_area_grips(), because that would add extra functionality to 3rd party plugins, don't you think?

Besides, providing a compressed version of the plugin would reduce overall bandwidth, also.

comment:5 by Carsten Klein <carsten.klein@…>, 15 years ago

Reconsidering this, perhaps make this an additional call to the chrome api, e.g. add_cookies_support() or something like that.

comment:6 by Carsten Klein <carsten.klein@…>, 15 years ago

regarding comment:1 - i do not have nor run windows except for gaming purposes ;)

in reply to:  5 comment:7 by Carsten Klein <carsten.klein@…>, 15 years ago

Replying to Carsten Klein <carsten.klein@…>:

Reconsidering this, perhaps make this an additional call to the chrome api, e.g. add_cookies_support() or something like that.

Macros would definitely require this, i presume. So, the cookies should be available by default, since AFAIK the macros cannot instruct chrome to add support for cookies, or am I wrong?

in reply to:  4 comment:8 by Remy Blank, 15 years ago

Replying to Carsten Klein <carsten.klein@…>:

Apart from that, I would rather see the cookies plugin be available on a general basis, and not just on add_text_area_grips(), because that would add extra functionality to 3rd party plugins, don't you think?

Plugins are free to add_script(req, 'common/js/jquery_cookies.js') if they need it. jQuery (core) is only included unconditionally because virtually all pages need it.

comment:9 by Carsten Klein <carsten.klein@…>, 14 years ago

How many days would be appropriate for the cookie to remain valid?

Or should the cookie be refreshed on every load of the page so that it will remain valid for at least 365 days, and if the user once again opens the page, it will be refreshed so that it will remain valid for the next 365 days?

comment:10 by Carsten Klein <carsten.klein@…>, 14 years ago

Here is a corrected version of the patch. It will automatically refresh existing cookies so that on page load the lifetime of the cookie will be extended even if the user does not alter the height property of the textareas on that page.

In addition, only those request handlers that require the jquery_cookies.js plugin to be present will include it. Basically this could be done on the template level, also. However, since genshi processing overhead is still subject to discussion, it is included directly in the respective request handler's handle_request() methods.

The naming of the individual cookies is subject to discussion, since the data must be transferred to the server and back shorter names would actually make sense here.

  • trac/ticket/web_ui.py

     
    163163            return True
    164164
    165165    def process_request(self, req):
     166
     167        add_script(req, 'common/js/jquery_cookies.js')
     168
    166169        if 'id' in req.args:
    167170            if req.path_info == '/newticket':
    168171                raise TracError(_("id can't be set for a new ticket request."))
  • trac/ticket/roadmap.py

     
    581582            milestone.name = milestone_id
    582583            action = 'edit' # rather than 'new' so that it works for POST/save
    583584
     585        add_script(req, 'common/js/jquery_cookies.js')
     586
    584587        if req.method == 'POST':
    585588            if req.args.has_key('cancel'):
    586589                if milestone.exists:
  • trac/htdocs/js/resizer.js

     
    44  $('textarea.trac-resizable').each(function() {
    55    var textarea = $(this);
    66    var offset = null;
    7    
     7    var stateCookieName = null;
     8
     9    if (textarea.attr("id")) {
     10      stateCookieName = "trac_ui.textarea." + textarea.attr("id") + ".height";
     11    }
     12    else if (textarea.attr("name")) {
     13      stateCookieName = "trac_ui.textarea." + textarea.attr("name") + ".height";
     14    }
     15
    816    function beginDrag(e) {
    917      offset = textarea.height() - e.pageY;
    1018      textarea.blur();
     
    1826    }
    1927   
    2028    function endDrag(e) {
     29      // set cookie for permanently storing information
     30      // on the height of the resizable, if possible
     31      if (stateCookieName) {
     32        $.cookie(stateCookieName, textarea.height(), { expires: 365 });         
     33      }
    2134      textarea.focus();
    2235      $(document).unbind('mousemove', dragging).unbind('mouseup', endDrag);
    2336    }
     
    2740            .parent().append(grip);
    2841    grip.style.marginLeft = (this.offsetLeft - grip.offsetLeft) + 'px';
    2942    grip.style.marginRight = (grip.offsetWidth - this.offsetWidth) +'px';
     43   
     44    // restore height from cookie, if avail
     45    // and refresh cookie so that it stays valid
     46    var height = Math.max(32, textarea.height());
     47    if (stateCookieName) {
     48      height = $.cookie(stateCookieName);
     49      $.cookie(stateCookieName, height, { expires: 365 });
     50    }
     51   
     52    textarea.height(height + 'px');
    3053  });
    3154});
     55
  • trac/admin/web_ui.py

     
    3030from trac.util.text import exception_to_unicode
    3131from trac.util.translation import _, ngettext
    3232from trac.web import HTTPNotFound, IRequestHandler
    33 from trac.web.chrome import add_notice, add_stylesheet, \
     33from trac.web.chrome import add_notice, add_stylesheet, add_script, \
    3434                            add_warning, Chrome, INavigationContributor, \
    3535                            ITemplateProvider
    3636
     
    141141        })
    142142
    143143        add_stylesheet(req, 'common/css/admin.css')
     144        add_script(req, 'common/js/jquery_cookies.js')
     145
    144146        return template, data, None
    145147
    146148    # ITemplateProvider methods
  • trac/wiki/web_ui.py

     
    126126                  num=version, name=page.name))
    127127
    128128        add_stylesheet(req, 'common/css/wiki.css')
     129        add_script(req, 'common/js/jquery_cookies.js')
    129130
    130131        if req.method == 'POST':
    131132            if action == 'edit':

comment:11 by Carsten Klein <carsten.klein@…>, 14 years ago

Still untested on IE, though. If someone would step in and test this on IE…

comment:12 by Carsten Klein <carsten.klein@…>, 14 years ago

Works on

Could someone please test

Thanks!

comment:13 by Carsten Klein <carsten.klein@…>, 14 years ago

Another issue still remains: the number of cookies is restricted by some user agents, with the least denominator being 20 per domain set forth by the original HTTP specification.

So if a user has multiple plugins installed that all include templates that would render resizables, then that number can be exceeded easily, provably resulting in javascript errors.

See http://www.nczonline.net/blog/2009/05/05/http-cookies-explained/ for more information.

In addition to that, each cookie can only be up to 4 KiB in size.

So that leaves us with the question on how we would deal with that.

My proposal would be to either use local storage when available, as is supported by most modern user agents, or implement a wrapper to a cookie store that then would persist user interface settings so that

a) will join multiple cookies using the subcookie approach into one, and b) will distribute cookie data across multiple cookies, so that c) the maximum size of a cookie never exceeds 4 KiB, and

E.g. there exists a "virtual" cookie named "trac.ui.settings", which then is automatically distributed across multiple "trac.ui.settings.0" … "trac.ui.settings.N" cookies. When accessing trac.ui.settings and querying or setting a value in it, the cookie store will instantiate a hash from all instances of "trac.ui.settings.*", and, when persisting information again, it will automatically distribute the data across multiple "trac.ui.settings.*" instances.

For when is 12.1 being scheduled? Maybe we should move this to a later milestone?

comment:14 by Remy Blank, 14 years ago

Another option would be to store on the server side, and to update the value on release through an XMLHttpRequest. This would also allow propagating the stored values when using different browsers / machines.

in reply to:  14 ; comment:15 by Christian Boos, 14 years ago

Replying to rblank:

Another option would be to store on the server side, and to update the value on release through an XMLHttpRequest. This would also allow propagating the stored values when using different browsers / machines.

Well, are you really sure this is a good idea at all? To me, the benefit (if any) doesn't seem worth the trouble of bundling a new dependency and the extra complexity (the user settings would be spread partly in the session, partly in cookies).

in reply to:  15 ; comment:16 by Remy Blank, 14 years ago

Replying to cboos:

Well, are you really sure this is a good idea at all? To me, the benefit (if any) doesn't seem worth the trouble of bundling a new dependency and the extra complexity (the user settings would be spread partly in the session, partly in cookies).

Actually, I meant dropping the cookie idea altogether and only relying on session data. Use the session data for the initial field heights on page load, and update the setting through xhr on mouse release.

in reply to:  16 comment:17 by Christian Boos, 14 years ago

Milestone: 0.12.1next-major-0.1X

Replying to rblank:

Actually, I meant dropping the cookie idea altogether and only relying on session data. Use the session data for the initial field heights on page load,

OK

and update the setting through xhr on mouse release.

That still sounds overkill, only sending the value during a real submit (preview or save) seems good enough.

Anyway, this is not appropriate for 0.12.x, I think.

in reply to:  16 ; comment:18 by Carsten Klein <carsten.klein@…>, 14 years ago

Replying to rblank:

Actually, I meant dropping the cookie idea altogether and only relying on session data. Use the session data for the initial field heights on page load, and update the setting through xhr on mouse release.

This is an excellent idea. We could make a request handler for this, e.g. SessionStateHandler which would provide an xhr api to the client for storing and, if required, also for getting session state from the server.

I will look into this during the upcoming weekend and provide a patch.

in reply to:  18 ; comment:19 by Carsten Klein <carsten.klein@…>, 14 years ago

Replying to Carsten Klein <carsten.klein@…>:

Problems with such a generic service are:

  • cross site scripting attacks (not that much of a problem with simple int values being stored, but in the long run one might expect other data to be stored as well, once it becomes available)
    • data spoofing attacks
    • data / script injection attacks

In order to overcome this problem, each XHR request must be guarded using a token. This token could be that of the parent form, provided that this will not invalidate the form token.

Would this be okay with you?

in reply to:  19 ; comment:20 by Carsten Klein <carsten.klein@…>, 14 years ago

Replying to Carsten Klein <carsten.klein@…>:

Dunno, but is such a token available on every form in order to prevent XSS?

in reply to:  20 ; comment:21 by Remy Blank, 14 years ago

Replying to Carsten Klein <carsten.klein@…>:

Dunno, but is such a token available on every form in order to prevent XSS?

Yes, it is. Look for __FORM_TOKEN. We already pass that for the automatic preview in the wiki and ticket comments.

in reply to:  21 comment:22 by Carsten Klein <carsten.klein@…>, 14 years ago

Replying to rblank:

Replying to Carsten Klein <carsten.klein@…>:

Dunno, but is such a token available on every form in order to prevent XSS?

Yes, it is. Look for __FORM_TOKEN. We already pass that for the automatic preview in the wiki and ticket comments.

Thanks for the hint.

As far as I can see, form token handling seems ok, but I can see possible attack vectors, thus I will also revise existing form token handling.

comment:23 by Carsten Klein <carsten.klein@…>, 14 years ago

Long time no input on my behalf. I set this to a low priority for now, since I need to learn more on behalf on both trac+xhr requests. Besides that I got carried away with cookies, so to say. Will definitely look into this in a few weeks.

comment:24 by Remy Blank, 13 years ago

Owner: Remy Blank removed

Refocusing.

comment:25 by carsten.klein@…, 11 years ago

I say we should close this issue and instead focus on using newer technologies than cookies, for example browser integrated SQLite storages and so on.

This, of course, will break with older browser versions, but who cares?

comment:26 by Jun Omae, 11 years ago

Cc: Jun Omae added

Modify Ticket

Change Properties
Set your email in Preferences
Action
as new The ticket will remain with no owner.
The ticket will be disowned.
as The resolution will be set. Next status will be 'closed'.
The owner will be changed from (none) to anonymous. Next status will be 'assigned'.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.