Revision 1e827d67

b/invitations/invitations.py
100 100
                                 'invitations_left':
101 101
                                     get_invitations_left(request.user)},
102 102
                                context_instance=RequestContext(request))
103
        response = HttpResponse(data)
103
        response = HttpResponse(data, content_type='application/json')
104 104
        log.warn("Error adding invitation %s -> %s: %s",
105 105
                    request.user.uniq, email, errors)
106 106
    else:
......
112 112
                                 'invitations_left':
113 113
                                    get_invitations_left(request.user)},
114 114
                                context_instance=RequestContext(request))
115
        response = HttpResponse(data)
116
        log.info("Added invitation %s -> %s", request.user.uniq, email)
115
        response = HttpResponse(data, content_type='application/json')
116
        #log.info("Added invitation %s -> %s", request.user.uniq, email)
117 117

  
118 118
    return response
119 119

  
b/invitations/templates/invitations.html
1
{% load i18n %}
2
<div class="invitations-left">({% blocktrans count invitations_left as left %}
3
{{ left }} invitation left
4
{% plural %}
5
{{ left }} invitations left
6
{% endblocktrans %})</div>
7

  
8
{% if invitations_left > 0 %}
9
<form action="/invitations/" method="post" id="invform">
10
    {% csrf_token %}
11
    
12
    {% if errors %}
13
    <div id="errors" class="error-msg">
14
        <p>{% trans "Invite error(s) occured" %}</p>
15

  
16
        <ul>
17
        {% for error in errors %}
18
            <li>- {{ error }}</li>
19
        {% endfor %}
20
        </ul>
21
        </div>
22
    {% endif %}
23

  
24
    <div id="fieldheaders" class="clearfix">
25
        <span id="field_name_name">{% trans "Name" %}<span class="description">(e.g. John Smith)</span></span>
26
        <span id="field_email_name">{% trans "Email" %}</span>
27
    </div>
28
    <div id="fields">
29
        <div id="removable-name-container-1" class="removable-field-row"><input
30
            type="text" name="name_1" id="name_1" value="" class="textInput
31
            removable" /><input type="text" name="email_1" id="email_1" value=""
32
            class="textInput removable" /><img src="{{ MEDIA_URL }}spacer.gif" width="16" height="16" alt="" title="Remove This Item" class="" />
33
        </div>
34
        <div id="add-name-container" class="add-field-container">
35
        <span class="add-field-trigger">
36
            <img src="{{ MEDIA_URL }}add.gif" alt="" title="Add New Item" />
37
            {% trans "Add invitation" %}
38
        </span>
39
        </div>
40
    </div>
41
    
42
    <p>
43
    <input class="submit" type="submit" value="{% trans "Send invitations" %}" />
44
    <img src="{{ MEDIA_URL }}progress-tiny.gif" class="sending" />
45
    </p>
46
</form>
47
{% else %}
48

  
49
<div class="no-invitations-left">
50
<p>
51
{% blocktrans %}
52
You have no invitations left in your account.
53
{% endblocktrans %}
54
</p>
55
</div>
56
{% endif %}
57

  
58
<div id="invsent">
59
    
60
    <h3 class="clearfix overlay-inner-title">{% trans "Sent invitations" %}</h3>
61

  
62
    <div class="messages">
63
        <div class="message errormsg"></div>
64
        <div class="message success"></div>
65
    </div>
66
    
67
    <div>
68
        <ul class="invsent-list">
1
{% spaceless %}
2
{"errors": [
3
        {% if errors %}
4
            {% for error in errors %}
5
                {
6
                    "msg": "{{ error|escapejs }}"
7
                }{% if not forloop.last %},{% endif %}
8
            {% endfor %}
9
        {% endif %}
10
    ],
11
    "invitations" : [
69 12
        {% for inv in invitations %}
70
        <li class="clearfix {% if inv.accepted %}accepted{% endif %}">
71
                {% if inv.accepted %}
72
                <img src="{{ MEDIA_URL }}invitation_accepted.png" alt="{% trans "Invitation accepted" %}">
73
                {% else %}
74
                    <span class="resend-invitation" id="inv-{{ inv.id }}">
75
                        <img src="{{ MEDIA_URL }}resend.png" alt="{% trans "Resend invitation" %}" class="resend">
76
                    </span>
77
                {% endif %}
78
                <span class="name">{{ inv.targetname }}</span> <span class="email">{{ inv.target }}</span>
79
            </li>
13
            {
14
                "updated": "{{ inv.updated|date }}",
15
                "accepted": {% if inv.accepted %}true{% else %}false{% endif %},
16
                "targetname": "{{ inv.targetname|escapejs }}",
17
                "target": "{{ inv.target|escapejs }}"
18
            }{% if not forloop.last %},{% endif %}
80 19
        {% endfor %}
81
    </ul>
82

  
83
    <div class="pages clearfix"></div>
20
    ],
21
    "invitations_left": "{{ invitations_left|escapejs }}"
84 22

  
85
    <div class="icons-info">
86
        <span>(<img src="{{ MEDIA_URL }}invitation_accepted.png" alt="{% trans "Invitation accepted" %}"> = {% trans "Invitation has been accepted" %})</span>
87
        <span>(<img src="{{ MEDIA_URL }}resend.png" alt="{% trans "Resend invitation" %}"> = {% trans "Send invitation again" %})</span>
88
    </div>
89
    </div>
90
</div>
23
    }
24
{% endspaceless %}
b/ui/static/snf/css/main.css
3860 3860
    background-image: url("../images/icons/actions/medium/shutdown.png");
3861 3861
}
3862 3862

  
3863

  
3864
.no-invitations-left {
3865
    margin-bottom: 20px;
3866
    color: #E44848;
3867
}
3868

  
3869
.invitations #field_name_name, .invitations #field_email_name {
3870
    float: left;
3871
    display: block;
3872
    width: 180px;
3873
}
3874
.invitations input {
3875
    width: 170px;
3876
}
3877

  
3878
.invitations #fieldheaders span.description {
3879
    font-size: 0.8em;
3880
    color: #666;
3881
    margin-left: 2px;
3882
}
3883

  
3884
.invitations #fields {
3885
    margin-top: 5px;
3886
}
3887

  
3888
.invitations .add-field-container {
3889
    margin-top: 5px;
3890
}
3891

  
3892
.invitations #errors {
3893
    padding: 5px;
3894
    background-color: #800000;
3895
}
3896

  
3897
.invitations #errors p {
3898
    margin-bottom: 10px;
3899
    font-size: 0.95em;
3900
    padding-top:0;
3901
    margin-top:0;
3902
    color: #fff;
3903
}
3904

  
3905
.invitations #errors li {
3906
    font-size: 0.85em;
3907
    color: #ddd;
3908
}
3909

  
3910
.invitations #errors {
3911
    font-size: 100%;
3912
    margin-bottom: 10px;
3913
}
3914

  
3915
#invsent {
3916
    padding-top: 10px;
3917
}
3918

  
3919
#invsent h3.overlay-inner-title {
3920
    font-size: 1.2em;
3921
    font-weight: normal;
3922
    border-bottom: 1px solid #B0D0E0;
3923
}
3924

  
3925
#invsent .icons-info img {
3926
    vertical-align: middle;
3927
}
3928

  
3929
#invsent .icons-info {
3930
    margin-top: 10px;
3931
    font-size: 0.7em;
3932
}
3933

  
3934
#invsent .icons-info span {
3935
    margin-right: 10px;
3936
}
3937

  
3938
.notification-box .invitations {
3939
    padding-bottom: 0;
3940
}
3941
.notification-box .invitations .sub-text {
3942
    display: none;
3943
}
3944

  
3945 3863
h3.overlay-inner-title {
3946 3864
    color: #4085A5;
3947 3865
    font-size: 2em;
3948 3866
}
3949 3867

  
3950
#invsent .message {
3951
    font-size: 0.9em;
3952
    padding: 5px 0;
3953
    margin-top: 5px;
3954
    margin-bottom: -10px;
3955
    color: #5CAD54;
3956
}
3957

  
3958
#invsent .errormsg {
3959
    color: #AE2B34;
3960
}
3961

  
3962 3868
#add-name-container {
3963 3869
    margin-bottom: 10px;   
3964 3870
}
......
3971 3877
    vertical-align: middle;
3972 3878
}
3973 3879

  
3974
.invitations form {
3975
    margin-bottom: 20px;
3976
}
3977

  
3978
#invsent h3 {
3979
    font-size: 0.8em;
3980
    font-weight: bold;
3981
}
3982

  
3983
#invsent h3 span {
3984
    font-weight: normal;
3985
    font-size: 0.9em;
3986
    margin-right: 5px;
3987
    display: block;
3988
    margin-top: -2px;
3989
}
3990

  
3991
#invsent ul {
3992
    margin-top: 3px;
3993
}
3994

  
3995
#invsent li {
3996
    color: #4085A5;
3997
    font-size: 1.1em;
3998
    padding-top: 0.5em;
3999
    border-bottom: 1px solid #efefef;
4000
    padding-bottom: 0.5em;
4001
    font-size: 0.9em;
4002
    position: relative;
4003
}
4004

  
4005
#invsent li:hover {
4006
    background-color: #efefef;
4007
}
4008

  
4009
#invsent li img {
4010
    position: absolute;
4011
    left:20px;
4012
    bottom: 2px;
4013
}
4014

  
4015
#invsent h3 img {
4016
    float: none;
4017
    vertical-align: middle;
4018
    margin-left: 3px;
4019
}
4020

  
4021
#invsent .name {
4022
    float: left;
4023
    width: 55%;
4024
    padding-left: 20px;
