Fixes issue #2103 - clickable URL (template) for ObjectManager Attribute.
This commit is contained in:
parent
29c9d3546b
commit
848175a290
12 changed files with 122 additions and 11 deletions
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -228,3 +228,6 @@ App.ViewHelpers =
|
|||
className: params.className
|
||||
iconset: params.iconset
|
||||
)
|
||||
|
||||
replacePlaceholder: (template, items, encodeLink = false) ->
|
||||
App.Utils.replaceTags(template, items, encodeLink)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -50,4 +50,5 @@
|
|||
<%- @Icon('trash') %> <%- @T('Remove') %>
|
||||
</div>
|
||||
</table>
|
||||
<div class="js-inputLinkTemplate"></div>
|
||||
</div>
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue