2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('frame', function(Y) {
11 * Creates a wrapper around an iframe. It loads the content either from a local
12 * file or from script and creates a local YUI instance bound to that new window and document.
17 * Creates a wrapper around an iframe. It loads the content either from a local
18 * file or from script and creates a local YUI instance bound to that new window and document.
25 var Frame = function() {
26 Frame.superclass.constructor.apply(this, arguments);
29 Y.extend(Frame, Y.Base, {
33 * @description Internal reference set when the content is ready.
40 * @description Internal reference set when render is called.
47 * @description Internal Node reference to the iFrame or the window
54 * @description Internal reference to the YUI instance bound to the iFrame or window
61 * @description Create the iframe or Window and get references to the Document & Window
62 * @return {Object} Hash table containing references to the new Document & Window
64 _create: function(cb) {
65 var win, doc, res, node;
67 this._iframe = Y.Node.create(Frame.HTML);
68 this._iframe.setStyle('visibility', 'hidden');
69 this._iframe.set('src', this.get('src'));
70 this.get('container').append(this._iframe);
72 this._iframe.set('height', '99%');
76 extra_css = ((this.get('extracss')) ? '<style id="extra_css">' + this.get('extracss') + '</style>' : '');
78 html = Y.substitute(Frame.PAGE_HTML, {
80 LANG: this.get('lang'),
81 TITLE: this.get('title'),
83 LINKED_CSS: this.get('linkedcss'),
84 CONTENT: this.get('content'),
85 BASE_HREF: this.get('basehref'),
86 DEFAULT_CSS: Frame.DEFAULT_CSS,
89 if (Y.config.doc.compatMode != 'BackCompat') {
91 //html = Frame.DOC_TYPE + "\n" + html;
92 html = Frame.getDocType() + "\n" + html;
98 res = this._resolveWinDoc();
103 if (this.get('designMode')) {
104 res.doc.designMode = 'on';
107 if (!res.doc.documentElement) {
108 var timer = Y.later(1, this, function() {
109 if (res.doc && res.doc.documentElement) {
121 * @method _resolveWinDoc
122 * @description Resolves the document and window from an iframe or window instance
123 * @param {Object} c The YUI Config to add the window and document to
124 * @return {Object} Object hash of window and document references, if a YUI config was passed, it is returned.
126 _resolveWinDoc: function(c) {
127 var config = (c) ? c : {};
128 config.win = Y.Node.getDOMNode(this._iframe.get('contentWindow'));
129 config.doc = Y.Node.getDOMNode(this._iframe.get('contentWindow.document'));
131 config.doc = Y.config.doc;
134 config.win = Y.config.win;
140 * @method _onDomEvent
141 * @description Generic handler for all DOM events fired by the iframe or window. This handler
142 * takes the current EventFacade and augments it to fire on the Frame host. It adds two new properties
143 * to the EventFacade called frameX and frameY which adds the scroll and xy position of the iframe
144 * to the original pageX and pageY of the event so external nodes can be positioned over the frame.
145 * @param {Event.Facade} e
147 _onDomEvent: function(e) {
150 e.frameX = e.frameY = 0;
152 if (e.pageX > 0 || e.pageY > 0) {
153 if (e.type.substring(0, 3) !== 'key') {
154 node = this._instance.one('win');
155 xy = this._iframe.getXY();
156 e.frameX = xy[0] + e.pageX - node.get('scrollLeft');
157 e.frameY = xy[1] + e.pageY - node.get('scrollTop');
161 e.frameTarget = e.target;
162 e.frameCurrentTarget = e.currentTarget;
165 this.fire('dom:' + e.type, e);
167 initializer: function() {
168 this.publish('ready', {
170 defaultFn: this._defReadyFn
173 destructor: function() {
174 var inst = this.getInstance();
176 inst.one('doc').detachAll();
178 this._iframe.remove();
183 * @description Simple pass thru handler for the paste event so we can do content cleanup
184 * @param {Event.Facade} e
186 _DOMPaste: function(e) {
187 var inst = this.getInstance(),
188 data = '', win = inst.config.win;
190 if (e._event.originalTarget) {
191 data = e._event.originalTarget;
193 if (e._event.clipboardData) {
194 data = e._event.clipboardData.getData('Text');
197 if (win.clipboardData) {
198 data = win.clipboardData.getData('Text');
199 if (data === '') { // Could be empty, or failed
201 if (!win.clipboardData.setData('Text', data)) {
208 e.frameTarget = e.target;
209 e.frameCurrentTarget = e.currentTarget;
215 getData: function() {
220 e.clipboardData = null;
223 this.fire('dom:paste', e);
227 * @method _defReadyFn
228 * @description Binds DOM events, sets the iframe to visible and fires the ready event
230 _defReadyFn: function() {
231 var inst = this.getInstance(),
232 fn = Y.bind(this._onDomEvent, this),
233 kfn = ((Y.UA.ie) ? Y.throttle(fn, 200) : fn);
235 inst.Node.DOM_EVENTS.activate = 1;
236 inst.Node.DOM_EVENTS.beforedeactivate = 1;
237 inst.Node.DOM_EVENTS.focusin = 1;
238 inst.Node.DOM_EVENTS.deactivate = 1;
239 inst.Node.DOM_EVENTS.focusout = 1;
241 //Y.each(inst.Node.DOM_EVENTS, function(v, k) {
242 Y.each(Frame.DOM_EVENTS, function(v, k) {
244 if (k !== 'focus' && k !== 'blur' && k !== 'paste') {
245 if (k.substring(0, 3) === 'key') {
246 if (k === 'keydown') {
247 inst.on(k, fn, inst.config.doc);
249 inst.on(k, kfn, inst.config.doc);
252 inst.on(k, fn, inst.config.doc);
258 inst.Node.DOM_EVENTS.paste = 1;
260 inst.on('paste', Y.bind(this._DOMPaste, this), inst.one('body'));
262 //Adding focus/blur to the window object
263 inst.on('focus', fn, inst.config.win);
264 inst.on('blur', fn, inst.config.win);
266 inst._use = inst.use;
267 inst.use = Y.bind(this.use, this);
268 this._iframe.setStyles({
269 visibility: 'inherit'
271 inst.one('body').setStyle('display', 'block');
273 this._fixIECursors();
277 * It appears that having a BR tag anywhere in the source "below" a table with a percentage width (in IE 7 & 8)
278 * if there is any TEXTINPUT's outside the iframe, the cursor will rapidly flickr and the CPU would occasionally
279 * spike. This method finds all <BR>'s below the sourceIndex of the first table. Does some checks to see if they
280 * can be modified and replaces then with a <WBR> so the layout will remain in tact, but the flickering will
282 * @method _fixIECursors
285 _fixIECursors: function() {
286 var inst = this.getInstance(),
287 tables = inst.all('table'),
288 brs = inst.all('br'), si;
290 if (tables.size() && brs.size()) {
292 si = tables.item(0).get('sourceIndex');
293 brs.each(function(n) {
294 var p = n.get('parentNode'),
295 c = p.get('children'), b = p.all('>br');
299 n.replace(inst.Node.create('<wbr>'));
301 if (n.get('sourceIndex') > si) {
303 n.replace(inst.Node.create('<wbr>'));
307 n.replace(inst.Node.create('<wbr>'));
318 * @method _onContentReady
319 * @description Called once the content is available in the frame/window and calls the final use call
320 * on the internal instance so that the modules are loaded properly.
322 _onContentReady: function(e) {
325 var inst = this.getInstance(),
326 args = Y.clone(this.get('use'));
328 this.fire('contentready');
331 inst.config.doc = Y.Node.getDOMNode(e.target);
333 //TODO Circle around and deal with CSS loading...
334 args.push(Y.bind(function() {
335 if (inst.Selection) {
336 inst.Selection.DEFAULT_BLOCK_TAG = this.get('defaultblock');
341 inst.use.apply(inst, args);
343 inst.one('doc').get('documentElement').addClass('yui-js-enabled');
348 * @method _resolveBaseHref
349 * @description Resolves the basehref of the page the frame is created on. Only applies to dynamic content.
350 * @param {String} href The new value to use, if empty it will be resolved from the current url.
353 _resolveBaseHref: function(href) {
354 if (!href || href === '') {
355 href = Y.config.doc.location.href;
356 if (href.indexOf('?') !== -1) { //Remove the query string
357 href = href.substring(0, href.indexOf('?'));
359 href = href.substring(0, href.lastIndexOf('/')) + '/';
366 * @description Get the content from the iframe
367 * @param {String} html The raw HTML from the body of the iframe.
370 _getHTML: function(html) {
372 var inst = this.getInstance();
373 html = inst.one('body').get('innerHTML');
380 * @description Set the content of the iframe
381 * @param {String} html The raw HTML to set the body of the iframe to.
384 _setHTML: function(html) {
386 var inst = this.getInstance();
387 inst.one('body').set('innerHTML', html);
389 //This needs to be wrapped in a contentready callback for the !_ready state
390 this.on('contentready', Y.bind(function(html, e) {
391 var inst = this.getInstance();
392 inst.one('body').set('innerHTML', html);
399 * @method _setLinkedCSS
400 * @description Set's the linked CSS on the instance..
402 _getLinkedCSS: function(urls) {
403 if (!Y.Lang.isArray(urls)) {
408 Y.each(urls, function(v) {
410 str += '<link rel="stylesheet" href="' + v + '" type="text/css">';
420 * @method _setLinkedCSS
421 * @description Set's the linked CSS on the instance..
423 _setLinkedCSS: function(css) {
425 var inst = this.getInstance();
432 * @method _setExtraCSS
433 * @description Set's the extra CSS on the instance..
435 _setExtraCSS: function(css) {
437 var inst = this.getInstance(),
438 node = inst.get('#extra_css');
441 inst.one('head').append('<style id="extra_css">' + css + '</style>');
447 * @method _instanceLoaded
448 * @description Called from the first YUI instance that sets up the internal instance.
449 * This loads the content into the window/frame and attaches the contentready event.
450 * @param {YUI} inst The internal YUI instance bound to the frame/window
452 _instanceLoaded: function(inst) {
453 this._instance = inst;
454 this._onContentReady();
456 var doc = this._instance.config.doc;
458 if (this.get('designMode')) {
461 //Force other browsers into non CSS styling
462 doc.execCommand('styleWithCSS', false, false);
463 doc.execCommand('insertbronreturn', false, false);
468 //BEGIN PUBLIC METHODS
471 * @description This is a scoped version of the normal YUI.use method & is bound to this frame/window.
472 * At setup, the inst.use method is mapped to this method.
475 var inst = this.getInstance(),
476 args = Y.Array(arguments),
479 if (Y.Lang.isFunction(args[args.length - 1])) {
483 args.push(function() {
484 cb.apply(inst, arguments);
488 inst._use.apply(inst, args);
492 * @description A delegate method passed to the instance's delegate method
493 * @param {String} type The type of event to listen for
494 * @param {Function} fn The method to attach
495 * @param {String} cont The container to act as a delegate, if no "sel" passed, the body is assumed as the container.
496 * @param {String} sel The selector to match in the event (optional)
497 * @return {EventHandle} The Event handle returned from Y.delegate
499 delegate: function(type, fn, cont, sel) {
500 var inst = this.getInstance();
508 return inst.delegate(type, fn, cont, sel);
511 * @method getInstance
512 * @description Get a reference to the internal YUI instance.
513 * @return {YUI} The internal YUI instance
515 getInstance: function() {
516 return this._instance;
520 * @description Render the iframe into the container config option or open the window.
521 * @param {String/HTMLElement/Node} node The node to render to
525 render: function(node) {
526 if (this._rendered) {
529 this._rendered = true;
531 this.set('container', node);
534 this._create(Y.bind(function(res) {
537 cb = Y.bind(function(i) {
538 this._instanceLoaded(i);
540 args = Y.clone(this.get('use')),
546 fn = Y.bind(function() {
547 config = this._resolveWinDoc(config);
551 inst.use('node-base', cb);
553 clearInterval(timer);
556 timer = setInterval(function() {
564 Y.use.apply(Y, args);
572 * @method _handleFocus
573 * @description Does some tricks on focus to set the proper cursor position.
575 _handleFocus: function() {
576 var inst = this.getInstance(),
577 sel = new inst.Selection();
579 if (sel.anchorNode) {
580 var n = sel.anchorNode,
581 c = n.get('childNodes');
584 if (c.item(0).test('br')) {
585 sel.selectNode(n, true, false);
587 if (c.item(0).test('p')) {
588 n = c.item(0).one('br.yui-cursor').get('parentNode');
589 sel.selectNode(n, true, false);
596 * @description Set the focus to the iframe
597 * @param {Function} fn Callback function to execute after focus happens
601 focus: function(fn) {
604 Y.one('win').focus();
605 this.getInstance().one('win').focus();
611 if (Y.Lang.isFunction(fn)) {
616 Y.one('win').focus();
617 Y.later(100, this, function() {
618 this.getInstance().one('win').focus();
622 if (Y.Lang.isFunction(fn)) {
633 * @description Show the iframe instance
638 this._iframe.setStyles({
644 this._instance.config.doc.designMode = 'on';
652 * @description Hide the iframe instance
657 this._iframe.setStyles({
658 position: 'absolute',
667 * @property DOM_EVENTS
668 * @description The DomEvents that the frame automatically attaches and bubbles
689 * @property DEFAULT_CSS
690 * @description The default css used when creating the document.
693 //DEFAULT_CSS: 'html { height: 95%; } body { padding: 7px; background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } img { cursor: pointer !important; border: none; }',
694 DEFAULT_CSS: 'body { background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } img { cursor: pointer !important; border: none; }',
698 * @description The template string used to create the iframe
701 //HTML: '<iframe border="0" frameBorder="0" marginWidth="0" marginHeight="0" leftMargin="0" topMargin="0" allowTransparency="true" width="100%" height="99%"></iframe>',
702 HTML: '<iframe border="0" frameBorder="0" marginWidth="0" marginHeight="0" leftMargin="0" topMargin="0" allowTransparency="true" width="100%" height="99%"></iframe>',
705 * @property PAGE_HTML
706 * @description The template used to create the page when created dynamically.
709 PAGE_HTML: '<html dir="{DIR}" lang="{LANG}"><head><title>{TITLE}</title>{META}<base href="{BASE_HREF}"/>{LINKED_CSS}<style id="editor_css">{DEFAULT_CSS}</style>{EXTRA_CSS}</head><body>{CONTENT}</body></html>',
714 * @description Parses document.doctype and generates a DocType to match the parent page, if supported.
715 * For IE8, it grabs document.all[0].nodeValue and uses that. For IE < 8, it falls back to Frame.DOC_TYPE.
716 * @returns {String} The normalized DocType to apply to the iframe
718 getDocType: function() {
719 var dt = Y.config.doc.doctype,
720 str = Frame.DOC_TYPE;
723 str = '<!DOCTYPE ' + dt.name + ((dt.publicId) ? ' ' + dt.publicId : '') + ((dt.systemId) ? ' ' + dt.systemId : '') + '>';
725 if (Y.config.doc.all) {
726 dt = Y.config.doc.all[0];
728 if (dt.nodeType === 8) {
730 if (dt.nodeValue.toLowerCase().indexOf('doctype') !== -1) {
731 str = '<!' + dt.nodeValue + '>';
743 * @description The DOCTYPE to prepend to the new document when created. Should match the one on the page being served.
746 DOC_TYPE: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
750 * @description The meta-tag for Content-Type to add to the dynamic document
753 //META: '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/><meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7">',
754 META: '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>',
758 * @description The name of the class (frame)
765 * @description The title to give the blank page.
773 * @description The default text direction for this new frame. Default: ltr
781 * @description The default language. Default: en-US
789 * @description The src of the iframe/window. Defaults to javascript:;
793 //Hackish, IE needs the false in the Javascript URL
794 value: 'javascript' + ((Y.UA.ie) ? ':false' : ':') + ';'
797 * @attribute designMode
798 * @description Should designMode be turned on after creation.
808 * @description The string to inject into the body of the new frame/window.
817 * @attribute basehref
818 * @description The base href to use in the iframe.
823 getter: '_resolveBaseHref'
827 * @description Array of modules to include in the scoped YUI instance at render time. Default: ['none', 'selector-css2']
833 value: ['substitute', 'node', 'node-style', 'selector-css3']
836 * @attribute container
837 * @description The container to append the iFrame to on render.
838 * @type String/HTMLElement/Node
842 setter: function(n) {
848 * @description The Node instance of the iframe.
860 * @description Set the id of the new Node. (optional)
866 getter: function(id) {
868 id = 'iframe-' + Y.guid();
874 * @attribute linkedcss
875 * @description An array of url's to external linked style sheets
880 getter: '_getLinkedCSS',
881 setter: '_setLinkedCSS'
884 * @attribute extracss
885 * @description A string of CSS to add to the Head of the Editor
890 setter: '_setExtraCSS'
894 * @description A reference to the Editor instance
901 * @attribute defaultblock
902 * @description The default tag to use for block level items, defaults to: p
916 }, '3.3.0' ,{requires:['base', 'node', 'selector-css3', 'substitute'], skinnable:false});