/* <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, TextDocument = require("js/document/text-document").TextDocument, NJUtils = require("js/lib/NJUtils").NJUtils; //////////////////////////////////////////////////////////////////////// // exports.HTMLDocument = Montage.create(TextDocument, { _selectionExclude: { value: null, enumerable: false }, _htmlTemplateUrl: { value: "js/document/templates/montage-html/index.html", enumerable: false}, _iframe: { value: null, enumerable: false }, _server: { value: null, enumerable: false }, _templateDocument: { value: null, enumerable: false }, _selectionModel: { value: [], enumerable: false }, _undoModel: { value: { "queue" : [], "position" : 0 }, enumerable: false}, _document: { value: null, enumerable: false }, _documentRoot: { value: null, enumerable: false }, _liveNodeList: { value: null, enumarable: false }, _stageBG: { value: null, enumerable: false }, _window: { value: null, enumerable: false }, _styles: { value: null, enumerable: false }, _stylesheets: { value: null, enumerable: false }, _stageStyleSheetId : { value: 'nj-stage-stylesheet', enumerable: false }, _userDocument: { value: null, enumerable: false }, _htmlSource: {value: "<html></html>", enumerable: false}, _glData: {value: null, enumerable: false }, _userComponents: { value: {}, enumarable: false}, _elementCounter: { value: 1, enumerable: false }, _snapping : { value: true, enumerable: false }, _layoutMode: { value: "all", enumerable: false }, _draw3DGrid: { value: false, writable: true }, _swfObject: { value: false, enumerable: false }, _zoomFactor: { value: 100, enumerable: false }, cssLoadInterval: { value: null, enumerable: false }, _savedLeftScroll: {value:null}, _savedTopScroll: {value:null}, _codeViewDocument:{ writable: true, enumerable: true, value:null }, //drawUtils state _gridHorizontalSpacing: {value:0}, _gridVerticalSpacing: {value:0}, //end - drawUtils state // GETTERS / SETTERS codeViewDocument:{ get: function() { return this._codeViewDocument; }, set: function(value) { this._codeViewDocument = value} }, savedLeftScroll:{ get: function() { return this._savedLeftScroll; }, set: function(value) { this._savedLeftScroll = value} }, savedTopScroll:{ get: function() { return this._savedTopScroll; }, set: function(value) { this._savedTopScroll = value} }, gridHorizontalSpacing:{ get: function() { return this._gridHorizontalSpacing; }, set: function(value) { this._gridHorizontalSpacing = value} }, gridVerticalSpacing:{ get: function() { return this._gridVerticalSpacing; }, set: function(value) { this._gridVerticalSpacing = value} }, selectionExclude: { get: function() { return this._selectionExclude; }, set: function(value) { this._selectionExclude = value; } }, iframe: { get: function() { return this._iframe; }, set: function(value) { this._iframe = value; } }, server: { get: function() { return this._server; }, set: function(value) { this._server = value; } }, selectionModel: { get: function() { return this._selectionModel; }, set: function(value) { this._selectionModel = value; } }, undoModel: { get: function() { return this._undoModel; }, set: function(value) { this._undoModel.queue = value.queue; this._undoModel.position = value.position; } }, documentRoot: { get: function() { return this._documentRoot; }, set: function(value) { this._documentRoot = value; } }, stageBG: { get: function() { return this._stageBG; }, set: function(value) { this._stageBG = value; } }, elementCounter: { set: function(value) { this._elementCounter = value; }, get: function() { return this._elementCounter; } }, snapping: { get: function() { return this._snapping; }, set: function(value) { if(this._snapping !== value) { this._snapping = value; } } }, // TODO SEND THE EVENT --> Redraw the desired layout layoutMode: { get: function() { return this._layoutMode; }, set: function(mode) { this._layoutMode = mode; } }, draw3DGrid: { get: function() { return this._draw3DGrid; }, set: function(value) { if(this._draw3DGrid !== value) { this._draw3DGrid = value; } } }, userComponents: { get: function() { return this._userComponents; } }, // _drawUserComponentsOnOpen:{ // value:function(){ // for(var i in this._userComponentSet){ // console.log(this._userComponentSet[i].control) // this._userComponentSet[i].control.needsDraw = true; // } // } // }, glData: { get: function() { var elt = this.iframe.contentWindow.document.getElementById("UserContent"); this._glData = null; if (elt) { var cdm = new CanvasDataManager(); this._glData = []; cdm.collectGLData( elt, this._glData ); } return this._glData; }, set: function(value) { var elt = this.documentRoot; if (elt) { console.log( "load canvas data: " , value ); var cdm = new CanvasDataManager(); cdm.loadGLData(elt, value); } } }, zoomFactor: { get: function() { return this._zoomFactor; }, set: function(value) { this._zoomFactor = value; } }, /** * Add a reference to a component instance to the userComponents hash using the * element UUID */ setComponentInstance: { value: function(instance, el) { this.userComponents[el.uuid] = instance; } }, /** * Returns the component instance obj from the element */ getComponentFromElement: { value: function(el) { if(el) { if(el.uuid) return this.userComponents[el.uuid]; } else { return null; } } }, //////////////////////////////////////////////////////////////////// // initialize: { value: function(file, uuid, iframe, callback) { this.application.ninja.documentController._hackRootFlag = false; // this._userDocument = file; // this.init(file.name, file.uri, file.extension, iframe, uuid, callback); // this.iframe = iframe; this.selectionExclude = ["HTML", "BODY", "Viewport", "UserContent", "stageBG"]; this.currentView = "design"; // this.iframe.src = this._htmlTemplateUrl; this.iframe.addEventListener("load", this, true); } }, //////////////////////////////////////////////////////////////////// collectGLData: { value: function( elt, dataArray ) { if (elt.elementModel && elt.elementModel.shapeModel && elt.elementModel.shapeModel.GLWorld) { var data = elt.elementModel.shapeModel.GLWorld.export(); dataArray.push( data ); } if (elt.children) { var nKids = elt.children.length; for (var i=0; i<nKids; i++) { var child = elt.children[i]; this.collectGLData( child, dataArray ); } } } }, // OLD inExclusion: { value: function(element) { if(this._selectionExclude.indexOf(element.id) === -1) { if(this._selectionExclude.indexOf(element.nodeName) === -1) { return -1; } } else if (this._selectionExclude.indexOf(element.id) === -1) { return -1; } else { return 1; } } }, /** * Return the specified inline attribute from the element. */ GetElementAttribute: { value: function(element, attribute) { var value; if(attribute === "src") { return element[attribute].replace(window.location.href, ''); } value = element[attribute]; if(value !== undefined) return value; // if(value || value === false) return [value, "inline"]; // 3. //value = this._document.defaultView.getComputedStyle(element,null).getPropertyValue(attribute); //if(value) return value; return null; } }, GetElementStyle: { value: function(element, style) { // return this._queryStylesheets(element, style); } }, SetStyle: { value: function(type, selector, style, value) { try { for(var j=0; j<this._stylesheets.length;j++){ for(var i=0; i<this._stylesheets[j].cssRules.length;i++) { if(this._stylesheets[j].cssRules[i].selectorText === type + selector) { this._stylesheets[j].cssRules[i].style[style] = value; return true; } } } } catch(err) { console.log("Cannot change the style of selector: " + selector + " " + err); } } }, GetElementFromPoint: { value: function(x, y) { return this._window.getElement(x,y); } }, /* DOM Mutation Events: DOMActivate, DOMFocusIn, DOMFocusOut, DOMAttrModified, DOMCharacterDataModified, DOMNodeInserted, DOMNodeInsertedIntoDocument, DOMNodeRemoved, DOMNodeRemovedFromDocument, DOMSubtreeModified, DOMContentLoaded */ /* //TODO: Remove and clean up event listener (DOMSubtreeModified) _hackCount: { value: 0 }, */ //////////////////////////////////////////////////////////////////// // handleEvent: { value: function(event){ //TODO: Clean up, using for prototyping save this._templateDocument = {}; this._templateDocument.html = this.iframe.contentWindow.document; this._templateDocument.head = this.iframe.contentWindow.document.getElementById("userHead"); this._templateDocument.body = this.documentRoot = this.iframe.contentWindow.document.getElementById("UserContent"); //TODO: Remove, also for prototyping this.application.ninja.documentController._hackRootFlag = true; // this.stageBG = this.iframe.contentWindow.document.getElementById("stageBG"); this.stageBG.onclick = null; this._document = this.iframe.contentWindow.document; this._window = this.iframe.contentWindow; // 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'); } } // if(!this.documentRoot.Ninja) this.documentRoot.Ninja = {}; //Inserting user's document into template this._templateDocument.head.innerHTML = this._userDocument.content.head; this._templateDocument.body.innerHTML = this._userDocument.content.body; //TODO: Use querySelectorAll var scripttags = this._templateDocument.html.getElementsByTagName('script'), webgldata; // for (var w in scripttags) { if (scripttags[w].getAttribute) { if (scripttags[w].getAttribute('data-ninja-webgl') !== null) { //TODO: Add logic to handle more than one data tag webgldata = JSON.parse((scripttags[w].innerHTML.replace("(", "")).replace(")", "")); } } } // if (webgldata) { for (var n=0; webgldata.data[n]; n++) { webgldata.data[n] = unescape(webgldata.data[n]); } this._templateDocument.webgl = webgldata.data; } //Temporarily checking for disabled special case var stags = this.iframe.contentWindow.document.getElementsByTagName('style'), ltags = this.iframe.contentWindow.document.getElementsByTagName('link'); // for (var m = 0; m < ltags.length; m++) { if (ltags[m].getAttribute('data-ninja-template') === null) { if (ltags[m].getAttribute('disabled')) { ltags[m].removeAttribute('disabled'); ltags[m].setAttribute('data-ninja-disabled', 'true'); } } } // for (var n = 0; n < stags.length; n++) { if (stags[n].getAttribute('data-ninja-template') === null) { if (stags[n].getAttribute('disabled')) { stags[n].removeAttribute('disabled'); stags[n].setAttribute('data-ninja-disabled', 'true'); } } } //Adding a handler for the main user document reel to finish loading this._document.body.addEventListener("userTemplateDidLoad", this.userTemplateDidLoad.bind(this), false); // Live node list of the current loaded document this._liveNodeList = this.documentRoot.getElementsByTagName('*'); // TODO Move this to the appropriate location var len = this._liveNodeList.length; for(var i = 0; i < len; i++) { NJUtils.makeModelFromElement(this._liveNodeList[i]); } /* this.iframe.contentWindow.document.addEventListener('DOMSubtreeModified', function (e) { */ //TODO: Remove events upon loading once //TODO: When re-written, the best way to initialize the document is to listen for the DOM tree being modified setTimeout(function () { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if(this._document.styleSheets.length > 1) { //Checking all styleSheets in document for (var i in this._document.styleSheets) { //If rules are null, assuming cross-origin issue if(this._document.styleSheets[i].rules === null) { //TODO: Revisit URLs and URI creation logic, very hack right now var fileUri, cssUrl, cssData, tag, query; if (this._document.styleSheets[i].href.indexOf('js/document/templates/montage-html') !== -1) { //Getting the url of the CSS file cssUrl = this._document.styleSheets[i].href.split('js/document/templates/montage-html')[1]; //Creating the URI of the file (this is wrong should not be splitting cssUrl) fileUri = this.application.ninja.coreIoApi.cloudData.root+this.application.ninja.documentController.documentHackReference.root.split(this.application.ninja.coreIoApi.cloudData.root)[1]+cssUrl.split('/')[1]; //Loading the data from the file cssData = this.application.ninja.coreIoApi.readFile({uri: fileUri}); //Creating tag with file content tag = this.iframe.contentWindow.document.createElement('style'); tag.setAttribute('type', 'text/css'); tag.setAttribute('data-ninja-uri', fileUri); tag.setAttribute('data-ninja-file-url', cssUrl); tag.setAttribute('data-ninja-file-read-only', JSON.parse(this.application.ninja.coreIoApi.isFileWritable({uri: fileUri}).content).readOnly); tag.setAttribute('data-ninja-file-name', cssUrl.split('/')[cssUrl.split('/').length-1]); //Copying attributes to maintain same properties as the <link> for (var n in this._document.styleSheets[i].ownerNode.attributes) { if (this._document.styleSheets[i].ownerNode.attributes[n].value && this._document.styleSheets[i].ownerNode.attributes[n].name !== 'disabled') { tag.setAttribute(this._document.styleSheets[i].ownerNode.attributes[n].name, this._document.styleSheets[i].ownerNode.attributes[n].value); } } tag.innerHTML = cssData.content; //Looping through DOM to insert style tag at location of link element query = this._templateDocument.html.querySelectorAll(['link']); for (var j in query) { if (query[j].href === this._document.styleSheets[i].href) { //Disabling style sheet to reload via inserting in style tag query[j].setAttribute('disabled', 'true'); //Inserting tag this._templateDocument.head.insertBefore(tag, query[j]); } } } else { console.log('ERROR: Cross-Domain-Stylesheet detected, unable to load in Ninja'); /* //None local stylesheet, probably on a CDN (locked) tag = this.iframe.contentWindow.document.createElement('style'); tag.setAttribute('type', 'text/css'); tag.setAttribute('data-ninja-external-url', this._document.styleSheets[i].href); tag.setAttribute('data-ninja-file-read-only', "true"); tag.setAttribute('data-ninja-file-name', this._document.styleSheets[i].href.split('/')[this._document.styleSheets[i].href.split('/').length-1]); //TODO: Figure out cross-domain XHR issue, might need cloud to handle var xhr = new XMLHttpRequest(); xhr.open("GET", this._document.styleSheets[i].href, true); xhr.send(); // if (xhr.readyState === 4) { console.log(xhr); } //tag.innerHTML = xhr.responseText //xhr.response; //Currently no external styles will load if unable to load via XHR request //Disabling external style sheets query = this._templateDocument.html.querySelectorAll(['link']); for (var j in query) { if (query[j].href === this._document.styleSheets[i].href) { //Disabling style sheet to reload via inserting in style tag query[j].setAttribute('disabled', 'true'); //Inserting tag this._templateDocument.head.insertBefore(tag, query[j]); } } */ } } } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //TODO: Check if this is needed this._stylesheets = this._document.styleSheets; //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //TODO Finish this implementation once we start caching Core Elements // Assign a model to the UserContent and add the ViewPort reference to it. NJUtils.makeElementModel(this.documentRoot, "Stage", "stage"); //this.documentRoot.elementModel.viewPort = this.iframe.contentWindow.document.getElementById("Viewport"); NJUtils.makeElementModel(this.stageBG, "Stage", "stage"); NJUtils.makeElementModel(this.iframe.contentWindow.document.getElementById("Viewport"), "Stage", "stage"); for(i = 0; i < this._stylesheets.length; i++) { if(this._stylesheets[i].ownerNode.id === this._stageStyleSheetId) { this.documentRoot.elementModel.defaultRule = this._stylesheets[i]; break; } } //Temporary create properties for each rule we need to save the index of the rule var len = this.documentRoot.elementModel.defaultRule.cssRules.length; for(var j = 0; j < len; j++) { //console.log(this.documentRoot.elementModel.defaultRule.cssRules[j].selectorText); if(this.documentRoot.elementModel.defaultRule.cssRules[j].selectorText === "*") { this.documentRoot.elementModel.transitionStopRule = this.documentRoot.elementModel.defaultRule.cssRules[j]; } else if(this.documentRoot.elementModel.defaultRule.cssRules[j].selectorText === "body") { this.documentRoot.elementModel.body = this.documentRoot.elementModel.defaultRule.cssRules[j]; } else if(this.documentRoot.elementModel.defaultRule.cssRules[j].selectorText === "#Viewport") { this.documentRoot.elementModel.viewPort = this.documentRoot.elementModel.defaultRule.cssRules[j]; } else if(this.documentRoot.elementModel.defaultRule.cssRules[j].selectorText === ".stageDimension") { this.documentRoot.elementModel.stageDimension = this.documentRoot.elementModel.defaultRule.cssRules[j]; } else if(this.documentRoot.elementModel.defaultRule.cssRules[j].selectorText === ".stageView") { this.documentRoot.elementModel.stageView = this.documentRoot.elementModel.defaultRule.cssRules[j]; } else if(this.documentRoot.elementModel.defaultRule.cssRules[j].selectorText === "#stageBG") { this.documentRoot.elementModel.stageBackground = this.documentRoot.elementModel.defaultRule.cssRules[j]; } } this.callback(this); //Setting webGL data if (this._templateDocument.webgl) { this.glData = this._templateDocument.webgl; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }.bind(this), 1000); } }, //////////////////////////////////////////////////////////////////// // Handler for user content main reel. Gets called once the main reel of the template // gets deserialized. // Setting up the currentSelectedContainer to the document body. userTemplateDidLoad: { value: function(){ this.application.ninja.currentSelectedContainer = this.documentRoot; } }, //////////////////////////////////////////////////////////////////// _setSWFObjectScript: { value: function() { if(!this._swfObject) { /* var swfObj = document.createElement("script"); swfObj.type = "text/javascript"; swfObj.src = "../../user-document-templates/external-libs/swf-object/swfobject.js"; swfObj.id = "swfObject"; var head= this._document.getElementsByTagName('head')[0]; head.appendChild(swfObj); this._swfObject = true; */ } } }, //////////////////////////////////////////////////////////////////// // livePreview: { enumerable: false, value: function () { //TODO: Add logic to handle save before preview this.application.ninja.documentController.handleExecuteSaveAll(null); //Launching 'blank' tab for testing movie window.open(this.application.ninja.coreIoApi.rootUrl+this.application.ninja.documentController._activeDocument.uri.split(this.application.ninja.coreIoApi.cloudData.root)[1]); //chrome.tabs.create({url: this.application.ninja.coreIoApi.rootUrl+this.application.ninja.documentController._activeDocument.uri.split(this.application.ninja.coreIoApi.cloudData.root)[1]}); } }, //////////////////////////////////////////////////////////////////// // save: { enumerable: false, value: function () { //TODO: Add code view logic and also styles for HTML if (this.currentView === 'design') { var styles = []; for (var k in this._document.styleSheets) { if (this._document.styleSheets[k].ownerNode && this._document.styleSheets[k].ownerNode.getAttribute) { if (this._document.styleSheets[k].ownerNode.getAttribute('ninjatemplate') === null) { styles.push(this._document.styleSheets[k]); } } } return {mode: 'html', document: this._userDocument, webgl: this.glData, styles: styles, head: this._templateDocument.head.innerHTML, body: this._templateDocument.body.innerHTML}; } else if (this.currentView === "code"){ //TODO: Would this get call when we are in code of HTML? } else { //Error } } }, //////////////////////////////////////////////////////////////////// // saveAll: { enumerable: false, value: function () { //TODO: Add code view logic and also styles for HTML if (this.currentView === 'design') { var css = []; for (var k in this._document.styleSheets) { if (this._document.styleSheets[k].ownerNode && this._document.styleSheets[k].ownerNode.getAttribute) { if (this._document.styleSheets[k].ownerNode.getAttribute('ninjatemplate') === null) { css.push(this._document.styleSheets[k]); } } } return {mode: 'html', document: this._userDocument, webgl: this.glData, css: css, head: this._templateDocument.head.innerHTML, body: this._templateDocument.body.innerHTML}; } else if (this.currentView === "code"){ //TODO: Would this get call when we are in code of HTML? } else { //Error } } }, //////////////////////////////////////////////////////////////////// saveAppState:{ enumerable: false, value: function () { this.savedLeftScroll = this.application.ninja.stage._iframeContainer.scrollLeft; this.savedTopScroll = this.application.ninja.stage._iframeContainer.scrollTop; this.gridHorizontalSpacing = this.application.ninja.stage.drawUtils.gridHorizontalSpacing; this.gridVerticalSpacing = this.application.ninja.stage.drawUtils.gridVerticalSpacing; if(typeof this.application.ninja.selectedElements !== 'undefined'){ this.selectionModel = this.application.ninja.selectedElements.slice(0); } this.draw3DGrid = this.application.ninja.appModel.show3dGrid; } }, //////////////////////////////////////////////////////////////////// restoreAppState:{ enumerable: false, value: function () { this.application.ninja.stage.drawUtils.gridHorizontalSpacing = this.gridHorizontalSpacing; this.application.ninja.stage.drawUtils.gridVerticalSpacing = this.gridVerticalSpacing; if((typeof this.selectionModel !== 'undefined') && (this.selectionModel !== null)){ this.application.ninja.selectedElements = this.selectionModel.slice(0); } if((this.savedLeftScroll!== null) && (this.savedTopScroll !== null)){ this.application.ninja.stage._iframeContainer.scrollLeft = this.savedLeftScroll; this.application.ninja.stage._scrollLeft = this.savedLeftScroll; this.application.ninja.stage._iframeContainer.scrollTop = this.savedTopScroll; this.application.ninja.stage._scrollLeft = this.savedTopScroll; } this.application.ninja.stage.handleScroll(); this.application.ninja.appModel.show3dGrid = this.draw3DGrid; } } //////////////////////////////////////////////////////////////////// });