0. (

When doing background, it is inevitable to encounter embedded IFrame. Customer feedback of a recent project could not be saved, indicating token error. After communication, it was found that multiple embedded pages (iframe) were opened, which would cause this problem (using the token() method of ThinkPHP5, a new token is generated each time it is called). The fix is also simple: broadcast the newly generated token directly when a new inline page is created.

This is often the case in background development, where the code is extracted and simply packaged as an open source project framecontroller.js.

Considering the background may be compatible with the very old platform, especially for IE7, IE8 made compatible processing, basic can achieve all browsers compatible.

In addition, this source does not rely on any JS class library, can be used to use.

Principle 1.

The principle is very simple, a simple event subscription, publishing process.

To facilitate the call, the same JS is called uniformly, and window.top == window.self is used to determine whether it is a child window or a parent window.

1.1 the parent window

The data model

var data = {
    events: { / / event
      _PAGE_ID: {EVENT_NAME_1: function(){},
        EVENT_NAME_2: function(){}}},counter: {// Event counter
      _PAGE_ID: {EVENT_NAME_1: 1.//EVENT_NAME_2 is bound only once
        EVENT_NAME_2: 5 //EVENT_NAME_2 is bound five times, so it is repeated five times}},_count: 1 / / window ID
  }
Copy the code

The window ID is generated by getId().

Get window ID

/** * get the new window number */
var getId = function() {
    return 'frame_' + data._count++;
};
Copy the code

Collect event

/** * Add listening event */
var addListener = function(event, func) {

    var _events = data.events,
        _counter = data.counter,
        _id = this.frameId;

    if(! (_idin _events)) {
        _events[_id] = {};
        _counter[_id] = {};
    }

    if(! (eventin _events[_id])) {
        _events[_id][event] = {};
        _counter[_id][event] = 1;
    }

    _events[_id][event][_counter[_id][event]++] = func;
};
Copy the code

Remove event

If func is not specified, all events of this type will be deleted */
 var removeListener = function(event, func) {
    var _events = data.events,
        _id = this.frameId;

    if ((_id in _events) && (event in _events[_id])) {
        var _funcs = _events[_id][event];
        for (var _funcId in _funcs) {
            if(_funcs[_funcId] == func || ! func) {delete _funcs[_funcId];
                if(!!!!! func) {// If func is specified, only one will be deleted
                    break; }}}}};Copy the code

Broadcast events

/** * event broadcast */
var broadcast = function(event, value) {

    var _events = data.events,
        count = 0;

    if (topFrameId === this.frameId) {
        value = {
            type: topFrameId,
            target: window.data: value,
            frameId: this.frameId
        };
    }

    for (var _frameId in _events) {
        if(_frameId ! = value.frameId && (eventin _events[_frameId])) {
            for (var _funcId in_events[_frameId][event]) { _events[_frameId][event][_funcId](value); count++; }}}return count;

};
Copy the code

export

Define FrameController to facilitate child window calls.

window.FrameController = {
    frameId: topFrameId,
    broadcast: broadcast,
    addListener: addListener,
    removeListener: removeListener,
    removeAllListener: removeAllListener,
    getId: getId
}
Copy the code

To avoid variable conflicts, you can define only FrameController in closures, as described in the source code in framecontroller.js.

1.2 the iframe window

Now you can grab the parent window’s FrameController and add events using the addListener function.

Gets child window data

Var TopController = window. Top. FrameController, / / get the parent window frameData = {/ / frameId iframe page data: Topcontroller.getid () //iframe window ID};Copy the code

Events are added and deleted and then encapsulated

At this point, the basic functions are complete, but there is a small problem that no matter which bound event is bound to the parent window, the iframe window needs to use call to change the execution of this

var bindFrameData = function(func) {
	// Note: older versions of IE do not support bind
    return function(event, data) {
        func.call(frameData, event, data);
    };
}
var addListener = bindFrameData(TopController.addListener)
var removeListener = bindFrameData(TopController.removeListener)
Copy the code

Broadcast events

Overrides the broadcast method to automatically carry iframe page data

 / / radio
var broadcast = function(event, value) {
    return TopController.broadcast.call(frameData, event, {
        event: event,
        type: 'child'.target: window.data: value,
        frameId: frameData.frameId
    });
};
Copy the code

export

Also use TopController to save all methods

var TopController = {
    broadcast: broadcast,
    addListener: bindFrameData(TopController.addListener),
    removeListener: bindFrameData(TopController.removeListener),
    removeAllListener: bindFrameData(TopController.removeAllListener),
    count: getCount
}
Copy the code

1.3 Built-in System Events

You can now call it from the TopController API method and add system-level events to the iframe initialization to make it easier to call

In order to be compatible with more browsers, the addEventListener event name is smoothen

// Window loads or closes
var listenerName = 'attachEvent';
var listenerPrefix = 'on';
if ('addEventListener' in window) {
    listenerName = 'addEventListener';
    listenerPrefix = ' ';
}
Copy the code

Window increment event

// Window registration event
window[listenerName](listenerPrefix + 'load'.function() {
    FrameController.broadcast('frame.add', {
        msg: 'New Window'
    });
});
Copy the code

Window closing event

// Window closing event
window[listenerName]('unload'.function() {
    // Window closing event
    FrameController.broadcast('frame.remove', {
        msg: 'Close window'
    });
});
Copy the code

After the iframe window closes, the event is still registered, so it needs to be removed at unload (automatically for users)

Window [listenerName] (' unload ', function () {/ / window closing event bindFrameData (TopController. RemoveAllListener); });Copy the code

Statistics the number of IFRames

We can add the frame._online event when the iframe page is loaded, and then call the frame._online to count The Times of execution to get the number of open iframe Windows (broadcast events will not be executed in this window, you need to manually +1).

// Count events, Used only for statistical framework for window [listenerName] (listenerPrefix + 'load', function () {FrameController. AddListener (' frame. _online ', function() {}); }); / / get number window var getCount = function () {return FrameController. Broadcast (' frame. _online) + 1; };Copy the code

2. Instructions

Before writing the example, let’s go back to the message body structure

{
    event: 'Event name'.type: 'child'.target: 'Inline window'.data: 'the transmission of data, namely FrameController. Broadcast (event, data) data'.frameId: 'Inline flag'
}
Copy the code

2.1 One IFrame page sends notifications to all other pages

var addLog = function(from, event, data) {
    var _old = $('#log').html().substring(0.3000);
    $('#log').html(
        (logTpl + _old)
        .replace('#EVENT#', event)
        .replace('#DATA#'.JSON.stringify(data))
        .replace('#SOURCE#'.from));console.log('the event:, event, 'data:', data);
};
 
// Synchronize notifications
FrameController.addListener('broadcast'.function(e) {$('#msg').val(e.data.msg);
    addLog(e.frameId, e.event, e.data);
});
 
// Send a broadcast
$('#send').click(function() {
    var nums = FrameController.broadcast('broadcast', {
        msg: $('#msg').val()
    });
    $('#log').html('Notification successful :' + nums + '\n\n' + $('#log').html());
});
 
// Update the input status
$('#msg').change(function() {
    FrameController.broadcast('change', {
        text: 'Input field content changed :' + $(this).val()
    });
});
 
// Update the status
FrameController.addListener('change'.function(e) {
    addLog(e.frameId, e.event, e.data);
});
Copy the code

2.2 After an IFrame is added or disabled, other Iframes receive notification

// Listen for system events
var addLog = function(from, event, data) {
    var _old = $('#log').html().substring(0.3000);
    $('#log').html(
        (logTpl + _old)
        .replace('#EVENT#', event)
        .replace('#DATA#'.JSON.stringify(data))
        .replace('#SOURCE#'.from));console.log('the event:, event, 'data:', data);
};
 
// Listen for system events
FrameController.addListener('frame.remove'.function(e) {
    addLog(e.frameId, e.event, e.data);
});
FrameController.addListener('frame.add'.function(e) {
    addLog(e.frameId, e.event, e.data);
});
Copy the code

2.3 Adding a User-defined Event

var logTpl = 'event :#EVENT# source :#SOURCE# data :#DATA#\n\n',
    addLog = function(from, event, data) {
        var _old = $('#log').html().substring(0.3000);
        $('#log').html(
            (logTpl + _old)
            .replace('#EVENT#', event)
            .replace('#DATA#'.JSON.stringify(data))
            .replace('#SOURCE#'.from));console.log('the event:, event, 'data:', data);
    },
    msgEventListener = function(e) {$('#log').html('Custom event already triggered, adding more times will trigger more \n\n' + $('#log').html());
    };
 
 
// Add custom events
$('#add_custom').click(function() {
    FrameController.addListener('broadcast', msgEventListener);
});
 
// Delete custom events
$('#remove_custom').click(function() {
    FrameController.removeListener('broadcast', msgEventListener);
});
Copy the code

2.4 Handling ThinkPHP Tokens

// Note: depends on jQuery
$(function(){
  // Simulate ThinkPHP Token
  $('input[name=__token__]').val(new Date().getTime());

  / / ThinkPHP Token broadcasting
  FrameController.broadcast('token', {
      token: $('input[name=__token__]').val(),
  });

  // Received ThinkPHP Token processing
  FrameController.addListener('token'.function(e) {$('input[name=__token__]').val(e.data.token);
  });
});
Copy the code

The token in the last page opened will be automatically synchronized to all pages

2.5 Listening to System Events

// Listen for system events
FrameController.addListener('frame.remove'.function(e) {
    console.log(e.frameId, e.event, e.data);
});
FrameController.addListener('frame.add'.function(e) {
    console.log(e.frameId, e.event, e.data);
});

// Get the number of Windows
$('#count').click(function() {
    alert('Currently open' + FrameController.count() + 'Window');
});
Copy the code

3. Demo and source code

The code is open source, address: gitee.com/mqycn/Frame…

The online test address: www.miaoqiyuan.cn/products/fr…

The embedded iframe address: www.miaoqiyuan.cn/products/fr…