@@ -122,7 +122,8 @@ | |||||
"public/css/form.css", | "public/css/form.css", | ||||
"public/css/mobile.css", | "public/css/mobile.css", | ||||
"public/css/kanban.css", | "public/css/kanban.css", | ||||
"public/css/controls.css" | |||||
"public/css/controls.css", | |||||
"public/css/tags.css" | |||||
], | ], | ||||
"css/frappe-rtl.css": [ | "css/frappe-rtl.css": [ | ||||
"public/css/bootstrap-rtl.css", | "public/css/bootstrap-rtl.css", | ||||
@@ -132,7 +133,6 @@ | |||||
"js/libs.min.js": [ | "js/libs.min.js": [ | ||||
"public/js/lib/awesomplete/awesomplete.min.js", | "public/js/lib/awesomplete/awesomplete.min.js", | ||||
"public/js/lib/Sortable.min.js", | "public/js/lib/Sortable.min.js", | ||||
"public/js/lib/taggle/taggle.min.js", | |||||
"public/js/lib/jquery/jquery.hotkeys.js", | "public/js/lib/jquery/jquery.hotkeys.js", | ||||
"public/js/lib/summernote/summernote.js", | "public/js/lib/summernote/summernote.js", | ||||
"public/js/lib/bootstrap.min.js", | "public/js/lib/bootstrap.min.js", | ||||
@@ -292,7 +292,6 @@ | |||||
"public/js/frappe/form/quick_entry.js" | "public/js/frappe/form/quick_entry.js" | ||||
], | ], | ||||
"css/list.min.css": [ | "css/list.min.css": [ | ||||
"public/js/lib/taggle/taggle.min.css", | |||||
"public/css/list.css", | "public/css/list.css", | ||||
"public/css/calendar.css", | "public/css/calendar.css", | ||||
"public/css/role_editor.css", | "public/css/role_editor.css", | ||||
@@ -307,6 +306,7 @@ | |||||
"public/js/frappe/ui/filters/filters.js", | "public/js/frappe/ui/filters/filters.js", | ||||
"public/js/frappe/ui/filters/edit_filter.html", | "public/js/frappe/ui/filters/edit_filter.html", | ||||
"public/js/frappe/ui/tags.js", | "public/js/frappe/ui/tags.js", | ||||
"public/js/frappe/ui/tag_editor.js", | |||||
"public/js/frappe/ui/like.js", | "public/js/frappe/ui/like.js", | ||||
"public/js/frappe/ui/liked_by.html", | "public/js/frappe/ui/liked_by.html", | ||||
"public/html/print_template.html", | "public/html/print_template.html", | ||||
@@ -0,0 +1,19 @@ | |||||
.tags-list { | |||||
float: left; | |||||
width: 100%; | |||||
padding-left: 3px; | |||||
} | |||||
.tags-input { | |||||
width: 100px; | |||||
font-size: 11px; | |||||
border: none; | |||||
outline: none; | |||||
} | |||||
.tags-list-item { | |||||
display: inline-block; | |||||
margin: 0px 3px; | |||||
} | |||||
.tags-placeholder { | |||||
display: inline-block; | |||||
font-size: 11px; | |||||
} |
@@ -0,0 +1,129 @@ | |||||
frappe.ui.TagEditor = Class.extend({ | |||||
init: function(opts) { | |||||
/* docs: | |||||
Arguments | |||||
- parent | |||||
- user_tags | |||||
- doctype | |||||
- docname | |||||
*/ | |||||
$.extend(this, opts); | |||||
this.setup_tags(); | |||||
if (!this.user_tags) { | |||||
this.user_tags = ""; | |||||
} | |||||
this.initialized = true; | |||||
this.refresh(this.user_tags); | |||||
}, | |||||
setup_tags: function() { | |||||
var me = this; | |||||
// hidden form, does not have parent | |||||
if (!this.parent) { | |||||
return; | |||||
} | |||||
this.wrapper = $('<div class="tag-line" style="position: relative">').appendTo(this.parent); | |||||
if(!this.wrapper.length) return; | |||||
this.tags = new frappe.ui.Tags({ | |||||
parent: this.wrapper, | |||||
placeholder: "Add a tag ...", | |||||
onTagAdd: (tag) => { | |||||
if(me.initialized && !me.refreshing) { | |||||
tag = toTitle(tag); | |||||
return frappe.call({ | |||||
method: 'frappe.desk.tags.add_tag', | |||||
args: me.get_args(tag), | |||||
callback: function(r) { | |||||
var user_tags = me.user_tags ? me.user_tags.split(",") : []; | |||||
user_tags.push(tag) | |||||
me.user_tags = user_tags.join(","); | |||||
me.on_change && me.on_change(me.user_tags); | |||||
} | |||||
}); | |||||
} | |||||
}, | |||||
onTagRemove: (tag) => { | |||||
if(!me.refreshing) { | |||||
return frappe.call({ | |||||
method: 'frappe.desk.tags.remove_tag', | |||||
args: me.get_args(tag), | |||||
callback: function(r) { | |||||
var user_tags = me.user_tags.split(","); | |||||
user_tags.splice(user_tags.indexOf(tag), 1); | |||||
me.user_tags = user_tags.join(","); | |||||
me.on_change && me.on_change(me.user_tags); | |||||
} | |||||
}); | |||||
} | |||||
} | |||||
}); | |||||
this.setup_awesomplete(); | |||||
this.setup_complete = true; | |||||
}, | |||||
setup_awesomplete: function() { | |||||
var me = this; | |||||
var $input = this.wrapper.find("input.tags-input"); | |||||
var input = $input.get(0); | |||||
this.awesomplete = new Awesomplete(input, { | |||||
minChars: 0, | |||||
maxItems: 99, | |||||
list: [] | |||||
}); | |||||
$input.on("awesomplete-open", function(e){ | |||||
$input.attr('state', 'open'); | |||||
}); | |||||
$input.on("awesomplete-close", function(e){ | |||||
$input.attr('state', 'closed'); | |||||
}); | |||||
$input.on("input", function(e) { | |||||
var value = e.target.value; | |||||
frappe.call({ | |||||
method:"frappe.desk.tags.get_tags", | |||||
args:{ | |||||
doctype: me.frm.doctype, | |||||
txt: value.toLowerCase(), | |||||
cat_tags: me.list_sidebar ? | |||||
JSON.stringify(me.list_sidebar.get_cat_tags()) : '[]' | |||||
}, | |||||
callback: function(r) { | |||||
me.awesomplete.list = r.message; | |||||
} | |||||
}); | |||||
}); | |||||
$input.on("focus", function(e) { | |||||
if($input.attr('state') != 'open') { | |||||
$input.trigger("input"); | |||||
} | |||||
}); | |||||
}, | |||||
get_args: function(tag) { | |||||
return { | |||||
tag: tag, | |||||
dt: this.frm.doctype, | |||||
dn: this.frm.docname, | |||||
} | |||||
}, | |||||
refresh: function(user_tags) { | |||||
var me = this; | |||||
if (!this.initialized || !this.setup_complete || this.refreshing) return; | |||||
me.refreshing = true; | |||||
try { | |||||
me.tags.clearTags(); | |||||
if(user_tags) { | |||||
me.tags.addTags(user_tags.split(',')); | |||||
} | |||||
} catch(e) { | |||||
me.refreshing = false; | |||||
// wtf bug | |||||
setTimeout( function() { me.refresh(); }, 100); | |||||
} | |||||
me.refreshing = false; | |||||
} | |||||
}) |
@@ -1,131 +1,135 @@ | |||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
// MIT License. See license.txt | // MIT License. See license.txt | ||||
frappe.ui.TagEditor = Class.extend({ | |||||
init: function(opts) { | |||||
/* docs: | |||||
Arguments | |||||
- parent | |||||
- user_tags | |||||
- doctype | |||||
- docname | |||||
*/ | |||||
$.extend(this, opts); | |||||
this.setup_taggle(); | |||||
if (!this.user_tags) { | |||||
this.user_tags = ""; | |||||
} | |||||
this.initialized = true; | |||||
this.refresh(this.user_tags); | |||||
}, | |||||
setup_taggle: function() { | |||||
var me = this; | |||||
// hidden form, does not have parent | |||||
if (!this.parent) { | |||||
return; | |||||
} | |||||
frappe.ui.Tags = class { | |||||
constructor({ | |||||
parent, placeholder, tagsList, | |||||
onTagAdd, | |||||
onTagRemove, | |||||
onTagClick, | |||||
onChange | |||||
}) { | |||||
this.tagsList = tagsList || []; | |||||
this.onTagAdd = onTagAdd; | |||||
this.onTagRemove = onTagRemove; | |||||
this.onTagClick = onTagClick; | |||||
this.onChange = onChange; | |||||
this.setup(parent, placeholder); | |||||
} | |||||
setup(parent, placeholder) { | |||||
this.$wrapper = $(`<div class="tags-wrapper"></div>`).appendTo(parent); | |||||
this.$ul = $(`<ul class="tags-list"></ul>`).appendTo(this.$wrapper); | |||||
this.$input = $(`<input class="tags-input"></input>`); | |||||
this.$inputWrapper = this.getListElement(this.$input); | |||||
this.$placeholder = this.getListElement($(`<span class="tags-placeholder text-muted">${placeholder}</span>`)); | |||||
this.$inputWrapper.appendTo(this.$ul); | |||||
this.$placeholder.appendTo(this.$ul); | |||||
this.wrapper = $('<div class="tag-line" style="position: relative">').appendTo(this.parent); | |||||
if(!this.wrapper.length) return; | |||||
var id = frappe.dom.set_unique_id(this.wrapper); | |||||
this.taggle = new Taggle(id, { | |||||
placeholder: __('Add a tag') + "...", | |||||
onTagAdd: function(e, tag) { | |||||
if(me.initialized && !me.refreshing) { | |||||
tag = toTitle(tag); | |||||
return frappe.call({ | |||||
method: 'frappe.desk.tags.add_tag', | |||||
args: me.get_args(tag), | |||||
callback: function(r) { | |||||
var user_tags = me.user_tags ? me.user_tags.split(",") : []; | |||||
user_tags.push(tag) | |||||
me.user_tags = user_tags.join(","); | |||||
me.on_change && me.on_change(me.user_tags); | |||||
} | |||||
}); | |||||
} | |||||
}, | |||||
onTagRemove: function(e, tag) { | |||||
if(!me.refreshing) { | |||||
return frappe.call({ | |||||
method: 'frappe.desk.tags.remove_tag', | |||||
args: me.get_args(tag), | |||||
callback: function(r) { | |||||
var user_tags = me.user_tags.split(","); | |||||
user_tags.splice(user_tags.indexOf(tag), 1); | |||||
me.user_tags = user_tags.join(","); | |||||
me.on_change && me.on_change(me.user_tags); | |||||
} | |||||
}); | |||||
} | |||||
this.deactivate(); | |||||
this.bind(); | |||||
this.boot(); | |||||
} | |||||
bind() { | |||||
this.$input.keypress((e) => { | |||||
if(e.which == 13 || e.keyCode == 13) { | |||||
this.addTag(this.$input.val()); | |||||
this.$input.val(''); | |||||
} | } | ||||
}); | }); | ||||
this.setup_awesomplete(); | |||||
this.setup_complete = true; | |||||
}, | |||||
setup_awesomplete: function() { | |||||
var me = this; | |||||
var $input = this.wrapper.find("input.taggle_input"); | |||||
var input = $input.get(0); | |||||
this.awesomplete = new Awesomplete(input, { | |||||
minChars: 0, | |||||
maxItems: 99, | |||||
list: [] | |||||
this.$input.on('blur', () => { | |||||
this.deactivate(); | |||||
}); | }); | ||||
$input.on("awesomplete-open", function(e){ | |||||
$input.attr('state', 'open'); | |||||
this.$placeholder.on('click', () => { | |||||
this.activate(); | |||||
}); | }); | ||||
$input.on("awesomplete-close", function(e){ | |||||
$input.attr('state', 'closed'); | |||||
} | |||||
boot() { | |||||
this.addTags(this.tagsList); | |||||
} | |||||
activate() { | |||||
this.$placeholder.hide(); | |||||
this.$inputWrapper.show(); | |||||
this.$input.focus(); | |||||
} | |||||
deactivate() { | |||||
this.$inputWrapper.hide(); | |||||
this.$placeholder.show(); | |||||
} | |||||
refresh() { | |||||
this.deactivate(); | |||||
this.activate(); | |||||
} | |||||
addTag(label) { | |||||
if(label && !this.tagsList.includes(label)) { | |||||
let $tag = this.getTag(label); | |||||
this.getListElement($tag).insertBefore(this.$inputWrapper); | |||||
this.tagsList.push(label); | |||||
this.onTagAdd && this.onTagAdd(label); | |||||
this.refresh(); | |||||
} | |||||
} | |||||
removeTag(label) { | |||||
if(this.tagsList.includes(label)) { | |||||
let $tag = this.$ul.find(`.frappe-tag[data-tag-label="${label}"]`); | |||||
$tag.remove(); | |||||
this.tagsList = this.tagsList.filter(d => d !== label); | |||||
this.onTagRemove && this.onTagRemove(label); | |||||
} | |||||
} | |||||
addTags(labels) { | |||||
labels.map(this.addTag.bind(this)); | |||||
} | |||||
clearTags() { | |||||
this.$ul.find('.frappe-tag').remove(); | |||||
this.tagsList = []; | |||||
} | |||||
getListElement($element, className) { | |||||
let $li = $(`<li class="tags-list-item ${className}"></li>`); | |||||
$element.appendTo($li); | |||||
return $li; | |||||
} | |||||
getTag(label) { | |||||
let $tag = $(`<div class="frappe-tag btn-group" data-tag-label=${label}> | |||||
<button class="btn btn-default btn-xs toggle-tag" | |||||
title="${ __("toggle Tag") }" | |||||
data-tag-label=${label}>${label} | |||||
</button> | |||||
<button class="btn btn-default btn-xs remove-tag" | |||||
title="${ __("Remove Tag") }" | |||||
data-tag-label=${label}> | |||||
<i class="fa fa-remove text-muted"></i> | |||||
</button></div>`); | |||||
let $removeTag = $tag.find(".remove-tag"); | |||||
$removeTag.on("click", () => { | |||||
this.removeTag($removeTag.attr('data-tag-label')); | |||||
}); | }); | ||||
$input.on("input", function(e) { | |||||
var value = e.target.value; | |||||
frappe.call({ | |||||
method:"frappe.desk.tags.get_tags", | |||||
args:{ | |||||
doctype: me.frm.doctype, | |||||
txt: value.toLowerCase(), | |||||
cat_tags: me.list_sidebar ? | |||||
JSON.stringify(me.list_sidebar.get_cat_tags()) : '[]' | |||||
}, | |||||
callback: function(r) { | |||||
me.awesomplete.list = r.message; | |||||
} | |||||
if(this.onTagClick) { | |||||
let $toggle_tag = $tag.find(".toggle-tag"); | |||||
$toggle_tag.on("click", () => { | |||||
this.onTagClick($toggle_tag.attr('data-tag-label')); | |||||
}); | }); | ||||
}); | |||||
$input.on("focus", function(e) { | |||||
if($input.attr('state') != 'open') { | |||||
$input.trigger("input"); | |||||
} | |||||
}); | |||||
}, | |||||
get_args: function(tag) { | |||||
return { | |||||
tag: tag, | |||||
dt: this.frm.doctype, | |||||
dn: this.frm.docname, | |||||
} | |||||
}, | |||||
refresh: function(user_tags) { | |||||
var me = this; | |||||
if (!this.initialized || !this.setup_complete || this.refreshing) return; | |||||
me.refreshing = true; | |||||
try { | |||||
me.taggle.removeAll(); | |||||
if(user_tags) { | |||||
me.taggle.add(user_tags.split(',')); | |||||
} | |||||
} catch(e) { | |||||
me.refreshing = false; | |||||
// wtf bug | |||||
setTimeout( function() { me.refresh(); }, 100); | |||||
} | } | ||||
me.refreshing = false; | |||||
return $tag; | |||||
} | } | ||||
}) | |||||
} |
@@ -1,103 +0,0 @@ | |||||
.taggle_list { | |||||
float: left; | |||||
width: 100%; | |||||
} | |||||
.taggle_input { | |||||
border: none; | |||||
outline: none; | |||||
font-size: 16px; | |||||
font-weight: 300; | |||||
} | |||||
.taggle_list li { | |||||
float: left; | |||||
display: inline-block; | |||||
white-space: nowrap; | |||||
font-weight: 500; | |||||
margin-bottom: 5px; | |||||
} | |||||
.taggle_list .taggle { | |||||
margin-right: 8px; | |||||
background: #E2E1DF; | |||||
padding: 5px 10px; | |||||
border-radius: 3px; | |||||
position: relative; | |||||
cursor: pointer; | |||||
transition: all .3s; | |||||
animation-duration: 1s; | |||||
animation-fill-mode: both; | |||||
} | |||||
.taggle_list .taggle_hot { | |||||
background: #cac8c4; | |||||
} | |||||
.taggle_list .taggle .close { | |||||
font-size: 1.1rem; | |||||
position: absolute; | |||||
top: 5px; | |||||
right: 3px; | |||||
text-decoration: none; | |||||
padding-left: 2px; | |||||
padding-top: 3px; | |||||
line-height: 0.5; | |||||
color: #ccc; | |||||
color: rgba(0, 0, 0, 0.2); | |||||
padding-bottom: 4px; | |||||
display: none; | |||||
border: 0; | |||||
background: none; | |||||
cursor: pointer; | |||||
} | |||||
.taggle_list .taggle:hover { | |||||
padding: 5px; | |||||
padding-right: 15px; | |||||
background: #ccc; | |||||
transition: all .3s; | |||||
} | |||||
.taggle_list .taggle:hover > .close { | |||||
display: block; | |||||
} | |||||
.taggle_list .taggle .close:hover { | |||||
color: #333; | |||||
} | |||||
.taggle_placeholder { | |||||
position: absolute; | |||||
color: #CCC; | |||||
top: 12px; | |||||
left: 8px; | |||||
transition: opacity, .25s; | |||||
user-select: none; | |||||
} | |||||
.taggle_input { | |||||
padding: 8px; | |||||
padding-left: 0; | |||||
float: left; | |||||
margin-top: -5px; | |||||
background: none; | |||||
width: 100%; | |||||
max-width: 100%; | |||||
} | |||||
.taggle_sizer { | |||||
padding: 0; | |||||
margin: 0; | |||||
position: absolute; | |||||
top: -500px; | |||||
z-index: -1; | |||||
visibility: hidden; | |||||
} | |||||
.clearfix:before, .clearfix:after { | |||||
display: block; | |||||
content: ""; | |||||
line-height: 0; | |||||
clear: both; | |||||
} |
@@ -0,0 +1,22 @@ | |||||
.tags-list { | |||||
float: left; | |||||
width: 100%; | |||||
padding-left: 3px; | |||||
} | |||||
.tags-input { | |||||
width: 100px; | |||||
font-size: 11px; | |||||
border: none; | |||||
outline: none; | |||||
} | |||||
.tags-list-item { | |||||
display: inline-block; | |||||
margin: 0px 3px; | |||||
} | |||||
.tags-placeholder { | |||||
display: inline-block; | |||||
font-size: 11px; | |||||
} |