API Docs for: 1.0.0
Show:

File: Resources/public/js/views/services/ez-serversideviewservice.js

/*
 * Copyright (C) eZ Systems AS. All rights reserved.
 * For full copyright and license information view LICENSE file distributed with this source code.
 */
YUI.add('ez-serversideviewservice', function (Y) {
    "use strict";
    /**
     * Provides the server side view service class
     *
     * @method ez-serversideviewservice
     */
    Y.namespace('eZ');

    var PJAX_DONE_REDIRECT = 205,
        PJAX_LOCATION_HEADER = 'PJAX-Location',
        DEFAULT_HEADERS = {'X-PJAX': 'true'};

    /**
     * The Server Side View Service class. It is meant to be used to load the
     * content of a server side view.
     *
     * @namespace eZ
     * @class ServerSideViewService
     * @constructor
     * @extends eZ.ViewService
     */
    Y.eZ.ServerSideViewService = Y.Base.create('serverSideViewService', Y.eZ.ViewService, [], {
        initializer: function () {
            this.on('*:submitForm', this._handleFormSubmit);
        },

        /**
         * Handles the `submitForm` event by preventing the original form to be
         * submitted by the browser and by submitting the form with an AJAX
         * request.
         *
         * @method _handleFormSubmit
         * @protected
         * @param {EventFacade} e
         */
        _handleFormSubmit: function (e) {
            var form = e.form,
                app = this.get('app');

            app.set('loading', true);
            e.originalEvent.preventDefault();
            Y.io(form.getAttribute('action'), {
                method: form.getAttribute('method'),
                headers: Y.merge(DEFAULT_HEADERS, {'Content-Type': form.get('encoding')}),
                data: e.formData,
                on: {
                    success: Y.bind(this._handleFormSubmitResponse, this, e.target),
                    failure: this._handleLoadFailure,
                },
                context: this,
            });
        },

        /**
         * Handles the response of a form submission. It detects if a
         * redirection needs to happen in the application.
         *
         * @method _handleFormSubmitResponse
         * @protected
         * @param {Y.View} view
         * @param {String} tId the transaction id
         * @param {XMLHttpRequest} response
         */
        _handleFormSubmitResponse: function (view, tId, response) {
            var app = this.get('app'),
                pjaxLocation = response.getResponseHeader(PJAX_LOCATION_HEADER);

            if ( response.status === PJAX_DONE_REDIRECT && pjaxLocation ) {
                app.navigate(this._getAdminRouteUri(pjaxLocation));
            } else {
                app.set('loading', false);
                this._updateView(view, response);
            }
        },

        /**
         * Handles the loading error.
         *
         * @method _handleLoadFailure
         * @param {String} tId
         * @param {XMLHttpRequest} response
         * @protected
         */
        _handleLoadFailure: function (tId, response) {
            var frag = Y.Node.create(response.responseText),
                notificationCount,
                errorMsg = '';

            this.get('app').set('loading', false);
            notificationCount = this._parseNotifications(frag);
            if ( notificationCount === 0 ) {
                errorMsg = "Failed to load '" + response.responseURL + "'";
            }
            this._error(errorMsg);
        },

        /**
         * Parses the notification node(s) in the PJAX response and sends the
         * corresponding notify events.
         *
         * @method _parseNotifications
         * @param {Y.Node} docFragment
         * @return {Number} the number of notifications
         * @protected
         */
        _parseNotifications: function (docFragment) {
            var notifications = docFragment.all('[data-name="notification"] li');

            notifications.each(this._notifyUser, this);
            return notifications.size();
        },

        /**
         * Updates the view attributes with the provided HTTP response
         *
         * @method _updateView
         * @private
         * @param {eZ.ServerSideView} view
         * @param {Response} response
         */
        _updateView: function (view, response) {
            this._parseResponse(response);
            view.setAttrs({
                'title': this.get('title'),
                'html': this.get('html'),
            });
        },

        /**
         * Load the content and the title of the server side view using a PJAX
         * like strategy, ie the server is supposed to response with an HTML
         * like document containing a title and the html code to use. The
         * loading is done, the next callback is called with the service itself
         * in parameter. If an error occurs, an error event is triggered.
         *
         * @method _load
         * @protected
         * @param {Function} next
         */
        _load: function (next) {
            var uri = this.get('app').get('apiRoot') + this.get('request').params.uri;

            Y.io(uri, {
                method: 'GET',
                headers: DEFAULT_HEADERS,
                on: {
                    success: function (tId, response) {
                        this._parseResponse(response);
                        next(this);
                    },
                    failure: this._handleLoadFailure,
                },
                context: this,
            });
        },

        /**
         * Parses the server response
         *
         * @method _parseResponse
         * @protected
         * @param {Object} response
         */
        _parseResponse: function (response) {
            var frag = Y.Node.create(response.responseText),
                html, title;

            html = frag.one('[data-name="html"]');
            title = frag.one('[data-name="title"]');

            if ( html ) {
                this.set('html', this._rewrite(html).getContent());
            }
            if ( title ) {
                this.set('title', title.get('text'));
            }

            this._parseNotifications(frag);
        },

        /**
         * Rewrites the server side generated HTML so that it's browseable in
         * the PlatformUI application
         *
         * @method _rewrite
         * @protected
         * @param {Node} node
         * @return {Node}
         */
        _rewrite: function (node) {
            node.all('a[href]').each(function (link) {
                if ( !this._isPjaxLink(link) ) {
                    return;
                }
                link.setAttribute('href', this._getAdminRouteUri(link.getAttribute('href')));
            }, this);
            return node;
        },

        /**
         * Returns the URI in PlatformUI App from the PJAX URI
         *
         * @method _getAdminRouteUri
         * @protected
         * @param {String} uri
         * @return {String}
         */
        _getAdminRouteUri: function (uri) {
            var app = this.get('app'),
                regexp = new RegExp('^' + app.get('apiRoot'));

            return app.routeUri('adminGenericRoute', {uri: uri.replace(regexp, '')});
        },

        /**
         * Checks whether the link can be transformed in a PJAX link.
         *
         * @method _isPjaxLink
         * @protected
         * @param {Y.Node} link
         * @return {Boolean}
         */
        _isPjaxLink: function (link) {
            var href = link.getAttribute('href');

            return (
                href.charAt(0) !== '#'
                && ( !link.hasAttribute('target') || link.getAttribute('target') === '_self' )
                && !href.match(/^http(s)?:\/\//)
            );
        },

        /**
         * Fires notify event based on a notification node in the PJAX response.
         *
         * @method _notifyUser
         * @protected
         * @param {Node} node
         */
        _notifyUser: function (node) {
            var app = this.get('app'),
                timeout = 5;

            if (node.getAttribute('data-state') === 'error') {
                timeout = 0;
            }

            // the app is not yet a bubble target of the view service,
            // so we are using the app to fire the event
            // see https://jira.ez.no/browse/EZP-23013
            app.fire('notify', {
                notification: {
                    text: node.getContent(),
                    state: node.getAttribute('data-state'),
                    timeout: timeout
                }
            });
        },

        /**
         * Returns the title and the html code as an object
         *
         * @method _getViewParameters
         * @protected
         * @return {Object}
         */
        _getViewParameters: function () {
            return {
                title: this.get('title'),
                html: this.get('html'),
            };
        }
    }, {
        ATTRS: {
            /**
             * The title parsed from the pjax response.
             *
             * @attribute title
             * @default ""
             * @type String
             */
            title: {
                value: "",
            },

            /**
             * The html code parsed from the pjax response.
             *
             * @attribute html
             * @default ""
             * @type String
             */
            html: {
                value: ""
            },
        }
    });
});