symphonycms/symphony-2

View on GitHub
symphony/assets/js/src/backend.views.js

Summary

Maintainability
D
2 days
Test Coverage
/**
 * Symphony backend views
 *
 * @package assets
 */

(function($, Symphony) {
    'use strict';

/*--------------------------------------------------------------------------
    General backend view
--------------------------------------------------------------------------*/

Symphony.View.add('/:context*:', function() {

    // Initialise core plugins
    Symphony.Elements.contents.find('select.picker[data-interactive]').symphonyPickable();
    Symphony.Elements.contents.find('ul.orderable[data-interactive]').symphonyOrderable();
    Symphony.Elements.contents.find('table.selectable[data-interactive]').symphonySelectable();
    Symphony.Elements.wrapper.find('.filters-duplicator[data-interactive]').symphonyDuplicator();
    Symphony.Elements.wrapper.find('.tags[data-interactive]').symphonyTags();
    Symphony.Elements.wrapper.find('div.drawer').symphonyDrawer();
    Symphony.Elements.header.symphonyNotify();

    // Fix for Webkit browsers to initially show the options. #2127
    $('select[multiple=multiple]').scrollTop(0);

    // Initialise plugins inside duplicators
    Symphony.Elements.contents.find('.duplicator').on('constructshow.duplicator', '.instance', function() {
        // Enable tag lists inside duplicators
        $(this).find('.tags[data-interactive]').symphonyTags();
        // Enable parameter suggestions
        Symphony.Interface.Suggestions.init($(this), 'input[type="text"]');
    });

    // Navigation sizing
    Symphony.Elements.window.on('resize.admin nav.admin', function() {
        var content = Symphony.Elements.nav.find('ul.content'),
            structure = Symphony.Elements.nav.find('ul.structure'),
            width = content.width() + structure.width() + 20;

        // Compact mode
        if(width > window.innerWidth) {
            Symphony.Elements.nav.removeClass('wide');
        }

        // Wide mode
        else {
            Symphony.Elements.nav.addClass('wide');
        }
    }).trigger('nav.admin');

    // Accessible navigation
    Symphony.Elements.nav.on('focus.admin blur.admin', 'a', function() {
        $(this).parents('li').eq(1).toggleClass('current');
    });

    // Notifier sizing
    Symphony.Elements.window.on('resize.admin', function() {
        Symphony.Elements.header.find('.notifier').trigger('resize.notify');
    });

    // Table sizing
    Symphony.Elements.window.on('resize.admin table.admin', function() {
        var table = Symphony.Elements.contents.find('table:first');

        // Fix table size, if width exceeds the visibile viewport area.
        if(table.width() > Symphony.Elements.html.width()){
            table.addClass('fixed');
        }
        else {
            table.removeClass('fixed');
        }
    }).trigger('table.admin');

    // Orderable tables
    var oldSorting = null,
        orderable = Symphony.Elements.contents.find('table.orderable[data-interactive]');

    // Ignore tables with less than two rows
    orderable = orderable.filter(function() {
        return ($(this).find('tbody tr').length > 1);
    });

    // Initalise ordering
    orderable.symphonyOrderable({
            items: 'tr',
            handles: 'td'
        })
        .on('orderstart.orderable', function() {
            // Store current sort order
            oldSorting = orderable.find('input').map(function(e) { return this.name + '=' + (e + 1); }).get().join('&');
        })
        .on('orderstop.orderable', function() {
            var newSorting = orderable.find('input').map(function(e) { return this.name + '=' + (e + 1); }).get().join('&');

            // Store sort order, if changed
            if(oldSorting !== null && newSorting !== oldSorting) {
                // Update UI
                orderable.addClass('busy');

                // Update items
                orderable.trigger('orderupdate.admin');

                // Update old value
                oldSorting = newSorting;

                // Add XSRF token
                newSorting += '&' + Symphony.Utilities.getXSRF(true);

                // Send request
                $.ajax({
                    type: 'POST',
                    url: Symphony.Context.get('symphony') + '/ajax/reorder' + Symphony.Context.get('route'),
                    data: newSorting,
                    error: function() {
                        Symphony.Message.post(Symphony.Language.get('Reordering was unsuccessful.'), 'error');
                    },
                    complete: function() {
                        orderable.removeClass('busy').find('tr').removeClass('selected');
                    }
                });
            }
        });

    // Suggest
    if(orderable.length) {
        Symphony.Elements.breadcrumbs.append('<p class="inactive"><span> – ' + Symphony.Language.get('drag to reorder') + '</span></p>');
    }

    // With Selected
    Symphony.Elements.contents.find('fieldset.apply').each(function() {
        var applicable = $(this),
            selection = Symphony.Elements.contents.find('table.selectable'),
            select = applicable.find('select'),
            button = applicable.find('button');

        // Set menu status
        if(selection.length > 0) {
            selection.on('select.selectable deselect.selectable check.selectable', 'tbody tr', function() {

                // Activate menu
                if(selection.has('.selected').length > 0) {
                    applicable.removeClass('inactive');
                    select.removeAttr('disabled');
                }

                // Deactivate menu
                else {
                    applicable.addClass('inactive');
                    select.attr('disabled', 'disabled');
                }
            });

            selection.find('tbody tr:first').trigger('check');

            // Respect menu state
            button.on('click.admin', function() {
                if(applicable.is('.inactive')) {
                    return false;
                }
            });
        }
    });

    // Confirm actions
    Symphony.Elements.contents.add(Symphony.Elements.context).on('click.admin', 'button.confirm', function() {
        var message = $(this).attr('data-message') || Symphony.Language.get('Are you sure you want to proceed?');

        return confirm(message);
    });

    // Confirm with selected actions
    Symphony.Elements.contents.find('> form').on('submit.admin', function() {
        var select = $('select[name="with-selected"]'),
            option = select.find('option:selected'),
            message = option.attr('data-message') ||  Symphony.Language.get('Are you sure you want to proceed?');

        // Needs confirmation
        if(option.is('.confirm')) {
            return confirm(message);
        }
    });

    // Timestamp validation overwrite
    Symphony.Elements.header.find('.notifier .js-tv-overwrite').on('click.admin', function (e){
        var action = $(this).attr('data-action') || 'save';
        var hidden = Symphony.Elements.contents.find('input[name="action[ignore-timestamp]"]');
        hidden.prop('checked', true);
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        // Click on the action button:
        // This is needed since we need to send the button's data in the POST
        Symphony.Elements.contents.find('> form').find('input, button')
            .filter('[name="action[' + action + ']"]').first().trigger('click');
        return false;
    });

    // Catch all JavaScript errors and write them to the Symphony Log
    Symphony.Elements.window.on('error.admin', function(event) {
        $.ajax({
            type: 'POST',
            url: Symphony.Context.get('symphony') + '/ajax/log/',
            data: {
                'error': event.originalEvent.message,
                'url': event.originalEvent.filename,
                'line': event.originalEvent.lineno,
                'xsrf': Symphony.Utilities.getXSRF()
            }
        });
    });
});

Symphony.View.add('/publish/:context*:', function() {

    // Filtering
    Symphony.Interface.Filtering.init();

    // Pagination
    Symphony.Elements.contents.find('.page').each(function() {
        var pagination = $(this),
            form = pagination.find('form'),
            jump = form.find('input'),
            active = jump.attr('data-active'),
            inactive = jump.attr('data-inactive'),
            helper = $('<span />').appendTo(form),
            width;

        // Measure placeholder text
        width = Math.max(helper.text(active).width(), helper.text(inactive).width());
        jump.width(width + 20);
        helper.remove();

        // Set current page
        jump.val(inactive);

        // Display "Go to page …" placeholder
        form.on('mouseover.admin', function() {
            if(!form.is('.active') && jump.val() == inactive) {
                jump.val(active);
            }
        });

        // Display current page placeholder
        form.on('mouseout.admin', function() {
            if(!form.is('.active') && jump.val() == active) {
                jump.val(inactive);
            }
        });

        // Edit page number
        jump.on('focus.admin', function() {
            if(jump.val() == active) {
                jump.val('');
            }
            form.addClass('active');
        });

        // Stop editing page number
        jump.on('blur.admin', function() {

            // Clear errors
            if(form.is('.invalid') || jump.val() === '') {
                form.removeClass('invalid');
                jump.val(inactive);
            }

            // Deactivate
            if(jump.val() == inactive) {
                form.removeClass('active');
            }
        });

        // Validate page number
        form.on('submit.admin', function() {
            if(parseInt(jump.val(), 10) > parseInt(jump.attr('data-max'), 10)) {
                form.addClass('invalid');
                return false;
            }
        });
    });

    // Upload field destructors
    $('<em />', {
        text: Symphony.Language.get('Remove File'),
        on: {
            click: function(event) {
                event.preventDefault();

                var span = $(this).parent(),
                    name = span.find('input').attr('name');

                span.empty().append('<input name="' + name + '" type="file">');
            }
        }
    }).appendTo('label.file:has(a) span.frame');

    // Calendars
    $('.field-date').each(function() {
        var field = $(this),
            datetime = Symphony.Context.get('datetime'),
            calendar;

        // Add calendar widget
        if(field.attr('data-interactive')) {
            calendar = new Symphony.Interface.Calendar();
            calendar.init(this);
        }

        // Add timezone offset information
        if(moment().utcOffset() !== datetime['timezone-offset']) {
            field.addClass('show-timezone');
        }
    });
});

Symphony.View.add('/:context*:/new', function() {
    Symphony.Elements.contents.find('input[type="text"], textarea').first().focus();
});

/*--------------------------------------------------------------------------
    Blueprints - Pages Editor
--------------------------------------------------------------------------*/

Symphony.View.add('/blueprints/pages/:action:/:id:/:status:', function() {
    // No core interactions yet
});

/*--------------------------------------------------------------------------
    Blueprints - Sections
--------------------------------------------------------------------------*/

Symphony.View.add('/blueprints/sections/:action:/:id:/:status:', function(action, id, status) {
    var duplicator = $('#fields-duplicator'),
        legend = $('#fields-legend'),
        expand, collapse, toggle;

    // Create toggle controls
    expand = $('<a />', {
        'class': 'expand',
        'text': Symphony.Language.get('Expand all')
    });
    collapse = $('<a />', {
        'class': 'collapse',
        'text': Symphony.Language.get('Collapse all')
    });
    toggle = $('<p />', {
        'class': 'help toggle'
    });

    // Add toggle controls
    toggle.append(expand).append('<br />').append(collapse).insertAfter(legend);

    // Toggle fields
    toggle.on('click.admin', 'a.expand, a.collapse', function toggleFields() {

        // Expand
        if($(this).is('.expand')) {
            duplicator.trigger('expandall.collapsible');
        }

        // Collapse
        else {
            duplicator.trigger('collapseall.collapsible');
        }
    });

    // Affix for toggle
    $('fieldset.settings > legend + .help').symphonyAffix();

    // Initialise field editor
    duplicator.symphonyDuplicator({
        orderable: true,
        collapsible: true,
        preselect: 'input'
    });

    // Load section list
    duplicator.on('constructshow.duplicator', '.instance', function() {
        var instance = $(this),
            sections = instance.find('.js-fetch-sections'),
            sectionsParent = sections.parent(),
            selected = [],
            options;

        if(sections.length) {
            options = sections.find('option').each(function() {
                selected.push(this.value);

                if(!isNaN(this.value)) {
                    $(this).remove();
                }
            });

            $.ajax({
                type: 'GET',
                dataType: 'json',
                url: Symphony.Context.get('symphony') + '/ajax/sections/',
                success: function(result) {
                    // offline DOM manipulation
                    sections.detach();

                    if(result.sections.length) {
                        sections.prop('disabled', false);
                    }
                    var options = $();

                    if (!sections.attr('data-required')) {
                        // Allow de-selection, if permitted
                        options = options.add($('<option />', {
                            text: Symphony.Language.get('None'),
                            value: ''
                        }));
                    }

                    // Append sections
                    $.each(result.sections, function(index, section) {
                        var optgroup = $('<optgroup />', {
                            label: section.name
                        });
                        options = options.add(optgroup);
                        // Append fields
                        $.each(section.fields, function(index, field) {
                            var option = $('<option />', {
                                value: field.id,
                                text: field.name
                            }).appendTo(optgroup);

                            if($.inArray(field.id, selected) > -1) {
                                option.prop('selected', true);
                            }
                        });
                    });
                    sections.append(options);
                    sectionsParent.append(sections);
                    sections.trigger('change.admin');
                }
            });
        }
    });
    duplicator.find('.instance').trigger('constructshow.duplicator');

    // Focus first input
    duplicator.on('constructshow.duplicator expandstop.collapsible', '.instance', function() {
        var item = $(this);
        if (!item.hasClass('js-animate-all')) {
            $(this).find('input:visible:first').trigger('focus.admin');
        }
    });

    // Update name
    duplicator.on('blur.admin input.admin', '.instance input[name*="[label]"]', function() {
        var label = $(this),
            value = label.val();

        // Empty label
        if(value === '') {
            value = Symphony.Language.get('Untitled Field');
        }

        // Update title
        label.parents('.instance').find('.frame-header strong').text(value);
    });

    // Update location
    duplicator.on('change.admin', '.instance select[name*="[location]"]', function() {
        var select = $(this);

        select.parents('.instance').find('.frame-header').removeClass('main').removeClass('sidebar').addClass(select.val());
    });

    // Update requirements
    duplicator.on('change.admin', '.instance input[name*="[required]"]', function() {
        var checkbox = $(this),
            headline = checkbox.parents('.instance').find('.frame-header h4');

        // Is required
        if(checkbox.is(':checked')) {
            $('<span />', {
                class: 'required',
                text: '— ' + Symphony.Language.get('required')
            }).appendTo(headline);
        }

        // Is not required
        else {
            headline.find('.required').remove();
        }
    });
    duplicator.find('.instance input[name*="[required]"]').trigger('change.admin');

    // Update select field
    duplicator.on('change.admin', '.instance select[name*="[dynamic_options]"]', function() {
        $(this).parents('.instance').find('[data-condition=associative]').toggle($.isNumeric(this.value));
    }).trigger('change.admin');

    // Update tag field
    duplicator.on('change.admin', '.instance select[name*="[pre_populate_source]"]', function() {
        var selected = $(this).val(),
            show = false;
        
        if(selected) {
            selected = jQuery.grep(selected, function(value) {
                return value != 'existing';
            });

            show = (selected.length > 0);
        }

        $(this).parents('.instance').find('[data-condition=associative]').toggle(show);
    }).trigger('change.admin');

    // Remove field
    duplicator.on('destructstart.duplicator', function(event) {
        var target = $(event.target),
            item = target.clone(),
            title = item.find('.frame-header strong').text(),
            type = item.find('.frame-header span').text(),
            index = target.index(),
            id = new Date().getTime();

        // Offer undo option after removing a field
        Symphony.Elements.header.find('div.notifier').trigger('attach.notify', [
            Symphony.Language.get('The field “{$title}” ({$type}) has been removed.', {
                title: title,
                type: type
            }) + '<a id="' + id + '">' + Symphony.Language.get('Undo?') + '</a>', 'protected undo']
        );

        // Prepare field recovery
        $('#' + id).data('field', item).data('preceding', index - 1).on('click.admin', function() {
            var undo = $(this),
                message = undo.parent(),
                field = undo.data('field').hide(),
                list = $('#fields-duplicator');

            // Add field
            list.parent().removeClass('empty');
            field.trigger('constructstart.duplicator');
            list.find('.instance:eq(' + undo.data('preceding') + ')').after(field);
            field.trigger('constructshow.duplicator');
            field.slideDown('fast', function() {
                field.trigger('constructstop.duplicator');
            });

            // Clear system message
            message.trigger('detach.notify');
        });
    });

    // Discard undo options because the field context changed
    duplicator.on('orderstop.orderable', function() {
        Symphony.Elements.header.find('.undo').trigger('detach.notify');
    });

    // Highlight instances with the same location when ordering fields
    duplicator.on('orderstart.orderable', function(event, item) {
        var duplicator = $(this),
            header = item.find('.frame-header'),
            position = (header.is('.main') ? 'main' : 'sidebar');

        duplicator.find('li:has(.' + position + ')').not(item).addClass('highlight');
    });

    duplicator.on('orderstop.orderable', function() {
        $(this).find('li.highlight').removeClass('highlight');
    });

    // Restore collapsible states for new sections
    if(status === 'created') {
        var storageId = Symphony.Context.get('context-id');
        storageId = storageId.split('.');
        storageId.pop();
        storageId = 'symphony.collapsible.' + storageId.join('.') + '.0.collapsed';

        if(Symphony.Support.localStorage === true && window.localStorage[storageId]) {
            $.each(window.localStorage[storageId].split(','), function(index, value) {
                var collapsed = duplicator.find('.instance').eq(value);
                if(collapsed.has('.invalid').length == 0) {
                    collapsed.trigger('collapse.collapsible', [0]);
                }
            });

            window.localStorage.removeItem(storageId);
        }
    }
});

/*--------------------------------------------------------------------------
    Blueprints - Datasource Editor
--------------------------------------------------------------------------*/

Symphony.View.add('/blueprints/datasources/:action:/:id:/:status:/:*:', function(action) {
    if(!action) {
        return;
    }

    var context = $('#ds-context'),
        source = $('#ds-source'),
        name = Symphony.Elements.contents.find('input[name="fields[name]"]').attr('data-updated', 0),
        nameChangeCount = 0,
        params = Symphony.Elements.contents.find('select[name="fields[param][]"]'),
        pagination = Symphony.Elements.contents.find('.pagination'),
        paginationInput = pagination.find('input');

    // Update data source handle
    name.on('blur.admin input.admin', function updateDsHandle() {
        var current = (nameChangeCount++),
        value = name.val();

        setTimeout(function fetchDsHandle(nameChangeCount, current, value) {
            if(nameChangeCount == current) {
                $.ajax({
                    type: 'GET',
                    data: { 'string': value },
                    dataType: 'json',
                    url: Symphony.Context.get('symphony') + '/ajax/handle/',
                    success: function(result) {
                        if(nameChangeCount == current) {
                            name.data('handle', result.handle);
                            params.trigger('update.admin');
                        }
                    }
                });
            }
        }, 500, nameChangeCount, current, value);
    })
    // Enable the default value for Data Source name
    .symphonyDefaultValue({
        sourceElement: context
    });

    // Update output parameters
    params.on('update.admin', function updateDsParams() {
        var handle = name.data('handle') || Symphony.Language.get('untitled');

        // Process parameters
        if(parseInt(name.attr('data-updated')) !== 0) {
            params.find('option').each(function updateDsParam() {
                var param = $(this),
                    field = param.attr('data-handle');

                // Set parameter
                param.text('$ds-' + handle + '.' + field);
            });
        }

        // Updated
        name.attr('data-updated', 1);
    }).trigger('update.admin');

    // Data source manager options
    Symphony.Elements.contents.find('.contextual select optgroup').each(function() {
        var optgroup = $(this),
            select = optgroup.parents('select'),
            label = optgroup.attr('data-label'),
            options = optgroup.remove().find('option').addClass('optgroup');

        // Show only relevant options based on context
        context.on('change.admin', function() {
            var option = $(this).find('option:selected'),
                context = option.attr('data-context') || 'section-' + option.val();

            if(context == label) {
                select.find('option.optgroup').remove();
                select.append(options.clone(true));
            }
        });
    });

    // Data source manager context
    context.on('change.admin', function() {
        var optgroup = context.find('option:selected').parent(),
            label = optgroup.attr('data-label') || optgroup.attr('label'),
            reference = context.find('option:selected').attr('data-context') || 'section-' + context.val(),
            components = Symphony.Elements.contents.find('.contextual');

        // Store context
        source.val(context.val());

        // Show only relevant interface components based on context
        components.addClass('irrelevant');
        components.filter('[data-context~=' + label + ']').removeClass('irrelevant');
        components.filter('[data-context~=' + reference + ']').removeClass('irrelevant');

        // Make sure parameter names are up-to-date
        Symphony.Elements.contents.find('input[name="fields[name]"]').trigger('blur.admin');
    }).trigger('change.admin');

    // Toggle pagination
    Symphony.Elements.contents.find('input[name*=paginate_results]').on('change.admin', function() {
        var disabled = !$(this).is(':checked');
        paginationInput.prop('disabled', disabled);
    }).trigger('change.admin');

    // Enabled fields on submit
    Symphony.Elements.contents.find('> form').on('submit.admin', function() {
        paginationInput.prop('disabled', false);
    });

    // Enable parameter suggestions
    Symphony.Elements.contents.find('.ds-param').each(function() {
        Symphony.Interface.Suggestions.init(this, 'input[type="text"]');
    });

    // Toggle filter help
    Symphony.Elements.contents.find('.filters-duplicator').on('input.admin change.admin', 'input', function toggleFilterHelp(event) {
        var item = $(event.target).parents('.instance'),
            value = event.target.value,
            filters = item.data('filters'),
            help = item.find('.help');

        // Handle values that don't contain predicates
        var filter = value.search(/:/)
            ? $.trim(value.split(':')[0])
            : $.trim(value);

        // Store filters
        if(!filters) {
            filters = {};
            item.find('.tags li').each(function() {
                var val = $.trim(this.getAttribute('data-value'));
                if (val.search(/:/)) {
                    val = val.slice(0, -1);
                }
                filters[val] = this.getAttribute('data-help');
            });

            item.data('filters', filters);
        }

        // Filter help
        if(filters[filter]) {
            help.html(filters[filter]);
        }
    });
});

/*--------------------------------------------------------------------------
    Blueprints - Event Editor
--------------------------------------------------------------------------*/

Symphony.View.add('/blueprints/events/:action:/:name:/:status:/:*:', function() {
    var context = $('#event-context'),
        source = $('#event-source'),
        filters = $('#event-filters'),
        name = Symphony.Elements.contents.find('input[name="fields[name]"]').attr('data-updated', 0),
        nameChangeCount = 0;

    // Update event handle
    name.on('blur.admin input.admin', function updateEventHandle() {
        var current = (nameChangeCount++);

        setTimeout(function checkEventHandle(nameChangeCount, current) {
            if(nameChangeCount == current) {
                Symphony.Elements.contents.trigger('update.admin');
            }
        }, 500, nameChangeCount, current);
    })
    // Enable the default value for Event name
    .symphonyDefaultValue({
        sourceElement: context
    });

    // Change context
    context.on('change.admin', function changeEventContext() {
        source.val(context.val());
        Symphony.Elements.contents.trigger('update.admin');
    }).trigger('change.admin');

    // Change filters
    filters.on('change.admin', function changeEventFilters() {
        Symphony.Elements.contents.trigger('update.admin');
    });

    // Update documentation
    Symphony.Elements.contents.on('update.admin', function updateEventDocumentation() {
        if(name.val() == '') {
            $('#event-documentation').empty();
        }
        else {
            $.ajax({
                type: 'GET',
                data: {
                    'section': context.val(),
                    'filters': filters.serializeArray(),
                    'name': name.val()
                },
                dataType: 'html',
                url: Symphony.Context.get('symphony') + '/ajax/eventdocumentation/',
                success: function(documentation) {
                    $('#event-documentation').replaceWith(documentation);
                }
            });
        }
    });
});

/*--------------------------------------------------------------------------
    System - Authors
--------------------------------------------------------------------------*/

Symphony.View.add('/system/authors/:action:/:id:/:status:', function(action, id, status) {
    var password = $('#password');

    // Add change password overlay
    if(!password.has('.invalid').length && id) {
        var overlay = $('<div class="password" />'),
            frame = $('<span class="frame centered" />'),
            button = $('<button />', {
                text: Symphony.Language.get('Change Password'),
                on: {
                    click: function(event) {
                        event.preventDefault();
                        overlay.hide();
                    }
                }
            }).attr('type', 'button');

        frame.append(button);
        overlay.append(frame);
        overlay.insertBefore(password);
    }

    // Focussed UI for password reset
    if(status == 'reset-password') {
        var fieldsets = Symphony.Elements.contents.find('fieldset'),
            essentials = fieldsets.eq(0),
            login = fieldsets.eq(1),
            legend = login.find('> legend');

        essentials.hide();
        login.children().not('legend, #password').hide();

        $('<p />', {
            class: 'help',
            text: Symphony.Language.get('Please reset your password')
        }).insertAfter(legend);
    }

    // Highlight confirmation promt
    Symphony.Elements.contents.find('input, select').on('change.admin input.admin', function() {
        $('#confirmation').addClass('highlight');
    });
});

/*--------------------------------------------------------------------------
    System - Extensions
--------------------------------------------------------------------------*/
Symphony.View.add('/system/extensions/:context*:', function() {
    Symphony.Language.add({
        'Enable': false,
        'Install': false,
        'Update': false
    });

    // Update controls contextually
    Symphony.Elements.contents.find('.actions select').on('focus.admin', function() {
        var selected = Symphony.Elements.contents.find('tr.selected'),
            canUpdate = selected.filter('.extension-can-update').length,
            canInstall = selected.filter('.extension-can-install').length,
            canEnable = selected.length - canUpdate - canInstall,
            control = Symphony.Elements.contents.find('.actions option[value="enable"]'),
            label = [];

        if(canEnable) {
            label.push(Symphony.Language.get('Enable'));
        }
        if(canUpdate) {
            label.push(Symphony.Language.get('Update'));
        }
        if(canInstall) {
            label.push(Symphony.Language.get('Install'));
        }

        control.text(label.join('/'));
    });
});

})(window.jQuery, window.Symphony);