UNPKG

json-object-editor

Version:

JOE the Json Object Editor | Platform Edition

1,597 lines (1,346 loc) 80 kB
/*/--------------------------------------------------------- Craydent LLC Copyright 2014 (http://craydent.com/joe) Dual licensed under the MIT or GPL Version 2 licenses. (http://craydent.com/license) ---------------------------------------------------------/*/ /*TODO: -merge specs (profile,schema,object,call?) -required fields !make rendering a field type as well as a datatype */ function JsonObjectEditor(specs){ var self = this; var listMode = false; this.VERSION = '1.0.0'; window._joes = window._joes || []; this.joe_index = window._joes.length; if(!window._joes.length){window._joe = this;} window._joes.push(this); this.history = []; /*--------------------------------------------------------------------> 0 | CONFIG <--------------------------------------------------------------------*/ var defaults = { container:'body', joeprofile:{ lockedFields:['joeUpdated'], hiddenFields:[] }, profiles:{}, fields:{}, schemas:{ 'rendering': { _title:'HTML Rendering', callback:function(){alert('yo');} //fields:['id','name','thingType','legs','species','weight','color','gender'], //_listID:'id', //_listTitle:'${name} ${species}' }}, compact:false, useControlEnter:true, autoInit:false, dynamicDisplay:30, listSubMenu:true }; this.specs = $.extend({},defaults,specs||{}); this.current = {}; //TODO: check for class/id selector this.container = $(this.specs.container); this.fields = this.specs.fields; //configure schemas this.schemas = this.specs.schemas; for(var s in _joe.schemas){ _joe.schemas[s].__schemaname = s; } this.defaultProfile = this.specs.defaultProfile || this.specs.joeprofile; this.current.profile = this.defaultProfile; this.ace_editors = {}; /*--------------------------------------------------------------------> 1 | INIT <--------------------------------------------------------------------*/ this.init = function() { self.current = {}; var html = self.renderFramework( self.renderEditorHeader() + self.renderEditorContent() + self.renderEditorFooter() ); self.container.append(html); self.overlay = $('.joe-overlay[data-joeindex=' + self.joe_index + ']'); self.panel = self.overlay.find('.joe-overlay-panel'); if (self.specs.useBackButton) { window.onkeydown = function (e) { var nonBackElements = ['input','select','textarea']; if (e.keyCode == 8) { if(self.history.length) { //if in editor fields, don't go back if (nonBackElements.indexOf(e.target.tagName.toLowerCase()) != -1) { //logit('t'); //return false; } else { //otherwise, go back. self.goBack(); return false; } }else{//no history var leavePage = confirm('Are you sure you want to leave the project dashboard?'); if(!leavePage){ return false; } } } } } self.readHashLink(); }; /*--------------------------------------------------------------------> 2 | FRAMEWORK START <--------------------------------------------------------------------*/ this.renderFramework = function(content){ var html = '<div class="joe-overlay '+((self.specs.compact && ' compact ') || '')+'" data-joeindex="'+this.joe_index+'">'+ '<div class="joe-overlay-panel">'+ (content || '')+ '</div>'+ //mini '<div class="joe-mini-panel">'+ '</div>'+ '</div>'; return html; }; this.populateFramework = function(data,setts){ var joePopulateBenchmarker = new Benchmarker(); joePopulateBenchmarker.start; var specs = setts || {}; self.current.specs = setts; self.current.data = data; //clean copy for later; self.current.userSpecs = $.extend({},setts); //update history 1/2 if(!self.current.specs.noHistory){ self.history.push({ /* _joeHistoryTitle:self.overlay.find('.joe-panel-title').html(), */ specs:self.current.userSpecs, data:self.current.data }); } var schema = setts.schema || ''; var profile = setts.profile || null; var callback = setts.callback || null; var datatype = setts.datatype || ''; var title = setts.title || ''; //callback if(callback){self.current.callback = callback;} else{self.current.callback = null;} //setup schema specs.schema = this.setSchema(schema); // specs.schema = ($.type(schema) == 'object')? schema : self.schemas[schema] || null; // self.current.schema = specs.schema; /*------------------------- Preformat Functions -------------------------*/ specs.preformat = (specs.schema && specs.schema.preformat) || specs.preformat || function(d){return d;}; data = specs.preformat(data); /*------------------------- Object -------------------------*/ //when object passed in if($.type(data) == 'object' || datatype =='object'){ specs.object = data; specs.menu = specs.menu || (specs.schema && specs.schema.menu) || self.specs.menu || (specs.multiedit && __defaultMultiButtons) || __defaultObjectButtons; specs.mode="object"; self.current.object = data; } /*------------------------- Lists (Arrays) -------------------------*/ //when array passed in listMode = false; if($.type(data) == 'array' || datatype =='array'){ listMode = true; specs.list = data; specs.menu = specs.listMenu || (specs.schema && specs.schema.listMenu )|| __defaultButtons;//__defaultMultiButtons; specs.mode="list"; //TODO: filter list items here. self.current.list = data; /*------------------------- Subsets -------------------------*/ //setup subsets self.current.subsets = setts.subsets || (specs.schema && specs.schema.subsets)||null; if(typeof self.current.subsets == 'function'){ self.current.subsets = self.current.subsets(); } //a current subset selected if(self.current.specs.subset && self.current.subsets.where({name:specs.subset}).length){ self.current.subset = self.current.subsets.where({name:specs.subset})[0]||false; }else{ //select deaulf subset if it exists self.current.subset = (self.current.subsets && self.current.subsets.where({default:true})[0])||null; } /*------------------------- Sorting -------------------------*/ //setup sorting self.current.sorter = setts.sorter || (self.current.subset && self.current.subset.sorter)||(specs.schema && specs.schema.sorter)|| 'name'; if($.type(self.current.sorter) == 'string'){self.current.sorter = [self.current.sorter];} //self.current.object = null; } /*------------------------- Submenu -------------------------*/ if(specs.mode == 'list') { self.current.submenu = self.current.specs.listsubmenu || self.current.specs.submenu || (specs.schema && specs.schema.listSubMenu) || self.specs.listSubMenu; }else{ self.current.submenu = self.current.specs.submenu || (specs.schema && specs.schema.subMenu) || self.specs.subMenu; } if(self.current.submenu == 'none'){ self.current.submenu = null; } /*------------------------- Rendering -------------------------*/ //when rendering passed in if($.type(data) == 'string' && datatype == 'rendering'){ specs.rendering = data; specs.menu = [__replaceBtn__]; specs.mode="rendering"; self.current.rendering = specs.rendering; //specs.schema } /*------------------------- String -------------------------*/ //when string passed in else if($.type(data) == 'string' || datatype == 'string'){ specs.text = data; specs.menu = __defaultButtons; //specs.menu = [{name:'save',label:'Save Object',action:'_joe.updateObject()'}]; specs.mode="text"; self.current.text = specs.text; } //setup window title //specs.title = title || (specs.schema)? specs.schema._title : "Viewing "+specs.mode.capitalize(); specs.listWindowTitle = ( specs.list && ( specs._listMenuTitle || specs._listWindowTitle || (specs.schema && (specs.schema._listMenuTitle || specs.schema._listWindowTitle)) ) ); specs.title =( title || specs.listWindowTitle || (specs.schema && specs.schema._title) || "Viewing "+specs.mode.capitalize()); //setup profile specs.profile = (profile)? (self.specs.profiles[profile]||self.specs.joeprofile): self.specs.joeprofile; self.current.profile = specs.profile; //cleanup variables self.cleanUp(); var html = self.renderEditorHeader(specs)+ self.renderEditorSubmenu(specs)+ self.renderEditorContent(specs)+ self.renderEditorFooter(specs)+ self.renderMessageContainer(); self.overlay.find('.joe-overlay-panel').html(html); //$('.joe-overlay-panel').html(html); //update history 2/2 if(!self.current.specs.noHistory && self.history.length){ $.extend({_joeHistoryTitle:self.overlay.find('.joe-panel-title').html()},self.history[self.history.length-1]); } //update hashlink self.updateHashLink(); logit('Joe Populated in '+joePopulateBenchmarker.stop()+' seconds'); return html; }; /*--------------------------------------------------------------------> 2e | FRAMEWORK END <--------------------------------------------------------------------*/ /*-----------------------------> A | Header <-----------------------------*/ this.renderEditorHeader = function(specs){ specs = specs || {}; var titleObj = self.current.object; if(specs.list){ var lcount = specs.list.length; if(self.current.subset){ lcount = specs.list.where(self.current.subset.filter).length; } titleObj = $.extend({},self.current.object,{_listCount:lcount||'0'}); } self.current.title = specs.title || 'Json Object Editor'; var title = fillTemplate(self.current.title,titleObj); action = specs.action||'onclick="getJoe('+self.joe_index+').closeButtonAction()"'; function renderHeaderBackButton(){ var html = ''; if(self.history.length > 1){ html+= '<div class="joe-header-back-btn" onclick="getJoe('+self.joe_index+').goBack();"> < </div>'; } return html; } //.replace(/(<([^>]+)>)/ig,""); var html = '<div class="joe-panel-header">'+ ((specs.schema && specs.schema.subsets && self.renderSubsetselector(specs.schema)) || (specs.subsets && self.renderSubsetselector(specs)) || '')+ renderHeaderBackButton()+ '<div class="joe-panel-title">'+ (('<div>'+title+'</div>').toDomElement().innerText || title || 'Json Object Editor')+ '</div>'+ '<div class="joe-panel-close" '+action+'></div>'+ '<div class="clear"></div>'+ '</div>'; return html; }; this.closeButtonAction = function(){ self.history = []; self.panel.addClass('centerscreen-collapse'); self.hide(500); self.clearAuxiliaryData(); }; this.goBack = function(){ self.history.pop(); var joespecs = self.history.pop(); if(!joespecs){ self.hide(); self.clearAuxiliaryData(); return; } //[self.history.length]; self.show(joespecs.data,joespecs.specs); }; this.clearAuxiliaryData = function(){ self.current.list = null; self.current.subsets = null; self.current.subset = null; }; this.cleanUp = function(){ for (var p in _joe.ace_editors){ _joe.ace_editors[p].destroy(); } _joe.ace_editors = {}; }; /*-----------------------------> B | SubMenu <-----------------------------*/ this.renderEditorSubmenu = function(specs) { if(!self.current.submenu){ return ''; } var subSpecs = { search:true }; var userSubmenu = ($.type(self.current.submenu) != 'object')?{}:self.current.submenu; $.extend(subSpecs,userSubmenu); var submenu = '<div class="joe-panel-submenu">' +((subSpecs.search && self.renderSubmenuSearch(subSpecs.search))||'') +'</div>'; return submenu; }; this.renderSubmenuSearch = function(s){ var action =' onkeyup="_joe.filterListFromSubmenu(this);" '; var submenusearch = "<div class='joe-submenu-search'>" +'<input class="joe-submenu-search-field" '+action+' placeholder="find" />' +"</div>"; return submenusearch; }; this.searchTimeout; this.filterListFromSubmenu = function(dom){ clearTimeout(self.searchTimeout ); self.searchTimeout = setTimeout(function(){searchFilter(dom);},400); function searchFilter(dom){ var value=dom.value.toLowerCase(); /*if(dom.value.length){ $('.joe-button[data-btnid=select_all]').hide(); }else{ $('.joe-button[data-btnid=select_all]').show(); }*/ var haystack; var items = $(dom).parents('.joe-panel-submenu') .siblings('.joe-panel-content') .find('.joe-panel-content-option-content').each(function(){ haystack = this.innerText.toLocaleLowerCase(); if(haystack.indexOf(value) ==-1){ this.hide(); }else{ this.show(); } }); var testable; var listables = (self.current.subset)?self.current.list.where(self.current.subset.filter):self.current.list; currentListItems = listables.filter(function(i){ testable = self.renderListItem(i); return (__removeTags(testable).toLowerCase().indexOf(value) != -1); }); self.panel.find('.joe-panel-content').html(self.renderListItems(currentListItems,0,self.specs.dynamicDisplay)); var titleObj = $.extend({},self.current.object,{_listCount:currentListItems.length||'0'}); self.panel.find('.joe-panel-title').html(fillTemplate(self.current.title,titleObj)); } }; /*-----------------------------> C | Content <-----------------------------*/ this.renderEditorContent = function(specs){ //specs = specs || {}; var content; if(!specs){ specs = { mode:'text', //text,list,single text:'No object or list selected' }; } var mode = specs.mode; switch(mode){ case 'text': content = self.renderTextContent(specs); break; case 'rendering': content = self.renderHTMLContent(specs); break; case 'list': content = self.renderListContent(specs); break; case 'object': content = self.renderObjectContent(specs); break; } var submenu = (self.current.submenu)?' with-submenu ':''; var scroll = 'onscroll="getJoe('+self.joe_index+').onListContentScroll(this);"' var html = '<div class="joe-panel-content joe-inset '+submenu+'" '+((listMode && scroll)||'')+'>'+ content+ '</div>'; return html; }; this.renderTextContent = function(specs){ specs = specs || {}; var text = specs.text || specs.object || ''; var html = '<div class="joe-text-content">'+text+'</div>'; return html; }; this.renderHTMLContent = function(specs){ specs = specs || {}; var html = '<textarea class="joe-rendering-field">'+(specs.rendering || '')+'</textarea>'; return html; }; /*-- //LIST --*/ var currentListItems; this.renderListContent = function(specs){ currentListItems = []; self.current.selectedListItems=[]; self.current.anchorListItem=null; specs = specs || {}; var schema = specs.schema; var list = specs.list || []; var html = ''; var filteredList; list = list.sortBy(self.current.sorter); var numItemsToRender; if(!self.current.subset){ currentListItems = list; } else{ filteredList = list.where(self.current.subset.filter); currentListItems = filteredList; } numItemsToRender = self.specs.dynamicDisplay || currentListItems.length; html+= self.renderListItems(currentListItems,0,numItemsToRender); return html; }; this.renderListItems = function(items,start,stop){ var html = ''; var listItem; var items = items || currentListItems; var start = start || 0; var stop = stop || currentListItems.length -1; for(var i=start;i <stop;i++){ listItem = items[i]; if(listItem) { html += self.renderListItem(listItem); } } return html; }; this.onListContentScroll = function(domObj){ // logit(domObj); var listItem = self.panel.find('.joe-panel-content-option').last()[0]; var currentItemCount = self.panel.find('.joe-panel-content-option').length; if( currentItemCount== currentListItems.length){ // logit('all items showing'); return; } //$('.app-list-group-content').not('.events-group').not('.collapsed').find('.app-list-divider-count').prev('.app-list-item'); //var listItem; var viewPortHeight = self.panel.find('.joe-panel-content').height(); var html = ''; try { if (listItem.getBoundingClientRect().bottom - 500 < viewPortHeight) { //self.generateGroupContent(groupIndex); // logit('more content coming'); html +=self.renderListItems(null,currentItemCount,currentItemCount+self.specs.dynamicDisplay); self.panel.find('.joe-panel-content').append(html); } }catch(e){ alert('error scrolling for more content: \n'+e); } }; /*-- //OBJECT --*/ this.renderObjectContent = function(specs){ specs = specs || {}; var object = specs.object; var fields = ''; var propObj; var fieldProp; if(!specs.schema || !specs.schema.fields){//no schema use items as own schema for( var prop in object){ if(object.hasOwnProperty(prop)){ propObj = $.extend({ name:prop, type:'text', //type:($.type(object[prop]) == 'object')?'rendering':'text', value:object[prop] }, self.fields[prop], //overwrite with value {value:object[prop]} ); fields += self.renderObjectField(propObj); } } } else{ (specs.schema.fields||[]).map(function(prop){ //prop is the property name //if(object.hasOwnProperty(prop)){ if($.type(prop) == "string") { fieldProp = self.fields[prop] || {}; //merge all the items propObj = $.extend( { name: prop, type: 'text' }, { onblur: specs.schema.onblur, onchange: specs.schema.onchange, onkeypress: specs.schema.onkeypress, onkeyup: specs.schema.onkeypress }, fieldProp, //overwrite with value {value: object[prop]} ); fields += self.renderObjectField(propObj); } if($.type(prop) == "object"){ if(prop.label){ fields += self.renderContentLabel(prop); } } //} }); //end map } var html = '<div class="joe-object-content">'+fields+'<div class="clear"></div></div>'; return html; }; //PROP LABELS self.renderContentLabel = function(specs){ var html="<div class='joe-content-label'>"+fillTemplate(specs.label,self.current.object)+"</div>"; return html; }; /*-----------------------------> D | Footer <-----------------------------*/ this.renderEditorFooter = function(specs){ specs = specs || this.specs || {}; var menu = (listMode && (specs.schema && specs.schema.listmenu)||specs.listmenu) ||//list mode specs.menu || (specs.multiedit && __defaultMultiButtons) || __defaultObjectButtons; if(typeof menu =='function'){ menu = menu(); } var title = specs.title || 'untitled'; var display,action; var html = '<div class="joe-panel-footer">'+ '<div class="joe-panel-menu">'; menu.map(function(m){ html+= self.renderFooterMenuItem(m); },this); if(self.current.list && $.type(self.current.data) == 'array'){ html+= self.renderFooterMenuItem(__selectAllBtn__); html+= self.renderFooterMenuItem( { label:'Multi-Edit', name:'multiEdit', css:'joe-multi-only', action:'getJoe('+self.joe_index+').editMultiple()'}); } html+= __clearDiv__+ '</div>'+ '</div>'; return html; }; this.renderFooterMenuItem=function(m){//passes a menu item var display,action,html=''; if(m.condition && !m.condition(m,self.current.object)){ return ''; }; display = m.label || m.name; action = m.action || 'alert(\''+display+'\')'; html+= '<div class="joe-button joe-footer-button '+(m.css ||'')+'" onclick="'+action+'" data-btnid="'+m.name+'" >'+display+'</div>'; return html; }; /*--------------------------------------------------------------------> 3 | OBJECT FORM <--------------------------------------------------------------------*/ var preProp; var prePropWidths = 0; this.renderObjectField = function(prop){ //field requires {name,type} //set default value if(prop.value == undefined && prop.default != undefined){ prop.value = prop.default; } if($.type(prop.value) == "function"){ try { prop.value = prop.value(self.current.object); }catch(e){ logit('error with propoerty "'+(prop.name||'')+'": '+e); prop.value= prop.value; } } var hidden = (prop.hidden)?'hidden':''; var html =''; //add clear div if the previous fields are floated. if(preProp){ //TODO:deal with 50,50,50,50 four way float //prePropWidths if(preProp.width && !prop.width){ //if((preProp.width && !prop.width)||){ html+='<div class="clear"></div>'; } } if(prop.width){ html+='<div class="joe-field-container" style="width:'+prop.width+';">'; } html+= '<div class="joe-object-field '+hidden+' '+prop.type+'-field " data-type="'+prop.type+'" data-name="'+prop.name+'">'+ '<label class="joe-field-label">' +fillTemplate((prop.display||prop.label||prop.name),self.current.object) +'</label>'; //add multi-edit checkbox if(self.current.userSpecs.multiedit){ html+='<div class="joe-field-multiedit-toggle" onclick="$(this).parent().toggleClass(\'multi-selected\')"></div>'; } /* if($.type(prop.value) == "object" && !prop.type){ prop.type = "rendering"; }*/ html += self.selectAndRenderFieldType(prop); html+='</div>'; if(prop.width){ html+='</div>'; } preProp = prop; return html; }; this.selectAndRenderFieldType = function(prop){ var html = ''; switch(prop.type){ case 'select': html+= self.renderSelectField(prop); break; case 'multisort': case 'multisorter': html+= self.renderMultisorterField(prop); break; case 'sorter': html+= self.renderSorterField(prop); break; /* case 'multi-select': html+= self.renderMultiSelectField(prop);*/ break; case 'guid': html+= self.renderGuidField(prop); break; case 'number': html+= self.renderNumberField(prop); break; case 'int': html+= self.renderIntegerField(prop); break; /* case 'textarea': prop.type = 'rendering';*/ case 'code': html+= self.renderCodeField(prop); break; case 'rendering': html+= self.renderRenderingField(prop); break; case 'date': html+= self.renderDateField(prop); break; case 'boolean': html+= self.renderBooleanField(prop); break; case 'geo': case 'map': html += self.renderGeoField(prop); break; case 'image': case 'img': html+= self.renderImageField(prop); break; case 'buckets': html+= self.renderBucketsField(prop); break; case 'content': html+= self.renderContentField(prop); break; case 'url': html+= self.renderURLField(prop); break; case 'objectList': html+= self.renderObjectListField(prop); break; default: html+= self.renderTextField(prop); break; } return html; } /*-----------------------------> 0 | Event Handlers <-----------------------------*/ this.getActionString = function(evt,prop){ var evt = prop[evt]; if(!evt){ return '';} if($.type(evt) == "string"){ return evt; } var str = (prop[evt])? ' '+self.functionName(prop[evt])+'(this); ' : '' ; return str; }; this.renderFieldAttributes = function(prop, evts){ evts = evts ||{}; var bluraction = ''; //var updateaction = ''; var changeaction = ''; var keypressaction = ''; var keyupaction = ''; var profile = self.current.profile; var disabled = (profile.lockedFields.indexOf(prop.name) == -1)? '':'disabled'; if(evts.onblur || prop.onblur){ bluraction = 'onblur="'+(evts.onblur||'')+' '+self.getActionString('onblur',prop)+'"'; } if(evts.onchange || prop.onchange){ changeaction = 'onchange="'+(evts.onchange||'')+' '+self.getActionString('onchange',prop)+'"'; } if(evts.onkeypress || prop.onkeypress){ keypressaction = 'onkeypress="'+(evts.onkeypress||'')+' '+self.getActionString('onkeypress',prop)+'"'; } if(evts.onkeyup || prop.onkeyup){ keyupaction = 'onkeyup="'+(evts.onkeyup||'')+' '+self.getActionString('onkeyup',prop)+'"'; } return ' '+keyupaction+' '+keypressaction+' '+bluraction+' '+changeaction+' '+disabled+' '; }; /*-----------------------------> A | Text Input <-----------------------------*/ this.renderTextField = function(prop){ var autocomplete; if(prop.autocomplete && prop.values){ if(typeof prop.values == "function"){ prop.values = prop.values(self.current.object); } if($.type(prop.values) != 'array'){ prop.values = [prop.values]; } autocomplete =true; } /* var disabled = (profile.lockedFields.indexOf(prop.name) == -1)? '':'disabled'; */ //var bluraction = 'onblur="'+self.getActionString('onblur',prop)+'"'; //show autocomplete var html= '<input class="joe-text-field joe-field" type="text" name="'+prop.name+'" value="'+(prop.value || '')+'" ' +self.renderFieldAttributes(prop) +((autocomplete && ' onblur="getJoe('+self.joe_index+').hideTextFieldAutoComplete($(this));"' +' onkeyup="getJoe('+self.joe_index+').showTextFieldAutoComplete($(this));"' ) ||'' ) +' />'; if(autocomplete){ html+='<div class="joe-text-autocomplete">'; var ac_opt; for(var v = 0, len = prop.values.length; v < len; v++){ ac_opt = ($.type(prop.values[v]) == "object")? prop.values[v]: {id:prop.values[v],name:prop.values[v]}; html+='<div class="joe-text-autocomplete-option" ' +'onclick="getJoe('+self.joe_index+').autocompleteTextFieldOptionClick(this);" ' +'data-value="'+(ac_opt._id||ac_opt.id||ac_opt.name)+'">'+ac_opt.name+'</div>'; } html+='</div>'; } //add onblur: hide panel return html; }; this.showTextFieldAutoComplete = function(dom){ var autocomplete = dom.next('.joe-text-autocomplete'); autocomplete.find('.joe-text-autocomplete-option').each(function(i,obj){ self.checkAutocompleteValue(dom.val(),obj.innerHTML,obj); }); autocomplete.addClass('active'); }; this.hideTextFieldAutoComplete = function(dom){ var autocomplete = dom.next('.joe-text-autocomplete'); autocomplete.removeClass('active'); }; this.autocompleteTextFieldOptionClick = function(dom){ $(dom).parent().prev('.joe-text-field').val($(dom).html()); $(dom).parent().removeClass('active'); //$(dom).previous('.joe-text-field').val($(dom).html()); }; this.checkAutocompleteValue = function(needle,haystack,dom){ var d = $(dom); if(haystack.indexOf(needle) != -1 || !needle){ d.addClass('visible'); }else{ d.removeClass('visible'); } }; /*-----------------------------> B | Number/Int Input <-----------------------------*/ this.renderNumberField = function(prop){ /*var disabled = (profile.lockedFields.indexOf(prop.name) != -1 || prop.locked)? 'disabled':''; */ //bluraction //var bluraction = (prop.onblur)? ' '+self.functionName(prop.onblur)+'(this); ' : '' ; //var bluraction = 'onblur=" '+self.getActionString('onblur',prop)+' "'; var html=/* '<label class="joe-field-label">'+(prop.display||prop.name)+'</label>'+*/ '<input class="joe-number-field joe-field" type="text" name="'+prop.name+'" value="'+(prop.value || '')+'" '+ self.renderFieldAttributes(prop,{onblur:'getJoe('+self.joe_index+').returnNumber(this);'})+ ' />'; return html; }; this.returnNumber = function(dom){ if(!$(dom).val()){return;} $(dom).val(parseFloat($(dom).val())); }; this.renderIntegerField = function(prop){ /*var disabled = (profile.lockedFields.indexOf(prop.name) != -1 || prop.locked)? 'disabled':''; */ //bluraction //var bluraction = 'onblur="_joe.returnInt(this); '+self.getActionString('onblur',prop)+'"'; var html=/* '<label class="joe-field-label">'+(prop.display||prop.name)+'</label>'+*/ '<input class="joe-int-field joe-field" type="text" name="'+prop.name+'" value="'+(prop.value || '')+'" '+ self.renderFieldAttributes(prop,{onblur:'getJoe('+self.joe_index+').returnInt(this);'})+ ' />'; return html; }; this.returnInt = function(dom){ if(!$(dom).val()){return;} $(dom).val(parseInt($(dom).val())); }; /*-----------------------------> C | Select <-----------------------------*/ this.renderSelectField = function(prop){ var values = ($.type(prop.values) == 'function')?prop.values(self.current.object):prop.values || [prop.value]; var valObjs = []; if($.type(values[0]) != 'object'){ values.map(function(v){ valObjs.push({name:v}); }); } else{ valObjs = values; } //bluraction // var bluraction = 'onblur="'+self.getActionString('onblur',prop)+'"'; /* var disabled = (profile.lockedFields.indexOf(prop.name) != -1 || prop.locked)? 'disabled':'';*/ var selected; var multiple =(prop.multiple)?' multiple ':''; var selectSize = prop.size || ((valObjs.length*.5) > 10)? 10 : valObjs.length/2; if(!prop.size && !prop.multiple){ selectSize = 1; } var html=/* '<label class="joe-field-label">'+(prop.display||prop.name)+'</label>'+*/ '<select class="joe-select-field joe-field" name="'+prop.name+'" value="'+(prop.value || '')+'" size="'+selectSize+'"'+ self.renderFieldAttributes(prop)+ multiple+ ' >'; var template = prop.template || ''; var val; var optionVal; valObjs.map(function(v){ optionVal = (template)?fillTemplate(template,v):(v.display||v.label||v.name); val = (prop.idprop && v[prop.idprop])||v.value||v.name||''; if($.type(prop.value) == 'array'){ selected = ''; selected = (prop.value.indexOf(val) != -1)?'selected':''; /*prop.value.map(function(pval){ if(pval.indexOf) });*/ }else{ selected = (prop.value == val)?'selected':''; } html += '<option value="'+val+'" '+selected+'>'+optionVal+'</option>' }); html+='</select>'; return html; }; /*-----------------------------> D | Date Field <-----------------------------*/ this.renderDateField = function(prop){ var html= '<input class="joe-date-field joe-field" type="text" name="'+prop.name+'" value="'+(prop.value || '')+'" '+ self.renderFieldAttributes(prop)+ ' />'; return html; }; /*-----------------------------> E | Geo Field <-----------------------------*/ this.renderGeoField = function(prop){ var center = (prop.value && eval(prop.value)) || prop.center || [40.513,-96.020]; var zoom = prop.zoom || 4; //map.setView([37.85750715625203,-96.15234375],3) //var map = L.map('map').setView(center, zoom); var mapDiv = 'joeGEO_'+prop.name; var val = prop.value||''; var html= '<div class="joe-geo-map joe-field" name="'+prop.name+'" id="'+mapDiv+'" ' +'data-center="'+JSON.stringify(center)+'" data-zoom="'+zoom+'" ' +'data-value="'+val+'" ' +'data-hideattribution="'+(prop.hideAttribution||'')+'" ' +'onload="getJoe('+self.joe_index+').initGeoMap(this);"></div>' +'<input class="joe-geo-field joe-field" type="text" value="'+val+'" name="'+prop.name+'"/>' +'<script type="text/javascript">setTimeout(function(){getJoe('+self.joe_index+').initGeoMap("'+mapDiv+'");},100)</script>' ; return html; }; this.initGeoMap = function(id){ var mapspecs = $('#'+id).data(); var map = L.map(id).setView(mapspecs.center,mapspecs.zoom); //var map = L.map(id).setView([51.505, -0.09], 13); L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { //attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' }).addTo(map); //add geocoder var searchControl = new L.esri.Controls.Geosearch().addTo(map); //hide leaflet and esri attribution if(mapspecs.hideattribution){ $('.leaflet-control-attribution').hide(); } map.on('click', self.onMapClick); map.prop = $('#'+id).attr('name'); if(mapspecs.value){ //var ll = eval(mapspecs.value); self.addMapIcon(map,mapspecs.value); } }; this.onMapClick = function(e){ var map = (e.type=="click")?e.target : this.map; //map.setView(e.latlng); var ll = (e && e.latlng) || this.getLatLng(); map.setView(ll); if(e.type=="dragend"){ }else if(map.marker){ map.marker.setLatLng(ll); }else{ self.addMapIcon(map,ll); } $('input[name='+map.prop+']').val('['+ll.lat+','+ll.lng+']'); }; this.addMapIcon = function(map,latlng,specs){ specs = specs || {}; var myIcon = L.icon({ iconUrl: specs.icon||'/JsonObjectEditor/img/mapstar.png', iconSize: [30, 30] //iconAnchor: [22, 94], //popupAnchor: [-3, -76], //shadowUrl: 'my-icon-shadow.png', //shadowRetinaUrl: 'my-icon-shadow@2x.png', //shadowSize: [68, 95], //shadowAnchor: [22, 94] }); map.marker = L.marker(latlng,{ draggable:true, icon:myIcon }).addTo(map); map.marker.map = map; map.marker.on('dragend', self.onMapClick); }; /*-----------------------------> F | Boolean <-----------------------------*/ this.renderBooleanField = function(prop){ var profile = self.current.profile; var html= //'<label class="joe-field-label">'+(prop.display||prop.name)+'</label>' '<label for="joe_checkbox-'+prop.name+'">' +'<input class="joe-boolean-field joe-field" type="checkbox" name="'+prop.name+'" id="joe_checkbox-'+prop.name+'" ' +(prop.value == true&&'checked' || '') +' /> <small>' +(prop.label ||'') +'</small></label>'; return html; }; /*-----------------------------> G | Guid <-----------------------------*/ this.renderGuidField = function(prop){ var profile = self.current.profile; var html=/* '<label class="joe-field-label">'+(prop.display||prop.name)+'</label>'+*/ '<input class="joe-guid-field joe-field" type="text" name="'+prop.name+'" value="'+(prop.value || cuid())+'" disabled />'; return html; }; /*-----------------------------> H | Sorter <-----------------------------*/ this.renderSorterField = function(prop){ /* var values = ($.type(prop.values) == 'function')?prop.values(self.current.object):prop.values || [prop.value]; var valObjs = []; if($.type(values[0]) != 'object'){ values.map(function(v){ valObjs.push({name:v}); }); } else{ valObjs = values; } var val; var opt=[]; var sel = []; valObjs.map(function(v){ val = v.value||v.name||''; if($.type(prop.value) == 'array'){ selected = ''; selected = (prop.value.indexOf(val) != -1)?'selected':''; }else{ selected = (prop.value == val)?'selected':''; } html += '<option value="'+val+'" '+selected+'>'+(v.display||v.label||v.name)+'</option>' }); var selected; var html= '<div class="joe-multisorter-field joe-field" name="'+prop.name+'">'+ '<ul class="joe-multisorter-bin options-bin"></ul>'+ '<ul class="joe-multisorter-bin selections-bin"></ul>'+ '<ul class="joe-multisorter-field joe-field" name="'+prop.name+'" '+ //self.renderFieldAttributes(prop)+ ' >'; html+='</div>'; return html;*/ }; /*-----------------------------> I | Image <-----------------------------*/ this.renderImageField = function(prop){ var html= '<input class="joe-image-field joe-field" type="text" name="'+prop.name+'" value="'+(prop.value || '')+'" ' + self.renderFieldAttributes(prop) +' onkeyup="_joe.updateImageFieldImage(this);"/>' +'<img class="joe-image-field-image" src="'+(prop.value||'')+'"/>' +'<span class="joe-image-field-size"></span>'; return html; }; this.updateImageFieldImage = function(dom){ var src = $(dom).val(); //var img = $(dom).next('.joe-image-field-image'); var img = $(dom).parent().find('.joe-image-field-image'); img.attr('src',src); $(dom).next('.joe-image-field-size').html(img.width() + 'w x '+img.height()+'h'); }; /*-----------------------------> J | Multisorter <-----------------------------*/ this.renderMultisorterField = function(prop){ var values = ($.type(prop.values) == 'function')?prop.values(self.current.object):prop.values||[]; var valObjs = []; //sort values into selected or option var val; var optionsHtml =''; var selectionsHtml =''; var idprop = prop['idprop'] ||'id'||'_id'; var template = prop.template || '${name} (${'+idprop+'})'; var value = prop.value || []; var selectionsArray = Array(value.length); var li; function renderMultisorterOption(v){ var html = '<li data-id="'+v[idprop]+'" ondblclick="_joe.toggleMultisorterBin(this);">'+fillTemplate(template,v)+'</li>'; return html; } //render selected list /* values.map(function(v){ //li = renderMultisorterOption(v);//'<li data-id="'+v[idprop]+'" onclick="_joe.toggleMultisorterBin(this);">'+fillTemplate(template,v)+'</li>'; selectionsHtml += renderMultisorterOption(v); });*/ var val_index; //render options list values.map(function(v){ li = renderMultisorterOption(v);//'<li data-id="'+v[idprop]+'" onclick="_joe.toggleMultisorterBin(this);">'+fillTemplate(template,v)+'</li>'; val_index = value.indexOf(v[idprop]); if(val_index != -1){//currently selected //selectionsHtml += li; selectionsArray[val_index] = li; }else{ optionsHtml += li; } }); var selectionsHtml = selectionsArray.join(''); var html= '<div class="joe-multisorter-field joe-field" name="'+prop.name+'" data-ftype="multisorter" data-multiple="'+(prop.allowMultiple||'false')+'">'+ '<div class="joe-filter-field-holder"><input type="text"class="" onkeyup="_joe.filterSorterOptions(this);"/></div>'+ '<p class="joe-tooltip"> double click or drag item to switch columns.</p>'+ '<ul class="joe-multisorter-bin options-bin">'+optionsHtml+'</ul>'+ '<ul class="joe-multisorter-bin selections-bin">'+selectionsHtml+'</ul>'+ __clearDiv__ +'</div>'; return html; }; this.filterSorterOptions = function(dom){ var query = $(dom).val().toLowerCase(); $(dom).parent().next('.joe-multisorter-bin').find('li').each(function(){$(this).toggle($(this).html().toLowerCase().indexOf(query) != -1 );}); logit(query); }; this.toggleMultisorterBin = function(dom) { var id = $(dom).data('id'); var parent = $(dom).parents('.joe-multisorter-bin'); var multisorter = parent.parents('.joe-multisorter-field'); var target = parent.siblings('.joe-multisorter-bin'); var newDom = parent.find('li[data-id=' + id + ']').detach(); //detach if no multiples allowed. /* if (!multisorter.data('multiple')) { newDom }*/ target.prepend(newDom); /* //reset divs var opts = $.unique($('.joe-multisorter-bin.options-bin').find('li')) $('.joe-multisorter-bin.options-bin').empty(); $('.joe-multisorter-bin.options-bin').html(opts);*/ }; /*-----------------------------> K | Buckets <-----------------------------*/ this.renderBucketsField = function(prop){ var values = ($.type(prop.values) == 'function')?prop.values(self.current.object):prop.values||[]; var valObjs = []; //sort values into selected or option var val; var bucketCount = (typeof prop.bucketCount =="function")?prop.bucketCount():prop.bucketCount || 3; var optionsHtml =''; var bucketsHtml =[]; var value = prop.value || [[],[],[]]; //add arrays to values for(var i = 0; i < bucketCount; i++){ bucketsHtml.push(''); if(!value[i]){ value.push([]); } } var idprop = prop[idprop] ||'id'||'_id'; var template = prop.template || '${name} <br/><small>(${'+idprop+'})</small>'; var lihtml; var selected; //populate selected buckets var foundItem; var bucketItem; var selectedIDs=[]; for(var i = 0; i < bucketCount; i++){ for(var li = 0; li < value[i].length; li++){ bucketItem = value[i][li]; //find object in values foundItem = values.filter(function(v){ return v[idprop] == bucketItem; })[0]||false; if(!foundItem){ foundItem = {}; foundItem[idprop] = li; } selectedIDs.push(bucketItem); lihtml = '<li data-id="'+foundItem[idprop]+'" >'+fillTemplate(template,foundItem)+'<div class="joe-bucket-delete cui-block joe-icon" onclick="$(this).parent().remove()"></div></li>'; bucketsHtml[i] += lihtml; } } values.map(function(v){ lihtml = '<li data-id="'+v[idprop]+'" >'+fillTemplate(template,v)+'<div class="joe-bucket-delete cui-block joe-icon" onclick="$(this).parent().remove()"></div></li>'; selected = false; //loop over buckets /*if(!prop.allowMultiple){ for(var i = 0; i < bucketCount; i++){ if(value[i].indexOf(v[idprop]) != -1){//currently selected bucketsHtml[i] += lihtml; } } }*/ if(selectedIDs.indexOf(v) ==-1 || prop.allowMultiple){ optionsHtml += lihtml; } }); function renderBucket(id){ return '<ul class="joe-buckets-bin selections-bin">'+bucketsHtml[id]+'</ul>'; } //renderHTML var html= '<div class="joe-buckets-field joe-field" name="'+prop.name+'" data-ftype="buckets">' +'<div class="joe-filter-field-holder"><input type="text"class="" onkeyup="_joe.filterBucketOptions(this);"/></div>' +'<div class="joe-buckets-field-holder" style="width:25%;">' +'<ul class="joe-buckets-bin options-bin '+(prop.allowMultiple && 'allow-multiple' || '')+'">'+optionsHtml+'</ul>' +'</div>' +'<div class="joe-buckets-field-holder" style="width:75%;">'; bucketsHtml.map(function(b,i){ html+=renderBucket(i); }); //+'<ul class="joe-buckets-bin selections-bin">'+bucketsHtml+'</ul>' html+= __clearDiv__ +'</div>'//end buckets field holder +__clearDiv__ +'</div>'; return html; }; this.filterBucketOptions = function(dom){ var query = $(dom).val().toLowerCase(); $(dom).parents('.joe-buckets-field').find('.options-bin').find('li').each(function(){$(this).toggle($(this).html().toLowerCase().indexOf(query) != -1 );}); logit(query); }; /*-----------------------------> L | Content <-----------------------------*/ this.renderContentField = function(prop){ var html = ''; if(prop.run){ html+= prop.run(self.current.object,prop); }else if(prop.template){ html += fillTemplate(prop.template,self.current.object); } return html; }; /*-----------------------------> M | URL <-----------------------------*/ this.renderURLField = function(prop){ var profile = self.current.profile; var disabled = (prop.locked &&'disabled')||''; var html= '<div class="joe-button" onclick="_joe.gotoFieldURL(this);">view</div>' +'<input class="joe-url-field joe-field" type="text" name="'+prop.name+'" value="'+(prop.value || '')+'" '+disabled+' />' + __clearDiv__; return html; }; this.gotoFieldURL = function(dom){ var url = $(dom).siblings('.joe-url-field').val(); window.open(url); }; /*-----------------------------> O | Object List Field <-----------------------------*/ this.renderObjectListField = function(prop){ var html ="<table class='joe-objectlist-table'>" +self.renderObjectListProperties(prop) +self.renderObjectListObjects(prop) //render a (table/divs) of properties //cross reference with array properties. //make sortable ? +"</table>"; return html; }; this.objectlistdefaultproperties = ['name','_id']; //render headers this.renderObjectListProperties = function(prop){ var properties = prop.properties || self.objectlistdefaultproperties; var property; var subprop; var html = '<thead><tr><th class="joe-objectlist-object-row-handle-header"></th>'; for(var p = 0,tot = properties.length; p<tot;p++){ subprop = properties[p]; subprop = ($.type(properties[p]) == "string")?{name:properties[p]}:subprop; property = { name: subprop.display||subprop.name, type: subprop.type||'text' }; html+="<th data-subprop='"+subprop.name+"'>" +(subprop.label || subprop.name)+"</th>"; } html+="</tr></thead>"; return html; }; //render objects this.renderObjectListObjects = function(prop){ var objects = self.current.object[prop.name] || []; var properties = prop.properties || self.objectlistdefaultproperties; var html = '<tbody id="joe-objectist-table">'; var objHtml = ''; var obj; for(var o = 0,objecttot = objects.length; o<objecttot;o++){ obj = objects[o]; html+=self.renderObjectListObject(obj,properties); //parse across properties } html+="</tbody>"; return html; }; this.renderObjectListObject = function(object,objectListProperties){ var properties = objectListProperties || self.objectlistdefaultproperties; var prop,property; var html = "<tr><td class='joe-objectlist-object-row-handle'>|||</td>"; function renderTextInput(prop){ var html = '<input type="text" class="joe-objectlist-object-input" style="width:auto;" value="'+prop.value+'"/>'; return html; } var renderInput = { // 'text':renderTextInput, 'text':self.renderTextField, select:self.renderSelectField }; //show all properties //TODO:create template in previous function and then use that to show values? for(var p = 0,tot = properties.length; p<tot;p++){ prop = properties[p]; prop = ($.type(properties[p]) == "string")?{name:properties[p]}:prop; property = $.extend({ name: prop.name, type:prop.type||'text', value:object[prop.name] || '' },prop); html+="<td>"+renderInput[property.type](property)+"</td>"; } html+= '</tr>'; return html; };