From a9c369c2e5d8f6fc8d936f7e1e6b84f693226ddf Mon Sep 17 00:00:00 2001 From: Ananya Sen Date: Thu, 16 Feb 2012 13:11:48 -0800 Subject: moved file picker in file io Signed-off-by: Ananya Sen --- js/io/ui/file-picker/file-picker-controller.js | 494 +++++++++++++++++++++++++ 1 file changed, 494 insertions(+) create mode 100755 js/io/ui/file-picker/file-picker-controller.js (limited to 'js/io/ui/file-picker/file-picker-controller.js') diff --git a/js/io/ui/file-picker/file-picker-controller.js b/js/io/ui/file-picker/file-picker-controller.js new file mode 100755 index 00000000..129bebad --- /dev/null +++ b/js/io/ui/file-picker/file-picker-controller.js @@ -0,0 +1,494 @@ +/* +This file contains proprietary software owned by Motorola Mobility, Inc.
+No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+(c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ + + +var Montage = require("montage/core/core").Montage, + pickerNavigatorReel = require("js/io/ui/file-picker/picker-navigator.reel").PickerNavigator, + filePickerModelModule = require("js/io/ui/file-picker/file-picker-model"), + Popup = require("montage/ui/popup/popup.reel").Popup; + +//singleton with functions to create a new file picker instance and utilities to format or filter the model data +var FilePickerController = exports.FilePickerController = Montage.create(require("montage/ui/component").Component, { + /** + * Register a listener for file open event + */ + deserializedFromTemplate:{ + writable:false, + enumerable:true, + value:function(){ + var that = this; + + this.eventManager.addEventListener("openFilePicker", function(evt){ + var settings; + if(typeof evt._event.settings !== "undefined"){ + settings = evt._event.settings; + } + that.showFilePicker(settings); + }, false); + + } + }, + + filePickerPopupType:{ + writable: true, + enumerable: false, + value: "filePicker" + }, + + /** + *this function is used to create an instance of a file picker + * + * parameters: + * settings is an object containing : + * callback [optional]: the call back function which will be used to send the selected URIs back. If undefined then an event is fired with the selected uri + * callbackScope : required if callback is set + * pickerMode [optional]: ["read", "write"] : specifies if the file picker is opened to read a file/folder or to save a file + * currentFilter [optional]: if a current filter needs to be applied [ex: .psd] + * allFileFilters [optional]: list of filters that user can use to filter the view + * inFileMode [optional]: true => allow file selection , false => allow directory selection + * allowNewFileCreation [optional]: flag to specify whether or not it should return URI(s) to item(s) that do not exist. i.e. a user can type a filename to a new file that doesn't yet exist in the file system. + * allowMultipleSelections [optional]: allowMultipleSelections + * pickerName: name for montage custom popup + * + * return: none + */ + + showFilePicker:{ + writable:false, + enumerable:true, + value:function(settings){ + var callback, callbackScope, pickerMode, currentFilter, allFileFilters, inFileMode, allowNewFileCreation, allowMultipleSelections, pickerName; + if(!!settings){ + if(typeof settings.callback !== "undefined"){callback = settings.callback;} + if(typeof settings.pickerMode !== "undefined"){pickerMode = settings.pickerMode;} + if(typeof settings.currentFilter !== "undefined"){currentFilter = settings.currentFilter;} + if(typeof settings.allFileFilters !== "undefined"){allFileFilters = settings.allFileFilters;} + if(typeof settings.inFileMode !== "undefined"){inFileMode = settings.inFileMode;} + if(typeof settings.allowNewFileCreation !== "undefined"){allowNewFileCreation = settings.allowNewFileCreation;} + if(typeof settings.allowMultipleSelections !== "undefined"){allowMultipleSelections = settings.allowMultipleSelections;} + if(typeof settings.pickerName !== "undefined"){this.filePickerPopupType = settings.pickerName;} + } + + if(settings.pickerName === "saveAsDirectoryPicker"){//need to set the picker mode in a better way + pickerMode = "write"; + }else{ + pickerMode = "read"; + } + + var aModel = filePickerModelModule.FilePickerModel.create(); + + var topLevelDirectories = null; + var driveData = this.application.ninja.coreIoApi.getDirectoryContents({uri:"", recursive:false, returnType:"all"}); + if(driveData.success){ + topLevelDirectories = (JSON.parse(driveData.content)).children; + }else{ + var errorCause = ""; + if(driveData.status === null){ + errorCause = "Service Unavailable" + }else{ + errorCause = driveData.status; + } + aModel.fatalError = " ** Unable to get files [Error: "+ errorCause +"]"; + } + + //dummy data - TODO:remove after testing + //aModel.currentFilter = "*.html, *.png"; + //aModel.currentFilter = "*.jpg"; + aModel.currentFilter = "*.*"; + aModel.inFileMode = true; + aModel.fileFilters = [".html, .htm", ".jpg, .jpeg, .png, .gif", ".js, .json", ".css", ".txt, .rtf", ".doc, .docx", ".pdf", ".avi, .mov, .mpeg, .ogg, .webm", "*.*"]; + //-end - dummy data + + if(!!currentFilter){aModel.currentFilter = currentFilter;} + if(typeof inFileMode !== "undefined"){aModel.inFileMode = inFileMode;} + + aModel.topLevelDirectories = topLevelDirectories; + + if(!!topLevelDirectories && !!topLevelDirectories[0]){ + aModel.currentRoot = topLevelDirectories[0].uri; + } + + //populate the last opened folder first, if none then populate default root + var storedUri = null; + var sessionStorage = window.sessionStorage; + try{ + if(pickerMode === "write"){ + storedUri = sessionStorage.getItem("lastSavedFolderURI"); + }else if(inFileMode === true){ + storedUri = sessionStorage.getItem("lastOpenedFolderURI_fileSelection"); + }else if(inFileMode === false){ + storedUri = sessionStorage.getItem("lastOpenedFolderURI_folderSelection"); + } + }catch(e){ + if(e.code == 22){ + sessionStorage.clear(); + } + } + + if(!!storedUri){ + aModel.currentRoot = unescape(storedUri); + } + + if(!!allFileFilters){aModel.fileFilters = allFileFilters;} + if(!!callback){aModel.callback = callback;} + if(!!callbackScope){aModel.callbackScope = callbackScope;} + if(typeof pickerMode !== "undefined"){aModel.pickerMode = pickerMode;} + + + + //logic: get file content data onDemand from the REST api for the default or last opened root. Cache the data in page [in local cache ? dirty fs? ]. Filter on client side to reduce network calls. + this.openFilePickerAsModal(callback, aModel); + + + //to open this on another modal dialog, make it a popup instead above the modal dialog container layer + + } + }, + + openFilePickerAsModal:{ + writable:false, + enumerable:true, + value:function(callback, aModel){ + //render modal dialog + var pickerNavContent = document.createElement("div"); + pickerNavContent.id = "filePicker"; + + pickerNavContent.style.color = "#fff"; + + //hack (elements needs to be on DOM to be drawn) + document.getElementById('modalContainer').appendChild(pickerNavContent); + + var pickerNavChoices = Montage.create(pickerNavigatorReel); + var initUri = aModel.currentRoot; + + //remove extra / at the end + if((initUri.length > 1) && (initUri.charAt(initUri.length - 1) === "/")){ + initUri = initUri.substring(0, (initUri.length - 1)); + } + + pickerNavChoices.mainContentData = this.prepareContentList(initUri, aModel); + pickerNavChoices.pickerModel = aModel; + pickerNavChoices.element = pickerNavContent; + + //hack - remove after rendering and add in modal dialog + document.getElementById('modalContainer').removeChild(pickerNavContent); + + var popup = Popup.create(); + popup.content = pickerNavChoices; + popup.modal = true; + popup.type = this.filePickerPopupType;//should be set always to not default to the single custom popup layer + popup.show(); + pickerNavChoices.popup = popup;//handle to be used for hiding the popup + } + }, + openFilePickerAsPopup:{ + writable:false, + enumerable:true, + value:function(){} + }, + + expandDirectory:{ + writable:false, + enumerable:true, + value: function(root, currentFilter, inFileMode){ + //populate children in dom + } + }, + + refreshDirectoryCache:{ + writable:false, + enumerable:true, + value:function(directoryUri){ + if(directoryContentCache[directoryUri] !== null){ + directoryContentCache[directoryUri] = null; //invalidate the cached content + //fetch fresh content + } + } + }, + + /** + * queries the cache to build contents array. If not found queries the file system + * + * parameters: + * folderUri + * aModel: model instance per picker instance, containing + */ + + prepareContentList:{ + writable: false, + enumerable:true, + value:function(folderUri, aModel, fromCache, checkStaleness){ + var contentList = [], + childrenArray = []; + + var folderContent = null; + // query filesystem and populate cache + if(((typeof fromCache !== "undefined") && (fromCache === false)) + || !this._directoryContentCache[folderUri] + || !this._directoryContentCache[folderUri].children){ + //get data using IO api + try{ + var iodata = this.application.ninja.coreIoApi.getDirectoryContents({uri:folderUri, recursive:false, returnType:"all"}); + //console.log("IO:getDirectoryContents:Response:\n"+"uri="+folderUri+"\n status="+iodata.status+"\n content= "+iodata.content); + if(iodata.success && (iodata.status === 200) && (iodata.content !== null)){ + folderContent = JSON.parse(iodata.content); + } + }catch(e){ + console.error("Error to IO uri: "+folderUri+"\n"+e.message); + } + + if(!!folderContent){ + //contentList = folderContent.children;//need to apply filters and mode + this.cacheContentForRandomAccess(folderUri, folderContent); + } + } + //now from cache - apply filters and mode + if((!!this._directoryContentCache[folderUri]) + && (this._directoryContentCache[folderUri].type === "directory") + && (typeof this._directoryContentCache[folderUri].children !== "undefined") + && (this._directoryContentCache[folderUri].children !== null)){ + + //console.log("$$$ this._directoryContentCache"); + //console.log(this._directoryContentCache); + + //check for directory staleness.... if stale query filesystem + if((typeof checkStaleness === "undefined") || (checkStaleness === true)){ + this.checkIfStale(folderUri); + } + + childrenArray = this._directoryContentCache[folderUri].children; + + //prepare content array for folder uri + childrenArray.forEach(function(item){ + if(this._directoryContentCache[item]){ + //apply mode and filtering here + if(aModel.inFileMode){// if in file selection mode, do filtering + if((this._directoryContentCache[item].type === "directory") || !aModel.currentFilter){//no filetering + contentList.push(this._directoryContentCache[item]); + } + else if(aModel.currentFilter){ + if(this.applyFilter(this._directoryContentCache[item].name, aModel.currentFilter)){ + contentList.push(this._directoryContentCache[item]); + } + } + }else{// if in folder selection mode + if(this._directoryContentCache[item].type === "directory"){ + contentList.push(this._directoryContentCache[item]); + } + } + } + }, this); + } + else if((typeof this._directoryContentCache[folderUri] !== 'undefined') && (this._directoryContentCache[folderUri].type === "file")){//if the uri is for a file + + //check for directory staleness.... if stale query filesystem + if((typeof checkStaleness === "undefined") || (checkStaleness === true)){ + this.checkIfStale(folderUri); + } + + contentList.push(this._directoryContentCache[folderUri]); + } + //end - from cache + + return contentList; + } + }, + + /** + * populates/updates cache for a uri + */ + cacheContentForRandomAccess:{ + writable:false, + enumerable:true, + value: function(directoryUri, directoryContents){ + + var that = this; + //assumption: directoryContents will have only its direct files and subfolders + //uri is the unique identifier + + + //check if the directoryUri exists in cache + //if not add uri content object, prepare children's uri array,then add/update objects for children + if(!this._directoryContentCache[directoryUri]){//uri not in cache... so add it + //add uri content object + this._directoryContentCache[directoryUri] = {"type":directoryContents.type,"name":directoryContents.name,"uri":directoryUri}; + if(!!directoryContents.size){ + this._directoryContentCache[directoryUri].size = directoryContents.size; + } + if(!!directoryContents.creationDate){ + this._directoryContentCache[directoryUri].creationDate = directoryContents.creationDate; + } + if(!!directoryContents.modifiedDate){ + this._directoryContentCache[directoryUri].modifiedDate = directoryContents.modifiedDate; + } + + //store the current queried time for refreshing cache logic + this._directoryContentCache[directoryUri].queriedTimeStamp = (new Date()).getTime(); + + if(!!directoryContents.children && directoryContents.children.length > 0){ + this._directoryContentCache[directoryUri].children = []; + + //add the uri to this._directoryContentCache[directoryUri].children, and add the child's description objects + directoryContents.children.forEach(function(obj){ + //add uri to parent's children list + that._directoryContentCache[directoryUri].children.push(obj.uri); + //add the child object + that._directoryContentCache[obj.uri] = obj; + + //store the current queried time for refreshing cache logic + that._directoryContentCache[obj.uri].queriedTimeStamp = (new Date()).getTime(); + + } ,this); + } + }else{//uri in cache... so update it AND its children + this._directoryContentCache[directoryUri].type = directoryContents.type; + this._directoryContentCache[directoryUri].name = directoryContents.name; + if(!!directoryContents.size){ + this._directoryContentCache[directoryUri].size = directoryContents.size; + } + if(!!directoryContents.creationDate){ + this._directoryContentCache[directoryUri].creationDate = directoryContents.creationDate; + } + if(!!directoryContents.modifiedDate){ + this._directoryContentCache[directoryUri].modifiedDate = directoryContents.modifiedDate; + } + + //store the current queried time for refreshing cache logic + this._directoryContentCache[directoryUri].queriedTimeStamp = (new Date()).getTime(); + + if(!!directoryContents.children && directoryContents.children.length > 0){ + + // logic to clear off objects from cache if they no longer exist in the filesystem + //better logic - use isUpdatedFlag for a folder .. then compare modified date + + //hack for now - clear up the old children and add new ones + var tempArr = this._directoryContentCache[directoryUri].children; + if( !!tempArr && Array.isArray(tempArr) && tempArr.length>0){ + tempArr.forEach(function(uriString){ + if(!!that._directoryContentCache[uriString]){ + delete that._directoryContentCache[uriString]; + } + }); + } + + this._directoryContentCache[directoryUri].children = []; + + //add the uri to this._directoryContentCache[directoryUri].children, and add the child's description objects + directoryContents.children.forEach(function(obj){ + //add uri to parent's children list + that._directoryContentCache[directoryUri].children.push(obj.uri); + //add the child object + that._directoryContentCache[obj.uri] = obj; + //store the current queried time for refreshing cache logic + that._directoryContentCache[obj.uri].queriedTimeStamp = (new Date()).getTime(); + } ,this); + }else{ + this._directoryContentCache[directoryUri].children = []; + } + } + + //console.log("$$$ "+directoryUri+" modifiedDate = "+this._directoryContentCache[directoryUri].modifiedDate); + //console.log("$$$ "+directoryUri+" queriedTimeStamp = "+this._directoryContentCache[directoryUri].queriedTimeStamp); + } + }, + + applyFilter:{ + writable: false, + enumerable:true, + value:function(fileName , filters){ + + if(filters.indexOf("*.*") !== -1){return true;} + + //console.log(""+fileName); + var filtersArr = filters.split(","); + var passed = false; + for(var i=0; i< filtersArr.length; i++){ + filtersArr[i] = filtersArr[i].trim(); + //console.log(filtersArr[i]); + var fileType = filtersArr[i].substring(filtersArr[i].indexOf(".") ); + //console.log(""+fileType); + + //ignore uppercase + fileName = fileName.toLowerCase(); + fileType = fileType.toLowerCase(); + + if(fileName.indexOf(fileType, fileName.length - fileType.length) !== -1){//ends with file type + passed = true; + break; + } + } + return passed; + } + }, + + /** + * Stale Time (ms) for each resource + * Logic: the last queried time for a resource is compared to stale time. If stale, then file system is queried + */ + cacheStaleTime:{ + writable: false, + enumerable: false, + value: 5000 + }, + + checkIfStale: { + writable: false, + enumerable: true, + value: function(folderUri){ + var wasStale = false; + var folderContent = null; + //check for directory staleness.... if stale query filesystem + if((new Date()).getTime() > (this._directoryContentCache[folderUri].queriedTimeStamp + this.cacheStaleTime)){ + try{ + var ifModifiedResponse = this.application.ninja.coreIoApi.isDirectoryModified({uri:folderUri, recursive:false, returnType:"all"}, this._directoryContentCache[folderUri].queriedTimeStamp); + //console.log("ifModifiedResponse"); + //console.log(ifModifiedResponse); + }catch(e){ + console.error("Error to IO uri with isDirectoryModified: "+folderUri+"\n"+e.message); + } + if(ifModifiedResponse && ifModifiedResponse.status === 304){ + //do nothing since the uri has not changed + }else if(ifModifiedResponse && (ifModifiedResponse.status === 200)){ + wasStale = true; + //uri has changed. so update cache + try{ + var iodata = this.application.ninja.coreIoApi.getDirectoryContents({uri:folderUri, recursive:false, returnType:"all"}); + //console.log("IO:getDirectoryContents:Response:\n"+"uri="+folderUri+"\n status="+iodata.status+"\n content= "+iodata.content); + if(iodata.success && (iodata.status === 200) && (iodata.content !== null)){ + folderContent = JSON.parse(iodata.content); + } + }catch(e){ + console.error("Error to IO uri: "+folderUri+"\n"+e.message); + } + + if(!!folderContent){ + this.cacheContentForRandomAccess(folderUri, folderContent); + } + } + } + + return wasStale; + } + }, + + /** + * This will store the directory content per session + * check session storage for this + */ + _directoryContentCache:{ + writable:true, + enumerable:false, + value:{} + }, + + clearCache:{ + writable:false, + enumerable: true, + value: function(){ + this._directoryContentCache = {}; + } + } +}); \ No newline at end of file -- cgit v1.2.3