Fixes issue #2103 - clickable URL (template) for ObjectManager Attribute.

This commit is contained in:
Martin Edenhofer 2019-08-12 09:53:24 +02:00 committed by Thorsten Eckel
parent 29c9d3546b
commit 848175a290
12 changed files with 122 additions and 11 deletions

View file

@ -360,11 +360,16 @@ class App.ControllerForm extends App.Controller
return item
else
placeholderObjects = {}
if @model.className && !_.isEmpty(attribute.linktemplate) && !_.isEmpty(@params[attribute.name])
placeholderObjects = { attribute: attribute, user: App.Session.get(), config: App.Config.all() }
placeholderObjects[@model.className.toLowerCase()] = @params
fullItem = $(
App.view('generic/attribute')(
attribute: attribute,
item: '',
bookmarkable: @bookmarkable
placeholderObjects: placeholderObjects
)
)
fullItem.find('.controls').prepend(item)

View file

@ -194,9 +194,21 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
noFieldset: true
params: params
)
configureAttributes = [
# coffeelint: disable=no_interpolation_in_single_quotes
{ name: 'data_option::linktemplate', display: 'Link-Template', tag: 'input', type: 'text', null: true, default: '', placeholder: 'https://example.com/?q=#{object.attribute_name} - use ticket, user or organization as object' },
# coffeelint: enable=no_interpolation_in_single_quotes
]
inputLinkTemplate = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
item.find('.js-inputDefault').html(inputDefault.form)
item.find('.js-inputType').html(inputType.form)
item.find('.js-inputMaxlength').html(inputMaxlength.form)
item.find('.js-inputLinkTemplate').html(inputLinkTemplate.form)
@datetime: (item, localParams, params) ->
configureAttributes = [
@ -311,6 +323,18 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
return
lastSelected = value
)
configureAttributes = [
# coffeelint: disable=no_interpolation_in_single_quotes
{ name: 'data_option::linktemplate', display: 'Link-Template', tag: 'input', type: 'text', null: true, default: '', placeholder: 'https://example.com/?q=#{ticket.attribute_name}' },
# coffeelint: enable=no_interpolation_in_single_quotes
]
inputLinkTemplate = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
item.find('.js-inputLinkTemplate').html(inputLinkTemplate.form)
@buildRow: (element, child, level = 0, parentElement) ->
newRow = element.find('.js-template').clone().removeClass('js-template')

View file

@ -46,10 +46,10 @@ class App extends Spine.Controller
if object[attributeNameWithoutRef]
valueRef = object[attributeNameWithoutRef]
@viewPrintItem(value, attributeConfig, valueRef, table)
@viewPrintItem(value, attributeConfig, valueRef, table, object)
# define print name helper
@viewPrintItem: (item, attributeConfig = {}, valueRef, table) ->
@viewPrintItem: (item, attributeConfig = {}, valueRef, table, object) ->
return '-' if item is undefined
return '-' if item is ''
return item if item is null
@ -107,18 +107,23 @@ class App extends Spine.Controller
# translate content
if attributeConfig.translate || (isObject && item.translate && item.translate())
isHtmlEscape = true
resultLocal = App.i18n.translateContent(resultLocal)
resultLocal = App.i18n.translateContent(resultLocal)
# transform date
if attributeConfig.tag is 'date'
isHtmlEscape = true
resultLocal = App.i18n.translateDate(resultLocal)
linktemplate = @_placeholderReplacement(object, attributeConfig, resultLocal)
if linktemplate && isHtmlEscape is false
resultLocal = linktemplate
isHtmlEscape = true
# transform input tel|url to make it clickable
if attributeConfig.tag is 'input'
if attributeConfig.tag is 'input' && !linktemplate
if attributeConfig.type is 'tel'
resultLocal = "<a href=\"#{App.Utils.phoneify(resultLocal)}\">#{App.Utils.htmlEscape(resultLocal)}</a>"
else if attributeConfig.type is 'url'
else if attributeConfig.type is 'url' && !linktemplate
resultLocal = App.Utils.linkify(resultLocal)
else
resultLocal = App.Utils.htmlEscape(resultLocal)
@ -146,6 +151,17 @@ class App extends Spine.Controller
result
@_placeholderReplacement: (object, attributeConfig, resultLocal) ->
return if !object
return if !attributeConfig
return if _.isEmpty(attributeConfig.linktemplate)
return if !object.constructor
return if !object.constructor.className
return if _.isEmpty(object[attributeConfig.name])
placeholderObjects = { attribute: attributeConfig, user: App.Session.get(), config: App.Config.all() }
placeholderObjects[object.constructor.className.toLowerCase()] = object
"<a href=\"#{App.Utils.replaceTags(attributeConfig.linktemplate, placeholderObjects, true)}\" target=\"blank\">#{App.Utils.htmlEscape(resultLocal)}</a>"
@view: (name) ->
template = (params = {}) ->
JST["app/views/#{name}"](_.extend(params, App.ViewHelpers))

View file

@ -704,7 +704,7 @@ class App.Utils
res.join('')
# textReplaced = App.Utils.replaceTags( template, { user: { firstname: 'Bob', lastname: 'Smith' } } )
@replaceTags: (template, objects) ->
@replaceTags: (template, objects, encodeLink = false) ->
template = template.replace( /#\{\s{0,2}(.+?)\s{0,2}\}/g, (index, key) ->
key = key.replace(/<.+?>/g, '')
levels = key.split(/\./)
@ -744,6 +744,7 @@ class App.Utils
else
value = ''
value = '-' if value is ''
value = encodeURIComponent(value) if encodeLink
value
)

View file

@ -228,3 +228,6 @@ App.ViewHelpers =
className: params.className
iconset: params.iconset
)
replacePlaceholder: (template, items, encodeLink = false) ->
App.Utils.replaceTags(template, items, encodeLink)

View file

@ -20,9 +20,12 @@
</h2>
<p class="help-text"><% if @attribute.help: %><%- @T(@attribute.help) %><% end %><%- @attribute.helpLink %></p>
<% end %>
<div class="controls">
<% if @attribute.remove: %><span><a href="#" class="glyphicon glyphicon-minus"></a></span><% end %>
<% if @attribute.add: %><span><a href="#" class="glyphicon glyphicon-plus"></a></span><% end %>
<div class="controls <% if !_.isEmpty(@attribute.linktemplate) && !_.isEmpty(@placeholderObjects): %>controls--button<% end %>">
<% if !_.isEmpty(@attribute.linktemplate) && !_.isEmpty(@placeholderObjects): %>
<a href="<%- @replacePlaceholder(@attribute.linktemplate, @placeholderObjects, true) %>" class="controls-button" target="_blank" rel="nofollow">
<span class="controls-button-inner"><%- @Icon('external') %></span>
</a>
<% end %>
<span class="help-inline"></span>
<% if @attribute.style != 'block': %>
<span class="help-block"><% if @attribute.help: %><%- @T(@attribute.help) %><% end %><%- @attribute.helpLink %></span>

View file

@ -2,4 +2,5 @@
<div class="js-inputDefault"></div>
<div class="js-inputType"></div>
<div class="js-inputMaxlength"></div>
<div class="js-inputLinkTemplate"></div>
</div>

View file

@ -50,4 +50,5 @@
<%- @Icon('trash') %> <%- @T('Remove') %>
</div>
</table>
<div class="js-inputLinkTemplate"></div>
</div>

View file

@ -2102,10 +2102,20 @@ input.has-error {
.controls--button {
display: flex;
flex-wrap: wrap;
.controls {
flex: 1;
}
.help-inline,
.help-block {
flex-basis: 100%;
}
input,
.form-control {
flex: 1;
flex: 1 1 0%;
@include bidi-style(border-right-width, 0, border-left-width, 1px);
@include bidi-style(border-top-right-radius, 0, border-top-left-radius, 3px);
@include bidi-style(border-bottom-right-radius, 0, border-bottom-left-radius, 3px);
@ -2154,6 +2164,14 @@ input.has-error {
position: relative;
border: 1px solid hsl(0, 0%, 90%);
@include bidi-style(border-radius, 0 3px 3px 0, border-radius, 3px 0 0 3px);
.icon {
fill: hsl(0,0%,61%);
}
&:hover .icon {
fill: hsl(0,0%,33%);
}
}
.searchfield {

View file

@ -133,6 +133,7 @@ possible types
maxlength: 200,
null: true,
note: 'some additional comment', # optional
link_template: '', # optional
},
# select
@ -150,6 +151,7 @@ possible types
multiple: false, # currently only "false" supported
translate: true, # optional
note: 'some additional comment', # optional
link_template: '', # optional
},
# tree_select

View file

@ -1107,6 +1107,7 @@ test("object manager form 1", function() {
var test_params = {
data_option: {
default: "",
linktemplate: "",
maxlength: 120,
type: "text"
},
@ -1218,6 +1219,7 @@ test("object manager form 2", function() {
var test_params = {
data_option: {
default: "",
linktemplate: "",
maxlength: 120,
type: "text"
},
@ -1271,6 +1273,7 @@ test("object manager form 3", function() {
var test_params = {
data_option: {
default: "",
linktemplate: "",
maxlength: 120,
type: "text"
},
@ -1308,6 +1311,7 @@ test("object manager form 3", function() {
test_params = {
data_option: {
default: "",
linktemplate: "",
maxlength: 120,
type: "text"
},
@ -1596,3 +1600,28 @@ test("form deep nesting", function() {
params = App.ControllerForm.params(el)
deepEqual(params, defaults, 'nested params')
});
test("form with external links", function() {
$('#forms').append('<hr><h1>form with external links</h1><div><form id="form20"></form></div>')
var el = $('#form20')
var defaults = {
a: '133',
b: 'abc d',
}
new App.ControllerForm({
el: el,
model: {
configure_attributes: [
{ name: 'a', display: 'Input1', tag: 'input', type: 'text', limit: 100, null: true, linktemplate: "https://example.com/?q=#{ticket.a}" },
{ name: 'b', display: 'Select1', tag: 'select', type: 'text', options: { a: 1, b: 2 }, limit: 100, null: true, linktemplate: "https://example.com/?q=#{ticket.b}" },
],
className: 'Ticket',
},
params: defaults,
});
params = App.ControllerForm.params(el)
deepEqual(params, defaults)
equal('https://example.com/?q=133', el.find('input[name="a"]').parents('.controls').find('a[href]').attr('href'))
equal('https://example.com/?q=abc%20d', el.find('select[name="b"]').parents('.controls').find('a[href]').attr('href'))
});

View file

@ -1425,7 +1425,7 @@ test("check replace tags", function() {
user = new App.User({
firstname: 'Bob',
lastname: 'Smith',
lastname: 'Smith Good',
created_at: '2018-10-31T10:00:00Z',
})
message = "<div>#{user.firstname} #{user.created_at}</div>"
@ -1451,6 +1451,14 @@ test("check replace tags", function() {
}
verify = App.Utils.replaceTags(message, data)
equal(verify, result)
message = "<a href=\"https://example.co/q=#{user.lastname}\">some text</a>"
result = '<a href=\"https://example.co/q=Smith%20Good\">some text</a>'
data = {
user: user
}
verify = App.Utils.replaceTags(message, data, true)
equal(verify, result)
});
// check attibute validation