/* <copyright> This file contains proprietary software owned by Motorola Mobility, Inc.<br/> No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/> (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. </copyright> */ //////////////////////////////////////////////////////////////////////// // var Montage = require("montage/core/core").Montage, BaseDocumentView = require("js/document/views/base").BaseDocumentView, ElementModel = require("js/models/element-model"); //////////////////////////////////////////////////////////////////////// // exports.DesignDocumentView = Montage.create(BaseDocumentView, { //////////////////////////////////////////////////////////////////// // hasTemplate: { value: false }, //////////////////////////////////////////////////////////////////// // _callback: { value: null }, //////////////////////////////////////////////////////////////////// // _viewCallback: { value: null }, //////////////////////////////////////////////////////////////////// // _template: { value: null }, //////////////////////////////////////////////////////////////////// // _bodyFragment: { value: null }, //////////////////////////////////////////////////////////////////// // _headFragment: { value: null }, //////////////////////////////////////////////////////////////////// // _observer: { value: {head: null, body: null} }, //////////////////////////////////////////////////////////////////// // content: { value: null }, //////////////////////////////////////////////////////////////////// // _liveNodeList: { value: null }, //////////////////////////////////////////////////////////////////// // _webGlHelper: { value: null }, //////////////////////////////////////////////////////////////////// // _baseHref: { value: null }, //////////////////////////////////////////////////////////////////// // baseHref: { get: function() {return this._baseHref;}, set: function(value) {this._baseHref = value;} }, //////////////////////////////////////////////////////////////////// // _document: { value: null }, //////////////////////////////////////////////////////////////////// // document: { get: function() {return this._document;}, set: function(value) {this._document = value;} }, //////////////////////////////////////////////////////////////////// // _documentRoot: { value: null }, //////////////////////////////////////////////////////////////////// // documentRoot: { get: function() {return this._documentRoot;}, set: function(value) {this._documentRoot = value;} }, //////////////////////////////////////////////////////////////////// // getLiveNodeList: { value: function(useFilter) { if(useFilter) { var filteredNodes = [], childNodes = Array.prototype.slice.call(this._liveNodeList, 0); childNodes.forEach(function(item) { if( (item.nodeType === 1) && (item.nodeName !== "STYLE") && (item.nodeName !== "SCRIPT")) { filteredNodes.push(item); } }); return filteredNodes; } else { return Array.prototype.slice.call(this._liveNodeList, 0); } } }, //////////////////////////////////////////////////////////////////// // initialize: { value: function (parent) { //Creating iFrame for view this.iframe = document.createElement("iframe"); //Setting default styles this.iframe.style.border = "none"; this.iframe.style.background = "#FFF"; this.iframe.style.height = "100%"; this.iframe.style.width = "100%"; //Returning reference to iFrame created return parent.appendChild(this.iframe); } }, //////////////////////////////////////////////////////////////////// // render: { value: function (callback, template, viewCallback) { //TODO: Remove, this is a temp patch for webRequest API gate this.application.ninja.documentController.redirectRequests = false; //Storing callback for dispatch ready this._callback = callback; this._template = template; this._viewCallback = viewCallback; //Adding listener to know when template is loaded to then load user content this.iframe.addEventListener("load", this.onTemplateLoad.bind(this), false); //TODO: Add source parameter and root (optional) if (template && template.type === 'banner' && template.size) { this.iframe.src = "js/document/templates/banner/index.html"; } else { this.iframe.src = "js/document/templates/html/index.html"; } } }, //////////////////////////////////////////////////////////////////// // onTemplateLoad: { value: function (e) { //TODO: Remove, this is a temp patch for webRequest API gate this.application.ninja.documentController.redirectRequests = true; //TODO: Add support to constructing URL with a base HREF var basetag = this.content.document.getElementsByTagName('base'); //Removing event this.iframe.removeEventListener("load", this.onTemplateLoad.bind(this), false); //TODO: Improve usage of this reference this.document = this.iframe.contentWindow.document; //Looping through template styles and marking them with ninja data attribute for I/O clean up for (var k in this.document.styleSheets) { if (this.document.styleSheets[k].ownerNode && this.document.styleSheets[k].ownerNode.setAttribute) { this.document.styleSheets[k].ownerNode.setAttribute('data-ninja-template', 'true'); } } //Checking for a base URL if (basetag.length) { if (basetag[basetag.length-1].getAttribute && basetag[basetag.length-1].getAttribute('href')) { //Setting base HREF in model this.baseHref = basetag[basetag.length-1].getAttribute('href'); } } //Checking to content to be template if (this._template) { if (this._template.type === 'banner') { //Loading contents into a fragment this._bodyFragment = this.document.createElement('body'); //Listening for content to be ready this._observer.body = new WebKitMutationObserver(this.insertBannerContent.bind(this)); this._observer.body.observe(this._bodyFragment, {childList: true}); //Inserting <body> HTML and parsing URLs via mediator method this._bodyFragment.innerHTML = '<ninjaloadinghack></ninjaloadinghack>'+(this.content.body.replace(/\b(href|src)\s*=\s*"([^"]*)"/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt))).replace(/url\(([^"]*)(.+?)\1\)/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt)); } } else { //Creating temp code fragement to load head this._headFragment = this.document.createElement('head'); //Adding event listener to know when head is ready, event only dispatched once when using innerHTML this._observer.head = new WebKitMutationObserver(this.insertHeadContent.bind(this)); this._observer.head.observe(this._headFragment, {childList: true}); //Inserting <head> HTML and parsing URLs via mediator method this._headFragment.innerHTML = (this.content.head.replace(/\b(href|src)\s*=\s*"([^"]*)"/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt))).replace(/url\(([^"]*)(.+?)\1\)/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt)); //Adding event listener to know when the body is ready and make callback (using HTML5 new DOM Mutation Events) this._observer.body = new WebKitMutationObserver(this.bodyContentLoaded.bind(this)); this._observer.body.observe(this.document.body, {childList: true}); //Inserting <body> HTML and parsing URLs via mediator method this.document.body.innerHTML += '<ninjaloadinghack></ninjaloadinghack>'+(this.content.body.replace(/\b(href|src)\s*=\s*"([^"]*)"/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt))).replace(/url\(([^"]*)(.+?)\1\)/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt)); //Copying attributes to maintain same properties as the <body> for (var n in this.content.document.body.attributes) { if (this.content.document.body.attributes[n].value) { this.document.body.setAttribute(this.content.document.body.attributes[n].name, this.content.document.body.attributes[n].value); } } //Copying attributes to maintain same properties as the <head> for (var m in this.content.document.head.attributes) { if (this.content.document.head.attributes[m].value) { this.document.head.setAttribute(this.content.document.head.attributes[m].name, this.content.document.head.attributes[m].value); } } //Copying attributes to maintain same properties as the <html> var htmlTagMem = this.content.document.getElementsByTagName('html')[0], htmlTagDoc = this.document.getElementsByTagName('html')[0]; for (var m in htmlTagMem.attributes) { if (htmlTagMem.attributes[m].value) { htmlTagDoc.setAttribute(htmlTagMem.attributes[m].name, htmlTagMem.attributes[m].value); } } } } }, //////////////////////////////////////////////////////////////////// // insertBannerContent: { value: function (e) { //Getting first element in DOM (assumes it's root) //TODO: Ensure wrapper logic is proper var wrapper = this._bodyFragment.getElementsByTagName('*')[1], banner = this._bodyFragment.getElementsByTagName('*')[2], ninjaBanner = this.document.body.getElementsByTagName('ninja-content')[0], ninjaWrapper = this.document.body.getElementsByTagName('ninja-viewport')[0]; //Copying attributes to maintain same properties as the banner wrapper for (var n in wrapper.attributes) { if (wrapper.attributes[n].value) { ninjaWrapper.setAttribute(wrapper.attributes[n].name, wrapper.attributes[n].value); } } //Copying attributes to maintain same properties as the banner content for (var n in banner.attributes) { if (banner.attributes[n].value) { ninjaBanner.setAttribute(banner.attributes[n].name, banner.attributes[n].value); } } //Adjusting margin per size of document this.document.head.getElementsByTagName('style')[0].innerHTML += '\n ninja-viewport {overflow: visible !important;} ninja-content, ninja-viewport {width: ' + this._template.size.width + 'px; height: ' + this._template.size.height + 'px;}'; //Setting content in template ninjaBanner.innerHTML = banner.innerHTML; //Garbage collection this._bodyFragment = null; //Calling standard method to finish opening document this.bodyContentLoaded(null); } }, //////////////////////////////////////////////////////////////////// // insertHeadContent: { value: function (e) { //Removing event this._observer.head.disconnect(); this._observer.head = null; //Adding the loaded nodes from code fragment into actual document head for(var i in this._headFragment.childNodes) { //Minor hack to know node is actual HTML node if(this._headFragment.childNodes[i].outerHTML) { this.document.head.appendChild(this._headFragment.childNodes[i]); } } //Garbage collection this._headFragment = null; } }, //////////////////////////////////////////////////////////////////// // bodyContentLoaded: { value: function (e) { //Removing event, only needed on initial load this._observer.body.disconnect(); this._observer.body = null; //Removing loading container (should be removed) this.document.body.removeChild(this.document.getElementsByTagName('ninjaloadinghack')[0]); //Getting style and link tags in document var htags = this.document.getElementsByTagName('html'), bannerWrapper, userStyles, stags = this.document.getElementsByTagName('style'), ltags = this.document.getElementsByTagName('link'), i, orgNodes, scripttags = this.document.getElementsByTagName('script'); //Temporarily checking for disabled special case (we must enabled for Ninja to access styles) this.ninjaDisableAttribute(stags); this.ninjaDisableAttribute(ltags); //Looping through all link tags to reload into style tags if(ltags.length > 0) { for (i = 0; i < ltags.length; i++) { // if (ltags[i].href) { //Inseting <style> right above of <link> to maintain hierarchy try { this.document.head.insertBefore(this.getStyleTagFromCssFile(ltags[i]), ltags[i]) } catch (e) { this.document.body.insertBefore(this.getStyleTagFromCssFile(ltags[i]), ltags[i]); } //Disabling tag once it has been reloaded ltags[i].setAttribute('disabled', 'true'); } else { //Error: TBD //TODO: Determine what link tags would not have href data and error } } } // Assign the modelGenerator reference from the template to our own modelGenerator this.document.modelGenerator = ElementModel.modelGenerator; //Checking for script tags then parsing check for montage and webgl if (scripttags.length > 0) { //Checking and initializing webGL this.initWebGl(scripttags); //Checking and initializing Montage this.initMontage(scripttags); } else { //Else there is not data to parse if(this._viewCallback) { this._viewCallback.viewCallback.call(this._viewCallback.context); } } //TODO: Verify appropiate location for this operation if (this._template && this._template.type === 'banner') { this.documentRoot = this.document.body.getElementsByTagName('ninja-content')[0]; bannerWrapper = this.documentRoot.parentNode; } else { this.documentRoot = this.document.body; } //Storing node list for reference (might need to store in the model) this._liveNodeList = this.documentRoot.getElementsByTagName('*'); //Getting list of original nodes orgNodes = this.document.getElementsByTagName('*'); //TODO: Figure out if this is ideal for identifying nodes created by Ninja for (var n in orgNodes) { if (orgNodes[n].getAttribute) orgNodes[n].setAttribute('data-ninja-node', 'true'); } // Save initial HTML and Body/ninja-content style attributes so we don't override them on save if(htags.length) { if(userStyles = htags[0].getAttribute('style')) { htags[0].setAttribute('data-ninja-style', userStyles); } } if(this.documentRoot) { if(userStyles = this.documentRoot.getAttribute('style')) { this.documentRoot.setAttribute('data-ninja-style', userStyles); } } if(bannerWrapper) { if(userStyles = bannerWrapper.getAttribute('style')) { bannerWrapper.setAttribute('data-ninja-style', userStyles); } } //Making callback if specified if (this._callback) this._callback(); } }, //////////////////////////////////////////////////////////////////// // ninjaDisableAttribute: { value: function (tags) { //Looping through tags for (var i = 0; i < tags.length; i++) { if (tags[i].getAttribute('data-ninja-template') === null) { if (tags[i].getAttribute('disabled')) { tags[i].removeAttribute('disabled'); tags[i].setAttribute('data-ninja-disabled', 'true'); } } } } }, //////////////////////////////////////////////////////////////////// //TODO: Move to url-parser helper class getStyleTagFromCssFile: { value: function (linktag) { // var tag, cssData, //TODO: Remove usage of hack reference of URL docRootUrl = this.application.ninja.coreIoApi.rootUrl+escape((this.application.ninja.documentController.documentHackReference.root.split(this.application.ninja.coreIoApi.cloudData.root)[1]).replace(/\/\//gi, '/')); //Creating style tag to load CSS content into tag = this.document.createElement('style'); tag.setAttribute('type', 'text/css'); //Checking for location of href to load (special case for cross-domain) if (linktag.href.indexOf(this.application.ninja.coreIoApi.rootUrl) !== -1) { //Loading data from file cssData = this.urlParser.loadLocalStyleSheet(linktag.href); //Setting properties of locally loaded styles tag.setAttribute('data-ninja-uri', cssData.fileUri); tag.setAttribute('data-ninja-file-url', cssData.cssUrl); tag.setAttribute('data-ninja-file-read-only', cssData.writable); tag.setAttribute('data-ninja-file-name', cssData.cssUrl.split('/')[cssData.cssUrl.split('/').length-1]); } else { //Cross-domain content cssData = this.urlParser.loadExternalStyleSheet(linktag.href); //Setting properties of externally loaded styles tag.setAttribute('data-ninja-external-url', linktag.href); tag.setAttribute('data-ninja-file-read-only', "true"); tag.setAttribute('data-ninja-file-name', linktag.href.split('/')[linktag.href.split('/').length-1]); } //Copying attributes to maintain same properties as the <link> for (var n in linktag.attributes) { if (linktag.attributes[n].value && linktag.attributes[n].name !== 'disabled') { if (linktag.attributes[n].value.indexOf(docRootUrl) !== -1) { tag.setAttribute(linktag.attributes[n].name, linktag.attributes[n].value.split(docRootUrl)[1]); } else { tag.setAttribute(linktag.attributes[n].name, linktag.attributes[n].value); } } } //Setting content from loaded data if (cssData.content) tag.innerHTML = cssData.content; //Returning <style> with loaded contents return tag; } }, //////////////////////////////////////////////////////////////////// //Method to parse and initialize all webGL data read from file initWebGl: { value: function (scripttags) { // var n, webgldata, fileRead; //Checking for webGL Data for (var w in scripttags) { // webgldata = null; //Checking for tags with webGL data if (scripttags[w].getAttribute) { if (scripttags[w].getAttribute('data-ninja-canvas') !== null) { //TODO: Add logic to handle more than one data tag webgldata = JSON.parse((scripttags[w].innerHTML.replace("(", "")).replace(")", "")); } else if (scripttags[w].getAttribute('data-ninja-canvas-json') !== null) { //TODO: Add check for hardcoded URL fileRead = this.application.ninja.ioMediator.fio.readFile({uri: this.application.ninja.documentController.documentHackReference.root+scripttags[w].getAttribute('data-ninja-canvas-json')}); // if (fileRead.status === 204) { webgldata = JSON.parse((fileRead.file.content.replace("(", "")).replace(")", "")); } else { //Error } } //Checking for webGL data and building data array if (webgldata && webgldata.data) { for (n = 0; webgldata.data[n]; n++) { webgldata.data[n] = unescape(webgldata.data[n]); } //TODO: Improve setter of webGL and reference this._webGlHelper.glData = webgldata.data; } } } } }, //////////////////////////////////////////////////////////////////// // initMontage: { value: function (scripttags) { var self = this; this.iframe.contentWindow.document.body.addEventListener('mjsTemplateReady', function () { //Initializing template with user's seriliazation var template = this.iframe.contentWindow.mjsTemplate.create(); template.initWithDocument(this.iframe.contentWindow.document); template.instantiateWithOwnerAndDocument(null, this.iframe.contentWindow.document, function (){ //TODO: Verify this is properly done, seems like a hack var objArray = []; for (var c in template._deserializer._objects) { //Forcing draw on components template._deserializer._objects[c].needsDraw = true; objArray.push(template._deserializer._objects[c]); } // Now call the view callback if(self._viewCallback) { self._viewCallback.viewCallback.call(self._viewCallback.context, objArray); } }); }.bind(this), false); } }, //////////////////////////////////////////////////////////////////// //Method to get element from point, used by Ninja getElementFromPoint: { value: function(x, y) { return this.iframe.contentWindow.getElement(x,y); } }, //////////////////////////////////////////////////////////////////// // pauseVideos:{ value:function(){ var i, videos = this.document.getElementsByTagName("video"); for(i = 0; i < videos.length; i++){ if(!videos[i].paused) videos[i].pause(); } } }, //////////////////////////////////////////////////////////////////// // stopVideos:{ value:function(){ var i, videos = this.document.getElementsByTagName("video"); for(i = 0; i < videos.length; i++){ videos[i].src = ""; } } }, //////////////////////////////////////////////////////////////////// // pauseAndStopVideos:{ value:function(){ var i, videos = this.document.getElementsByTagName("video"); for(i = 0; i < videos.length; i++){ if(!videos[i].paused) videos[i].pause(); videos[i].src = ""; } } } //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// }); //////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////