json-object-editor
Version:
JOE the Json Object Editor | Platform Edition
1,597 lines (1,346 loc) • 80 kB
JavaScript
/*/---------------------------------------------------------
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: '© <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;
};