4025
}
4026

  
4027
#invsent .email {
4028
    float: left;
4029
    width: 40%;
4030
}
4031

  
4032
#invsent li.accepted {
4033
    color: #ABD49C;
4034
}
4035

  
4036
#invsent li img {
4037
    margin-left: -20px;
4038
    margin-top: 1px;
4039
}
4040

  
4041
#invsent li img.resend {
4042
    cursor: pointer;
4043
}
4044

  
4045
.invitations #field_email_name {
4046
}
4047

  
4048
#invform #fields input {
4049
    margin-right: 10px;
4050
}
4051

  
4052 3880
.machine-now-building {
4053 3881
    padding-right: 15px !important;
4054 3882
    text-align: justify;
......
4564 4392
	background:#fff;
4565 4393
}
4566 4394

  
4567
#invsent .pagination {
4568
    margin-top: 10px;
4569
}
4570

  
4571 4395
table.list-machines .wave {
4572 4396
    float: none !important;
4573 4397
    margin: 0 !important;
......
5868 5692
}
5869 5693

  
5870 5694
.password-cont .clip-copy { right: 10px; top: 8px;}
5695

  
5696

  
5697
.overlay-invitations {
5698
    width: 680px;
5699
}
5700
.invitations-form .sending .sending-msg {
5701
    font-size: 0.8em;
5702
    display: block !important;
5703
    margin-bottom: 5px;
5704
    color: #008800;
5705
}
5706

  
5707
.invitations-form .send-error {
5708
    font-size: 0.8em;
5709
    margin-bottom: 5px;
5710
    color: #800;
5711
    padding: 3px;
5712
    display: none;
5713
}
5714

  
5715
.invitations-form .error label {
5716
    color: #000 !important;
5717
}
5718

  
5719
.invitations-form .form-field {
5720
    width: 49%;
5721
    float: left;
5722
}
5723

  
5724
.invitations-view .left.none {
5725
    background-color: #880000;
5726
}
5727

  
5728
.invitations-form {
5729
    position: relative;
5730
}
5731

  
5732
.invitations-form .add-new-invitation {
5733
    position: absolute;
5734
    top:2px;
5735
    right: 20px;
5736
    width: 16px;
5737
    height: 16px;
5738
    background-image: url("../images/option-action-add.png");
5739
    background-repeat: no-repeat;
5740
    background-position: center;
5741
    text-indent: 2000em;
5742
    background-color: #4085A5;
5743
}
5744

  
5745
.invitations-view .left {
5746
    font-size: 1em;
5747
    color: #fff;
5748
    font-weight: bold;
5749
    background-color: #4085A5;
5750
    padding: 4px;
5751
}
5752

  
5753
input.has-errors {
5754
    border-color: #ff0000;
5755
}
5756

  
5757
.none-left .invitations-wrapper .invitations-list {
5758
    width: 100% !important;
5759
    border-left: none !important;
5760
    padding-left: 0;
5761
}
5762

  
5763
.invitations-wrapper .invitations-list {
5764
    width: 33%;
5765
    float: left;
5766
    padding-left: 10px;
5767
    border-left: 1px solid #4085A5;
5768
    margin-left: -1px
5769
}
5770

  
5771
.invitations-wrapper .invitations-form {
5772
    width: 65%;
5773
    float: left;
5774
    border-right: 1px solid #4085A5;
5775
}
5776

  
5777
.invitations-wrapper label span {
5778
    color: #aaa;
5779
    font-size: 0.8em;
5780
}
5781

  
5782
.invitations-wrapper label {
5783
    display: block;
5784
    font-size: 0.9em;
5785
    margin-bottom:10px;
5786
    font-weight: bold;
5787
}
5788

  
5789
.invitations-view .add-new-invitation {
5790
    cursor: pointer;
5791
}
5792

  
5793
.invitations-wrapper input.name {
5794
    width: 180px;
5795
}
5796

  
5797
.invitations-wrapper input {
5798
    font-size: 0.9em;
5799
    width: 160px;
5800
    padding:4px;
5801
}
5802

  
5803
.invitations-list .invitation-sent {
5804
    border-bottom: 1px solid #A1C8DB;
5805
    padding-bottom: 5px;
5806
    margin-bottom: 5px;
5807
}
5808

  
5809
.invitations-view h3 {
5810
    margin-bottom: 10px;
5811
    color: #4085A5;
5812
}
5813

  
5814
.invitations-list .invitation-sent.last,
5815
.invitations-list .invitation-sent:last-child {
5816
    border-bottom: none;
5817
}
5818

  
5819

  
5820
.invitations-list .email {
5821
    color: #D98147;
5822
    font-size: 0.8em;
5823
    font-weight: bold;
5824
    margin-top: 2px;
5825
}
5826

  
5827
.invitations-list .name {
5828
    color: #444;
5829
    font-size: 0.9em;
5830
}
b/ui/static/snf/js/lib/jquery.pagination.js
1
/**
2
 * This jQuery plugin displays pagination links inside the selected elements.
3
 * 
4
 * This plugin needs at least jQuery 1.4.2
5
 *
6
 * @author Gabriel Birke (birke *at* d-scribe *dot* de)
7
 * @version 2.2
8
 * @param {int} maxentries Number of entries to paginate
9
 * @param {Object} opts Several options (see README for documentation)
10
 * @return {Object} jQuery Object
11
 */
12
 (function($){
13
	/**
14
	 * @class Class for calculating pagination values
15
	 */
16
	$.PaginationCalculator = function(maxentries, opts) {
17
		this.maxentries = maxentries;
18
		this.opts = opts;
19
	}
20
	
21
	$.extend($.PaginationCalculator.prototype, {
22
		/**
23
		 * Calculate the maximum number of pages
24
		 * @method
25
		 * @returns {Number}
26
		 */
27
		numPages:function() {
28
			return Math.ceil(this.maxentries/this.opts.items_per_page);
29
		},
30
		/**
31
		 * Calculate start and end point of pagination links depending on 
32
		 * current_page and num_display_entries.
33
		 * @returns {Array}
34
		 */
35
		getInterval:function(current_page)  {
36
			var ne_half = Math.floor(this.opts.num_display_entries/2);
37
			var np = this.numPages();
38
			var upper_limit = np - this.opts.num_display_entries;
39
			var start = current_page > ne_half ? Math.max( Math.min(current_page - ne_half, upper_limit), 0 ) : 0;
40
			var end = current_page > ne_half?Math.min(current_page+ne_half + (this.opts.num_display_entries % 2), np):Math.min(this.opts.num_display_entries, np);
41
			return {start:start, end:end};
42
		}
43
	});
44
	
45
	// Initialize jQuery object container for pagination renderers
46
	$.PaginationRenderers = {}
47
	
48
	/**
49
	 * @class Default renderer for rendering pagination links
50
	 */
51
	$.PaginationRenderers.defaultRenderer = function(maxentries, opts) {
52
		this.maxentries = maxentries;
53
		this.opts = opts;
54
		this.pc = new $.PaginationCalculator(maxentries, opts);
55
	}
56
	$.extend($.PaginationRenderers.defaultRenderer.prototype, {
57
		/**
58
		 * Helper function for generating a single link (or a span tag if it's the current page)
59
		 * @param {Number} page_id The page id for the new item
60
		 * @param {Number} current_page 
61
		 * @param {Object} appendopts Options for the new item: text and classes
62
		 * @returns {jQuery} jQuery object containing the link
63
		 */
64
		createLink:function(page_id, current_page, appendopts){
65
			var lnk, np = this.pc.numPages();
66
			page_id = page_id<0?0:(page_id<np?page_id:np-1); // Normalize page id to sane value
67
			appendopts = $.extend({text:page_id+1, classes:""}, appendopts||{});
68
			if(page_id == current_page){
69
				lnk = $("<span class='current'>" + appendopts.text + "</span>");
70
			}
71
			else
72
			{
73
				lnk = $("<a>" + appendopts.text + "</a>")
74
					.attr('href', this.opts.link_to.replace(/__id__/,page_id));
75
			}
76
			if(appendopts.classes){ lnk.addClass(appendopts.classes); }
77
			lnk.data('page_id', page_id);
78
			return lnk;
79
		},
80
		// Generate a range of numeric links 
81
		appendRange:function(container, current_page, start, end, opts) {
82
			var i;
83
			for(i=start; i<end; i++) {
84
				this.createLink(i, current_page, opts).appendTo(container);
85
			}
86
		},
87
		getLinks:function(current_page, eventHandler) {
88
			var begin, end,
89
				interval = this.pc.getInterval(current_page),
90
				np = this.pc.numPages(),
91
				fragment = $("<div class='pagination'></div>");
92
			
93
			// Generate "Previous"-Link
94
			if(this.opts.prev_text && (current_page > 0 || this.opts.prev_show_always)){
95
				fragment.append(this.createLink(current_page-1, current_page, {text:this.opts.prev_text, classes:"prev"}));
96
			}
97
			// Generate starting points
98
			if (interval.start > 0 && this.opts.num_edge_entries > 0)
99
			{
100
				end = Math.min(this.opts.num_edge_entries, interval.start);
101
				this.appendRange(fragment, current_page, 0, end, {classes:'sp'});
102
				if(this.opts.num_edge_entries < interval.start && this.opts.ellipse_text)
103
				{
104
					jQuery("<span>"+this.opts.ellipse_text+"</span>").appendTo(fragment);
105
				}
106
			}
107
			// Generate interval links
108
			this.appendRange(fragment, current_page, interval.start, interval.end);
109
			// Generate ending points
110
			if (interval.end < np && this.opts.num_edge_entries > 0)
111
			{
112
				if(np-this.opts.num_edge_entries > interval.end && this.opts.ellipse_text)
113
				{
114
					jQuery("<span>"+this.opts.ellipse_text+"</span>").appendTo(fragment);
115
				}
116
				begin = Math.max(np-this.opts.num_edge_entries, interval.end);
117
				this.appendRange(fragment, current_page, begin, np, {classes:'ep'});
118
				
119
			}
120
			// Generate "Next"-Link
121
			if(this.opts.next_text && (current_page < np-1 || this.opts.next_show_always)){
122
				fragment.append(this.createLink(current_page+1, current_page, {text:this.opts.next_text, classes:"next"}));
123
			}
124
			$('a', fragment).click(eventHandler);
125
			return fragment;
126
		}
127
	});
128
	
129
	// Extend jQuery
130
	$.fn.pagination = function(maxentries, opts){
131
		
132
		// Initialize options with default values
133
		opts = jQuery.extend({
134
			items_per_page:10,
135
			num_display_entries:11,
136
			current_page:0,
137
			num_edge_entries:0,
138
			link_to:"#",
139
			prev_text:"Prev",
140
			next_text:"Next",
141
			ellipse_text:"...",
142
			prev_show_always:true,
143
			next_show_always:true,
144
			renderer:"defaultRenderer",
145
			load_first_page:false,
146
			callback:function(){return false;}
147
		},opts||{});
148
		
149
		var containers = this,
150
			renderer, links, current_page;
151
		
152
		/**
153
		 * This is the event handling function for the pagination links. 
154
		 * @param {int} page_id The new page number
155
		 */
156
		function paginationClickHandler(evt){
157
			var links, 
158
				new_current_page = $(evt.target).data('page_id'),
159
				continuePropagation = selectPage(new_current_page);
160
			if (!continuePropagation) {
161
				evt.stopPropagation();
162
			}
163
			return continuePropagation;
164
		}
165
		
166
		/**
167
		 * This is a utility function for the internal event handlers. 
168
		 * It sets the new current page on the pagination container objects, 
169
		 * generates a new HTMl fragment for the pagination links and calls
170
		 * the callback function.
171
		 */
172
		function selectPage(new_current_page) {
173
			// update the link display of a all containers
174
			containers.data('current_page', new_current_page);
175
			links = renderer.getLinks(new_current_page, paginationClickHandler);
176
			containers.empty();
177
			links.appendTo(containers);
178
			// call the callback and propagate the event if it does not return false
179
			var continuePropagation = opts.callback(new_current_page, containers);
180
			return continuePropagation;
181
		}
182
		
183
		// -----------------------------------
184
		// Initialize containers
185
		// -----------------------------------
186
		current_page = opts.current_page;
187
		containers.data('current_page', current_page);
188
		// Create a sane value for maxentries and items_per_page
189
		maxentries = (!maxentries || maxentries < 0)?1:maxentries;
190
		opts.items_per_page = (!opts.items_per_page || opts.items_per_page < 0)?1:opts.items_per_page;
191
		
192
		if(!$.PaginationRenderers[opts.renderer])
193
		{
194
			throw new ReferenceError("Pagination renderer '" + opts.renderer + "' was not found in jQuery.PaginationRenderers object.");
195
		}
196
		renderer = new $.PaginationRenderers[opts.renderer](maxentries, opts);
197
		
198
		// Attach control events to the DOM elements
199
		var pc = new $.PaginationCalculator(maxentries, opts);
200
		var np = pc.numPages();
201
		containers.bind('setPage', {numPages:np}, function(evt, page_id) { 
202
				if(page_id >= 0 && page_id < evt.data.numPages) {
203
					selectPage(page_id); return false;
204
				}
205
		});
206
		containers.bind('prevPage', function(evt){
207
				var current_page = $(this).data('current_page');
208
				if (current_page > 0) {
209
					selectPage(current_page - 1);
210
				}
211
				return false;
212
		});
213
		containers.bind('nextPage', {numPages:np}, function(evt){
214
				var current_page = $(this).data('current_page');
215
				if(current_page < evt.data.numPages - 1) {
216
					selectPage(current_page + 1);
217
				}
218
				return false;
219
		});
220
		
221
		// When all initialisation is done, draw the links
222
		links = renderer.getLinks(current_page, paginationClickHandler);
223
		containers.empty();
224
		links.appendTo(containers);
225
		// call callback function
226
		if(opts.load_first_page) {
227
			opts.callback(current_page, containers);
228
		}
229
	} // End of $.fn.pagination block
230
	
231
})(jQuery);
b/ui/static/snf/js/ui/web/ui_invitations_view.js
1
;(function(root){
2

  
3
    // root
4
    var root = root;
5
    
6
    // setup namepsaces
7
    var snf = root.synnefo = root.synnefo || {};
8
    var api = snf.api = snf.api || {};
9
    var models = snf.models = snf.models || {}
10
    var storage = snf.storage = snf.storage || {};
11
    var ui = snf.ui = snf.ui || {};
12
    var util = snf.util = snf.util || {};
13

  
14
    var views = snf.views = snf.views || {}
15

  
16
    // shortcuts
17
    var bb = root.Backbone;
18

  
19
    views.InvitationsView = views.Overlay.extend({
20
        
21
        view_id: "invitations_view",
22
        content_selector: "#invitations-overlay-content",
23
        css_class: 'overlay-invitations overlay-info',
24
        overlay_id: "invitations-overlay",
25

  
26
        subtitle: "",
27
        title: "Invitations",
28

  
29
        initialize: function(options) {
30
            views.InvitationsView.__super__.initialize.apply(this, arguments);
31

  
32
            _.bindAll(this);
33

  
34
            this.entry_tpl = this.$(".form-entry-tpl");
35
            this.form_entries = this.$(".form-entries");
36
            this.add = this.$(".add-new-invitation");
37
            this.remove = this.$(".remove-invitation");
38
            this.send = this.$(".send-invitations");
39
            this.top_info = this.$(".top-info");
40
            this.sent = this.$(".invitations-sent-cont");
41
            this.sent_pages = this.$(".invitations-sent-pages");
42
            this.sent_tpl = this.$(".invitation-sent-tpl");
43
            this.entry_tpl.hide();
44

  
45
            this.inv_sent_per_page = 9;
46

  
47
            this.init_handlers();
48
        },
49

  
50
        init_handlers: function() {
51
            var self = this;
52
            this.add.click(this.add_new_entry);
53
            this.send.click(this.send_entries);
54
            this.remove.live('click', function() {
55
                return self.remove_entry($(this).parent().parent());
56
            });
57
        },
58
        
59
        remove_entry: function(entry) {
60
            if (entry.hasClass("sending")) { return };
61
            entry.remove();
62
            this.fix_entries();
63
        },
64

  
65
        add_new_entry: function() {
66
            var new_entry = this.create_form_entry().show()
67
            this.form_entries.append(new_entry).show();
68
            $(new_entry.find("input").get(0)).focus();
69
            this.fix_entries();
70
        },
71
        
72
        show_entry_error: function(entry, error) {
73
            entry.find(".send-error").text(error)
74
            entry.find(".send-error").show();
75
            entry.addClass("error");
76
            entry.find("input").attr("disabled", false);
77
        },
78
        
79
        get_entry_data: function(entry) {
80
            var data = {name: entry.find("input.name").val(), email:entry.find("input.email").val()};
81
            return data;
82
        },
83

  
84
        entry_is_valid: function(entry) {
85
            var data = this.get_entry_data(entry);
86

  
87
            entry.find(".send-error").hide();
88
            entry.removeClass("error");
89
            entry.find("input").removeClass("has-errors");
90

  
91
            error = false;
92
            if (!data.name || data.name.split(" ").length == 1) {
93
                error = "Invalid name";
94
                entry.find("input.name").addClass("has-errors");
95
            }
96

  
97
            var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
98
            if (!data.email || reg.test(data.email) == false) {
99
                error = "Invalid email";
100
                entry.find("input.email").addClass("has-errors");
101
            }
102
            
103
            if (error) { this.show_entry_error(entry, error) };
104
            return error
105
        },
106
        
107
        send_entries: function() {
108
            var self = this;
109
            this.form_entries.find(".form-entry").each(function(index, el) { self.entry_is_valid($(el)) });
110
            var entries_to_send = this.form_entries.find(".form-entry:not(.error):not(.sending)");
111
            this._send_entries(entries_to_send);
112
        },
113

  
114
        _send_entries: function(entries) {
115
            $(entries).addClass("sending").find("input").attr("disabled", true);
116
            var self = this;
117
            _.each(entries, function(e) {
118
                var e = $(e);
119
                var data = self.get_entry_data(e);
120
                self.send_invitation(data.name, 
121
                                     data.email,
122
                                     _.bind(self.invitation_send, this, e), 
123
                                     _.bind(self.invitation_failed, this, e));
124
            });
125
        },
126

  
127
        invitation_send: function(entry, data) {
128
            entry.removeClass("sending");
129
            if (data.errors && data.errors.length) {
130
                this.show_entry_error($(entry), data.errors[0]);
131
                return;
132
            } else {
133
                entry.remove();
134
                this.show_send_success(entry.find("input.name").val(), data);
135
            }
136
        },
137

  
138
        show_send_success: function(to, data) {
139
            var msg = "Invitation to " + to + " was sent.";
140
            var msg_el = $('<div class="msg">{0}</div>'.format(msg));
141

  
142
            this.top_info.append(msg_el);
143

  
144
            window.setTimeout(function(){
145
                msg_el.fadeOut(600, function(){$(this).remove()});
146
            }, 2000);
147

  
148
            this.fix_entries();
149
            this.reset_invitations_sent();
150
        },
151

  
152
        invitation_failed: function(entry) {
153
            entry.removeClass("sending");
154
            this.show_entry_error(entry, "Cannot send email, please try again later");
155
        },
156

  
157
        send_invitation: function(name, email, success, fail) {
158
            var url = snf.config.invitations_url;
159
            var payload = {name_1: name, email_1: email, csrftoken: $.cookie('csrftoken')};
160
            params = {
161
                success: success,
162
                error: fail,
163
                url: url,
164
                data: $.param(payload),
165
                skip_api_error: true
166
            }
167
            snf.api.sync("create", undefined, params);
168
        },
169

  
170
        get_entries: function() {
171
            return this.form_entries.find(".form-entry");
172
        },
173

  
174
        fix_entries: function() {
175
            this.$(".remove-invitation").hide();
176
            if (this.get_entries().length == 0) {
177
                this.add_new_entry();
178
            }
179

  
180
            if (this.get_entries().length > 1) {
181
                this.$(".remove-invitation").show();
182
            }
183
            this.$(".form-entry:first-child label").show();
184
            this.$(".form-entry:not(:first-child) label").hide();
185
        },
186

  
187
        show: function() {
188
            views.InvitationsView.__super__.show.apply(this, arguments);
189
            this.current_page = 0;
190
            this.reset_invitations_sent();
191
            this.reset();
192

  
193
            this.add_new_entry();
194
            this.add_new_entry();
195
            this.add_new_entry();
196
        },
197

  
198
        create_form_entry: function() {
199
            return this.entry_tpl.clone().removeClass("form-entry-tpl").addClass("form-entry").removeClass("hidden");
200
        },
201
            
202
        reset: function() {
203
            this.get_entries().remove();
204
            this.add_new_entry();
205
        },
206
        
207
        new_invitation_sent_el: function() {
208
            return this.sent_tpl.clone().removeClass("invitation-sent-tpl").addClass("invitation-sent");
209
        },
210
        
211
        show_invitations_sent_error: function() {
212
            this.sent.hide();
213
            this.$(".invitations-sent-error").show();
214
        },
215

  
216
        reset_invitations_sent: function() {
217
            var self = this;
218
            var url = snf.config.invitations_url;
219
            params = {
220
                success: function(data) {
221
                    if (!data || !data.invitations) {
222
                        self.show_invitations_sent_error();
223
                    } else {
224
                        self.sent.empty();
225
                        self.add_invitations_sent(data.invitations);
226
                    }
227
                    
228
                    //data.invitations_left = 0;
229
                    self.$(".description .left").text(data.invitations_left);
230
                    if(data.invitations_left > 0) {
231
                        self.$(".invitations-form").show();
232
                        self.$(".description .left").removeClass("none");
233
                        self.el.removeClass("none-left");
234
                    } else {
235
                        self.$(".invitations-form").hide();
236
                        self.$(".description .left").addClass("none");
237
                        self.el.addClass("none-left");
238
                    }
239
                },
240
                error: _.bind(this.show_invitations_sent_error, this),
241
                url: url,
242
                skip_api_error: true
243
            }
244

  
245
            snf.api.sync("read", undefined, params);
246
        },
247

  
248
        add_invitations_sent: function(invs) {
249
            _.each(invs, _.bind(function(inv) {
250
                var el = this.new_invitation_sent_el();
251
                el.find(".name").text(inv.targetname);
252
                el.find(".email").text(inv.target);
253
                el.find(".action").addClass("sent");
254
                if (!inv.accepted) {
255
                    el.find(".action").removeClass("resend").addClass("resend")
256
                }
257
                el.removeClass("hidden");
258
                this.sent.append(el);
259
            }, this));
260
            this.update_pagination();
261
            this.sent_pages.trigger("setPage", this.current_page || 0);
262
        },
263
        
264
        inv_sent_per_page: 5,
265
        update_pagination: function() {
266
            this.sent.css({minHeight:this.inv_sent_per_page * 35 + "px"})
267
            this.sent_pages.pagination(this.sent.children().length, {items_per_page:this.inv_sent_per_page, callback: this.page_cb});
268
        },
269

  
270
        page_cb: function(index, pager) {
271
            this.current_page = index;
272
            var start = index * this.inv_sent_per_page;
273
            var end = start + this.inv_sent_per_page -1;
274
            var items = this.sent.children();
275
            items.hide().removeClass("last");
276
            for (var i = start; i<=end; i++) {
277
                $(items.get(i)).show();
278
            }
279
            $(items.get(end)).addClass("last");
280
            return false;
281
        }
282
        
283
    });
284
})(this);
285

  
b/ui/static/snf/js/ui/web/ui_main_view.js
546 546
        load: function() {
547 547
            this.error_view = new views.ErrorView();
548 548
            this.feedback_view = new views.FeedbackView();
549
            this.invitations_view = new views.InvitationsView();
549 550
            var self = this;
550 551
            // initialize overlay views
551 552
            
......
584 585
        },
585 586

  
586 587
        init_menu: function() {
588
            $(".usermenu .invitations").click(_.bind(function(){
589
                this.invitations_view.show();
590
            }, this));
587 591
            $(".usermenu .feedback").click(_.bind(function(){
588 592
                this.feedback_view.show();
589 593
            }, this));
b/ui/templates/home.html
27 27
    <script src="{{ SYNNEFO_JS_LIB_URL}}jquery.client.js"></script>
28 28
    <script src="{{ SYNNEFO_JS_LIB_URL}}jquery.tools.min.js"></script>
29 29
    <script src="{{ SYNNEFO_JS_LIB_URL}}jquery.dataTables.min.js"></script>
30
    <script src="{{ SYNNEFO_JS_LIB_URL}}jquery.pagination.js"></script>
30 31
    <script src="{{ SYNNEFO_JS_LIB_URL}}ZeroClipboard.js"></script>
31 32

  
32 33

  
......
57 58
    <script src="{{ SYNNEFO_JS_WEB_URL}}ui_networks_view.js"></script>
58 59
    <script src="{{ SYNNEFO_JS_WEB_URL}}ui_metadata_view.js"></script>
59 60
    <script src="{{ SYNNEFO_JS_WEB_URL}}ui_feedback_view.js"></script>
61
    <script src="{{ SYNNEFO_JS_WEB_URL}}ui_invitations_view.js"></script>
60 62
    <script src="{{ SYNNEFO_JS_WEB_URL}}ui_create_view.js"></script>
61 63
    <script src="{{ SYNNEFO_JS_WEB_URL}}ui_connect_view.js"></script>
62 64
    <script src="{{ SYNNEFO_JS_WEB_URL}}ui_main_view.js"></script>
......
293 295
                synnefo.ui.logout();
294 296
            });
295 297

  
296
            $(".usermenu .invitations").click(show_invitations);
297 298
            $(".usermenu .api").click(function(){
298 299
                synnefo.ui.main.api_info_view.show();
299 300
            });
......
401 402
            <span class="reload-app">{% trans "Reload" %}</span>
402 403
        </div>
403 404
    </div>
404

  
405
    {% include "partials/invitations.html" %}
405 406
    <div id="feedback-overlay-content" class="hidden overlay-content feedback-form">
406 407
        <div class="description">
407 408
            <p>
......
528 529
            synnefo.config.handle_window_exceptions = {{ handle_window_exceptions }};
529 530
            synnefo.config.ajax_timeout = {{ timeout }};
530 531
            synnefo.config.skip_timeouts = {{ skip_timeouts }};
532
            synnefo.config.invitations_url = "{% url invitations %}";
531 533
            synnefo.VERSION = "{{ synnefo_version }}";
532 534

  
533 535
            // TODO: make it dynamic
......
541 543

  
542 544
            synnefo.ui.init();
543 545

  
544
            synnefo.ui.main.bind("initial", function(){})
546
            synnefo.ui.main.bind("initial", function(){
547
                $(".usermenu .invitations").click();
548
            })
545 549

  
546 550
        })
547 551
    </script>
