Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / template_functions.go
1 package revel
2
3 import (
4         "bytes"
5         "errors"
6         "fmt"
7         "github.com/xeonx/timeago"
8         "html"
9         "html/template"
10         "reflect"
11         "strings"
12         "time"
13 )
14
15 var (
16         // The functions available for use in the templates.
17         TemplateFuncs = map[string]interface{}{
18                 "url": ReverseURL,
19                 "set": func(viewArgs map[string]interface{}, key string, value interface{}) template.JS {
20                         viewArgs[key] = value
21                         return template.JS("")
22                 },
23                 "append": func(viewArgs map[string]interface{}, key string, value interface{}) template.JS {
24                         if viewArgs[key] == nil {
25                                 viewArgs[key] = []interface{}{value}
26                         } else {
27                                 viewArgs[key] = append(viewArgs[key].([]interface{}), value)
28                         }
29                         return template.JS("")
30                 },
31                 "field": NewField,
32                 "firstof": func(args ...interface{}) interface{} {
33                         for _, val := range args {
34                                 switch val.(type) {
35                                 case nil:
36                                         continue
37                                 case string:
38                                         if val == "" {
39                                                 continue
40                                         }
41                                         return val
42                                 default:
43                                         return val
44                                 }
45                         }
46                         return nil
47                 },
48                 "option": func(f *Field, val interface{}, label string) template.HTML {
49                         selected := ""
50                         if f.Flash() == val || (f.Flash() == "" && f.Value() == val) {
51                                 selected = " selected"
52                         }
53
54                         return template.HTML(fmt.Sprintf(`<option value="%s"%s>%s</option>`,
55                                 html.EscapeString(fmt.Sprintf("%v", val)), selected, html.EscapeString(label)))
56                 },
57                 "radio": func(f *Field, val string) template.HTML {
58                         checked := ""
59                         if f.Flash() == val {
60                                 checked = " checked"
61                         }
62                         return template.HTML(fmt.Sprintf(`<input type="radio" name="%s" value="%s"%s>`,
63                                 html.EscapeString(f.Name), html.EscapeString(val), checked))
64                 },
65                 "checkbox": func(f *Field, val string) template.HTML {
66                         checked := ""
67                         if f.Flash() == val {
68                                 checked = " checked"
69                         }
70                         return template.HTML(fmt.Sprintf(`<input type="checkbox" name="%s" value="%s"%s>`,
71                                 html.EscapeString(f.Name), html.EscapeString(val), checked))
72                 },
73                 // Pads the given string with &nbsp;'s up to the given width.
74                 "pad": func(str string, width int) template.HTML {
75                         if len(str) >= width {
76                                 return template.HTML(html.EscapeString(str))
77                         }
78                         return template.HTML(html.EscapeString(str) + strings.Repeat("&nbsp;", width-len(str)))
79                 },
80
81                 "errorClass": func(name string, viewArgs map[string]interface{}) template.HTML {
82                         errorMap, ok := viewArgs["errors"].(map[string]*ValidationError)
83                         if !ok || errorMap == nil {
84                                 templateLog.Warn("errorClass: Called 'errorClass' without 'errors' in the view args.")
85                                 return template.HTML("")
86                         }
87                         valError, ok := errorMap[name]
88                         if !ok || valError == nil {
89                                 return template.HTML("")
90                         }
91                         return template.HTML(ErrorCSSClass)
92                 },
93
94                 "msg": func(viewArgs map[string]interface{}, message string, args ...interface{}) template.HTML {
95                         str, ok := viewArgs[CurrentLocaleViewArg].(string)
96                         if !ok {
97                                 return ""
98                         }
99                         return template.HTML(MessageFunc(str, message, args...))
100                 },
101
102                 // Replaces newlines with <br>
103                 "nl2br": func(text string) template.HTML {
104                         return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1))
105                 },
106
107                 // Skips sanitation on the parameter.  Do not use with dynamic data.
108                 "raw": func(text string) template.HTML {
109                         return template.HTML(text)
110                 },
111
112                 // Pluralize, a helper for pluralizing words to correspond to data of dynamic length.
113                 // items - a slice of items, or an integer indicating how many items there are.
114                 // pluralOverrides - optional arguments specifying the output in the
115                 //     singular and plural cases.  by default "" and "s"
116                 "pluralize": func(items interface{}, pluralOverrides ...string) string {
117                         singular, plural := "", "s"
118                         if len(pluralOverrides) >= 1 {
119                                 singular = pluralOverrides[0]
120                                 if len(pluralOverrides) == 2 {
121                                         plural = pluralOverrides[1]
122                                 }
123                         }
124
125                         switch v := reflect.ValueOf(items); v.Kind() {
126                         case reflect.Int:
127                                 if items.(int) != 1 {
128                                         return plural
129                                 }
130                         case reflect.Slice:
131                                 if v.Len() != 1 {
132                                         return plural
133                                 }
134                         default:
135                                 templateLog.Error("pluralize: unexpected type: ", "value", v)
136                         }
137                         return singular
138                 },
139
140                 // Format a date according to the application's default date(time) format.
141                 "date": func(date time.Time) string {
142                         return date.Format(DateFormat)
143                 },
144                 "datetime": func(date time.Time) string {
145                         return date.Format(DateTimeFormat)
146                 },
147                 // Fetch an object from the session.
148                 "session": func(key string, viewArgs map[string]interface{}) interface{} {
149                         if viewArgs != nil {
150                                 if c, found := viewArgs["_controller"]; found {
151                                         if v, err := c.(*Controller).Session.Get(key); err == nil {
152                                                 return v
153                                         } else {
154                                                 templateLog.Errorf("template.session, key %s error %v", key, err)
155                                         }
156                                 } else {
157                                         templateLog.Warnf("template.session, key %s requested without controller", key)
158                                 }
159                         } else {
160                                 templateLog.Warnf("template.session, key %s requested passing in view args", key)
161                         }
162                         return ""
163                 },
164
165                 "slug": Slug,
166                 "even": func(a int) bool { return (a % 2) == 0 },
167
168                 // Using https://github.com/xeonx/timeago
169                 "timeago": TimeAgo,
170                 "i18ntemplate": func(args ...interface{}) (template.HTML, error) {
171                         templateName, lang := "", ""
172                         var viewArgs interface{}
173                         switch len(args) {
174                         case 0:
175                                 templateLog.Error("i18ntemplate: No arguments passed to template call")
176                         case 1:
177                                 // Assume only the template name is passed in
178                                 templateName = args[0].(string)
179                         case 2:
180                                 // Assume template name and viewArgs is passed in
181                                 templateName = args[0].(string)
182                                 viewArgs = args[1]
183                                 // Try to extract language from the view args
184                                 if viewargsmap, ok := viewArgs.(map[string]interface{}); ok {
185                                         lang, _ = viewargsmap[CurrentLocaleViewArg].(string)
186                                 }
187                         default:
188                                 // Assume third argument is the region
189                                 templateName = args[0].(string)
190                                 viewArgs = args[1]
191                                 lang, _ = args[2].(string)
192                                 if len(args) > 3 {
193                                         templateLog.Error("i18ntemplate: Received more parameters then needed for", "template", templateName)
194                                 }
195                         }
196
197                         var buf bytes.Buffer
198                         // Get template
199                         tmpl, err := MainTemplateLoader.TemplateLang(templateName, lang)
200                         if err == nil {
201                                 err = tmpl.Render(&buf, viewArgs)
202                         } else {
203                                 templateLog.Error("i18ntemplate: Failed to render i18ntemplate ", "name", templateName, "error", err)
204                         }
205                         return template.HTML(buf.String()), err
206                 },
207         }
208 )
209
210 /////////////////////
211 // Template functions
212 /////////////////////
213
214 // ReverseURL returns a url capable of invoking a given controller method:
215 // "Application.ShowApp 123" => "/app/123"
216 func ReverseURL(args ...interface{}) (template.URL, error) {
217         if len(args) == 0 {
218                 return "", errors.New("no arguments provided to reverse route")
219         }
220
221         action := args[0].(string)
222         if action == "Root" {
223                 return template.URL(AppRoot), nil
224         }
225
226         pathData, found := splitActionPath(nil, action, true)
227
228         if !found {
229                 return "", fmt.Errorf("reversing '%s', expected 'Controller.Action'", action)
230         }
231
232         // Look up the types.
233
234         if pathData.TypeOfController == nil {
235                 return "", fmt.Errorf("Failed reversing %s: controller not found %#v", action, pathData)
236         }
237
238         // Note method name is case insensitive search
239         methodType := pathData.TypeOfController.Method(pathData.MethodName)
240         if methodType == nil {
241                 return "", errors.New("revel/controller: In " + action + " failed to find function " + pathData.MethodName)
242         }
243
244         if len(methodType.Args) < len(args)-1 {
245                 return "", fmt.Errorf("reversing %s: route defines %d args, but received %d",
246                         action, len(methodType.Args), len(args)-1)
247         }
248         // Unbind the arguments.
249         argsByName := make(map[string]string)
250         // Bind any static args first
251         fixedParams := len(pathData.FixedParamsByName)
252
253         for i, argValue := range args[1:] {
254                 Unbind(argsByName, methodType.Args[i+fixedParams].Name, argValue)
255         }
256
257         return template.URL(MainRouter.Reverse(args[0].(string), argsByName).URL), nil
258 }
259
260 func Slug(text string) string {
261         separator := "-"
262         text = strings.ToLower(text)
263         text = invalidSlugPattern.ReplaceAllString(text, "")
264         text = whiteSpacePattern.ReplaceAllString(text, separator)
265         text = strings.Trim(text, separator)
266         return text
267 }
268
269 var timeAgoLangs = map[string]timeago.Config{}
270
271 func TimeAgo(args ...interface{}) string {
272
273         datetime := time.Now()
274         lang := ""
275         var viewArgs interface{}
276         switch len(args) {
277         case 0:
278                 templateLog.Error("TimeAgo: No arguments passed to timeago")
279         case 1:
280                 // only the time is passed in
281                 datetime = args[0].(time.Time)
282         case 2:
283                 // time and region is passed in
284                 datetime = args[0].(time.Time)
285                 switch v := reflect.ValueOf(args[1]); v.Kind() {
286                 case reflect.String:
287                         // second params type string equals region
288                         lang, _ = args[1].(string)
289                 case reflect.Map:
290                         // second params type map equals viewArgs
291                         viewArgs = args[1]
292                         if viewargsmap, ok := viewArgs.(map[string]interface{}); ok {
293                                 lang, _ = viewargsmap[CurrentLocaleViewArg].(string)
294                         }
295                 default:
296                         templateLog.Error("TimeAgo: unexpected type: ", "value", v)
297                 }
298         default:
299                 // Assume third argument is the region
300                 datetime = args[0].(time.Time)
301                 if reflect.ValueOf(args[1]).Kind() != reflect.Map {
302                         templateLog.Error("TimeAgo: unexpected type", "value", args[1])
303                 }
304                 if reflect.ValueOf(args[2]).Kind() != reflect.String {
305                         templateLog.Error("TimeAgo: unexpected type: ", "value", args[2])
306                 }
307                 viewArgs = args[1]
308                 lang, _ = args[2].(string)
309                 if len(args) > 3 {
310                         templateLog.Error("TimeAgo: Received more parameters then needed for timeago")
311                 }
312         }
313         if lang == "" {
314                 lang, _ = Config.String(defaultLanguageOption)
315                 if lang == "en" {
316                         timeAgoLangs[lang] = timeago.English
317                 }
318         }
319         _, ok := timeAgoLangs[lang]
320         if !ok {
321                 timeAgoLangs[lang] = timeago.Config{
322                         PastPrefix:   "",
323                         PastSuffix:   " " + MessageFunc(lang, "ago"),
324                         FuturePrefix: MessageFunc(lang, "in") + " ",
325                         FutureSuffix: "",
326                         Periods: []timeago.FormatPeriod{
327                                 {time.Second, MessageFunc(lang, "about a second"), MessageFunc(lang, "%d seconds")},
328                                 {time.Minute, MessageFunc(lang, "about a minute"), MessageFunc(lang, "%d minutes")},
329                                 {time.Hour, MessageFunc(lang, "about an hour"), MessageFunc(lang, "%d hours")},
330                                 {timeago.Day, MessageFunc(lang, "one day"), MessageFunc(lang, "%d days")},
331                                 {timeago.Month, MessageFunc(lang, "one month"), MessageFunc(lang, "%d months")},
332                                 {timeago.Year, MessageFunc(lang, "one year"), MessageFunc(lang, "%d years")},
333                         },
334                         Zero:          MessageFunc(lang, "about a second"),
335                         Max:           73 * time.Hour,
336                         DefaultLayout: "2006-01-02",
337                 }
338
339         }
340         return timeAgoLangs[lang].Format(datetime)
341 }