/
symphony.js
410 lines (349 loc) · 9.68 KB
/
symphony.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
/*!
* Symphony 2.7.x, https://www.getsymphony.com, MIT license
*/
/**
* @package assets
*/
/**
* The Symphony object provides language, message and context management.
*
* @class
*/
var Symphony = (function($, crossroads) {
// Internal Symphony storage
var Storage = {
Context: {},
Dictionary: {},
Support: {}
};
/*-------------------------------------------------------------------------
Functions
-------------------------------------------------------------------------*/
// Replace variables in string
function replaceVariables(string, inserts) {
if($.type(string) === 'string' && $.type(inserts) === 'object') {
$.each(inserts, function(index, value) {
string = string.replace('{$' + index + '}', value);
});
}
return string;
}
// Get localised strings
function translate(strings) {
var env = Symphony.Context.get('env');
if (!env) {
// no env, can't continue
return;
}
var namespace = $.trim(env['page-namespace']),
data = {
'strings': strings
};
// Validate and set namespace
if($.type(namespace) === 'string' && namespace !== '') {
data.namespace = namespace;
}
// Request translations
$.ajax({
async: false,
type: 'GET',
url: Symphony.Context.get('symphony') + '/ajax/translate/',
data: data,
dataType: 'json',
// Add localised strings
success: function(result) {
$.extend(true, Storage.Dictionary, result);
},
// Use English strings on error
error: function(jqXHR, textStatus, errorThrown) {
$.extend(true, Storage.Dictionary, strings);
}
});
}
// request animation frame
var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
window.oRequestAnimationFrame || function (f) { return window.setTimeout(f, 16/1000) };
var craf = window.cancelAnimationFrame || window.webkitCancelRequestAnimationFrame ||
window.mozCancelRequestAnimationFrame || window.oCancelRequestAnimationFrame ||
window.msCancelRequestAnimationFrame || function (t) { window.clearTimeout(t); };
/*-----------------------------------------------------------------------*/
// Set browser support information
try {
Storage.Support.localStorage = !!localStorage.getItem;
} catch(e) {
Storage.Support.localStorage = false;
}
// Deep copy jQuery.support
$.extend(true, Storage.Support, $.support);
/*-------------------------------------------------------------------------
Symphony API
-------------------------------------------------------------------------*/
return {
/**
* Symphony backend view using Crossroads
*
* @since Symphony 2.4
*/
View: {
/**
* Add function to view
*
* @param {String} pattern
* Expression to match the view, using the Symphony URL as base
* @param {Function} handler
* Function that should be applied to a view
* @param {Integer} priority
* Priority of the function
* @param {Boolean} greedy
* If set to `false`, only executes the first matched view, defaults to `true`
* @return {Route}
* Returns a route object
*/
add: function addRoute(pattern, handler, priority, greedy) {
var route;
pattern = Symphony.Context.get('path') + pattern;
route = crossroads.addRoute(pattern, handler, priority);
if(greedy !== false) {
route.greedy = true;
}
return route;
},
/**
* Render given URL, defaults to the current backend URL
*
* @param {String} url
* The URL of the view that should be rendered, optional
* @param {Boolean} greedy
* Determines, if only the first or all matching views are rendered,
* defaults to `true (all)
*/
render: function renderRoute(url, greedy) {
if(!url) {
url = Symphony.Context.get('path') + Symphony.Context.get('route');
}
if(greedy === false) {
crossroads.greedyEnabled = false;
}
crossroads.parse(url);
}
},
/**
* Storage for the main Symphony elements`.
* Symphony automatically adds all main UI areas.
*
* @since Symphony 2.4
*/
Elements: {
window: null,
html: null,
body: null,
wrapper: null,
header: null,
nav: null,
session: null,
context: null,
contents: null
},
/**
* The Context object contains general information about the system,
* the backend, the current user. It includes an add and a get function.
* This is a private object and can only be accessed via add and get.
*
* @class
*/
Context: {
/**
* Add data to the Context object
*
* @param {String} group
* Name of the data group
* @param {String|Object} values
* Object or string to be stored
*/
add: function addContext(group, values) {
// Add multiple groups
if(!group && $.type(values) === 'object') {
$.extend(Storage.Context, values);
}
// Add to existing group
else if(Storage.Context[group] && $.type(values) !== 'string') {
$.extend(Storage.Context[group], values);
}
// Add new group
else {
Storage.Context[group] = values;
}
// Always return
return true;
},
/**
* Get data from the Context object
*
* @param {String} group
* Name of the group to be returned
*/
get: function getContext(group) {
// Return full context, if no group is set
if(!group) {
return Storage.Context;
}
// Return false if group does not exist in Storage
if(typeof Storage.Context[group] === 'undefined') {
return false;
}
// Default: Return context group
return Storage.Context[group];
}
},
/**
* The Language object stores the dictionary with all needed translations.
* It offers public functions to add strings and get their translation and
* it offers private functions to handle variables and get the translations via
* an synchronous AJAX request.
* Since Symphony 2.3, it is also possible to define different translations
* for the same string, by using page namespaces.
* This is a private object
*
* @class
*/
Language: {
/**
* Add strings to the Dictionary
*
* @param {Object} strings
* Object with English string as key, value should be false
*/
add: function addStrings(strings) {
// English system
if(Symphony.Context.get('lang') === 'en') {
$.extend(true, Storage.Dictionary, strings);
}
// Localised system
else {
// Check if strings have already been translated
$.each(strings, function checkStrings(index, key) {
if(key in Storage.Dictionary) {
delete strings[key];
}
});
// Translate strings
if(!$.isEmptyObject(strings)) {
translate(strings);
}
}
},
/**
* Get translated string from the Dictionary.
* The function replaces variables like {$name} with the a specified value if
* an object of inserts is passed in the function call.
*
* @param {String} string
* English string to be translated
* @param {Object} inserts
* Object with variable name and value pairs
* @return {String}
* Returns the translated string
*/
get: function getString(string, inserts) {
var translation = Storage.Dictionary[string];
// Validate and set translation
if($.type(translation) === 'string') {
string = translation;
}
// Insert variables
string = replaceVariables(string, inserts);
// Return translated string
return string;
}
},
/**
* A collection of properties that represent the presence of
* different browser features and also contains the test results
* from jQuery.support.
*
* @class
*/
Support: Storage.Support,
/**
* A namespace for core interface components
*
* @since Symphony 2.6
*/
Interface: {},
/**
* A namespace for extension to store global functions
*
* @since Symphony 2.3
*/
Extensions: {},
/**
* Helper functions
*
* @since Symphony 2.4
*/
Utilities: {
/**
* Get a jQuery object of all elements within the current viewport
*
* @since Symphony 2.4
*/
inSight: function inSight(elements) {
var windowHeight = window.innerHeight,
visibles = $();
elements.each(function() {
var context = this.getBoundingClientRect();
if(
(context.top >= 0 && context.top <= windowHeight) || // Element top in sight
(context.bottom >= 0 && context.bottom <= windowHeight) || // Element bottom in sight
(context.top <= 0 && context.bottom >= windowHeight) // Element overflowing viewport
) {
visibles = visibles.add(this);
}
else if (visibles.length > 0) {
return false;
}
});
return visibles;
},
/**
* Returns the XSRF token for the backend
*
* @since Symphony 2.4
* @param boolean $serialised
* If passed as true, this function will return the string as a serialised
* form elements, ie. field=value. If omitted, or false, this function
* will just return the XSRF token.
*/
getXSRF: function(serialised) {
var xsrf = Symphony.Elements.contents.find('input[name=xsrf]').val();
if(serialised === true) {
return 'xsrf=' + encodeURIComponent(xsrf);
}
else {
return xsrf;
}
},
/**
* Cross browser wrapper around requestFrameAnimation
*
* @since Symphony 2.5
* @param function $func
* The callback to schedule for frame animation
*/
requestAnimationFrame: function (func) {
return raf.call(window, func);
},
/**
* Cross browser wrapper around cancelAnimationFrame
*
* @since Symphony 2.5
* @param Integer $t
* The request id
*/
cancelAnimationFrame: function (t) {
return craf.call(window, t);
}
}
};
}(window.jQuery, window.crossroads));