Skip to content

Commit 7b8c848

Browse files
committed
Fix sorting of attributes in tag/content_tag
1 parent 6adc1de commit 7b8c848

File tree

4 files changed

+179
-195
lines changed

4 files changed

+179
-195
lines changed

lib/phoenix_html/link.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ defmodule Phoenix.HTML.Link do
153153
else
154154
{csrf_data, opts} = csrf_data(to, opts)
155155
opts = Keyword.put_new(opts, :rel, "nofollow")
156-
content_tag(:a, text, [href: to, data: [method: method, to: to] ++ csrf_data] ++ opts)
156+
content_tag(:a, text, [data: csrf_data ++ [method: method, to: to], href: to] ++ opts)
157157
end
158158
end
159159

@@ -217,7 +217,7 @@ defmodule Phoenix.HTML.Link do
217217
content_tag(:button, text, [data: [method: method, to: to]] ++ opts)
218218
else
219219
{csrf_data, opts} = csrf_data(to, opts)
220-
content_tag(:button, text, [data: [method: method, to: to] ++ csrf_data] ++ opts)
220+
content_tag(:button, text, [data: csrf_data ++ [method: method, to: to]] ++ opts)
221221
end
222222
end
223223

lib/phoenix_html/tag.ex

Lines changed: 50 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ defmodule Phoenix.HTML.Tag do
2424

2525
import Phoenix.HTML
2626

27-
@special_attributes ["data", "aria", "class"]
2827
@csrf_param "_csrf_token"
2928

3029
@doc ~S"""
@@ -64,7 +63,7 @@ defmodule Phoenix.HTML.Tag do
6463
def tag(name), do: tag(name, [])
6564

6665
def tag(name, attrs) when is_list(attrs) do
67-
{:safe, [?<, to_string(name), build_attrs(attrs) |> Enum.sort() |> tag_attrs(), ?>]}
66+
{:safe, [?<, to_string(name), sorted_attrs(attrs), ?>]}
6867
end
6968

7069
@doc ~S"""
@@ -103,96 +102,71 @@ defmodule Phoenix.HTML.Tag do
103102
name = to_string(name)
104103
{:safe, escaped} = html_escape(content)
105104

106-
{:safe,
107-
[?<, name, build_attrs(attrs) |> Enum.sort() |> tag_attrs(), ?>, escaped, ?<, ?/, name, ?>]}
105+
{:safe, [?<, name, sorted_attrs(attrs), ?>, escaped, ?<, ?/, name, ?>]}
108106
end
109107

110-
@doc """
108+
@doc ~S"""
111109
Escapes an enumerable of attributes, returning iodata.
112110
113111
Pay attention that, unlike `tag/2` and `content_tag/2`, this
114112
function does not sort the attributes. However if given a map,
115113
note also that the key ordering may change.
116114
117-
iex> attributes_escape(title: "the title", id: "the id", selected: true)
118-
{:safe,
119-
[
120-
[32, "title", 61, 34, "the title", 34],
121-
[32, "id", 61, 34, "the id", 34],
122-
[32, "selected"]
123-
]}
124-
125-
iex> attributes_escape(%{data: [phx: [value: [foo: "bar"]]], class: "foo"})
126-
{:safe,
127-
[
128-
[32, "class", 61, 34, "foo", 34],
129-
[32, "data-phx-value-foo", 61, 34, "bar", 34]
130-
]}
115+
iex> safe_to_string attributes_escape(title: "the title", id: "the id", selected: true)
116+
" title=\"the title\" id=\"the id\" selected"
117+
118+
iex> safe_to_string attributes_escape(%{data: [phx: [value: [foo: "bar"]]], class: "foo"})
119+
" class=\"foo\" data-phx-value-foo=\"bar\""
131120
132121
"""
122+
def attributes_escape(attrs) when is_list(attrs) do
123+
{:safe, build_attrs(attrs)}
124+
end
125+
133126
def attributes_escape(attrs) do
134-
{:safe, attrs |> build_attrs() |> Enum.reverse() |> tag_attrs()}
127+
{:safe, attrs |> Enum.to_list() |> build_attrs()}
135128
end
136129

137-
defp build_attrs([]), do: []
138-
defp build_attrs([_ | _] = attrs), do: build_attrs(attrs, [])
139-
defp build_attrs(attrs), do: attrs |> Enum.to_list() |> build_attrs([])
130+
defp build_attrs([{"data", v} | t]) when is_list(v),
131+
do: nested_attrs(v, " data", t)
140132

141-
defp build_attrs([], acc), do: acc
133+
defp build_attrs([{"aria", v} | t]) when is_list(v),
134+
do: nested_attrs(v, " aria", t)
142135

143-
defp build_attrs([{k, v} | t], acc) when k in @special_attributes do
144-
build_attrs([{String.to_atom(k), v} | t], acc)
145-
end
136+
defp build_attrs([{"class", v} | t]) when is_list(v),
137+
do: [" class=\"", class_value(v), ?" | build_attrs(t)]
146138

147-
defp build_attrs([{:data, v} | t], acc) when is_list(v) do
148-
build_attrs(t, nested_attrs("data", v, acc))
149-
end
139+
defp build_attrs([{:data, v} | t]) when is_list(v),
140+
do: nested_attrs(v, " data", t)
150141

151-
defp build_attrs([{:aria, v} | t], acc) when is_list(v) do
152-
build_attrs(t, nested_attrs("aria", v, acc))
153-
end
142+
defp build_attrs([{:aria, v} | t]) when is_list(v),
143+
do: nested_attrs(v, " aria", t)
154144

155-
defp build_attrs([{:class, v} | t], acc) when is_list(v) do
156-
build_attrs(t, [{"class", class_value(v)} | acc])
157-
end
145+
defp build_attrs([{:class, v} | t]) when is_list(v),
146+
do: [" class=\"", class_value(v), ?" | build_attrs(t)]
158147

159-
defp build_attrs([{k, true} | t], acc) do
160-
build_attrs(t, [key_escape(k) | acc])
161-
end
148+
defp build_attrs([{k, true} | t]),
149+
do: [?\s, key_escape(k) | build_attrs(t)]
162150

163-
defp build_attrs([{_, false} | t], acc) do
164-
build_attrs(t, acc)
165-
end
151+
defp build_attrs([{_, false} | t]),
152+
do: build_attrs(t)
166153

167-
defp build_attrs([{_, nil} | t], acc) do
168-
build_attrs(t, acc)
169-
end
154+
defp build_attrs([{_, nil} | t]),
155+
do: build_attrs(t)
170156

171-
defp build_attrs([{k, v} | t], acc) do
172-
build_attrs(t, [{key_escape(k), v} | acc])
173-
end
157+
defp build_attrs([{k, v} | t]),
158+
do: [?\s, key_escape(k), ?=, ?", attr_escape(v), ?" | build_attrs(t)]
174159

175-
defp tag_attrs([]), do: []
160+
defp build_attrs([]), do: []
176161

177-
defp tag_attrs(attrs) do
178-
for a <- attrs do
179-
case a do
180-
{k, v} -> [?\s, k, ?=, ?", attr_escape(v), ?"]
181-
k -> [?\s, k]
182-
end
183-
end
184-
end
162+
defp nested_attrs([{k, v} | kv], attr, t) when is_list(v),
163+
do: [nested_attrs(v, "#{attr}-#{key_escape(k)}", []) | nested_attrs(kv, attr, t)]
185164

186-
defp nested_attrs(attr, dict, acc) do
187-
Enum.reduce(dict, acc, fn {k, v}, acc ->
188-
attr_name = "#{attr}-#{key_escape(k)}"
165+
defp nested_attrs([{k, v} | kv], attr, t),
166+
do: [attr, ?-, key_escape(k), ?=, ?", attr_escape(v), ?" | nested_attrs(kv, attr, t)]
189167

190-
case is_list(v) do
191-
true -> nested_attrs(attr_name, v, acc)
192-
false -> [{attr_name, v} | acc]
193-
end
194-
end)
195-
end
168+
defp nested_attrs([], _attr, t),
169+
do: build_attrs(t)
196170

197171
defp class_value(value) when is_list(value) do
198172
value
@@ -212,6 +186,16 @@ defmodule Phoenix.HTML.Tag do
212186
defp attr_escape(other) when is_binary(other), do: Phoenix.HTML.Engine.encode_to_iodata!(other)
213187
defp attr_escape(other), do: Phoenix.HTML.Safe.to_iodata(other)
214188

189+
defp sorted_attrs(attrs) when is_list(attrs),
190+
do: attrs |> normalize_attrs() |> Enum.sort() |> build_attrs()
191+
192+
defp sorted_attrs(attrs),
193+
do: attrs |> Enum.to_list() |> sorted_attrs()
194+
195+
defp normalize_attrs([{k, v} | tail]), do: [{k, v} | normalize_attrs(tail)]
196+
defp normalize_attrs([k | tail]), do: [{k, true} | normalize_attrs(tail)]
197+
defp normalize_attrs([]), do: []
198+
215199
@doc ~S"""
216200
Generates a form tag.
217201

0 commit comments

Comments
 (0)