b/ui/templates/partials/invitations.html
1
{% load i18n %}
2
<div id="invitations-overlay-content" class="hidden overlay-content invitations-view">
3
    <div class="description">
4
        <p>You have <span class="left"></span> invitations left</p>
5
    </div>
6
    <div class="invitations-wrapper clearfix">
7
        <div class="invitations-form clearfix">
8
            <h3>{% trans "Send new intivations" %}</h3>
9
            <div class="add-new-invitation">add</div>
10
            <div class="top-info success"></div>
11
            <div class="form-entry-tpl clearfix hidden">
12
                <div class="fields clearfix">
13
                    <div class="form-field">
14
                        <label>Name <span class="desc">e.g. John Smith</span></label>
15
                        <input type="text" class="name"/>
16
                    </div>
17
                    <div class="form-field">
18
                        <label>Email</label>
19
                        <input type="text" class="email"/>
20
                    </div>
21
                    <div class="remove-invitation">remove</div>
22
                </div>
23
                <div class="send-error"></div>
24
                <div class="sending-msg hidden">{% trans "Sending..." %}</div>
25
            </div>
26
            <div class="form-entries">
27
            </div>
28
            <div class="send-invitations">send invitations</div>
29
        </div>
30
        <div class="invitations-list clearfix">
31
            <h3>{% trans "Intivations sent" %}</h3>
32
            <div class="invitation-sent-tpl hidden">
33
                <div class="action resend"></div>
34
                <div class="name"></div>
35
                <div class="email"></div>
36
            </div>
37
            <div class="invitations-sent-cont">
38
            </div>
39
            <div class="invitations-sent-pages"></div>
40
            <div class="hidden invitations-sent-error">{% trans "Cannot display invitations history" %}</div>
41
        </div>
42
    </div>
43
</div>
44

  

Also available in: Unified diff