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