~xibo-maintainers/xibo/tempel

408 by Dan Garner
Started adding the core pages
1
{#
2
/*
3
 * Spring Signage Ltd - http://www.springsignage.com
4
 * Copyright (C) 2015 Spring Signage Ltd
5
 * (${FILE_NAME})
6
 */
7
8
#}
9
{% extends "authed.twig" %}
10
{% import "inline.twig" as inline %}
11
12
{% block actionMenu %}
13
    <ul class="nav nav-pills pull-right">
513 by Dan Garner
Button improvements (#183)
14
        <li class="btn btn-success btn-xs"><a class="XiboFormButton btns" title="{% trans "Add a new Display Group" %}" href="{{ urlFor("displayGroup.add.form") }}"> <i class="fa fa-desktop" aria-hidden="true"></i> {% trans "Add Display Group" %}</a></li>
408 by Dan Garner
Started adding the core pages
15
    </ul>
16
{% endblock %}
17
18
{% block pageContent %}
19
    <div class="widget">
20
        <div class="widget-title">{% trans "Display Groups" %}</div>
21
        <div class="widget-body">
22
            <div class="XiboGrid" id="{{ random() }}">
542.1.7 by Dan Garner
Add a filter to the display group page
23
                <div class="XiboFilter well">
408 by Dan Garner
Started adding the core pages
24
                    <div class="FilterDiv" id="Filter">
25
                        <form class="form-inline">
542.1.7 by Dan Garner
Add a filter to the display group page
26
                            {% set title %}{% trans "Name" %}{% endset %}
27
                            {{ inline.input("displayGroup", title) }}
28
650.1.13 by Dan Garner
Display paged select in appropriate places.
29
                            {% set title %}{% trans "Display" %}{% endset %}
542.1.7 by Dan Garner
Add a filter to the display group page
30
                            {% set attributes = [
650.1.13 by Dan Garner
Display paged select in appropriate places.
31
                                { name: "data-width", value: "200px" },
650.1.7 by Dan Garner
Look at replacing bootstrap-select with select2 to solve paging and searching in lists.
32
                                { name: "data-allow-clear", value: "true" },
33
                                { name: "data-placeholder--id", value: null },
650.1.13 by Dan Garner
Display paged select in appropriate places.
34
                                { name: "data-placeholder--value", value: "" },
35
                                { name: "data-search-url", value: urlFor("display.search") },
36
                                { name: "data-search-term", value: "display" },
37
                                { name: "data-id-property", value: "displayId" },
38
                                { name: "data-text-property", value: "display" }
542.1.7 by Dan Garner
Add a filter to the display group page
39
                            ] %}
40
                            {% set helpText %}{% trans "Return Display Groups that directly contain the selected Display." %}{% endset %}
650.1.13 by Dan Garner
Display paged select in appropriate places.
41
                            {{ inline.dropdown("displayId", "single", title, "", null, "displayId", "display", helpText, "pagedSelect", "", "", "", attributes) }}
542.1.7 by Dan Garner
Add a filter to the display group page
42
43
                            {% set title %}{% trans "Nested Display" %}{% endset %}
44
                            {% set helpText %}{% trans "Return Display Groups that contain the selected Display somewhere in the nested Display Group relationship tree." %}{% endset %}
650.1.13 by Dan Garner
Display paged select in appropriate places.
45
                            {{ inline.dropdown("nestedDisplayId", "single", title, "", null, "displayId", "display", helpText, "pagedSelect", "", "", "", attributes) }}
542.1.7 by Dan Garner
Add a filter to the display group page
46
47
                            {% set title %}{% trans "Dynamic Criteria" %}{% endset %}
48
                            {{ inline.input("dynamicCriteria", title) }}
581.1.18 by Dan Garner
Support for tags on DisplayGroups/Displays.
49
50
                            {% set title %}{% trans "Tags" %}{% endset %}
51
                            {% set helpText %}{% trans "A comma separated list of tags to filter by. Enter --no-tag to see items without tags." %}{% endset %}
52
                            {{ inline.inputWithTags("tags", title, null, helpText) }}
408 by Dan Garner
Started adding the core pages
53
                        </form>
54
                    </div>
55
                </div>
56
                <div class="XiboData">
57
                    <table id="displaygroups" class="table table-striped">
58
                        <thead>
59
                            <tr>
542.1.7 by Dan Garner
Add a filter to the display group page
60
                                <th>{% trans "ID" %}</th>
408 by Dan Garner
Started adding the core pages
61
                                <th>{% trans "Name" %}</th>
62
                                <th>{% trans "Description" %}</th>
454.4.128 by Dan Garner
UI/Model/Structure for dynamic display groups.
63
                                <th>{% trans "Is Dynamic?" %}</th>
64
                                <th>{% trans "Criteria" %}</th>
581.1.18 by Dan Garner
Support for tags on DisplayGroups/Displays.
65
                                <th>{% trans "Tags" %}</th>
408 by Dan Garner
Started adding the core pages
66
                                <th></th>
67
                            </tr>
68
                        </thead>
69
                        <tbody>
70
71
                        </tbody>
72
                    </table>
73
                </div>
74
            </div>
75
        </div>
76
    </div>
77
{% endblock %}
78
79
{% block javaScript %}
80
    <script type="text/javascript">
454.4.20 by Dan Garner
Added i18n for dataTables library
81
        var table = $("#displaygroups").DataTable({ "language": dataTablesLanguage,
605.2.9 by Dan Garner
Set an unlimited save state duration for dataTables.
82
            serverSide: true, stateSave: true, stateDuration: 0,
591.1.37 by Dan Garner
Apply settings saving out to other grids.
83
            stateLoadCallback: function (settings, callback) {
84
                var data;
85
                $.ajax({
86
                    type: "GET",
87
                    async: false,
88
                    url: "{{ urlFor("user.pref") }}?preference=displayGroupGrid",
89
                    dataType: 'json',
90
                    success: function (json) {
650.1.2 by Dan Garner
Protect against a bad save grid save causing the data table to fail loading
91
                        try {
92
                            if (json.success) {
93
                                data = JSON.parse(json.data.value);
94
                            }
95
                        } catch (e) {
96
                            // Do nothing
97
                        }
591.1.37 by Dan Garner
Apply settings saving out to other grids.
98
                    }
99
                });
100
                return data;
101
            },
102
            stateSaveCallback: function (settings, data) {
103
                $.ajax({
104
                    type: "POST",
105
                    url: "{{ urlFor("user.pref") }}",
106
                    data: {
107
                        preference: [{
108
                            option: "displayGroupGrid",
109
                            value: JSON.stringify(data)
110
                        }]
111
                    }
112
                });
113
            },
434.1.1 by Dan Garner
Converting phpunit over to webserver-less integration testing. Commit before replacing session static with DI
114
            "filter": false,
408 by Dan Garner
Started adding the core pages
115
            searchDelay: 3000,
542.1.15 by Dan Garner
Nested view for DisplayGroup membership
116
            "order": [[ 1, "asc"]],
434.1.1 by Dan Garner
Converting phpunit over to webserver-less integration testing. Commit before replacing session static with DI
117
            ajax: {
118
                "url": "{{ urlFor("displayGroup.search") }}",
119
                "data": function(d) {
120
                    $.extend(d, $("#displaygroups").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
121
                }
122
            },
408 by Dan Garner
Started adding the core pages
123
            "columns": [
542.1.7 by Dan Garner
Add a filter to the display group page
124
                { "data": "displayGroupId" },
581.1.13 by Dan Garner
Formatting for double spaces on key fields.
125
                { "data": "displayGroup", "render": dataTableSpacingPreformatted },
408 by Dan Garner
Started adding the core pages
126
                { "data": "description" },
454.4.128 by Dan Garner
UI/Model/Structure for dynamic display groups.
127
                { "data": "isDynamic", "render": dataTableTickCrossColumn },
128
                { "data": "dynamicCriteria" },
408 by Dan Garner
Started adding the core pages
129
                {
581.1.18 by Dan Garner
Support for tags on DisplayGroups/Displays.
130
                    "name": "tags",
131
                    "sortable": false,
132
                    "data": dataTableCreateTags
133
                },
134
                {
408 by Dan Garner
Started adding the core pages
135
                    "orderable": false,
136
                    "data": dataTableButtonsColumn
137
                }
138
            ]
139
        });
140
141
        table.on('draw', dataTableDraw);
581.1.18 by Dan Garner
Support for tags on DisplayGroups/Displays.
142
        table.on('draw', { form: $("#displaygroups").closest(".XiboGrid").find(".FilterDiv form") }, dataTableCreateTagEvents);
408 by Dan Garner
Started adding the core pages
143
        table.on('processing.dt', dataTableProcessing);
581.1.18 by Dan Garner
Support for tags on DisplayGroups/Displays.
144
        dataTableAddButtons(table, $('#displaygroups_wrapper').find('.col-sm-6').eq(1));
454.4.128 by Dan Garner
UI/Model/Structure for dynamic display groups.
145
146
        var displayTable;
147
        var criteria;
148
149
        function displayGroupFormOpen(dialog) {
454.4.129 by Dan Garner
Notification on Display Add/Edit and on Membership change for Display Group
150
            displayTable = null;
454.4.128 by Dan Garner
UI/Model/Structure for dynamic display groups.
151
152
            $(dialog).find("input[name=dynamicCriteria]").on("keyup", $.debounce(500, function() {
153
                displayGroupQueryDynamicMembers(dialog);
154
            }));
155
156
            // First time in there
157
            displayGroupQueryDynamicMembers(dialog);
158
        }
159
160
        function displayGroupQueryDynamicMembers(dialog) {
161
162
            if ($(dialog).find("input[name=isDynamic]")[0].checked) {
163
164
                criteria = $(dialog).find("input[name=dynamicCriteria]").val();
165
166
                if (criteria == "") {
167
                    if (displayTable != null) {
168
                        displayTable.destroy();
169
                        displayTable = null;
170
                        $("#displays tbody").empty();
171
                    }
172
173
                    return;
174
                }
175
176
                if (displayTable != null) {
177
                    displayTable.ajax.reload();
178
                } else {
179
                    displayTable = $("#displays").DataTable({
180
                        "language": dataTablesLanguage,
181
                        serverSide: true,
605.2.9 by Dan Garner
Set an unlimited save state duration for dataTables.
182
                        stateSave: true, stateDuration: 0,
454.4.128 by Dan Garner
UI/Model/Structure for dynamic display groups.
183
                        filter: false,
184
                        searchDelay: 3000,
185
                        "order": [[1, "asc"]],
186
                        ajax: {
187
                            "url": "{{ urlFor("display.search") }}",
188
                            "data": function (d) {
189
                                console.log(criteria);
190
                                $.extend(d, {display: criteria});
191
                            }
192
                        },
193
                        "columns": [
194
                            {"data": "displayId"},
195
                            {"data": "display"},
196
                            {
197
                                "data": "mediaInventoryStatus",
198
                                "render": function (data, type, row) {
199
                                    if (type != "display")
200
                                        return data;
201
202
                                    var icon = "";
203
                                    if (data == 1)
204
                                        icon = "fa-check";
205
                                    else if (data == 0)
206
                                        icon = "fa-times";
207
                                    else
208
                                        icon = "fa-cloud-download";
209
210
                                    return "<span class='fa " + icon + "'></span>";
211
                                }
212
                            },
213
                            {"data": "licensed", "render": dataTableTickCrossColumn}
214
                        ]
215
                    });
216
217
                    displayTable.on('processing.dt', dataTableProcessing);
218
                }
219
            }
220
        }
454.4.132 by Dan Garner
Initial draft implementation of Nested Display Groups. The UI part in particular will need some work as there are 2 API calls involved.
221
222
        function displayGroupMembersFormOpen(dialog) {
548 by Dan Garner
1.8.0-rc2 bug fixes and small enchancements (#228)
223
            
224
            var control = $(dialog).find(".controlDiv");
225
226
            // This contains the changes made since the form open
227
            if (control.data().members == undefined)
228
                control.data().members = {
454.4.132 by Dan Garner
Initial draft implementation of Nested Display Groups. The UI part in particular will need some work as there are 2 API calls involved.
229
                    displays: {},
230
                    displayGroups: {}
231
                };
232
548 by Dan Garner
1.8.0-rc2 bug fixes and small enchancements (#228)
233
            var table = $("#displaysMembersTable").DataTable({ 
234
                "language": dataTablesLanguage,
235
                serverSide: true, 
605.2.9 by Dan Garner
Set an unlimited save state duration for dataTables.
236
                stateSave: true, stateDuration: 0,
548 by Dan Garner
1.8.0-rc2 bug fixes and small enchancements (#228)
237
                filter: false,
238
                searchDelay: 3000,
239
                "order": [[1, "asc"]],
240
                ajax: {
241
                    "url": "{{ urlFor("display.search") }}",
242
                    "data": function(dataDisplay) {
243
                        $.extend(dataDisplay, $(dialog).find("#displayForm").serializeObject());
244
                        return dataDisplay;
245
                    }
246
                },
247
                "columns": [
248
                    { "data": "displayId"},
249
                    { "data": "display" },
250
                    {
251
                        "data": "mediaInventoryStatus",
252
                        "render": function (data, type, row) {
253
                            if (type != "display")
254
                                return data;
255
256
                            var icon = "";
257
                            if (data == 1)
258
                                icon = "fa-check";
259
                            else if (data == 0)
260
                                icon = "fa-times";
261
                            else
262
                                icon = "fa-cloud-download";
263
264
                            return "<span class='fa " + icon + "'></span>";
265
                        }
266
                    },
267
                    { "data": "loggedIn", "render": dataTableTickCrossColumn},
268
                    {
269
                        "name": "clientVersion",
270
                        "data": function (data, type) {
271
                            if (type != "display")
272
                                return data.clientVersion;
273
274
                            return data.clientType + ' ' + data.clientVersion + '-' + data.clientCode;
275
                        },
276
                        "visible": false
277
                    },
278
                    {
279
                        "name": "member",
280
                        "orderable": false,
281
                        "data": function (data, type, row) {
282
                            if (type != "display")
283
                                return data;
284
285
                            var checked = '';
286
                            
287
                            // Check if the element is already been checked/unchecked
288
                            if( typeof control.data().members != "undefined" && control.data().members.displays[data.displayId] != undefined){
289
                                checked = (control.data().members.displays[data.displayId]) ? 'checked' : '';
290
                            } else {
291
                                // If its not been altered, check for the original state
292
                                if( dialog.data().extra ){
293
                                    dialog.data().extra.displaysAssigned.forEach(function(extraElement) {
294
                                        if( extraElement.displayId == data.displayId ){
295
                                            checked = 'checked';
296
                                        }
297
                                    });    
298
                                }
299
                            }
300
                            
301
                            var checkBox = '<input type="checkbox" class="checkbox" data-member-id=' + data.displayId + ' data-member-type="display" ' + checked + '>';
302
                        
303
                            // Create checkbox
304
                            return checkBox;
305
                        }
306
                    },
307
                ]
308
            });
309
310
            table.on('draw', dataTableDraw);
311
            table.on('processing.dt', dataTableProcessing);
312
            
313
            var tableGroup = $("#displaysGroupsMembersTable").DataTable({ 
314
                "language": dataTablesLanguage,
605.2.9 by Dan Garner
Set an unlimited save state duration for dataTables.
315
                serverSide: true, stateSave: true, stateDuration: 0,
548 by Dan Garner
1.8.0-rc2 bug fixes and small enchancements (#228)
316
                filter: false,
317
                searchDelay: 3000,
318
                "order": [[1, "asc"]],
319
                ajax: {
320
                    "url": "{{ urlFor("displayGroup.search") }}",
321
                    "data": function(dataGroup) {
322
                        $.extend(dataGroup, $("#displaysGroupsMembersTable").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
323
                        return dataGroup;
324
                    }
325
                },
326
                "columns": [
327
                    { "data": "displayGroupId"},
328
                    { "data": "displayGroup"},
329
                    {
330
                        "name": "member",
331
                        "orderable": false,
332
                        "data": function (data, type, row) {
333
                            if (type != "display")
334
                                return data;
335
336
                            var checked = '';
337
                            
338
                            // Check if the element is already been checked/unchecked
339
                            if( typeof control.data().members != "undefined" && control.data().members.displayGroups[data.displayGroupId] != undefined){
340
                                checked = (control.data().members.displayGroups[data.displayGroupId]) ? 'checked' : '';
341
                            } else {
342
                                // If its not been altered, check for the original state
343
                                if( dialog.data().extra ){
344
                                    dialog.data().extra.displayGroupsAssigned.forEach(function(extraElement) {
345
                                        if( extraElement.displayGroupId == data.displayGroupId ){
346
                                            checked = 'checked';
347
                                        }
348
                                    });    
349
                                }
350
                            }
351
                            
352
                            var checkBox = '<input type="checkbox" class="checkbox" data-member-id=' + data.displayGroupId + ' data-member-type="displayGroup" ' + checked + '>';
353
                        
354
                            // Create checkbox
355
                            return checkBox;
356
                        }
357
                    },
358
                ]
359
            });
360
            
361
            tableGroup.on('draw', dataTableDraw);
362
            tableGroup.on('processing.dt', dataTableProcessing);
363
                
454.4.132 by Dan Garner
Initial draft implementation of Nested Display Groups. The UI part in particular will need some work as there are 2 API calls involved.
364
            // Bind to the checkboxes change event
548 by Dan Garner
1.8.0-rc2 bug fixes and small enchancements (#228)
365
            control.on("change", ".checkbox", function() {
366
                
454.4.132 by Dan Garner
Initial draft implementation of Nested Display Groups. The UI part in particular will need some work as there are 2 API calls involved.
367
                // Update our global members data with this
368
                var memberId = $(this).data().memberId;
369
                var memberType = $(this).data().memberType;
370
                var value = $(this).is(":checked");
371
372
                if (memberType == "display")
548 by Dan Garner
1.8.0-rc2 bug fixes and small enchancements (#228)
373
                    control.data().members.displays[memberId] = (value) ? 1 : 0;
374
                else if (memberType == "displayGroup")
375
                    control.data().members.displayGroups[memberId] = (value) ? 1 : 0;
454.4.132 by Dan Garner
Initial draft implementation of Nested Display Groups. The UI part in particular will need some work as there are 2 API calls involved.
376
            });
377
        }
378
379
        function displayGroupMembersFormSubmit(id) {
380
381
            var form = $("#" + id);
454.4.135 by Dan Garner
jqmq for members form
382
            var members = form.data().members;
454.4.132 by Dan Garner
Initial draft implementation of Nested Display Groups. The UI part in particular will need some work as there are 2 API calls involved.
383
384
            // There may not have been any changes
385
            if (members == undefined) {
386
                // No changes
387
                XiboDialogClose();
388
                return;
389
            }
390
454.4.135 by Dan Garner
jqmq for members form
391
            // Create a new queue.
392
            window.queue = $.jqmq({
393
394
                // Next item will be processed only when queue.next() is called in callback.
395
                delay: -1,
396
397
                // Process queue items one-at-a-time.
398
                batch: 1,
399
400
                // For each queue item, execute this function, making an AJAX request. Only
401
                // continue processing the queue once the AJAX request's callback executes.
402
                callback: function( data ) {
403
404
                    // Make an AJAX call
405
                    $.ajax({
406
                        type: "POST",
407
                        url: data.url,
408
                        cache: false,
409
                        dataType: "json",
410
                        data: $.param(data.data),
411
                        success: function(response, textStatus, error) {
412
413
                            if (response.success) {
414
415
                                // Success - what do we do now?
416
                                if (response.message != '')
417
                                    SystemMessage(response.message, true);
418
419
                                // Process the next item
420
                                queue.next();
421
                            }
422
                            else {
423
                                // Why did we fail?
424
                                if (response.login) {
425
                                    // We were logged out
426
                                    LoginBox(response.message);
427
                                }
428
                                else {
429
                                    // Likely just an error that we want to report on
430
                                    form.find(".saving").remove();
431
                                    SystemMessageInline(response.message, form.closest(".modal"));
432
                                }
433
                            }
434
                        },
435
                        error: function(responseText) {
436
                            SystemMessage(responseText, false);
437
                        }
438
                    });
439
                },
440
                // When the queue completes naturally, execute this function.
441
                complete: function() {
442
                    // Remove the save button
443
                    form.find(".saving").parent().remove();
444
445
                    // Refresh the grids
446
                    // (this is a global refresh)
447
                    XiboRefreshAllGrids();
448
449
                    // Close the dialog
450
                    XiboDialogClose();
451
                }
452
            });
453
454
            var addedToQueue = false;
455
454.4.132 by Dan Garner
Initial draft implementation of Nested Display Groups. The UI part in particular will need some work as there are 2 API calls involved.
456
            // Build an array of id's to assign and an array to unassign
457
            var assign = [];
458
            var unassign = [];
459
460
            $.each(members.displays, function(name, value) {
461
                if (value == 1)
462
                    assign.push(name);
463
                else
464
                    unassign.push(name);
465
            });
466
454.4.135 by Dan Garner
jqmq for members form
467
            if (assign.length > 0 || unassign.length > 0) {
468
469
                var data = {
470
                    data: {},
471
                    url: form.data().url
472
                };
473
                data.data[form.data().param] = assign;
474
                data.data[form.data().paramUnassign] = unassign;
475
476
                // Queue
477
                queue.add(data);
478
479
                addedToQueue = true;
480
            }
481
482
            // Build an array of id's to assign and an array to unassign
483
            var assignGroup = [];
484
            var unassignGroup = [];
485
486
            $.each(members.displayGroups, function(name, value) {
487
                if (value == 1)
488
                    assignGroup.push(name);
489
                else
490
                    unassignGroup.push(name);
454.4.132 by Dan Garner
Initial draft implementation of Nested Display Groups. The UI part in particular will need some work as there are 2 API calls involved.
491
            });
454.4.135 by Dan Garner
jqmq for members form
492
493
            if (assignGroup.length > 0 || unassignGroup.length > 0) {
494
                var dataGroup = {
495
                    data: {},
496
                    url: form.data().groupsUrl
497
                };
498
                dataGroup.data[form.data().groupsParam] = assignGroup;
499
                dataGroup.data[form.data().groupsParamUnassign] = unassignGroup;
500
501
                // Queue
502
                queue.add(dataGroup);
503
504
                addedToQueue = true;
505
            }
506
507
            if (!addedToQueue) {
508
                XiboDialogClose();
509
            } else {
510
                // Start the queue
511
                queue.start();
512
            }
454.4.132 by Dan Garner
Initial draft implementation of Nested Display Groups. The UI part in particular will need some work as there are 2 API calls involved.
513
        }
408 by Dan Garner
Started adding the core pages
514
    </script>
515
{% endblock %}