Commit 7f664cd8 by Piotr Mitros

Merge pull request #1 from pmitros/edx-platform

finished-major-functionality-starting-UX-polish
parents 424efa13 a89c4d30
......@@ -58,12 +58,16 @@ class RecommenderXBlock(XBlock):
# Scope-wide. List of JSON objects corresponding to recommendations combine XML and user.
default_recommendations = List(help="List of help resources", default=[], scope=Scope.content)
# Scope-wide. List of JSON objects corresponding to recommendations as defined in XML.
recommendations = List(help="List of help resources", default=[], scope=Scope.content)
recommendations = List(help="List of help resources", default=[], scope=Scope.user_state_summary)
# Upvotes for this particular user
upvotes = List(help="List of items user gave upvote to", default=[], scope=Scope.user_state)
# Downvotes for this particular user
downvotes = List(help="List of items user gave downvote to", default=[], scope=Scope.user_state)
# Flag resource id (for problematic resource) for this particular user
flagId = List(help="List of items user flagged to", default=[], scope=Scope.user_state)
# Flag resource reason (for problematic resource) for this particular user
flagReason = List(help="List of reasons of items user flagged to", default=[], scope=Scope.user_state)
template_lookup = None
def resource_string(self, path):
......@@ -81,62 +85,57 @@ class RecommenderXBlock(XBlock):
resourceId = recom['id']
return resourceId + 1
def getRecommendationIndex(self, recomId):
recoms = self.recommendations
if not recoms:
recoms = self.default_recommendations
for idx in range(0, len(recoms)):
if recoms[idx]['id'] == recomId:
def getEntryIndex(self, entryId, entryList):
for idx in range(0, len(entryList)):
if entryList[idx]['id'] == entryId:
return idx
return -1
@XBlock.json_handler
def handle_upvote(self, data, suffix=''):
''' untested '''
resource = data['resource']
idx = self.getRecommendationIndex(resource)
if resource in self.upvotes:
print "Upvote failed!"
return {"Success": False}
if resource in self.downvotes:
del self.downvotes[self.downvotes.index(resource)]
self.recommendations[idx]['downvotes'] -= 1
else:
self.upvotes.append(resource)
self.recommendations[idx]['upvotes'] += 1
print "Upvote clicked!"
''' untested '''
resource = data['resource']
idx = self.getEntryIndex(resource, self.recommendations)
if resource in self.upvotes:
del self.upvotes[self.upvotes.index(resource)]
self.recommendations[idx]['upvotes'] -= 1
print "Upvote unclicked!"
return {"Success": True}
if resource in self.downvotes:
del self.downvotes[self.downvotes.index(resource)]
self.recommendations[idx]['downvotes'] -= 1
self.upvotes.append(resource)
self.recommendations[idx]['upvotes'] += 1
print "Upvote clicked!"
return {"Success": True}
@XBlock.json_handler
def handle_downvote(self, data, suffix=''):
''' untested '''
resource = data['resource']
idx = self.getRecommendationIndex(resource)
if resource in self.downvotes:
print "Downvote failed!"
return {"Success": False}
if resource in self.upvotes:
del self.upvotes[self.upvotes.index(resource)]
self.recommendations[idx]['upvotes'] -= 1
else:
self.downvotes.append(resource)
self.recommendations[idx]['downvotes'] += 1
print "Downvote clicked!"
''' untested '''
resource = data['resource']
idx = self.getEntryIndex(resource, self.recommendations)
if resource in self.downvotes:
del self.downvotes[self.downvotes.index(resource)]
self.recommendations[idx]['downvotes'] -= 1
print "Downvote unclicked!"
return {"Success": True}
if resource in self.upvotes:
del self.upvotes[self.upvotes.index(resource)]
self.recommendations[idx]['upvotes'] -= 1
self.downvotes.append(resource)
self.recommendations[idx]['downvotes'] += 1
print "Downvote clicked!"
return {"Success": True}
@XBlock.json_handler
def add_resource(self, data, suffix=''):
''' untested '''
resource = data['resource']
# check url for redundancy
recoms = self.recommendations
#if not recoms:
# recoms = self.default_recommendations
for recom in recoms:
for recom in self.recommendations:
if recom['url'] == data['resource']['url']:
print "add redundant resource"
return {"Success": False}
......@@ -149,7 +148,8 @@ class RecommenderXBlock(XBlock):
new_resource['upvotes'] = 0
new_resource['downvotes'] = 0
new_resource['id'] = self.getResourceNewId()
new_resource['isMisuse'] = "notMisuse"
new_resource['isProblematic'] = "notProblematic"
new_resource['problematicReason'] = ""
# self.resources.append(new_resource)
self.recommendations.append(new_resource)
return {"Success": True, "id": new_resource['id']}
......@@ -157,15 +157,16 @@ class RecommenderXBlock(XBlock):
@XBlock.json_handler
def edit_resource(self, data, suffix=''):
resource = data['resource']
idx = self.getRecommendationIndex(resource)
idx = self.getEntryIndex(resource, self.recommendations)
if idx not in range(0, len(self.recommendations)):
print "Edit failed!"
return {"Success": False}
# check url for redundancy
recoms = self.recommendations
for recom in recoms:
for recom in self.recommendations:
if self.recommendations[idx]['url'] == data['url']:
continue
if recom['url'] == data['url']:
print "edit to esisting resource"
print "provided url is existing"
return {"Success": False}
for key in data:
......@@ -178,13 +179,17 @@ class RecommenderXBlock(XBlock):
@XBlock.json_handler
def flag_resource(self, data, suffix=''):
resource = data['resource']
idx = self.getRecommendationIndex(resource)
if idx not in range(0, len(self.recommendations)):
print "Flag failed!"
return {"Success": False}
self.recommendations[idx]['isMisuse'] = data['isMisuse']
if data['isProblematic'] == True:
if data['resource'] in self.flagId:
self.flagReason[self.flagId.index(data['resource'])] = data['reason']
else:
self.flagId.append(data['resource'])
self.flagReason.append(data['reason'])
else:
if data['resource'] in self.flagId:
idx = self.flagId.index(data['resource'])
del self.flagId[idx]
del self.flagReason[idx]
return {"Success": True}
# TO-DO: change this view to display your data your own way.
......@@ -196,19 +201,20 @@ class RecommenderXBlock(XBlock):
print "entered"
if not self.recommendations:
self.recommendations = self.default_recommendations
if not self.recommendations:
self.recommendations = []
if not self.template_lookup:
self.template_lookup = TemplateLookup()
self.template_lookup.put_string("recommender.html", self.resource_string("static/html/recommender.html"))
self.template_lookup.put_string("resourcebox.html", self.resource_string("static/html/resourcebox.html"))
# TODO: Sort by votes
# Ideally, we'd estimate score based on votes, such that items with 1 vote have a sensible ranking (rather than a perfect rating)
#
resources = [{'id' : r['id'], 'title' : r['title'], "votes" : r['upvotes'] - r['downvotes'], 'url' : r['url'], 'description' : r['description'], 'isMisuse': r['isMisuse']} for r in self.recommendations]
resources = [{'id' : r['id'], 'title' : r['title'], "votes" : r['upvotes'] - r['downvotes'], 'url' : r['url'], 'description' : r['description']} for r in self.recommendations]
resources = sorted(resources, key = lambda r: r['votes'], reverse=True)
frag = Fragment(self.template_lookup.get_template("recommender.html").render(resources = resources))
frag = Fragment(self.template_lookup.get_template("recommender.html").render(resources = resources, upvotes = self.upvotes, downvotes = self.downvotes, flagId = self.flagId, flagReason = self.flagReason))
frag.add_css_url("//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/themes/smoothness/jquery-ui.css")
frag.add_javascript_url("//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js")
frag.add_css(self.resource_string("static/css/recommender.css"))
......@@ -226,15 +232,15 @@ class RecommenderXBlock(XBlock):
"""<vertical_demo>
<html_demo><img class="question" src="http://people.csail.mit.edu/swli/edx/recommendation/img/pset.png"></img></html_demo>
<recommender>
{"id": 1, "title": "Covalent bonding and periodic trends", "upvotes" : 15, "downvotes" : 5, "url" : "https://courses.edx.org/courses/MITx/3.091X/2013_Fall/courseware/SP13_Week_4/SP13_Periodic_Trends_and_Bonding/", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/videopage1.png", "isMisuse": "notMisuse"}
{"id": 2, "title": "Polar covalent bonds and electronegativity", "upvotes" : 10, "downvotes" : 7, "url" : "https://courses.edx.org/courses/MITx/3.091X/2013_Fall/courseware/SP13_Week_4/SP13_Covalent_Bonding/", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/videopage2.png", "isMisuse": "notMisuse"}
{"id": 3, "title": "Longest wavelength able to to break a C-C bond ...", "upvotes" : 10, "downvotes" : 7, "url" : "https://answers.yahoo.com/question/index?qid=20081112142253AA1kQN1", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/dispage1.png", "isMisuse": "notMisuse"}
{"id": 4, "title": "Calculate the maximum wavelength of light for ...", "upvotes" : 10, "downvotes" : 7, "url" : "https://answers.yahoo.com/question/index?qid=20100110115715AA6toHw", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/dispage2.png", "isMisuse": "notMisuse"}
{"id": 1, "title": "Covalent bonding and periodic trends", "upvotes" : 15, "downvotes" : 5, "url" : "https://courses.edx.org/courses/MITx/3.091X/2013_Fall/courseware/SP13_Week_4/SP13_Periodic_Trends_and_Bonding/", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/videopage1.png"}
{"id": 2, "title": "Polar covalent bonds and electronegativity", "upvotes" : 10, "downvotes" : 7, "url" : "https://courses.edx.org/courses/MITx/3.091X/2013_Fall/courseware/SP13_Week_4/SP13_Covalent_Bonding/", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/videopage2.png"}
{"id": 3, "title": "Longest wavelength able to to break a C-C bond ...", "upvotes" : 1230, "downvotes" : 7, "url" : "https://answers.yahoo.com/question/index?qid=20081112142253AA1kQN1", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/dispage1.png"}
{"id": 4, "title": "Calculate the maximum wavelength of light for ...", "upvotes" : 10, "downvotes" : 3457, "url" : "https://answers.yahoo.com/question/index?qid=20100110115715AA6toHw", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/dispage2.png"}
</recommender>
</vertical_demo>
"""),
# {"id": 5, "title": "Covalent bond - wave mechanical concep", "upvotes" : 10, "downvotes" : 7, "url" : "", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/textbookpage1.png", "isMisuse": "notMisuse"}
# {"id": 6, "title": "Covalent bond - Energetics of covalent bond", "upvotes" : 10, "downvotes" : 7, "url" : "", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/textbookpage2.png", "isMisuse": "notMisuse"}
# {"id": 5, "title": "Covalent bond - wave mechanical concep", "upvotes" : 10, "downvotes" : 7, "url" : "", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/textbookpage1.png", "isProblematic": "notProblematic"}
# {"id": 6, "title": "Covalent bond - Energetics of covalent bond", "upvotes" : 10, "downvotes" : 7, "url" : "", "description" : "http://people.csail.mit.edu/swli/edx/recommendation/img/textbookpage2.png", "isProblematic": "notProblematic"}
# </recommender>
# </vertical_demo>
# """),
......
......@@ -7,26 +7,55 @@
.recommender_content {
width: 700px;
/* width : 100%;*/
float: left;
}
.recommender_row { overflow:hidden; }
.recommender_row_top {
/* overflow:scroll;
max-height:200px;*/
font-size: 20px;
background-color:rgb(204, 198, 198);
}
.recommender_row {
height: 380px;
}
.question {
width:700px;
}
.recommender_resource {
/* border : 1;*/
padding-top : 0.25em;
padding-bottom : 0.25em;
/* margin : 1;*/
/* display:table-cell;*/
float:left;
/* width:340px;*/
width : 100%;
/* border-color:gray;
border-width:1px;
border-style:solid;
border-radius:5px;*/
}
.recommender_panel {
float: right;
width: 50px;
}
.resource_list_less, .resource_list_more {
border : 1;
padding : 1;
margin : 1;
/* display:table-cell;*/
float:left;
width:340px;
width : 95%;
border-color:gray;
border-width:1px;
border-style:solid;
border-radius:5px;
}
.recommender_header {
......@@ -39,107 +68,213 @@
}
.recommender_modify {
width: 500px;
width: 700px;
float: left;
}
.descriptionImg {
height: 400px;
overflow-x: scroll;
margin-top: 2em;
float: left;
}
.recommender_vote_arrow_down {
width: 100%;
margin-left:auto;
margin-right:auto;
text-align:center;
}
.recommender_vote_arrow_up {
width: 100%;
margin-left:auto;
margin-right:auto;
text-align:center;
}
.recommender_recommendations {
display:table;
.recommender_vote_score {
width: 100%;
text-align: center;
cursor: default;
}
.recommender_recommendations { display: table; }
.pagination {
display: table;
margin: 0 auto;
}
.recommender_blurb {
display:inline-block;
vertical-align: middle;
width:225px;
width:525px;
overflow:hidden;
text-overflow:ellipsis;
}
.recommender_entryId, .recommender_url, .recommender_descriptionSlot {
.recommender_entryId, .recommender_descriptionSlot, .recommender_problematicReason {
display:none;
}
.recommender_vote_box {
display:inline-block;
vertical-align: middle;
width:45px;
width:50px;
}
.recommender_vote_arrow_up {
margin-left:auto;
margin-right:auto;
text-align:center;
.recommender_edit{
display:inline-block;
vertical-align: top;
float: right;
width: 40px;
}
.recommender_vote_arrow_down {
margin-left:auto;
margin-right:auto;
text-align:center;
.redTxt {
color: red;
}
.recommender_vote_score {
width:100%;
.recommender_vote_arrow_down, .recommender_vote_arrow_up, .recommender_title, .resource_edit_button, .flagResource, .resource_add_button, .hide-show, .paginationCell, .backToViewButton { cursor: pointer; }
.resource_add_button { float: left; font-weight: bold; }
.resource_edit_button { float: left; }
/*.hide-show {
float: left;
font-size: small;
}*/
.addSourceBlockTitle, .editSourceBlockTitle, .flagSourceBlockTitle {
margin-bottom: 1em;
}
.recommender_edit{
display:inline-block;
vertical-align: top;
.editSourceBlock, .recommender_add, .flagSourceBlock {
padding-left: 1em;
}
.misuse, .redTxt {
color: red;
.paginationCell {
display:table-cell;
text-align:center;
vertical-align: middle;
width:25px;
}
.addSourceBlockTitle, .editSourceBlockTitle {
margin-top: 1em;
margin-bottom: 1em;
.backToViewButton { color: #1d9dd9; float: left; }
.recommender_modify_title { position: relative; left: 50px; overflow: auto; }
.ui-icon.problematic {
background-image: url(http://download.jqueryui.com/themeroller/images/ui-icons_ff0000_256x240.png);
}
.resource_hovered { background-color:#F2F7FA }
.recommender_modify_title_container { background: rgb(204, 198, 198); font-size: 20px; }
.nonevoting { color: rgb(204, 198, 198); }
.hide-show-icon { margin-left: 0.5em; }
.recommender_vote_score.upvoting, .recommender_vote_arrow_up.upvoting { color: rgb(69, 194, 10); }
.recommender_vote_score.downvoting, .recommender_vote_arrow_down.downvoting { color: red; }
.recommender_vote_arrow_up.downvoting, .recommender_vote_arrow_down.upvoting { color: rgb(204, 198, 198); }
/*.tooltip {
display:none;
background-color:#ffa;
border:1px solid #cc9;
padding:3px;
font-size:13px;
-moz-box-shadow: 2px 2px 11px #666;
-webkit-box-shadow: 2px 2px 11px #666;
} */
/*
[title]{
position:relative;
}
[title]:after{
content:attr(title);
color:#fff;
background:#333;
background:rgba(51,51,51,0.75);
padding:5px;
position:absolute;
left:-9999px;
opacity:0;
bottom:100%;
white-space:nowrap;
-webkit-transition:0.25s linear opacity;
}
[title]:hover:after{
left:5px;
opacity:1;
}*/
.downArrowIcon:before { content: '▼'; }
.upArrowIcon:before { content: '▲'; }
.plusIcon:before { content: '+'; }
.rightArrowIcon:before { content: '►'; }
.leftArrowIcon:before { content: '◄'; }
.moreIcon:before { content: '...'; }
.lightgreyBg { background-color: lightgrey; }
.resource_hovered { background: lightsteelblue; }
.recommender_modify { margin-left: 2em; }
a:link { color: black; }
form { margin: 0em; }
</style>
<div class="recommender_block">
<div class="recommender_header">
<div class="recommender_title">Helpful resources</div>
</div>
<div class="recommender_recommendations">
<div class="recommender_content">
<div class="recommender_row">
% for elem in resources:
<%include file="resourcebox.html" args="id=elem['id'],title=elem['title'],votes=elem['votes'],url=elem['url'],description=elem['description'],isMisuse=elem['isMisuse']" />
% endfor
</div>
<div class="recommender_description">
<div class="descriptionImg"></div>
<div class="recommender_content">
<div class="recommender_row_top hide-show resource_list_expanded">
Related resources<div class='recommender_panel'><span class="resource_add_button plusIcon"></span> <span class='hide-show-icon upArrowIcon'></span> </div>
</div>
<div class='recommender_row_inner'>
<div class="recommender_row">
% for elem in resources:
<%
if elem['id'] in downvotes:
voteMode = "downvoting"
elif elem['id'] in upvotes:
voteMode = 'upvoting'
else:
voteMode = 'nonevoting'
if elem['id'] in flagId:
flagMode = 'problematic'
reason = flagReason[flagId.index(elem['id'])]
else:
flagMode = ''
reason = ''
%>
<%include file="resourcebox.html" args="id=elem['id'],title=elem['title'],votes=elem['votes'],url=elem['url'],description=elem['description'],isProblematic=flagMode,problematicReason=reason,voteMode=voteMode" />
% endfor
</div>
<div class='pagination'> </div>
<div class="recommender_description">
<div class="descriptionImg"></div>
</div>
</div>
</div>
</div>
<div class='recommender_modify'>
<div class='recommender_modify_title_container'><div class='backToViewButton'>&lt; Related resources</div><div class='recommender_modify_title'></div></div>
<div class="flagSourceBlock">
<div class="flagSourceBlockTitle">Give or edit the reason why you flagged this resource, or unflag it.</div>
Reason: <input type="text" class="flag_reason" style="height: 25px; position: relative; left: 10px;"><br>
<input type="button" value="Save the reason" class="flag_reason_submit" style="margin-top: 0.5em">
<input type="button" value="Unflag the resource" class="unflag_button" style="margin-top: 0.5em">
</div>
<div class="editSourceBlock">
</div>
<div class="recommender_add">
<div class="addSourceBlockTitle">Add new resource</div>
Title: <input type="text" class="in_title"><br>
Url: <input type="text" class="in_url"><br>
<div class="addSourceBlockTitle">Recommend a new helpful resource for this problem with a short description, hyperlink, and previewing screenshot to the new resource.</div>
Description: <input type="text" class="in_title" style="height: 25px; position: relative; left: 10px;"><br>
HyperLink: <input type="text" class="in_url" style="height: 25px; position: relative; left: 22px;"><br>
<form id="addResourceForm" action="http://danielswli.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
<input type="hidden" name="acl" value="public-read">
<input type="hidden" name="Content-Type" value="image/jpeg">
<input type="hidden" name="AWSAccessKeyId" value="AKIAIRDHSV6YZJZ4RFGA">
<input type="hidden" name="key" value="">
</form>
<!-- Resource description: <input type="text" class="in_description"><br>-->
</div>
</div>
</div>
<!--
<div class="recommender_description">
<div class="descriptionImg"></div>
</div>-->
</div>
<%page args="title,votes,url,description,id,isMisuse"/>
<div class="recommender_resource">
<%page args="title,votes,url,description,id,isProblematic,problematicReason,voteMode"/>
<div class="recommender_resource">
<div class="recommender_vote_box">
<div class="recommender_vote_arrow_up" role="button" aria-label="upvote" tabindex="0"><span class="ui-icon ui-icon-triangle-1-n"></span></div>
<!--div class="recommender_vote_score_dislikes">2927</div-->
<div class="recommender_vote_score">${votes}</div>
<!--div class="recommender_vote_score_likes">2927</div-->
<div class="recommender_vote_arrow_down" role="button" aria-label="downvote" tabindex="0"><span class="ui-icon ui-icon-triangle-1-s"></span></div>
<div class="recommender_vote_arrow_up ${voteMode}" role="button" aria-label="upvote" tabindex="0"><b></b></div>
<div class="recommender_vote_score ${voteMode}"><b>${votes}</b></div>
<div class="recommender_vote_arrow_down ${voteMode}" role="button" aria-label="downvote" tabindex="0"><b></b></div>
</div>
<div class="recommender_blurb"><div class="recommender_title">${title}</div><div class="recommender_url">${url}</div><div class="recommender_descriptionSlot">${description}</div><div class="recommender_entryId">${id}</div></div>
<div class="recommender_edit"><input type="button" value="Edit" class="editResource"><br><input type="button" value="Misuse" class="flagResource ${isMisuse}"></div>
</div>
<div class="recommender_blurb">
<div class="recommender_title"><a href="${url}" target="_blank">${title}</a></div>
<div class="recommender_descriptionSlot">${description}</div>
<div class="recommender_entryId">${id}</div>
<div class="recommender_problematicReason">${problematicReason}</div>
</div>
<div class="recommender_edit"> <span class="ui-icon ui-icon-pencil resource_edit_button"></span> <span class="ui-icon ui-icon-flag flagResource ${isProblematic}"></span></div>
</div>
......@@ -5,59 +5,161 @@ function RecommenderXBlock(runtime, element) {
var editResourceUrl = runtime.handlerUrl(element, 'edit_resource');
var flagResourceUrl = runtime.handlerUrl(element, 'flag_resource');
var baseUrl = 'http://s3-us-west-2.amazonaws.com/danielswli/'
/* POLICY_JSON = { "expiration": "2020-12-01T12:00:00.000Z",
"conditions": [
{"bucket": 'danielswli'},
["starts-with", "$key", "uploads/"],
{"acl": "public-read"},
["starts-with", "$Content-Type", ""],
["content-length-range", 0, 524288000]
]
};
var secret = 'cqAakBE0RVpl/Z5aFX8IffAhXDoIvFVSbKxvddK2';
var secret = this.get('AWSSecretKeyId');
var policyBase64 = Base64.encode(JSON.stringify(POLICY_JSON));
var signature = b64_hmac_sha1(secret, policyBase64);
b64_hmac_sha1(secret, policyBase64);*/
var baseUrl = 'http://s3-us-west-2.amazonaws.com/danielswli/';
var currentPage = 1;
var entriesPerPage = 5;
var pageSpan = 2;
/* resource list collapse or expansion */
$(".hide-show").click(function () {
if ($(this).hasClass('resource_list_expanded')) {
//$(this).find('.hide-show-icon').removeClass('upArrowIcon').addClass('downArrowIcon');
$(".recommender_row_inner", element).slideUp('fast');
$('.resource_add_button').css('visibility', 'hidden');
$(this).css('cursor', 's-resize');
}
else {
//$(this).find('.hide-show-icon').removeClass('downArrowIcon').addClass('upArrowIcon');
$(".recommender_row_inner", element).slideDown('fast');
$('.resource_add_button').css('visibility', 'visible');
$(this).css('cursor', 'n-resize');
}
$(this).find('.hide-show-icon').toggleClass('upArrowIcon').toggleClass('downArrowIcon');
$(this).toggleClass('resource_list_expanded');
addTooltip();
});
/* show content/icon for different page */
function pagination() {
/* show resource for each page */
$('.recommender_resource').each(function(index, element) {
if (index < (currentPage-1)*entriesPerPage || index >= currentPage*entriesPerPage) { $(element).hide(); }
else { $(element).show(); }
});
/* change icon for each page */
$('.paginationRow').each(function(index, element) {
if (index + 1 == currentPage) { $(element).show(); }
else { $(element).hide(); }
});
}
/* creating pagination (icon and page-change event) for each page of resource list */
function paginationRow() {
var totalPage = Math.ceil($('.recommender_resource').length/entriesPerPage);
if (totalPage == 1) { return; }
$('.pagination').empty();
$('.paginationCell').unbind();
/* each paginationRow correspond to each page of resource list */
for (var pageIdx = 1; pageIdx <= totalPage; pageIdx++) {
var content = '<div class="paginationRow">';
/* no previous page if current page = 1 */
if (pageIdx == 1) { content += '<div class="paginationCell leftArrowIcon" style="visibility: hidden;"></div>'; }
else { content += '<div class="paginationCell leftArrowIcon"></div>'; }
if (pageIdx - pageSpan > 1) { content += '<div class="paginationCell moreIcon" style="cursor: default;"></div>'; }
for (var i = pageIdx - pageSpan; i <= pageIdx + pageSpan; i++) {
if (i == pageIdx) { content += '<div class="paginationCell lightgreyBg">' + i.toString() + '</div>'; }
else if (i > 0 && i <= totalPage) { content += '<div class="paginationCell">' + i.toString() + '</div>'; }
}
if (pageIdx + pageSpan < totalPage) { content += '<div class="paginationCell moreIcon" style="cursor: default;"></div>'; }
/* no next page if current page is last page */
if (pageIdx == totalPage) { content += '<div class="paginationCell rightArrowIcon" style="visibility: hidden;"></div>'; }
else { content += '<div class="paginationCell rightArrowIcon"></div>'; }
content += '</div>';
$('.pagination').append(content);
}
/* page change */
$('.paginationCell').click(function () {
if ($(this).hasClass('moreIcon')) { return; }
else if ($(this).hasClass('leftArrowIcon')) { currentPage -= 1; }
else if ($(this).hasClass('rightArrowIcon')) { currentPage += 1; }
else { currentPage = parseInt($(this).text()); }
pagination();
});
}
/* button for adding new resource */
$('.resource_add_button').click(function() {
addResourceReset();
$('.recommender_add').show();
$('.recommender_content').hide();
$('.recommender_modify').show();
$('.recommender_modify_title').text('Recommend new resource');
});
/* change between different mode (resource list or add/edit mode) */
function backToView() {
$('.recommender_modify').hide();
$('.flagSourceBlock').hide();
$('.editSourceBlock').hide();
$('.recommender_add').hide();
$('.recommender_content').show();
if ($('.recommender_row_top').css('cursor') == 's-resize') { $(".hide-show").click(); }
}
$('.backToViewButton').click(function(){
backToView();
});
/* prepare the form for uploading file to S3: TODO, handle this with pyfilesystem */
var policyBase64 = 'CnsiZXhwaXJhdGlvbiI6ICIyMDIwLTEyLTAxVDEyOjAwOjAwLjAwMFoiLAogICJjb25kaXRpb25zIjogWwogICAgeyJidWNrZXQiOiAiZGFuaWVsc3dsaSJ9LAogICAgWyJzdGFydHMtd2l0aCIsICIka2V5IiwgInVwbG9hZHMvIl0sCiAgICB7ImFjbCI6ICJwdWJsaWMtcmVhZCJ9LAogICAgWyJzdGFydHMtd2l0aCIsICIkQ29udGVudC1UeXBlIiwgIiJdLAogICAgWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsIDAsIDUyNDI4ODAwMF0KICBdCn0=';
var signature = 'uRVljXwwHfM5K351eTL2MbYLwcI=';
$('#addResourceForm').append('<input type="hidden" name="Policy" value="' + policyBase64 + '">'
+ '<input type="hidden" name="Signature" value="' + signature + '">'
+ 'Previewing screenshot: <input type="file" name="file"><br>'
+ '<input type="submit" class="submitAddResourceForm" name="submit" value="Upload File">'
+ '<input type="button" value="Add resource" class="add_submit" disabled>');
//+ '<input type="submit" class="submitAddResourceForm" name="submit" value="Upload File" style="margin-top: 0.5em">'
+ '<input type="button" value="Add resource" class="add_submit" style="margin-top: 0.5em" disabled >');
/* initialize add resource mode */
function addResourceReset() {
$('.in_title').val('');
$('.in_url').val('')
$('.in_url').val('');
$('#addResourceForm').find("input[name='file']").val('')
/* get time for unique filenames: TODO, randomize filenames */
var key = "uploads/" + (new Date).getTime();
$('#addResourceForm').find("input[name='key']").val(key);
$('.add_submit').attr('disabled', true);
$('.submitAddResourceForm').attr('disabled', false);
}
addResourceReset();
/* check whether enough information (title/url) is provided for recommending a resource, if yes, enable summission button */
function enableAddSubmit(divPtr) {
if ($('.in_title').val() == '' || $('.in_url').val() == '') {
$('.add_submit').attr('disabled', true);
return;
}
$('.add_submit').attr('disabled', false);
}
/* check whether the input text area is changed, if yes, check whether student can submit the resource */
$('.in_title').bind('input propertychange', function() { enableAddSubmit(); });
$('.in_url').bind('input propertychange', function() { enableAddSubmit(); });
/* upload once student select a file */
$('#addResourceForm').find("input[name='file']").change(function (){
if ($(this).val() == '') { return false; }
$("#addResourceForm").submit();
});
$("#addResourceForm").submit( function(e) {
if ($('#addResourceForm').find("input[name='file']").val() == '') { return false; }
$('.add_submit').attr('disabled', false);
$('.submitAddResourceForm').attr('disabled', true);
enableAddSubmit();
return true;
});
/* submit the resource, save to database, update the current view */
$('.add_submit').click(function() {
/* data: parameter passed to database */
var data = {};
data['resource'] = {};
data['resource']['url'] = $('.in_url').val();
data['resource']['title'] = $('.in_title').val();
data['resource']['description'] = baseUrl + $(this).parent().find("input[name='key']").val();
// data['resource']['description'] = $(this).parent().find('.in_description').val();
$.ajax({
type: "POST",
......@@ -65,44 +167,70 @@ function RecommenderXBlock(runtime, element) {
data: JSON.stringify(data),
success: function(result) {
if (result['Success'] == true) {
$('.recommender_row').append(
'<div class="recommender_resource">' +
/* decide the rigth place for the added resource (pos), based on sorting the votes */
var pos = -1;
$('.recommender_vote_score').each(function(idx, ele){
if (parseInt($(ele).text()) < 0) {
pos = idx;
return false;
}
});
/* html for the new resource */
var content = '<div class="recommender_resource">' +
'<div class="recommender_vote_box">' +
'<div class="recommender_vote_arrow_up" role="button" aria-label="upvote" tabindex="0">' +
'<span class="ui-icon ui-icon-triangle-1-n"></span></div>' +
'<div class="recommender_vote_score">0</div>' +
'<div class="recommender_vote_arrow_down" role="button" aria-label="downvote" tabindex="0">' +
'<span class="ui-icon ui-icon-triangle-1-s"></span></div>' +
'<div class="recommender_vote_arrow_up nonevoting" role="button" aria-label="upvote" tabindex="0">' +
'<b>↑</b></div>' +
'<div class="recommender_vote_score nonevoting"><b>0</b></div>' +
'<div class="recommender_vote_arrow_down nonevoting" role="button" aria-label="downvote" tabindex="0">' +
'<b>↓</b></div>' +
'</div>' +
'<div class="recommender_blurb"><div class="recommender_title">' +
data['resource']['title'] + '</div>' +
'<div class="recommender_url">' + data['resource']['url'] +
'</div><div class="recommender_descriptionSlot">' + data['resource']['description'] +
'<a href="' + data['resource']['url'] + '" target="_blank">' + data['resource']['title'] + '</a>' + '</div>' +
'<div class="recommender_descriptionSlot">' + data['resource']['description'] +
'</div><div class="recommender_entryId">' + result['id'] +
'</div></div><div class="recommender_edit"><input type="button" value="Edit" class="editResource">' +
'<br><input type="button" value="Misuse" class="flagResource notMisuse"></div></div>');
'</div><div class="recommender_problematicReason"></div>' +
'</div><div class="recommender_edit">' +
'<span class="ui-icon ui-icon-pencil resource_edit_button"></span>' +
'<span class="ui-icon ui-icon-flag flagResource" title="Flag irrelevant resource">' +
'</span></div></div>';
/* show the added resource at right place (pos), based on sorting the votes, and lead student to that page */
if (pos == -1) {
$('.recommender_resource:last').after(content);
currentPage = Math.ceil($('.recommender_resource').length/entriesPerPage);
}
else {
$('.recommender_resource:eq(' + pos.toString() + ')').before(content);
currentPage = Math.ceil((pos + 1)/entriesPerPage);
}
addResourceReset();
unbindEvent();
bindEvent();
paginationRow();
pagination();
backToView();
}
addResourceReset();
unbindEvent();
bindEvent();
else { alert('add redundant resource'); }
}
});
});
/* unbind event for each entry of resources */
function unbindEvent() {
$('.recommender_vote_arrow_up').unbind();
$('.recommender_vote_arrow_down').unbind();
$('.recommender_blurb').unbind();
$('.recommender_resource').unbind();
$('.editResource').unbind();
$('.resource_edit_button').unbind();
$('.flagResource').unbind();
}
/* bind event for each entry of resources */
function bindEvent() {
/* upvoting event */
$('.recommender_vote_arrow_up').click(function() {
var data = {};
data['resource'] = parseInt($(this).parent().parent().find('.recommender_entryId').text());
// data['resource'] = findEntry($(this).parent().parent().find('.recommender_entryId').text());
if (data['resource'] == -1) { return; }
var divArrowUp = this;
$.ajax({
......@@ -112,143 +240,260 @@ function RecommenderXBlock(runtime, element) {
success: function(result) {
if (result['Success'] == true) {
var scoreDiv = $(divArrowUp).parent().find('.recommender_vote_score');
scoreDiv.text((parseInt(scoreDiv.text()) + 1).toString());
/* change downvoting to upvoting */
if ($(divArrowUp).hasClass('downvoting')) {
$(divArrowUp).parent().find('.downvoting').removeClass('downvoting').addClass('upvoting');
scoreDiv.html('<b>' + (parseInt(scoreDiv.text()) + 2).toString() + '</b>');
}
/* upvoting */
else if ($(divArrowUp).hasClass('nonevoting')) {
$(divArrowUp).parent().find('.nonevoting').removeClass('nonevoting').addClass('upvoting');
scoreDiv.html('<b>' + (parseInt(scoreDiv.text()) + 1).toString() + '</b>');
}
/* undo upvoting */
else if ($(divArrowUp).hasClass('upvoting')) {
$(divArrowUp).parent().find('.upvoting').removeClass('upvoting').addClass('nonevoting');
scoreDiv.html('<b>' + (parseInt(scoreDiv.text()) - 1).toString() + '</b>');
}
}
}
});
});
/* downvoting event */
$('.recommender_vote_arrow_down').click(function() {
var data = {};
data['resource'] = parseInt($(this).parent().parent().find('.recommender_entryId').text());
// data['resource'] = findEntry($(this).parent().parent().find('.recommender_entryId').text());
if (data['resource'] == -1) { return; }
var divArrowDown = this;
$.ajax({
$.ajax({
type: "POST",
url: handleDownvoteUrl,
data: JSON.stringify(data),
success: function(result) {
if (result['Success'] == true) {
var scoreDiv = $(divArrowDown).parent().find('.recommender_vote_score');
scoreDiv.text((parseInt(scoreDiv.text()) - 1).toString());
}
/* undo downvoting */
if ($(divArrowDown).hasClass('downvoting')) {
$(divArrowDown).parent().find('.downvoting').removeClass('downvoting').addClass('nonevoting');
scoreDiv.html('<b>' + (parseInt(scoreDiv.text()) + 1).toString() + '</b>');
}
/* downvoting */
else if ($(divArrowDown).hasClass('nonevoting')) {
$(divArrowDown).parent().find('.nonevoting').removeClass('nonevoting').addClass('downvoting');
scoreDiv.html('<b>' + (parseInt(scoreDiv.text()) - 1).toString() + '</b>');
}
/* change voting to downvoting */
else if ($(divArrowDown).hasClass('upvoting')) {
$(divArrowDown).parent().find('.upvoting').removeClass('upvoting').addClass('downvoting');
scoreDiv.html('<b>' + (parseInt(scoreDiv.text()) - 2).toString() + '</b>');
}
}
}
});
});
$('.recommender_blurb').click(function(){
var win = window.open($(this).find('.recommender_url').text(), '_blank');
win.focus();
});
});
/* show preview when hover a entry of resource*/
$('.recommender_resource').hover(
function() {
$('.recommender_resource').removeClass('resource_hovered');
$(this).addClass('resource_hovered');
$('.descriptionImg').empty();
$('.descriptionImg').append('<img src="' + $(this).find('.recommender_descriptionSlot').text() + '" height=100%>');
$('.descriptionImg').append('<img class="previewingImg" src="'
+ $(this).find('.recommender_descriptionSlot').text() + '" height=100%>');
}, function() {
// $('.descriptionImg').empty();
}
);
$('.editResource').click(function() {
$('.editResource').removeClass('redTxt');
$(this).addClass('redTxt');
$('.editSourceBlock').empty();
var key = "uploads/" + (new Date).getTime();
var path = 'http://danielswli.s3.amazonaws.com/';
var uploadForm = '<form id="editResourceForm" action="' + path + '" method="post" enctype="multipart/form-data">'
+ '<input type="hidden" name="key" value="' + key + '">'
+ '<input type="hidden" name="acl" value="public-read">'
+ '<input type="hidden" name="Content-Type" value="image/jpeg">'
+ '<input type="hidden" name="AWSAccessKeyId" value="AKIAIRDHSV6YZJZ4RFGA">'
+ '<input type="hidden" name="Policy" value="' + policyBase64 + '">'
+ '<input type="hidden" name="Signature" value="' + signature + '">'
+ 'Previewing screenshot: <input type="file" name="file"><br>'
+ '<input type="submit" class="submitEditResourceForm" name="submit" value="Upload File">'
+ '<input type="button" value="Edit resource" class="edit_submit" disabled></form>';
$('.editSourceBlock').append(
'<div class="editSourceBlockTitle">Edit selected resource</div>' +
'Title: ' + '<input type="text" class="edit_title"><br>' +
'Url: <input type="text" class="edit_url"><br>' + uploadForm);
//'Edited resource description: ' +
//'<input type="text" class="edit_description"><br>' +
var divEdit = this;
$("#editResourceForm").submit( function(e) {
if ($('#editResourceForm').find("input[name='file']").val() == '') { return false; }
$('.edit_submit').attr('disabled', false);
$('.submitEditResourceForm').attr('disabled', 'disabled');
return true;
});
/* edit existing resource */
$('.resource_edit_button').click(function() {
$('.editSourceBlock').show();
$('.recommender_content').hide();
$('.recommender_modify').show();
$('.recommender_modify_title').text('Edit existing resource');
$('.editSourceBlock').empty();
/* get time for unique filenames: TODO, randomize filenames */
var key = "uploads/" + (new Date).getTime();
/* prepare the form for uploading file to S3: TODO, handle this with pyfilesystem */
var path = 'http://danielswli.s3.amazonaws.com/';
var uploadForm = '<form id="editResourceForm" action="' + path + '" method="post" enctype="multipart/form-data">'
+ '<input type="hidden" name="key" value="' + key + '">'
+ '<input type="hidden" name="acl" value="public-read">'
+ '<input type="hidden" name="Content-Type" value="image/jpeg">'
+ '<input type="hidden" name="AWSAccessKeyId" value="AKIAIRDHSV6YZJZ4RFGA">'
+ '<input type="hidden" name="Policy" value="' + policyBase64 + '">'
+ '<input type="hidden" name="Signature" value="' + signature + '">'
+ 'Previewing screenshot: <input type="file" name="file"><br>'
+ '<input type="button" value="Save change" class="edit_submit" style="margin-top: 0.5em" disabled></form>';
$('.editSourceBlock').append(
'<div class="editSourceBlockTitle">Edit the description, hypelink, and previewing screenshot for the selected resource</div>' +
'Description: ' + '<input type="text" class="edit_title" style="height: 25px; position: relative; left: 10px;"><br>' +
'HyperLink: <input type="text" class="edit_url" style="height: 25px; position: relative; left: 22px;"><br>' + uploadForm);
/* initialize the text area */
$('.edit_title').val($(this).parent().parent().find('.recommender_title').find('a').text());
$('.edit_url').val($(this).parent().parent().find('.recommender_title').find('a').attr('href'));
addTooltip();
var divEdit = this;
/* check whether enough information (title/url) is provided for editing a resource, if yes, enable summission button */
function enableEditSubmit() {
if ($('.edit_title').val() == '' || $('.edit_url').val() == '') {
$('.edit_submit').attr('disabled', true);
return;
}
$('.edit_submit').attr('disabled', false);
}
/* check whether the input text area is changed, if yes, check whether student can submit the resource */
$('.edit_title').bind('input propertychange', function() { enableEditSubmit(); });
$('.edit_url').bind('input propertychange', function() { enableEditSubmit(); });
$('.edit_submit').click(function() {
var data = {};
data['resource'] = parseInt($(divEdit).parent().parent().find('.recommender_entryId').text());
// data['resource'] = findEntry($(divEdit).parent().parent().find('.recommender_entryId').text());
data['url'] = $('.edit_url').val();
data['title'] = $('.edit_title').val();
if (data['url'] == '' || data['title'] == '') { return; }
/* upload once student select a file */
$('#editResourceForm').find("input[name='file']").change(function (){
if ($(this).val() == '') { return false; }
$("#editResourceForm").submit();
});
$("#editResourceForm").submit( function(e) {
if ($('#editResourceForm').find("input[name='file']").val() == '') { return false; }
data['description'] = baseUrl + key;
$.ajax({
enableEditSubmit();
return true;
});
/* submit the edited resource, save to database, update the current view */
$('.edit_submit').click(function() {
/* data: parameter passed to database */
var data = {};
data['resource'] = parseInt($(divEdit).parent().parent().find('.recommender_entryId').text());
data['url'] = $('.edit_url').val();
data['title'] = $('.edit_title').val();
if (data['url'] == '' || data['title'] == '') { return; }
if ($('#editResourceForm').find("input[name='file']").val() != '') { data['description'] = baseUrl + key; }
$.ajax({
type: "POST",
url: editResourceUrl,
data: JSON.stringify(data),
success: function(result) {
if (result['Success'] == true) {
$(divEdit).parent().parent().find('.recommender_title').text(data['title']);
$(divEdit).parent().parent().find('.recommender_url').text(data['url']);
$(divEdit).parent().parent().find('.recommender_descriptionSlot').text(data['description']);
/* show the edited resource */
$(divEdit).parent().parent().find('.recommender_title').find('a').text(data['title']);
$(divEdit).parent().parent().find('.recommender_title').find('a').attr('href', data['url']);
if ("description" in data) { $(divEdit).parent().parent().find('.recommender_descriptionSlot').text(data['description']); }
$('.editSourceBlock').empty();
$('.editResource').removeClass('redTxt');
backToView();
}
else { alert('add redundant resource'); }
}
});
});
});
});
$('.flagResource').click(function() {
var data = {};
if ($(this).hasClass('notMisuse')) {
data['isMisuse'] = 'misuse';
$(this).removeClass('notMisuse').addClass('misuse');
}
else {
data['isMisuse'] = 'notMisuse';
$(this).removeClass('misuse').addClass('notMisuse');
}
// data['resource'] = findEntry($(this).parent().parent().find('.recommender_entryId').text());
data['resource'] = parseInt($(this).parent().parent().find('.recommender_entryId').text());
$.ajax({
type: "POST",
url: flagResourceUrl,
data: JSON.stringify(data)
});
});
}
bindEvent();
function findEntry(id) {
var entryId = -1;
$('.recommender_resource').find('.recommender_entryId').each(
function(idx, ele){
if ($(ele).text() == id) {
entryId = idx;
return;
}
}
);
return entryId;
/* flag problematic resource */
$('.flagResource').click(function() {
$('.flagSourceBlock').show();
$('.recommender_content').hide();
$('.recommender_modify').show();
$('.recommender_modify_title').text('Reason for flagging the resource');
var flagDiv = $(this);
var flaggedResourceDiv = $(this).parent().parent();
$('.flag_reason').val($(flaggedResourceDiv).find('.recommender_problematicReason').text());
/* record the flagging once user click on the flag button */
if (!$(this).hasClass('problematic')){
data = {};
data['resource'] = parseInt($(flaggedResourceDiv).find('.recommender_entryId').text());
data['reason'] = '';
data['isProblematic'] = true;
$.ajax({
type: "POST",
url: flagResourceUrl,
data: JSON.stringify(data),
success: function(result) {
$(flagDiv).addClass('problematic');
}
});
}
addTooltip();
$('.flag_reason_submit').unbind();
$('.unflag_button').unbind();
/* record the reason for problematic resource */
$('.flag_reason_submit').click(function() {
data = {};
data['resource'] = parseInt($(flaggedResourceDiv).find('.recommender_entryId').text());
data['reason'] = $('.flag_reason').val();
data['isProblematic'] = true;
$.ajax({
type: "POST",
url: flagResourceUrl,
data: JSON.stringify(data),
success: function(result) {
$(flaggedResourceDiv).find('.recommender_problematicReason').text(data['reason']);
backToView();
}
});
});
/* unflag the resource */
$('.unflag_button').click(function() {
data = {};
data['resource'] = parseInt($(flaggedResourceDiv).find('.recommender_entryId').text());
data['isProblematic'] = false;
$.ajax({
type: "POST",
url: flagResourceUrl,
data: JSON.stringify(data),
success: function(result) {
$(flagDiv).removeClass('problematic');
$(flaggedResourceDiv).find('.recommender_problematicReason').text('');
backToView();
}
});
});
});
addTooltip();
}
function addTooltip() {
$('.resource_add_button').attr('title', 'Recommend a new helpful resource for this problem with a short description, hyperlink, and previewing screenshot to the new resource');
$('.resource_edit_button').attr('title', 'Edit the description, hypelink, and previewing screenshot of this resource');
$('.recommender_vote_arrow_up').attr('title', 'Upvote for a helpful resource');
$('.recommender_vote_arrow_down').attr('title', 'Downvote for an irrelevant resource');
$('.recommender_vote_score').attr('title', 'Votes');
$('.recommender_blurb').attr('title', 'The description of a helpful resource');
$('.previewingImg').attr('title', 'Previewing screenshot');
$('.in_title').attr('title', 'Type in the description of the resource');
$('.in_url').attr('title', 'Type in the hyperlink to the resource');
$('.edit_title').attr('title', 'Type in the description of the resource');
$('.edit_url').attr('title', 'Type in the hyperlink to the resource');
$('.backToViewButton').attr('title', 'Back to list of related resources');
$('.flag_reason').attr('title', 'Type in the reason why you flag the resource');
if ($('.flagResource').hasClass('problematic')) { $('.flagResource').attr('title', 'Unflag this problematic resource or edit the reason for it'); }
else { $('.flagResource').attr('title', 'Flag this resource as problematic and give the reason' ); }
if ($('.recommender_row_top').hasClass('resource_list_expanded')) { $('.recommender_row_top').attr('title', 'Select to hide the list'); }
else { $('.recommender_row_top').attr('title', 'Select for expanding resource list' ); }
}
/* $('.ui-icon-zoomin').click(function(){
//alert($(this).parent().parent().find('.recommender_url').text());
$(this).colorbox({iframe:true, width:800, height:600, href:$(this).parent().parent().find('.recommender_url').text()});
});*/
function initial() {
$(".hide-show").click();
$('.recommender_modify').hide();
$('.flagSourceBlock').hide();
$('.editSourceBlock').hide();
$('.recommender_add').hide();
paginationRow();
pagination();
addResourceReset();
bindEvent();
}
initial();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment