$.intervals = {
'all':'(DATE(`created`) <= CURDATE())',
'today':'(DATE(`created`) = CURDATE())',
'yesterday':'(DATE(`created`) = (CURDATE() - INTERVAL 1 DAY))',
'from-yesterday':'(DATE(`created`) >= (CURDATE() - INTERVAL 1 DAY))',
'from-oneweek':'(DATE(`created`) >= (CURDATE() - INTERVAL 7 DAY))',
'from-onemonth':'(DATE(`created`) >= (CURDATE() - INTERVAL 31 DAY))',
'week':'(`created`>=DATE(NOW()- INTERVAL 7 DAY))',
'month':'(`created`>=DATE(NOW()- INTERVAL 31 DAY))',
}
$.preSaveCalls = {}
$.store = {
preSave:function(item){
let type = item.type
if($.preSaveCalls[type]) item = $.preSaveCalls[type](item)
return item
},
saveItem:function(item, callback){
let id = item.id
if(id == undefined || id == ''){
id = '_'
if(item.time == undefined) item.time = $.date.shortDateTime()
}
let items = {}
items[id] = $.store.preSave(item)
$.store.save(items, callback)
},
saveItems:function(vals, callback){
let items = {}
_.forEach(vals, function(item){
let id = item.id
if(id == undefined || id == '') id = '_'
items[id] = $.store.preSave(item)
})
$.store.save(items, callback)
},
save:function(vals, callback){
$.post('store_jsonx_save', vals, function(res){
if(res) console.log(res)
if($.debug == 1) $.bubble(res)
if(callback) callback(res)
})
.fail(function(XMLHttpRequest, textStatus, errorThrown){
$.bubble('Cannot connect to server, check connection')
})
},
load:function(query, callback){
// basic query: matching all comma delimited words
//$.bubble('query: '+query)
if($.debug) alert(query)
$.post('store_jsonx_load', {'query':query}, (res)=>{
//if($.debug) alert(res)
var items = ''
eval('items = '+res)
if($.store.items == undefined) $.store.items = {}
_.forEach(items, function(item){$.store.items[item.id] = item})
callback(items)
})
.fail(function(XMLHttpRequest, textStatus, errorThrown){
$.bubble(textStatus)
})
},
update:function(id, _update){
var update = {}
update[id] = _update
update[id].id = id
$.store.save(update)
},
delete:function(id, updatenote){
$.post('store_jsonx_delete', {id:id, updatenote:updatenote}, (res)=>{
if(res) $.bubble(res)
})
.fail(function(XMLHttpRequest, textStatus, errorThrown){
$.bubble('Cannot connect to server, check connection')
})
},
}
$.fn.ap = $.fn.append
$.fn.api = function(p){return this.ap($.ip(p))}
// n=name, l=label, t=type
$.ip = function(p){
if(p.f===undefined) p.f = ''
if(p.l===undefined) p.l = ''
// *** button
if(p.f == 'b'){
out = ''+p.l+' '
return out
}
let div = $._d('inputfield="'+p.n+'"')
if(p.l) div.ap(''+p.l+' ')
div.attr('class', 'input-block')
if(p.c) div.addClass(p.c)
if(p.f.includes('t')){
let out = ''
div.ap(out)
}else{
let out = ' '
div.ap(out)
}
if(p.s){
let out = ''
$.each(p.s, function(i, v){
out += ''+v+' '
})
out += ' '
div.ap(out)
}
if(p.f.includes('l')) div.ap(' ')
return div
}
// *** add a table row with table headers
$.fn.addTableHeads = function(headers){
var row = $('
')
$.each(headers, function(id, val){
row.ap($('').ap(val))
})
return this.ap(row)
}
// *** add a table row with cells
$.fn.addTableCells = function(cells){
var row = $(' ')
$.each(cells, function(id, val){
row.ap($('').ap(val))
})
return this.ap(row)
}
$.listQuery = function(p){
let type = p.type
let filter = p.filter ? p.filter : 1
let match = p.match ? (','+p.match) : ''
let limit = p.limit ? (' LIMIT '+p.limit) : ''
let out = 't_'+type+match+';(`type`="t_'+type+'") AND '+filter+';;`created` ASC'+limit
if(p.queryfields) out += ';' + p.queryfields
return out
}
$.fn.apResource = function(p){
p.type = 'resource'
p.fields = 'code,title,photo,description'
p.div = $._d()
$(this).ap(p.div)
$.store.load($.listQuery(p), function(items){
if(p.done) p.done(p.div, items)
p.div.set('resource-item')
_.forEach(items, (item)=>{
if(p.form == 'text') p.div.p({text:item[p.field], attr:'resource-text'})
else if(p.form == 'image') p.div.image({url:item[p.field], size:p.size, attr:p.attr})
})
})
return this
}
$.fn.addPage = function(name){
var page = $.store.load('type=page,name='+name)
return this
}
// Get items from DB using tags and display as divs with attr
$.fn.queryItems = async function(query, buildParams){
let target = $(this)
let container = $._d()
$.store.load(query, function(items){
container.itemsInGroups(items, buildParams)
if(buildParams.callback != undefined) return buildParams.callback(container)
target.empty().ap(container)
if(buildParams.done != undefined) setTimeout(function(){buildParams.done(target)}, 1000)
})
return this
}
$.fn.itemsInGroups = function(items, buildParams){
let count = 0
let container = this
let groupfield = 'group'
if(buildParams.groupfield) groupfield = buildParams.groupfield
// organize items into group/sub1/sub2/sub3 array
let groups = new Map()
for(var i in items){
let item = items[i]
let group_id = U(item[groupfield]).toUpperCase().trim()
count++
if(!groups.has(group_id)){
groups.set(group_id, new Map())
}
if(buildParams.groupFn){
if(groups.get(group_id).attrs == undefined) groups.get(group_id).attrs = {}
buildParams.groupFn(groups.get(group_id), item)
}
item.sub1 = ''
if(U(item.sub1) == ''){
groups.get(group_id).set(item.id, item)
continue
}
let sub1_id = U(item.sub1)
if(!groups.get(group_id).has(sub1_id)) groups.get(group_id).set(sub1_id, new Map())
if(U(item.sub2) == ''){
groups.get(group_id).get(sub1_id).set(item.id, item)
continue
}
let sub2_id = U(item.sub2)
if(!groups.get(group_id).get(sub1_id).has(sub2_id)) groups.get(group_id).get(sub1_id).set(sub2_id, new Map())
if(U(item.sub3) == ''){
groups.get(group_id).get(sub1_id).get(sub2_id).set(item.id, item)
continue
}
let sub3_id = U(item.sub3)
if(!groups.get(group_id).get(sub1_id).has(sub3_id)) groups.get(group_id).get(sub1_id).get(sub2_id).set(sub3_id, new Map())
groups.get(group_id).get(sub1_id).get(sub2_id).get(sub3_id).set(item.id, item)
}
groups.forEach(function(groupItems, group, map){
let groupDiv = $._d('item_group="'+group+'"').attr('groupsize', groupItems.size).appendTo(container)
if(groupItems.attrs){
_.map(groupItems.attrs, function(val, key){
groupDiv.attr(key, val)
})
}
let itemDiv = $._d()
groupItems.forEach(function(item, sub1, map){
if(item.type != undefined){
return itemDiv.ap($.itemToBlock(item, buildParams))
}
let sub1Items = item
let sub1Div = $._d('item_sub1="'+sub1+'"').attr('groupsize', item.size).appendTo(itemDiv)
if(!(item instanceof Map)) return sub1Div.ap($.itemToBlock(item, buildParams))
item.forEach(function(item, sub2, map){
if(item.type != undefined){
return sub1Div.ap($.itemToBlock(item, buildParams))
}
let sub2Items = item
let sub2Div = $._d('item_sub2="'+sub2+'"').attr('groupsize', item.size).appendTo(sub1Div)
if(!(item instanceof Map) ) return sub2Div.ap($.itemToBlock(item, buildParams))
item.forEach(function(item, sub3, map){
if(item.type != undefined){
return sub2Div.ap($.itemToBlock(item, buildParams))
}
let sub3Items = item
let sub3Div = $._d('item_sub3="'+sub3+'"').attr('groupsize', item.size).appendTo(sub2Div)
if(!(item instanceof Map)) return sub3Div.ap($.itemToBlock(item, buildParams))
item.forEach(function(item, key, map){
sub3Div.ap($.itemToBlock(item, buildParams))
})
})
})
})
groupDiv.heading(T(group), 'group-heading').ap(itemDiv)
})
if(count == 0) $(this).append('No items')
$(this).attr('itemcount', count)
return this
}
$.itemToBlock = function(item, buildParams){
let fields = buildParams.fields
let attr = buildParams.attr
let fieldFn = function(div, field, val, buildParams, show){
if(field == 'class') return div.attr('class', val)
else if(field == 'group') return div.attr('group', val)
else if(field == 'tag') return div.attr('tag', val)
else if(field == 'imagestyle') return _.each(val.split(' '), (v)=>{div.attr(v, '')})
else if(field == 'color') return div.attr('style', 'background-color:'+val)
else if(field == 'status') div.attr('status', val)
else if(field == 'price') val = CUR(val*1)
else if(field == 'unit_price') val = CUR(val*1)
else if(field == 'cost') val = CUR(val*1)
else if(field == 'image'){
if(NULL(val)) return
if(buildParams.imagewidth) val = 'sized-image/'+buildParams.imagewidth+'/'+val
return $(' ').zoomable(val).attr('loading', 'lazy').addClass('block-image')
.attr('src', val).attr('field', field).set('show')
.appendTo(div.attr('hasimage', 1).attr('style', 'background-image:url("'+val+'")'))
}
else if(field == 'photo' || field == 'photo2'){
if(NULL(val)) return
if(buildParams.imagewidth) val = 'sized-image/'+buildParams.imagewidth+'/'+val
return $(' ').zoomable(val).attr('loading', 'lazy').addClass('block-image')
.attr('src', val).set('show')
.appendTo(div).attr('field', field)
}
let labels = buildParams.labels
let fieldDiv = $._d('field="'+field+'"').appendTo(div)
if(labels){
if(labels[field]) fieldDiv.ap($.bold(labels[field]+': '))
}
fieldDiv.ap(val)
if(show) fieldDiv.set('show')
if(field == 'datetime') fieldDiv.attr('ago', val)
if($.fn.field) fieldDiv.field()
}
if(fields != undefined) fields = fields.split(',')
var div = $._d('block=1 item_type="'+item.type+'" item_id="'+item.id+'" id="'+item.id+'" '+U(attr))
if(buildParams.tags){
let tags = buildParams.tags.split(',')
_.each(tags, function(tag){
div.attr(tag, '' + U(item[tag]))
})
}
let fieldsDone = {}
if(fields != undefined){
_.each(fields, function(field){
var val = item[field]
if((field != 'image') && val == undefined) return
fieldFn(div, field, val, buildParams, 1)
fieldsDone[field] = 1
})
}
for(var field in item){
if(fieldsDone[field] != 1){
var val = item[field]
fieldFn(div, field, val, buildParams)
}
}
if($.fn.block) div.block()
if(buildParams.controls) buildParams.controls(div)
if(buildParams.build) buildParams.build(item, div)
return div
}
$.fn.valuesToBlocks = function(blocks, buildParams){
let itemcount = 0
for(var i in blocks){
$(this).append($.itemToBlock(blocks[i], buildParams).attr('index', itemcount))
itemcount++
}
if(itemcount == 0) $(this).append('No items')
$(this).attr('itemcount', itemcount)
return this
}
$.fn.blockToValues = function(){
var blocks = {}
var count = 0
$(this).find('[block]').each(function(){
var block = {}
count++
var id = $(this).attr('id')
if(id == undefined) id = '_'+count
blocks[id] = $(this).fieldsToArray()
})
return blocks
}
$.fn.items = function(attr, tags, type){
let out = ''
if(type == undefined) type = ''
else type = ';`type`="'+type+'"'
tags = tags.split(',')
_.forEach(tags, function(tag){
out += '
'
})
$(this).append(out)
return this
}
$.fn.fieldsToArray = function(){
var arr = {}
arr.class = $(this).attr('class')
$(this).find('[field]').each(function(){
var field = $(this).attr('field')
var val = $(this).text()
arr[field] = val
})
return arr
}
$.fn.addField = function(field, attr, val){
$(this).ap(''+(val?val:'')+'
')
return this
}
$._button = function(title, onclick, attr, o){
return $('')
.html(title)
.on('click', function(e){
this.event = e
onclick(this, o)//.call(this)
})
}
$.fn.addButton =
$.fn.button = function(title, onclick, attr, o){
return $(this).ap($._button(title, onclick, attr, o))
}
$.fn.set = function(attr, off){
if(off == false) $(this).removeAttr(attr)
else $(this).attr(attr, 1)
return this
}
$.fn.unset = function(attr){return $(this).removeAttr(attr)}
$.fn.flip = function(attr){
if($(this).attr(attr)) $(this).removeAttr(attr)
else $(this).attr(attr, 1)
return this
}
$.fn.apfn = function(fn){
fn($(this))
return this
}
$.fn.set = function(attr, off){
if(off == false) $(this).removeAttr(attr)
else $(this).attr(attr, 1)
return this
}
$.fn.switch = function(a, b, onclick, attr){
$('')
.html(a)
.attr('switch', 'a')
.attr('switch-a', a)
.attr('switch-b', b)
.appendTo($(this))
.click(function(e){
const sw = $(this)
let v = sw.attr('switch')
onclick(sw, v)
v = (v == 'a')?'b':'a'
sw.attr('switch', v).html(sw.attr('switch-'+v))
})
return this
}
$.fn.addTextInp = function(id){return $(this).ap('')}
$.fn.editable = function(){
$('[contenteditable]').removeAttr('contenteditable')
if($(this).attr('contenteditable') != 'true'){
$(this).attr('contenteditable', true).focus()
}
return this
}
$.fn.blockDeleteButton = function(){
$(this).addButton('Delete', function(){
if(confirm('Delete this?'))
$.store.update($(this).getBlockId(), {type:"deleted"})
}, 'delete-button')
return this
}
$.fn.blockSaveButton = function(){
$(this).addButton('Save', function(){
$(this).updateBlock($(this).getBlock().fieldsToArray())
})
return this
}
$.wordHighlight = function(text){
// split words, put each word in a and set color per first 2 letters
$.wordsToHighlight = {
'americano':'#000000',
'espresso':'#050000',
'latte':'#000099',
'oat':'#ff0000',
'cappuccino':'#ff3311',
'mocha':'#ff0033',
'ethiopia':'#22aaff',
'yellow':'#999922',
'almonds':'#ff2244',
'caramel':'#ff22ff',
'vanilla':'#332288',
'matcha':'#008800',
'pure':'#009900',
'premium':'#999900',
}
let out = ''
_.forEach(_.split(text, ' '), function(word){
if(word.length <= 1){
out += word + ' '
return
}
if($.wordsToHighlight[word.toLowerCase()]){
out += ''+word+' '
return
}
out += word + ' '
})
return out
}
$.stringToColor = (str)=>{
let hash = 0
str.split('').forEach(char =>{
hash = char.charCodeAt(0) + ((hash << 5) - hash)
})
let colour = '#'
for(let i = 0; i < 3; i++){
const value = (hash >> (i * 8)) & 0xff
colour += value.toString(16).padStart(2, '0')
}
return colour
}
$.fn.d = function(attr, content){
return $(this).ap($._d(U(attr), content))
}
$._d = function(attr, content){
return $(''+U(content)+'
')
}
$.div = function(id, cls, attr, content){
if(typeof id === 'object'){
let params = id
id = params.id
cls = params.class
attr = params.attr
content = params.content
}
return $(''+U(content)+'
')
}
$.fn.div = function(id, cls, attr, content){
$(this).ap($.div(id, cls, attr, content))
return this
}
$.fn.label = $.fn.addLabel = function(text){ return $(this).ap(''+text+' ') }
$.fn.br = function(){return $(this).ap(' ')}
$.bold = function(o, attr){return $(' ').ap(o)}
$.BBr = function(t){
if(t == '') return ''
return ''+t+' '
}
$.fixed = function(id, cls, attr, content){
return $.div(id, cls, attr, content).appendTo('body')
}
$.fn.subtitle = function(title, attr){
this.ap(''+title+' ')
return this
}
$.fn.link = function(title, url, attr){
this.ap(''+title+' ')
return this
}
$.fn.span = function(content, id, cls, attr){
$(this).ap(''+content+' ')
return this
}
$.fn.startPage = function(){
$('body').div('page').div('blocks')
return this
}
$.fn.heading = function(heading, attr){
return $(this).ap(''+heading+' ')
}
$.fn.thumb = function(url, attr){
let file = ''
if(U(url)) file = url+'.thumb.jpg'
return $(this).ap(' ')
}
$.fn.image = function(p){
let url = p.url
if(url == undefined) return this
let size = p.size
let attr = p.attr
url = 'sized-image/'+size+'/'+url
return $(this).ap(' ')
}
$.fn.s = function(t, size, attr, onclick){
if(typeof t === 'object' && size == undefined){
if(t.text){
size = t.size
attr = t.attr
onclick = t.click
t = t.text
}
}
let s = $(' ').ap(t).appendTo(this)
if(onclick) s.click(function(){onclick(this)})
return this
}
$.fn.p = function(t, size, attr, onclick){
let fn = 0
if(typeof t === 'object'){
if(t.postfn) fn = t.postfn
size = t.size
attr = t.attr
onclick = t.click
t = t.text
}
let p = $('
').ap(t).appendTo(this)
if(fn != 0) fn(p)
if(onclick) p.click(function(){onclick(this)})
return this
}
$.fn.input = function(p){
let div = $._d('inputbox ' + U(p.attr))
if(p.noBox) div = $(this)
if(p.label) div.ap(''+p.label+' ')
let inp
if(p.type == 'text'){
inp = $('')
div.ap(inp)
} else if(p.type == 'file'){
inp = $(' ')
div.ap(inp)
inp.setupImageUpload()
p.val = undefined
div.button('Clear', (b)=>{
$(b).next().attr('value', 'clear')
})
} else if(p.type == 'color'){
inp = $(' ').attr('bgcolor', p.val)
div.ap(inp)
inp.click(()=>{
$.promptColor('Which color', inp.val(), (color)=>{
inp.val(color).attr('bgcolor', color)
})
})
}else{
inp = $(' ')
div.ap(inp)
if(p.type) inp.attr('type', p.type)
}
if(p.id) inp.attr('id', p.id)
if(p.name) inp.attr('name', p.name)
if(p.val) inp.val(p.val)
inp.on('keydown', function(e){
let val = _.trim($(this).val())
if(val == '') return
if(val[0] == '=' && val[val.length - 1] == ';'){
let res = 0
eval('res '+val)
if(val != undefined) inp.val(res)
}
})
if(p.enter) inp.enter(p.enter)
if(p.valp != undefined){
div.attr('vals-popup', 1).button('>', function(e){
$.promptChoice(p.valp)
})
}
return $(this).ap(div)
}
$.fn.colorPicker = function(){
let inputdiv = $(this)
let colordiv = $._d('color-picker').appendTo(inputdiv)
let colors = ['#fff', '#f00', '#0f0', '#00f', '#ff0', '#0ff', '#f0f']
_.forEach(colors, (color)=>{
colordiv.button(color, (but)=>{
inputdiv.find('input').val(color).css('background-color', color)
}, 'color-pick style="background-color:'+color+'"')
})
return this
}
$.fn.refreshPage = function(fn){
$.refresh = f;
//$.setRefreshCalls('messenger', $.messengerLoad)
return this
}
$.fn.loadPage = function(pageName){
// load page block by page name or id
// after page is loaded, it then loads
// its child blocks
$.store.load('item', function(res){
$('#page').empty().valuesToBlocks(res)
})
}
$.fn.rotator = function(){
$(this).div('rotator')
$('#rotator')
.button('A', ()=>{
screen.orientation.lock('portrait')
}, 'rotator-button')
return this
}
$.fn.clock = function(){
$(this).div('clock')
$('#clock').html($.date.form('ddd DD/MM h:mm a'))
setInterval(function(){
$('#clock').html($.date.form('ddd DD/MM h:mm a'))
}, 60000)
return this
}
$.fn.battery = function(){
$(this).div('battery')
$.checkBattery = function(){
if(navigator.getBattery){
navigator.getBattery().then(function(battery){
let level = parseInt(battery.level * 100)
let charging = battery.charging
let b = $('#battery').removeAttr('low')
b.html(level + '%')
if(level <= 60) b.set('low')
if(charging == false) b.set('low')
setTimeout(()=>{$.checkBattery()}, 3000)
})
}
}
$.checkBattery()
return this
}
$.fn.menubar = function(title, menuItems){
var menubar = $('')
menubar.pop(title, function(pop){
_.forEach(menuItems, function(item){
pop.link(item.title, item.url)
})
})
$(this).ap(menubar)
return this
}
$.fn.querySelect = function(title, choices){
$(this).pop(title, function(t){
_.forEach(choices, function(val, key){
t.addButton(key, function(e){
$.searchQuery = val
$.refresh()
})
})
})
return this
}
$.fn.pop = function(title, callback){
let div = $('
')
div.ap('
'+title+' ')
callback(div)
$(this).ap(div)
return this
}
$.fn.uploadButton = function(title, callback, attr){
$('
')
.html(title).appendTo($(this)).attr('button', title)
.click(function(e){
$('#upload-box').show()
$.uploadDone = onclick
})
return this
}
// create HTML table with rows and cells
$.fn.table = function(rows){
let table = $(' ')
_.forEach(row, function(cell){
let td = $('')
td.ap(cell)
tr.ap(td)
})
table.ap(tr)
})
$(this).ap(table)
return this
}
// Button selectors to set attribute to a DOM
$.fn.arraySelectors = function(targetText, arr){
var out = ''
_.each(arr, function(value, key){
out += ''+value+' '
})
$(this).append(out)
return this
}
// Fill form inputs with object with fields and values
$.fn.fillForm = function(fields){
$(this).emptyInputs()
$.each(fields,
function(name, val){
let input = $('[name="'+name+'"]')
if(input.attr('type') == 'file'){
input.removeUploadImage()
if(name){
let imagestyle = ''+fields['imagestyle']
input.after(' ')
}
input.attr('log', '')
input.val('')
input.attr('value', val)
return
}
input.val(val)
}
)
return this
}
$.fn.removeUploadImage = function(){
$(this).val('').attr('value', '').parent().find('.uploaded-image').remove()
return this
}
$.popup = function(id, title){
if(title == 0) return $('#'+id).remove()
if(title == undefined) title = ''
return $.div(id).appendTo('body').set('fullpage').set('popup').heading(title).show().removeButton()
}
$.fn.removeButton = function(){
let div = $(this)
return $(this).button('X', ()=>{div.remove()}, 'popup-close-button')
}
$.fn.hideButton = function(){
let div = $(this)
return $(this).button('X', ()=>{div.hide()}, 'popup-close-button')
}
$.fn.fold = function(name){
$(this).set('fold').heading(name).click(()=>{
clearTimeout($.foldTimeout)
if($(this).attr('fold') == 'expanded'){
$(this).set('fold')
$(this).scrollTop(0)
}else{
$(this).attr('fold', 'expanded')
$.foldTimeout = setTimeout(()=>{$(this).set('fold')}, 5000)
}
})
return this
}
$.fn.tabs = function(id, tabs){
if($.tabsIds == undefined) $.tabsId = 0
++$.tabsId
let parent = $(this)
let count = 0
let tabbar = $._d('tabs="'+id+'"').attr('tabsid', $.tabsId).attr('tabsnow', 0).appendTo(this)
let tabdiv = $._d().appendTo(tabbar).button('>', (t)=>{
t.event.stopPropagation()
tabbar.set('tab-expanded')
.click(()=>{tabbar.removeAttr('tab-expanded')})
setTimeout(()=>{tabbar.removeAttr('tab-expanded')}, 2400)
}, 'tab-expand-button')
_.each(tabs, function(param, id){
if(param.id == undefined) param.id = id + '-tab'
if(param.title == undefined) param.title = _.startCase(id)
parent.div(param.id)
let tab = $('#'+param.id).attr('tab', $.tabsId).attr('tabsno', count)
if(count >= 1) tab.hide()
tabdiv.button(param.title, function(){
tabbar.unset('tab-expanded').attr('tabsnow', tab.attr('tabsno'))
$('[tab="'+$.tabsId+'"]').hide()
if(tab.show().attr('tab-viewed') == undefined){
tab.attr('tab-viewed', 1)
if(param.firstview) param.firstview()
}
}, 'tabsno="'+count+'"')
if(count == 0) tab.attr('tab-viewed', 1)
count++
})
tabbar.attr('width', count % 5)
return this
}
$.fn.filterbar = function(id, filters){
if($.filterbarIds == undefined) $.filterbarId = 0
$.filterbarId++
let filterbar = $._d('filterbar="'+id+'"').attr('filterbarid', $.filterbarId).attr('filternow', 0).appendTo(this)
let count = 0
_.each(filters, function(param){
filterbar.button(param.title, function(){
filterbar.attr('filternow', count)
if(param.callback) param.callback()
}, 'filterno="'+count+'"')
count++
})
return this
}
$.fn.divItems = async function(items, itemFn, attr){
let container = $(this)
attr = U(attr)
_.forEach(items, function(item){
let div = $('
')
.appendTo(container)
itemFn(div, item)
})
return this
}
$.fn.divFields = function(item, fields, attr){
attr = U(attr)
let container = $(this)
_.split(fields, ',').forEach(function(field){
if(item[field] == undefined) return
container.append(''+item[field]+'
')
})
return this
}
$.fn.commas = function(){
_.forEach($(this), function(i){
let t = $(i).text()
if(t == ''){
$(i).text('')
}else{
$(i).text(N(t).toLocaleString())
}
})
}
$.fn.numpad = function(_t, keyFn, zeros, okFn, params){
let commas = 1
if(params){
if(params.commas == false) commas = 0
}
if(keyFn == 0) keyFn = ()=>{}
let t = $(_t)
let div = $._d('numpad').appendTo(this)
.button('7', function(){$._numpadPress(t, 7, commas); keyFn()})
.button('8', function(){$._numpadPress(t, 8, commas); keyFn()})
.button('9', function(){$._numpadPress(t, 9, commas); keyFn()})
.button('', function(){})
.br()
.button('4', function(){$._numpadPress(t, 4, commas); keyFn()})
.button('5', function(){$._numpadPress(t, 5, commas); keyFn()})
.button('6', function(){$._numpadPress(t, 6, commas); keyFn()})
.button('', function(){})
.br()
.button('1', function(){$._numpadPress(t, 1, commas); keyFn()})
.button('2', function(){$._numpadPress(t, 2, commas); keyFn()})
.button('3', function(){$._numpadPress(t, 3, commas); keyFn()})
.button('<', function(){$._numpadPress(t, '<', commas); keyFn()})
.br()
.button('0', function(){$._numpadPress(t, 0, commas); keyFn()})
if(zeros == 1){
div.button('00', function(){$._numpadPress(t, '00', commas); keyFn()})
div.button('000', function(){$._numpadPress(t, '000', commas); keyFn()})
}
div.button('C', function(){$._numpadPress(t, 'C'); keyFn()})
if(okFn) div.button('OK', function(){okFn()}, 'numpad-ok')
return this
}
$._numpadPress = function(t, key, commas){
if(key == '<'){
let v =t.text()
v = v.substring(0, v.length - 1)
if(v == '') v = '0'
t.text(v)
return
}
if(key == 'C') return t.text('0')
let v = t.text()
if(v == 0 || v == '0') v = ''
v = v + '' + key
t.text(v)
if(commas == 1) t.commas()
}
$.fn.keyboard = function(_t, keyFn){
let t = $(_t)
let div = $._d('keyboard').appendTo(this)
let allkeys = [
'1234567890<',
'QWERTYUIOP',
'ASDFGHJKL',
'ZXCVBNM_'
]
_.forEach(allkeys, (row)=>{
for(let i=0; i'+JSON.stringify(o)+''
}
function T(text){
let _text = text
if($._T != undefined){
if($._T[$.language]){
if($._T[$.language][text]) _text = $._T[$.language][text]
}
}
return ''+_text+' '
}
function N(n){
let v = parseFloat((n+'').replace(/,/g, ''))
if(isNaN(v)) v = 0
return v
}
function U(v, prefix){return (v==undefined)?'':(prefix?prefix:''+v)}
function NULL(v){return v == '' || v == undefined}
function R(v){ return v+"\n" }
function nearest1000(v){ return Math.floor(v/1000)*1000 }
function nearest500(v){ return Math.floor(v/500)*500 }
function INC(o, key, val = 1){ if(o[key] == undefined) o[key] = 0; o[key] += val }
function TLS(val){ return val.toLocaleString() }
function CUR(val, cur){
if(val == undefined) return ''
if(cur == undefined) cur = $.defaultCurrency
val = Math.round(1*val, 2)
return val.toLocaleString() + ' ' + U(cur)
}
function PERCENT(v, percent){return Math.round(v * percent/100)}
// check server for data updates
$.updateTimeoutTime = 5 * 60 * 2.1 // 200 * 5 * 60 * 2.2 = 2.2 min
$.updateRefreshTime = 1000 *60 * 5
$.updatePointer = 0
$.checkUpdateSpeed = 2
$.updateCount = 0
$.lastUpdateCheck = ''
$.refreshCalls = {}
Error.stackTraceLimit = 1000
$.consoleLog = ''
window.addEventListener('beforeunload', (event) => {
if($.refreshLock == undefined) return;
if($.refreshPageConfirmed == 1) return
event.preventDefault()
event.returnValue = ''
})
window.onerror = function(message, source, lineno, colno, error){
let line = source
+ '\r\n' + message
+ '\r\n[' +lineno
+ ',' + colno
+ ']'
alert(line)
$.log('\r\n\r\n' + line)
$.bubble('Error: ' + line)
}
$.getCSS = function(elem){
let out = ''
let html = elem.outerHTML
html = html
.replace(/>\r\n<')
.replace(/&/g, '&')
.replace(//g, '>')
out += html+'\r\n\r\n'
let css = window.getComputedStyle(elem)
_.forOwn(css, (v, k)=>{out += k + ': ' + v + '\r\n'})
$.consoleCSS.html(out)
}
$.docLocation = function(location){
$.refreshPageConfirmed = 1
document.location = location
}
$.consoleBuild = ()=>{
if($.consoleDiv != undefined) return
$.consoleDiv = $._d('console').appendTo('body')
$.consoleControls =
$._d('console-controls').appendTo($.consoleDiv)
.button('<', ()=>{$.consoleDiv.attr('position', 0)}, 'console-up')
.button('>', ()=>{$.consoleDiv.attr('position', 1)}, 'console-down')
.br().br()
.button('X', ()=>{$.consoleDiv.hide()})
.br().br()
.button('CSS', (t)=>{$.cssProbe = 1; $(t).attr('css-probe', '1')}, 'css-probe')
.br()
.button('Clear', ()=>{$.consoleLog.empty()})
$.consoleLog = $._d('console-log').appendTo($.consoleDiv)
$.consoleCSS = $._d('console-css').appendTo($.consoleDiv)
}
$.consoleOn = ()=>{
$.consoleBuild()
$.consoleDiv.show()
}
$.log = (t)=>{
$.consoleBuild()
$.consoleLog.ap($.date.shortDateTime() + '\r\n' + t + '\r\n').scrollTop(50000)
}
$.setRefreshCalls = function(updatenote, loadFn){
if($.refreshCalls[updatenote] == undefined) $.refreshCalls[updatenote] = []
$.refreshCalls[updatenote].push(function(update){$.listCheck(update, updatenote, loadFn)})
}
$.listCheck = function(update, updatenote, callback){
if(!$.updatenoteIncludes(updatenote)) return
callback()
}
$.check_update = function(){
if($.updateTimeout > 0) return $.updateTimeout = $.updateTimeout - 1
if($.updateTimeout == undefined) $('body').div('last-updated')
$.updateTimeout = $.updateTimeoutTime
if($.last_refresh && (($.last_refresh + $.updateRefreshTime) < $.date.now())){
$.last_refresh = $.date.now()
if($.refresh) $.refresh()
}
$.ajax({
type:'POST',
url:'check_update.php',
data:{last_updated:$.last_updated, speed:$.checkUpdateSpeed, pointer:$.updatePointer}
})
.done(function(res){
if(res != 'no update'){
eval('$.lastUpdateCheck = '+res)
$.last_updated = $.lastUpdateCheck.last_updated
let updatenote = $.lastUpdateCheck.updatenote
if($.refresh) $.refresh()
$.last_refresh = $.date.now()
_.forEach($.refreshCalls, function(callbacks, note){
if(!$.updatenoteIncludes(note)) return
$.bubble(updatenote)
_.forEach(callbacks, function(callback){
callback($.lastUpdateCheck)
})
})
$.intercomPlayback()
if($.updateCount > 0){
if($.updatenoteIncludes('system_recss')) $.reCSS()
if($.systemRefreshOn == 1){
if($.updatenoteIncludes('system_refresh')) $.reloadPage()
}
}
$.updateCount++
}
$('#last-updated').text('updated:'+$.date.form('h:mm:ss'))
$.updateTimeout = 0
})
.fail(function(XMLHttpRequest, textStatus, errorThrown){
$.bubble('Cannot connect to server, check connection')
console.error('Check update post failed, retrying')
$.updateTimeout = 0
setTimeout(function(){$.check_update()}, 200)
})
}
const observer = new MutationObserver(
function(mutations_list){
mutations_list.forEach(function(mutation){
mutation.addedNodes.forEach(function(node){
if($(node).hasClass('image-upload')){
let uploadImage = $(node).find('.image-upload')
if(uploadImage.length != undefined){
if(typeof setupImageUpload == 'function') setupImageUpload(uploadImage)
}
}
if($(node).attr('loadtag')){
let tag = $(node).attr('loadtag')
$(node).removeAttr('loadtag')
$.store.load(tag, function(items){
if(!(_.isEmpty(items))) $(node).valuesToBlocks(items)
})
}
})
})
})
observer.observe(document, {childList:true, subtree:true})
$.updatenoteIncludes = function(note){
return _.includes(U($.lastUpdateCheck.updatenote), note)
}
$(document)
.change(function(e){
if($(e.target).hasClass('input-select')){
let sel = $(e.target)
sel.prev().val(sel.val())
}
if($(e.target).attr('filter')){
let item = $(e.target)
$.refresh()
}
})
.on('contextmenu', function(e){
if($.debug == undefined) e.preventDefault()
})
.click(function(e){
let item = $(e.target)
let parent = item.parent()
let parent2 = parent.parent()
if($.cssProbe != undefined){
if(!item[0].hasAttribute('css-probe')){
$.getCSS(e.target)
delete $.cssProbe
$('[css-probe]').attr('css-probe', 0)
e.stopPropagation()
return
}
}
if($.userAlive) $.userAlive()
if($.pick){
if(item.attr('pick') != undefined) return $.pick(item) & $.clickSound()
if(parent.attr('pick') != undefined) return $.pick(parent) & $.clickSound()
if(parent2.attr('pick') != undefined) return $.pick(parent2) & $.clickSound()
}
if($.listItemClicked){
if(item.hasClass('side-panel-item-event')) return $.listItemClicked(item) & $.clickSound()
if(parent.hasClass('side-panel-item-event')) return $.listItemClicked(parent) & $.clickSound()
if(parent2.hasClass('side-panel-item-event')) return $.listItemClicked(parent2) & $.clickSound()
}
if(item.attr('attr-selector')){
$.clickSound()
let targetText = item.attr('attr-selector')
let attrs = /(.*?)\[(.*?)\]/.exec(targetText)
let target = $(attrs[1])
target.attr(attrs[2], item.attr('key'))
target.trigger('change')
return
}
if(item.hasClass('uploaded-image')){
$.clickSound()
if($('body').attr('image-preview') == 'on'){
$('body').removeAttr('image-preview')
item.removeAttr('preview')
}else{
$('body').attr('image-preview', 'on')
item.attr('preview', 1).attr('landscape', screen.width < screen.height)
}
return
}
if(item.attr('field') != undefined) return
if(item.attr('pop-button')){
let popDiv = parent()
let pop = popDiv.attr('pop')
$('[pop="show"]').attr('pop', 'hide')
if(pop == 'hide') popDiv.attr('pop', 'show')
}
if(item[0] instanceof HTMLButtonElement) $.clickSound()
else if(parent[0] instanceof HTMLButtonElement) $.clickSound()
else if(parent2[0] instanceof HTMLButtonElement) $.clickSound()
$.noSleep()
if($.notificationAsk == undefined){
$.notificationAsk = 1
$.swAskPermission()
}
})
.on('keydown', function(e){
$.clickSound()
if($.userAlive) $.userAlive()
if(e.ctrlKey){
if(e.key == '1'){$.refreshPage(); e.preventDefault()}
else if(e.key == '2'){$.reCSS(); e.preventDefault()}
else if(e.key == '3'){$.systemRefresh(); e.preventDefault()}
else if(e.key == '4'){$.systemReCSS(); e.preventDefault()}
else if(e.key == 'd'){$.debug = 1; e.preventDefault()}
else if(e.key == 'e'){$.promptInfo($.consoleLog); e.preventDefault()}
else if($.keydowns[e.key]) $.keydowns[e.key]()
}
})
.on('scroll', function(e){
$.noSleep()
})
$.fn.nodrag = function(){
$(this)[0].onselectstart = ()=>false
return $(this).set('no-select').on('dragstart', (e)=>{e.preventDefault()})
}
$(window).on("orientationchange", function(e) {
e.stopPropagation()
screen.orientation.lock('portrait')
return false
})
$.keydowns = {}
$.systemRefresh = function(){
$.store.saveItem({type:'t_system', updatenote:'system_refresh'})
}
$.systemReCSS = function(){
$.bubble('System ReCSS')
$.store.saveItem({type:'t_system', updatenote:'system_recss'})
}
$.batch = function(fn, data, callback){
$.post(fn, data, function(res){
if(res) console.log(res)// + $.bubble(res)
if(callback) callback(res)
})
}
$.backend = $.batch
$.setCookie = function(name, val, _default){
if(_default == undefined) _default = ''
if(val != undefined) Cookies.set(name, val, {expires:365})
val = $[name] = (Cookies.get(name) ? Cookies.get(name) : _default)
Cookies.set(name, val, {expires:365})
return val
}
$.fn.enter = function(callback){
this.keyup(function(e){
console.log('enter key')
if(e.keyCode == 13) callback.call(this, e)
})
return this
}
$.bubble = function(content, attr){
if($.bubbleDiv == undefined)
$.bubbleDiv = $('
').appendTo('body')
$.log(content)
let timeout = 700
if(attr) timeout = 1000
$('')
.appendTo($.bubbleDiv)
.append(content)
.click((e)=>{$(e.target).remove()})
.delay(timeout)
.fadeOut('slow', function(){
$(this).remove()
})
}
$.fn.getInputVals = function(){
var elem = $(this)
var out = {}
$.each(elem.find('[name]'), function(id){
let name = $(this).attr('name')
let val = $(this).attr('value')
if(val == undefined || val == '') val = $(this).val()
out[name] = val
})
return out
}
$.fn.emptyInputs = function(){
let inp = this.find('[name]')
inp.next('img').remove()
inp.val('')
inp.removeUploadImage()
return this
}
// ... get items from server to fill side panel
$.fn.getListItems = function(url, params, fields, callback){
let container = $(this)
$.getJSON(url, params, function(items){
container.empty()
let group = ''
let count = 0
$.each(items, function(id, item){
if(group != item.group){
if(group != ''){
container.p('Total:'+count).br()
count = 0
}
group = item.group
container.ap('
'+group+' ')
}
count++
if(item.id == undefined) item.id = id
let p = $('
'
+(item.image?(' '):'')+'
')
_.split(fields, ',').forEach(function(field){
p.s(U(item[field]), '5', 'field="'+field+'"')
})
container.ap(p)
})
if(count > 0) container.p('Total:'+count).br()
if(callback) callback()
})
return this
}
$.fn.listItemQuery = function(query, params, fields, callback){
let container = $(this)
$.store.load(query, function(items){
container.empty()
let group = ''
let count = 0
$.each(items, function(id, item){
if(group != item.group){
if(group != ''){
container.p('Total:'+count).br()
count = 0
}
group = item.group
container.ap('
'+U(group)+' ')
}
count++
if(item.id == undefined) item.id = id
let p = $('
'
+(item.image?(' '):'')+'
')
_.split(fields, ',').forEach(function(field){
if(item[field] == '') return
p.s(U(item[field]), '5', 'field="'+field+'"')
})
container.ap(p)
})
if(count > 0) container.p('Total:'+count).br()
if(callback) callback()
})
return this
}
$.fn.parentTag = function(tag){ return $($(this).parents('['+tag+']')[0]) }
$.fn.getBlock = function(){ return $(this).parentTag('block') }
$.fn.getBlockId = function(){ return $(this).getBlock().attr('id') }
$.fn.updateBlock = function(update){
$.store.update($(this).getBlockId(), update)
return this
}
$.fn.editing = function(){
$('[editing]').removeAttr('editing')
$(this).attr('editing', 1)
return this
}
$.fullscreen = function(){
if($.fullscreenOn == 0) return
if(document.fullscreen) return
var elem = document.body
if(elem.requestFullscreen){
elem.requestFullscreen()
$.inFullscreen = 1
} else if (elem.webkitRequestFullscreen) { /* Safari */
elem.webkitRequestFullscreen()
} else if (elem.msRequestFullscreen){ /* IE11 */
elem.msRequestFullscreen()
}
}
$.noSleep = function(){
if($.silentMode == 1) return
if($.noSleepObj == undefined){
$.noSleepObj = new NoSleep()
$.noSleepObj.enable()
}
}
$.setAlarm = function(title, body, minutes, soundfile){
if($.alarmContainer == undefined){
$.alarmContainer = $('
')
$('body').ap($.alarmContainer)
$.alarms = {}
}
$.alarms[title] = {
title:title, body:body, soundfile:soundfile,
minutes:minutes, seconds:minutes * 60
}
$.alarms.count++
$('[alarm="'+title+'"]').remove()
let div = $('
')
div.addButton(' '+title, function(){
let alarmDiv = $(this).parentTag('alarm')
let title = alarmDiv.attr('alarm')
clearTimeout($.alarms[title].timeout)
clearInterval($.alarms[title].countdownInterval)
$.playSound(soundfile, false)
$('[alarm="'+title+'"]').remove()
delete $.alarms[title]
$.alarms.count--
if($.alarms.count <= 0){
clearInterval($.alarms.countdownInterval)
delete $.alarms.countdownInterval
}
}, 'seconds="'+$.alarms[title].seconds+'"'
)
$.alarmContainer.ap(div)
$.alarms[title].timeout = setTimeout(function(){
}, $.alarms[title].seconds * 1000)
if($.alarms.countdownInterval == undefined){
$.alarms.countdownInterval = setInterval(function(){
$('[seconds]').each(function(){
let seconds = $(this).attr('seconds') * 1 - 1
$(this).attr('seconds', seconds)
if(seconds < 0){
$.sendNotification(title, body)
$.playSound(soundfile, true)
$(this).removeAttr('seconds')
$(this).parentTag('alarm').attr('over', 1)
return
}
let countdown = new Date(seconds * 1000)
.toISOString().slice((seconds >= 3600)?11:14, 19)
$(this).attr('countdown', countdown)
})
}, 1000)
}
}
$.showhide = function(shows, hides){
$('[fullpage]').hide()
_.split(shows, ',').forEach(function(v){$(''+v).show()})
_.split(hides, ',').forEach(function(v){$(''+v).hide()})
}
$.fn.inputAppend = function(val){
return $(this).val($(this).val() + '' + val).focus()
}
$.promptText = function(type, question, val, callback, tags){
$.promptOK = function(){
let val = _.trim($('#prompt-input').val())
$('#prompt').remove()
callback(val)
}
$.promptDone = function(){
let val = _.trim($('#prompt-input').val())
$('#prompt').remove()
$.promptParams.index = 9999
callback(val)
}
$.fixed('prompt').set('fullpage').show()
if(type == 'input'){
$('#prompt').input({id:'prompt-input', label:question}).enter(()=>{$.promptOK()})
} else $('#prompt').ap('
')
let div = $('#prompt').br()
//.button('👍', ()=>{$('#prompt-input').inputAppend('👍')})
if($.promptParams) div.button('Next', ()=>{$.promptOK()})
else div.button('OK', ()=>{$.promptOK()})
div.button('Cancel', function(){$('#prompt').remove()})
if($.promptParams) div.button('Done', ()=>{$.promptDone()}, 'prompt-done')
$('#prompt-input').focus().val(U(val))
if(tags) $('#prompt').attr(tags, tags)
}
$.prompt = function(question, val, callback){
$.promptText('input', question, val, callback)
}
$.promptInfo = function(text){
$.fixed('prompt-info').set('fullpage').set('prompt-info').show()
let div = $('#prompt-info').br()
div.ap(text).br()
div.button('OK', function(){$('#prompt-info').remove()})
}
$.promptMultiple = function(params, item){
if(params != undefined){
$.promptParams = params
$.promptParams.index = 0
$.promptItem = {}
if(item) $.promptItem = item
}
$.promptParams.field = $.promptParams.fields[$.promptParams.index]
if($.promptParams.field.question == undefined) $.promptParams.field.question = _.startCase($.promptParams.field.name)
$.prompt($.promptParams.field.question, U($.promptItem[$.promptParams.field.name]),
function(val){
$.promptItem[$.promptParams.field.name] = _.trim(val)
$.promptParams.index++
if($.promptParams.index < $.promptParams.fields.length) return $.promptMultiple()
if($.promptParams.callback) $.promptParams.callback($.promptItem)
delete $.promptParams
})
}
$.promptForm = function(params, item){
div = $.fixed('prompt').set('fullpage').attr('prompt-form', 1).show()
$('#prompt').d('prompt-head')
if(params.heading) $('[prompt-head]').s(params.heading)
if(params.controls) params.controls($('[prompt-head]'))
$('[prompt-head]')
.button('Done', ()=>{$.promptDone()})
.button('Delete', ()=>{$.promptDelete(item)})
.button('Cancel', function(){div.remove()})
_.forEach(params.fields, function(field){
if(field.question == undefined) field.question = _.startCase(field.name)
val = ''
if(item) val = item[field.name]
$('#prompt').input({
name:field.name,
type:field.type,
label:field.question,
valp:field.valp,
attr:field.attr,
val:val
})
})
if(item){
if(item['id']) $('#prompt').input({name:'id', val:item['id'], type:'hidden'})
}
_.first($('#prompt').find('input')).focus()
$.promptDone = function(){
let vals = {}
$('#prompt').find('[name]').each(function(index, inp){
if($(inp).attr('type') == 'file'){
let val = _.trim($(inp).attr('value'))
if(val != undefined && val != ''){
if(val == 'clear') val = ''
vals[$(inp).attr('name')] = val
}
}else{
vals[$(inp).attr('name')] = _.trim($(inp).val())
}
})
div.remove()
params.callback(vals)
}
$.promptDelete = function(item){
div.remove()
alert(item.id + ' ' + item.updatenote)
$.store.delete(item.id, item.updatenote)
}
}
$.promptChoice = $.choice = function(question, vals, callback, namefield){
let p = {}
if(typeof question === 'object'){
p = question
question = p.question
vals = p.vals
callback = p.callback
}
let div = $.fixed('prompt').set('fullpage').heading(question).show().div('prompt-choice-buttons')
_.forEach(vals, function(val, key){
let title = key
if(p.namefield) title = val[p.namefield]
$('#prompt-choice-buttons').button(title, function(){
div.remove()
callback(val)
})
})
div.br().button('Cancel', function(){div.remove()})
}
$.promptColor = function(question, _color, callback){
let div = $.fixed('prompt').set('fullpage').set('prompt-color')
.heading(question).show().div('prompt-color-buttons')
let colors = [
'#222222', '#cccccc', '#ffffff', '#444444',
'#220000', '#cc4444', '#ffcccc', '#440000',
'#002200', '#44cc44', '#ccffcc', '#004400',
'#000022', '#4444cc', '#ccccff', '#000044',
'#222200', '#cccc44', '#ffffcc', '#444400',
'#002222', '#44cccc', '#ccffff', '#004444',
'#f22613', '#e74c3c', '#f62459',
'#663399', '#9a12b3', '#bf55ec', '#19b5fe',
'#1e8bc3', '#1f3a93', '#89c4f4', '#03c9a9',
'#26c281', '#16a085', '#2eec71', '#f2784b',
'#f89406', '#f9bf3b']
$.promptColorVal = _color
_.forEach(colors, function(color, i){
let selected = (color == _color)?'selected':''
$('#prompt-color-buttons').button('', function(o){
$('#prompt-color-buttons button[selected]').unset('selected')
$(o).set('selected')
$.promptColorVal = color
}, 'bgcolor="'+color+'" ' + selected)
})
div.button('OK', function(){
div.remove()
callback($.promptColorVal)
})
.button('Cancel', function(){div.remove()})
}
$.promptNumber = function(question, val, callback, namefield){
let div = $.fixed('prompt').set('fullpage').set('prompt-number').heading(question).show()
.div('prompt-number-input')
.numpad('#prompt-number-input', 0, 0, 0, {commas:false})
.br()
.button('OK', ()=>{
let num = $('#prompt-number-input').text().trim()
div.remove()
callback(num)
})
.button('Cancel', ()=>{div.remove()})
$('#prompt-number-input').text(val)
}
$.editItemField = function(p){
$.promptText(p.inputType, p.question, p.item[p.field], function(val){
let o = {id:p.item.id, updatenote:p.updatenote}
o[p.field] = _.trim(val)
$.store.saveItem(o)
})
}
$.fn.attrToggle = function(att, onval, offval){
return $(this).attr(att, function(index, attr){
return attr == onval ? offval : onval
})
}
$.fn.zoomable = function(src){
$(this).attr('imagesrc', src)
.click(function(){
let src = $(this).attr('imagesrc')
if(src.indexOf('sized-image') == 0) src += '.orig'
$('#zoomed-image').remove()
/*
let parts = src.split(/\//g)
let o = ''
_.forEach(parts, (v, i)=>{
o += ' + ' + v
})
alert(o)
*/
$('body').ap(
$('
').attr('src', src)
.click(function(){$(this).remove()})
)
})
return this
}
$.dateNow = function(){
return $.date.shortDateTime()
}
$.yesno = function(question, yesfn, nofn){
var div = $('#yesno').remove()
if(question == undefined) return
$('body').div('yesno')
$('#yesno').set('fullpage').heading(question).show()
.button(T('Yes'), function(){
$('#yesno').remove()
if(yesfn) yesfn()
})
.button(T('No'), function(){
$('#yesno').remove()
if(nofn) nofn()
})
return div
}
$.reCSS = function(){
$.bubble('ReCSS')
let a = document.getElementsByTagName('link')
for(i=0; i< a.length; i++){
let s = a[i]
if (s.rel.toLowerCase().indexOf('stylesheet') >= 0 && s.href){
let h = s.href.replace(/(&|\?)forceReload=\d+/, '');
s.href = h + (h.indexOf('?') >= 0 ? '&' : '?') + 'forceReload=' + new Date().valueOf()
}
}
}
$.refreshPage = $.reloadPage = function(){
$.refreshPageConfirmed = 1
document.location = document.location
}
$.setCookie('silentMode')
$.silentModeToggle = function(){
if($.silentMode == 1) return $.setCookie('silentMode', 0) & $.bubble('Silent Mode Off')
$.setCookie('silentMode', 1) & $.bubble('Silent Mode On')
}
$.startAudioRecord = function(){
navigator.mediaDevices.getUserMedia({audio:true})
.then(stream => {
$.audioRecorder = new MediaRecorder(stream)
$.audioRecorder.addEventListener('dataavailable', e=>{
let chunks = []
chunks.push(e.data)
$.audioBlob = new Blob(chunks, {type:'audio/mp3'})
})
$.audioRecorder.start()
console.log('Audio recording ...')
})
}
$.stopAudioRecord = function(){
$.audioRecorder.stop()
console.log('Audio recording stopped')
}
$.playAudioRecord = function(url){
const audio = new Audio(url)
audio.play()
}
$.getAudioData = function(audioBlob, callback){
var reader = new FileReader()
reader.readAsDataURL(audioBlob)
reader.onloadend = function(){
callback(reader.result)
}
}
$.uploadAudio = function(filename, audioBlob, callback){
$.getAudioData(audioBlob, function(audiodata){
$.post('save-media.php', {filename: filename, imagedata:audiodata},
function(uri){
if(callback) callback(uri)
})
})
}
$.alarmSounds = {
'alarm-jazz':'media/alarm-jazz-guitar.mp3?v=1',
}
$.playVideo = function(src){
if($.video == undefined){
const video = document.createElement('video')
video.controls = true
video.width = 640
video.height = 360
$('body').ap(video)
$.video = video
}
$.video.src = src
$.video.play()
}
$.playSound = function(src, play, loop){
if($.silentModeIgnore != 1){
if($.silentMode == 1) return
}
if($.audio == undefined){
$.audio = new Audio(src)
$.audio.preload = "none"
$.audio.src = src
if(loop == 1) $.audio.loop = true
}
if(play == undefined) play = $.audio.paused
$.audio.pause()
$.audio.currentTime = 0
if($.audio.src != src){
delete $.audio
$.audio = new Audio(src)
$.audio.src = src
$.audio.currentTime = 0
}
if(play){
$.audio.play()
if(loop == 1) $.audio.paused = false
}else{
$.audio.pause()
if(loop == 1) $.audio.paused = true
}
}
$.clickSound = function(){
$.playSound('media/click.wav', true)
}
$.playSoundInterval = function(uri, period, id){
if($.playSoundIntervals == undefined) $.playSoundIntervals = {}
if(uri == undefined){
uri = ''
period = 0
}
if(uri == '' && period != undefined) return clearInterval($.playSoundIntervals[period])
$.playSound(uri, true)
if(id == undefined) id = 0
$.playSoundIntervals[id] = setInterval(function(){$.playSound(uri, true)}, period)
}
$.fn.signaturePad = function(callback){
$(this).ap($.signaturePad(callback))
}
$.signaturePad = function(callback){
$('#signature-pad').remove()
//$('body').div('signature-pad')
$.fixed('signature-pad').set('fullpage').show()
.p('Name', 5).div('signature-input').br()
.keyboard($('#signature-input'), function(val){})
.br()
.p('Signature', 5)
.ap('
')
.button('-', function(){$.signaturePadObj.penColor = 'rgb(0,0,0)'}, 'pad-color signature-pad-color-black')
.button('-', function(){$.signaturePadObj.penColor = 'rgb(109,0,0)'}, 'pad-color signature-pad-color-brown')
.button('-', function(){$.signaturePadObj.penColor = 'rgb(255,40,40)'}, 'pad-color signature-pad-color-red')
.button('-', function(){$.signaturePadObj.penColor = 'rgb(255, 69, 169)'}, 'pad-color signature-pad-color-pink')
.button('-', function(){$.signaturePadObj.penColor = 'rgb(50,114,180)'}, 'pad-color signature-pad-color-blue')
.button('-', function(){$.signaturePadObj.penColor = 'rgb(79,169,79)'}, 'pad-color signature-pad-color-green')
.button('-', function(){$.signaturePadObj.penColor = 'rgb(255, 245, 70)'}, 'pad-color signature-pad-color-yellow')
.button('Clear', function(){$.signaturePadObj.clear()})
.button('Ok', function(){
$('#signature-pad').hide()
$.bubble('sig ok')
callback($.signaturePadObj.toDataURL('image/jpeg'))
})
.button('Cancel', function(){$('#signature-pad').hide()})
const canvas = $('#signature-canvas')[0]
$.signaturePadObj = new SignaturePad(canvas)
$.signaturePadObj.penColor = "rgb(66, 133, 244)"
$.signaturePadObj.backgroundColor = "rgb(255,255,255)"
var ctx = canvas.getContext("2d")
ctx.fillStyle = "white"
ctx.fillRect(0, 0, canvas.width, canvas.height)
return $('#signature-pad')
}
$.printBT = function(text){
text += "\n"+$.date.stamp()
text = text.replace(/
/g, "-------------------------\n")
var S = "#Intent;scheme=rawbt;"
var P = "package=ru.a402d.rawbtprinter;end;"
var textEncoded = encodeURI(text)
window.location.href="intent:"+textEncoded+S+P
}
$.checkCamera = async function(){
$('#camera-video').remove()
$('body').ap('
')
$('#camera-video').show().click(function(){
$('#camera-video').remove()
})
let stream = await navigator.mediaDevices.getUserMedia({
audio:false,
video:{
deviceId:{exact:$.cameraId},
facingMode:'user'
}
})
$('#camera-video')[0].srcObject = stream
}
$.selectCamera = async function(){
$.div('camera-select').appendTo('body').set('fullpage').show()
let devices = await navigator.mediaDevices.enumerateDevices()
$.cameras = {}
_.forEach(devices, function(device){
if(_.startsWith(device.kind, 'videoinput')){
$.cameras[device.label] = device
$('#camera-select').button(device.label, function(){
$.setCookie('cameraid', $.cameraId = device.deviceId)
$('#camera-select').remove()
})
}
})
}
$.getCameras = async function(){
if($.cameras != undefined) return
$.setupVideoCanvas()
let devices = await navigator.mediaDevices.enumerateDevices()
$.cameras = {}
$.cameraIds = []
_.forEach(devices, function(device){
if(_.startsWith(device.kind, 'videoinput')){
$.cameras[device.label] = device
$.cameraIds.push(device)
}
})
}
$.promptPhoto = async function(callback){
await $.getCameras()
$.fixed('prompt').set('fullpage').attr('prompt-photo', 1).show()
.div('prompt-photo-buttons')
$('#prompt-photo-buttons')
.button('Shoot', ()=>{
$.captureVideo($.photoVideo.srcObject)
$('[prompt-photo]').set('shot')
$('#camera-canvas').set('prompt-photo-video').show()
$('#camera-video').hide()
}, 'prompt-photo-shoot')
.button('Again', async()=>{
$('[prompt-photo]').unset('shot')
$.photoVideo.srcObject = await $.startVideoStream($.promptPhotoCameraId)
$('#camera-canvas').hide()
$('#camera-video').show()
}, 'prompt-photo-again')
.button('Flip', async()=>{
$.stopVideoStream($.photoVideo.srcObject)
if($.promptPhotoCameraId < ($.cameraIds.length - 1)) $.promptPhotoCameraId++
else $.promptPhotoCameraId = 0
$.photoVideo.srcObject = await $.startVideoStream($.promptPhotoCameraId)
}, 'prompt-photo-flip')
.button('Done', ()=>{
$.promptPhotoClose()
$.capturedPhotoUri = $.saveImageToStampFile('image', $.capturedImageData)
callback()
}, 'prompt-photo-done')
.button('Cancel', ()=>{ $.promptPhotoClose() })
$.promptPhotoClose = ()=>{
$.stopVideoStream($.photoVideo.srcObject)
$('[prompt-photo-video]').hide()
$('#prompt').remove()
}
if($.promptPhotoCameraId == undefined) $.promptPhotoCameraId = 0
let videoWidth = 300
$.photoVideo.srcObject = await $.startVideoStream($.promptPhotoCameraId)
$('#camera-canvas').hide()
$('#camera-video').set('prompt-photo-video').attr('width', videoWidth).show()
}
$.stopVideoStream = async function(stream){
await stream.getTracks()[0].stop()
}
$.startVideoStream = async function(id){
let device = $.cameraIds[id]
let facing = 'user'
if(device.label.indexOf('back') >= 0) facing = 'environment'
//$.bubble('Camera: ' + id + '/' + facing + '/' + device.label)
return await navigator.mediaDevices.getUserMedia({
video:{deviceId:{exact:device.deviceId, facingMode:facing}},
audio:false
})
}
$.setupVideoCanvas = function(){
if($.photoCanvas == undefined){
$('body')
.ap('
')
.ap('
')
$.photoCanvas = $('#camera-canvas')[0]
$.photoVideo = $('#camera-video')[0]
}
}
$.captureVideo = function(stream, callback){
$.photoCallback = callback
setTimeout(function(){
$.photoWidth = $.photoVideo.videoWidth
$.photoCanvas.width = $.photoWidth
$.photoCanvas.height = $.photoVideo.videoHeight * $.photoWidth / $.photoVideo.videoWidth
let context = $.photoCanvas.getContext('2d')
context.filter = $.getCameraFilters()
context.drawImage($.photoVideo, 0, 0, $.photoCanvas.width, $.photoCanvas.height)
$.capturedImageData = $.photoCanvas.toDataURL('image/jpeg')
$.stopVideoStream(stream)
}, 100)
}
$.takePhoto = async function(callback){
await $.getCameras()
let stream = await $.startVideoStream(0)
$.photoCallback = callback
$.photoVideo.srcObject = stream
setTimeout(function(){
$.photoWidth = 200
$.photoCanvas.width = $.photoWidth
$.photoCanvas.height = $.photoVideo.videoHeight * $.photoWidth / $.photoVideo.videoWidth
let context = $.photoCanvas.getContext('2d')
context.filter = $.getCameraFilters()
context.drawImage($.photoVideo, 0, 0, $.photoCanvas.width, $.photoCanvas.height)
let imagedata = $.photoCanvas.toDataURL('image/jpeg')
if($.photoCallback) $.photoCallback(imagedata)
else $.photoUri = $.saveImageToStampFile('photo', imagedata)
$.stopVideoStream(stream)
}, 1000)
}
$.imageNameStamp = function(id, field){
return field+'-'+id+'-'+$.date.stamp()+'-'+_.random(1, 10000)+'.jpg'
}
$.saveImageToItemField = function(id, field, filename, imagedata, otherfields){
$.post('save-media.php', {filename: filename, imagedata:imagedata},
function(uri){
let o = {}
if(otherfields) o = otherfields
o.id = id
o[field] = uri
$.store.saveItem(o)
})
}
$.saveImageToStampFile = function(field, imagedata){
let filename = $.imageNameStamp('', field)
$.tempPhotoFilename = filename
$.post('save-media.php', {filename: filename, imagedata:imagedata}, function(uri){
console.log('image saved: ' + uri)
})
return 'upload/'+filename
}
$.setCookie('cameraid')
$.cameraFilters = {
contrast:$.setCookie('contrast', undefined, 1),
brightness:$.setCookie('brightness', undefined, 1),
hue:$.setCookie('hue', undefined, 0)
}
$.setCameraFilters = function(){
$.div('set-camera-filters').appendTo('body').set('fullpage').show()
.input({id:'filter-contrast', label:'Contrast'})
.input({id:'filter-brightness', label:'Brightness'})
.input({id:'filter-hue', label:'Hue'})
.button('Set', function(){
$.cameraFilters.contrast = $('#filter-contrast').val()
$.cameraFilters.brightness = $('#filter-brightness').val()
$.cameraFilters.hue = $('#filter-hue').val()
$.setCookie('contrast', $.cameraFilters.contrast)
$.setCookie('brightness', $.cameraFilters.brightness)
$.setCookie('hue', $.cameraFilters.hue)
$.takePhoto(function(url){
$('#set-camera-filters-img').remove()
$('#set-camera-filters').ap('
')
})
})
.button('Close', function(){
$('#set-camera-filters').remove()
})
$('#filter-contrast').val($.cameraFilters.contrast)
$('#filter-brightness').val($.cameraFilters.brightness)
$('#filter-hue').val($.cameraFilters.hue)
}
$.getCameraFilters = function(){
return 'contrast('+$.cameraFilters.contrast+') '
+'brightness('+$.cameraFilters.brightness+') '
+'hue-rotate('+$.cameraFilters.hue+'deg) '
}
$.exchangeRates = {
'THB':630,
'USD':21500
}
$.currencyEx = (val, currency)=>{
return _.round(val / $.exchangeRates[currency], 2)
}
var getStackTrace = function(){
var obj = {}
Error.captureStackTrace(obj, getStackTrace)
return obj.stack
}
if(navigator.userAgentData){
$.isMobile = navigator.userAgentData.mobile
}
$.setCookie('intercomId')
$.setCookie('timer_count', $.timerCount)
.intercom = function(){
$.popup('intercom', 'Intercom')
.p('My Intercom ID:'+$.intercomId)
.button('Stop', function(){$.intercomStop()}, 'intercom-stop')
.br()
.button(7, function(){$.intercomCall(7)}, 'intercom-call')
.button(8, function(){$.intercomCall(8)}, 'intercom-call')
.button(9, function(){$.intercomCall(9)}, 'intercom-call')
.br()
.button(4, function(){$.intercomCall(4)}, 'intercom-call')
.button(5, function(){$.intercomCall(5)}, 'intercom-call')
.button(6, function(){$.intercomCall(6)}, 'intercom-call')
.br()
.button(1, function(){$.intercomCall(1)}, 'intercom-call')
.button(2, function(){$.intercomCall(2)}, 'intercom-call')
.button(3, function(){$.intercomCall(3)}, 'intercom-call')
}
$.intercomCall = function(id){
if($.intercomOn == undefined){
$.intercomOn = id
$('#intercom').set('on')
$.startAudioRecord()
}
}
$.intercomStop = function(){
$('#intercom').set('on', false)
let id = $.intercomOn
delete $.intercomOn
$.stopAudioRecord()
setTimeout(function(){
$.getAudioData($.audioBlob, function(audiodata){
$.store.saveItem({
type:'t_intercom',
receiver:'receiver_'+id,
updatenote:'intercom-'+id,
audio:audiodata
})
})
}, 100)
}
$.intercomPlayback = function(){
if($.updatenoteIncludes('intercom-'+$.intercomId)){
$.store.load('t_intercom,receiver_'+$.intercomId, function(items){
_.forEach(items, function(item){
if(item.receiver == 'receiver_'+$.intercomId){
$.playSound(item.audio, true)
$.store.delete(item.id)
}
})
})
}
}
$.fn.messenger = function(){
$.messengerLoad = function(){
$('#messenger').queryItems(
't_message;(`type`="t_message") AND '+$.intervals['from-oneweek']+';;`created` ASC',
{
attr:'message',
imagewidth:150,
fields:'photo,sender,image,message,time',
callback:function(container){
$('#messenger').empty().ap(container)
setTimeout(()=>{$('#messenger').scrollTop(500000)}, 100)
$.playSound('media/new-message.mp3', true)
}
}
)
}
$.messengerName = ''
$.setRefreshCalls('messenger', $.messengerLoad)
$.messageSend = function(){
$.takePhoto()
$.promptText('text', "Message", '', function(message){
// process commands
$.messageSendCommit(message)
}, 'messenger-prompt')
}
$.messageSendCommit = function(message, sender){
if(sender) $.messengerName = sender
setTimeout(function(){
$.store.saveItem(
{type:'t_message', sender:$.messengerName, message:message, photo:$.photoUri, updatenote:'messenger'})
}, 1000)
}
$.keydowns['m'] = $.messageSend
$(this).div('messenger', 'messenger', 'messenger')
.button('Send', ()=>{$.messageSend()}, 'send-message')
.button('C', ()=>{
$.promptPhoto(()=>{
$.takePhoto()
setTimeout(function(){
$.store.saveItem({
type:'t_message',
sender:$.messengerName,
message:'',
updatenote:'messenger',
photo:$.photoUri,
photo2:$.capturedPhotoUri
})
}, 2500)
})
}, 'messenger-photo-button')
$.messengerLoad()
return this
}
$.fn.timer = function(id){
let note = $.profileGet('timer_note_'+id)
if(note == '' || note == undefined) note = 'Timer '+ id
let timerNote = $._d('timer-note="'+id+'"', note)
let color = $.profileGet('timer_color_'+id)
let started = $.profileGet('timer_started_'+id)
let time = $.profileGet('timer_time_'+id)
if(time == '' || time == undefined) time = '1:00'
let timerTime = $._d('timer-time="'+id+'" bgcolor="'+color+'"')
timerTime.html(time).attr('time', time)
.nodrag()
.click(function(){$.timerSet(id)})
$._d('timer="'+id+'"')
.ap(timerNote)
.ap(timerTime)
.button('', function(o){
$(o).blur()
let timer = $('[timer="'+id+'"]')
if(timer.attr('timeup')){
timer.unset('timeup').unset('started')
return $.playSoundInterval('', 'timer-'+id)
}
if(timer.attr('started') == undefined){
timer.unset('progress').set('started', 1)
return $.timerStart(id)
}
$.playSoundInterval('', 'timer-'+id)
$('[timer="'+id+'"]').unset('started').unset('timeup')
timerTime.text(timerTime.attr('time'))
clearTimeout($.timerTimeouts[id])
}, 'timer-start bgcolor="'+color+'" timerid="'+id+'"')
.appendTo(this)
return this
}
$.timerStart = function(id){
let timerTime = $('[timer-time="'+id+'"]')
let timer = $('[timer="'+id+'"]')
if(timer.attr('started') == undefined) return
$.swShowNotification('Sugamelt Timer '+id)
//$.playSoundInterval('media/payment-ding.mp3', 10000, 'timere-'+id)
//$.playSound('media/payment-ding.mp3', true, true)
let mmss = timerTime.text().split(':')
let seconds = mmss[0] * 60 + mmss[1] * 1
if(timer.attr('started')*1 == 1) timer.attr('started', seconds)
timerTime.attr('seconds', seconds)
if($.timerTimouts == undefined) $.timerTimeouts = {}
//$.playVideo('media/short-vid.mp4')
$.timerTimeouts[id] = setTimeout(function(){
seconds--;
let total = timer.attr('started')*1
let progress = Math.round((total - seconds)/total * (5+ timer.width()))
timer.attr('progress', progress + 'px')
if(seconds <= 0 && timer.attr('timeup') == undefined){
timer.set('timeup')
$.playSoundInterval('media/alarm.mp3', 2000, 'timer-'+id)
timerTime.text(timerTime.attr('time'))
return
}
timerTime.text(parseInt(seconds / 60) + ':' + String(seconds % 60).padStart(2, 0))
$.timerStart(id)
}, 1000)
}
$.timerSet = function(id){
let time = $('[timer-time="'+id+'"]').text()
let mmss = time.split(':')
let min = mmss[0], sec = mmss[1]
let color = $('[timer="'+id+'"] [timer-time]').attr('bgcolor')
let note = $('[timer-note="'+id+'"]').text()
$.prompt('Timer Note', note, function(val){
note = val.trim()
$.promptColor('Color??', color, function(color){
$.promptNumber('Minutes ??:00', min, function(val){
min = Math.min(val, 300)
$.promptNumber('Seconds ' + min + ':??', sec, function(val){
sec = Math.min(val, 59)
sec = String(sec).padStart(2, 0)
let time = min + ':'+sec
let settings = {}
settings['timer_note_'+id] = note
settings['timer_color_'+id] = color
settings['timer_time_'+id] = time
$.profileSet(settings)
$.setCookie('timer_note_'+id, note)
$.setCookie('timer_color_'+id, color)
$.setCookie('timer_time_'+id, time)
$('[timer="'+id+'"] button').attr('bgcolor', color)
$('[timer-note="'+id+'"]').text(note)
$('[timer-time="'+id+'"]').text(time).attr('time', time).attr('bgcolor', color)
})
})
})
})
}
$.fn.timerbox = function(){
let timerMax = 20, timerMin = 0
$.timerCount = $.profileGet('timer_count')
if($.timerCount == undefined) $.timerCount = 0
$.timerCount *= 1
$._d('timerbox')
.button('-', function(){
$.timerCount = Math.max(timerMin, $.timerCount - 1)
$.profileSet({'timer_count': $.timerCount})
$('[timerbox]').attr('timerbox', $.timerCount)
})
.button('+', function(){
$.timerCount = Math.min(timerMax, $.timerCount + 1)
$.profileSet({'timer_count': $.timerCount})
$('[timerbox]').attr('timerbox', $.timerCount)
})
.button('x', function(){
let c = $('[timerbox]').attr('timerbox')
if(c == 0) c = $.timerCount
else c = 0
$('[timerbox]').attr('timerbox', c)
})
.d('timers')
.appendTo(this)
$('[timerbox]').attr('timerbox', $.timerCount)
for(id = 1; id <= 18; id++){ $('[timers]').timer(id) }
return this
}
$.fn.shoppingList = function(){
$.setRefreshCalls('shoppinglist', $.shoppinglistLoad)
$(this).div('shoppinglist', '', '')
.filterbar('shoppinglist', {
'pending':{title:'Pending', callback:()=>{$('#shoppinglist').attr('filter', 'pending')}},
'done':{title:'Done', callback:()=>{$('#shoppinglist').attr('filter', 'done')}},
'cancelled':{title:'Cancelled', callback:()=>{$('#shoppinglist').attr('filter', 'cancelled')}}
})
.button('New Shopping Item', function(){$.shoppinglistPrompt()}, 'new-shoppinglist')
$('#shoppinglist').attr('filter', 'pending').attr('autolist', 'shopping')
$.shoppinglistLoad()
return this
}
$.shoppinglistPrompt = function(item){
let description = ''
let supplier = ''
let quantity = ''
if(item){
$.shoppinglistItem = item
description = U(item.description)
supplier = U(item.supplier)
quantity = U(item.quantity)
}
$.prompt("What to Buy?", description, function(description){
$.prompt("How many?", quantity, function(quantity){
$.prompt("Where to Buy?", supplier, function(supplier){
let item = {
type:'t_shoppinglist',
updatenote:'shoppinglist',
description:description,
supplier:supplier,
group:supplier,
quantity:quantity
}
if($.shoppinglistItem) item.id = $.shoppinglistItem.id
$.store.saveItem(item)
delete $.shoppinglistItem
})
})
})
}
$.getCostCodes = function(){
let codes = {}
codes = _.orderBy($.localLists, ['group'])
return codes
}
$.shoppinglistLoad = function(){
$('#shoppinglist').queryItems(
't_shoppinglist;(`type`="t_shoppinglist") AND '+$.intervals['from-onemonth']+';;`created` ASC',
{
attr:'shoppinglist',
fields:'group,description,quantity,time,status',
tags:'status',
callback:function(container){
$('#shoppinglist').empty().ap(container)
if($('#shoppinglist').attr('scrolled')) return
},
groupFn:function(group, item){
console.log(group.attrs)
if(NULL(item.status)) INC(group.attrs, 'pending')
else if(item.status == 'done') INC(group.attrs, 'done')
console.log(group.attrs)
},
build:function(item, div){
div.button('Done', function(){
$.store.saveItem({
id:item.id, type:'t_shoppinglist', status:'done', updatenote:'shoppinglist'
})
})
.button('Edit', function(){
$.shoppinglistPrompt(item)
}, 'shoppinglist-edit-button')
.button('Re-Order', function(){
let newitem = _.clone(item)
delete newitem.id
$.shoppinglistPrompt(nepitem)
}, 're-order-button')
.button('Cancel', function(){
$.store.saveItem({
id:item.id, type:'t_shoppinglist', status:'cancelled', updatenote:'shoppinglist'
})
})
}
}
)
}
$.listLoad = function(p){
let id = '#list_'+p.type
let type = p.type
let fields = p.fields
if(p.attr == undefined) p.attr = type
if(p.tags == undefined) p.tags = 'status'
p.callback = function(container){
$(id).empty().ap(container)
if(p.fn != undefined && p.fn.callback != undefined) p.fn.callback(container)
if($(id).attr('scrolled')) return
}
p.build = function(o, div){
let builddiv = $._d('list-item-buttons')
if(p.fn != undefined && p.fn.build != undefined) p.fn.build(o, builddiv)
builddiv
.button('Clone', function(){
o.id = '_'
o.updatenote =o.type
$.store.saveItem(o)
})
.button('Edit', function(){$.autoLists[type].prompt(o)}, 'edit-button')
div.ap(builddiv)
}
$(id).queryItems($.listQuery(p), p)
}
$.fn.autoList = function(p){
if($.autoLists == undefined){
$.autoLists = {}
$.keydowns['+'] = function(e){
}
}
let type = p.type
if(p.updatenote == undefined) p.updatenote = ''
p.updatenote += ' ' + p.type
$.autoLists[type] = p
$.autoLists[type].load = function(){$.listLoad(p)}
$.autoLists[type].save = function(item){
item.type = 't_'+$.autoLists[type].type
item.updatenote = $.autoLists[type].updatenote
$.store.saveItem(item)
}
$.autoLists[type].prompt = function(item){
if($.autoLists[type].promptFn == undefined) $.autoLists[type].promptFn = $.promptMultiple
if(item){
item.type = 't_'+$.autoLists[type].type
item.updatenote = $.autoLists[type].updatenote
}
$.autoLists[type].promptFn({
heading:$.autoLists[type].niceName,
fields:$.autoLists[type].promptFields,
controls:$.autoLists[type].promptControls,
callback:$.autoLists[type].save
}, item)
}
$.autoLists[type].setFilter = function(name){
let filter = $.autoLists[type].filters[name]
$.autoLists[type].groupfield = filter.groupfield ? filter.groupfield : 'group'
$.autoLists[type].filterMatch = filter.match
}
$.autoLists[type].search = function(match){
$.autoLists[type].match = ''
if(match != undefined) $.autoLists[type].match += match
if($.autoLists[type].filterMatch != undefined){
$.autoLists[type].match += ','+$.autoLists[type].filterMatch
}
$.autoLists[type].match += ','+$('#list_search_'+type).val().trim()
$.autoLists[type].load()
}
$(this).attr('autolist', type).div('list_'+type)
let autolistbar = $.div({attr:'autolist-bar'}).appendTo(this)
if(p.heading) autolistbar.s({text:p.heading, attr:'autolist-heading'})
autolistbar.br()
.button('+', ()=>{$.autoLists[type].prompt()}, 'list-new-button')
.input({
id:'list_search_'+type,
attr:'inline',
enter:()=>{$.autoLists[type].search()}
})
.button('Search', ()=>{$.autoLists[type].search()})
.button('x', ()=>{$('#list_search_'+type).val('').focus()})
if(p.controlFn){p.controlFn(autolistbar)}
if(p.filters){
autolistbar.br()
_.forEach(p.filters, function(filter){
autolistbar.button(filter.name, ()=>{
$.autoLists[type].setFilter(filter.name)
$.autoLists[type].search(filter.match)
})
})
}
$.setRefreshCalls(type, ()=>{$.autoLists[type].load()})
$.autoLists[type].load()
return this
}
$.loadSettings = function(){
$.settings = {}
$.store.load($.listQuery({type:'setting', fields:'code,value'}), function(items){
_.forEach(items, function(item){
if(item.code) $.settings[item.code.toLowerCase()] = item.value
})
$.exchangeRates['USD'] = $.settings['usd rate']
$.exchangeRates['THB'] = $.settings['thb rate']
})
}
$.setRefreshCalls('setting', ()=>{$.loadSettings(); $.bubble('update settings')})
$.loadSettings()
$.localList = function(p, storefn){
// keep a live local list of items indexed by item codes
if($.localLists == undefined){
$.localLists = {}
$.localListParams = {}
}
let type, query, prefix = ''
if(typeof p === 'object'){
type = p.type
query = p
if(p.prefix) prefix = p.prefix + '_'
}else{
type = p
query = {type:type}
}
$.localListParams[type] = {}
if(storefn) $.localListParams[type].storefn = storefn
$.localListParams[type].load = ()=>{
$.store.load($.listQuery(query), function(items){
if($.localListParams[type].storefn){
return $.localListParams[type].storefn(items)
}
_.forEach(items, function(item){
if(item.code) $.localLists[prefix + item.code.toLowerCase()] = item
})
})
}
$.localListParams[type].load()
$.setRefreshCalls(type, ()=>{
$.localListParams[type].load()
$.bubble('update locallist')
})
}
$.toggleField = function(o, fieldname){
if(o[fieldname] == fieldname) o[fieldname] = ''
else o[fieldname] = fieldname
return o
}
$.fn.productsList = function(){
$.preSaveCalls['t_product'] = function(o){
if(o.name == '') o.name = o.code + ' ' + o.title
o.updatenote = 'product product_update'
return o
}
return $(this).autoList({
type:'product',
niceName:'Product',
heading:'Products',
updatenote:'product_update',
fields:'image,group,title,code,price,cost_code,customizers,comment,takeaway_cost,takeaway_note,instructions,soldout,notavailable,premium,time',
promptFn:$.promptForm,
promptFields:[
{name:'name'},
{name:'title'},
{name:'title_la', question:'Lao Title'},
{name:'code'},
{name:'group'},
{name:'description'},
{name:'price', question:'Price (LAK)'},
{name:'cost_code'},
{name:'customizers'},
{name:'comment', type:'text'},
{name:'takeaway_cost'},
{name:'takeaway_note'},
{name:'instructions', type:'text'},
{name:'note'},
{name:'tag'},
{name:'tag2', question:'Tag 2'},
{name:'soldout'},
{name:'premium'},
{name:'newproduct'},
{name:'bestseller'},
{name:'image', type:'file'},
{name:'imagestyle', type:'text'}
],
fn:{
build:function(o, div){
div
.button('Sold Out', function(){
$.store.saveItem($.toggleField(o, 'soldout'))
})
.button('Not Available', function(){
$.store.saveItem($.toggleField(o, 'notavailable'))
})
.button('+2,000', function(){
o.price = o.price * 1 + 2000
$.store.saveItem(o)
})
}
},
})
}
$.fn.adsList = function(){
$.preSaveCalls['t_ads'] = function(o){
if(o.code == '') o.code = o.title
return o
}
return $(this).autoList({
type:'ad',
niceName:'Ad',
heading:'Ads',
fields:'photo,group,title,title_la,code,description,description_la,note,status,time',
promptFn:$.promptForm,
promptFields:[
{name:'title'},
{name:'group'},
{name:'code'},
{name:'status'},
{name:'title_la', question:'Lao Title'},
{name:'description', type:'text'},
{name:'description_la', type:'text'},
{name:'photo', type:'file'},
{name:'note', question:'Note'},
],
labels:{
description:'Description',
description_la:'Description in Lao',
title_la:'Title in Lao'
}
})
}
$.fn.profilesList = function(){
$.preSaveCalls['t_profiles'] = function(o){
if(o.code == '') o.code = o.name
return o
}
return $(this).autoList({
type:'profile',
niceName:'profile',
heading:'Profiles',
fields:'group,name,code,note,status,time',
promptFn:$.promptForm,
promptFields:[
{name:'name'},
{name:'group'},
{name:'code'},
{name:'status'},
{name:'note', question:'Note'},
]
})
}
$.fn.profileSwitcher = function(){
$._d('profile-switcher')
.button('Profile:
' + $.profileName, ()=>{
$.prompt('Profile name', $.profileName, (v)=>{
$.setCookie('profile_name', v)
$.refreshPage()
})
})
.appendTo(this)
return this
}
$.profileLoad = function(){
$.profile = {}
$.profile.name = $.profileName
if($.profile.name){
$.bubble('loading profile: ' + $.profile.name)
$.store.load('t_profile,"name":"'+$.profile.name+'"',
(items)=>{
_.forEach(items, (item)=>{
$.profile = item
$.setCookie('profile_name', $.profile.name)
return
})
})
}
}
$.profileSet = function(p){
if($.profile == undefined) $.profile = {
id:'',
type:'t_profile'
}
if($.profile.name == undefined) $.profile.name = $.profileName
$.profile.type = 't_profile'
$.profile = {...$.profile, ...p}
$.store.saveItem($.profile, (r)=>{
$.bubble('Profile saved: ' + r)
})
}
$.profileGet = function(key){
if($.profile[key]) return $.profile[key]
//return Cookies.get(key)
}
$.fn.loansList = function(){
$.preSaveCalls['t_loans'] = function(o){
if(o.code == '') o.code = o.title
return o
}
return $(this).autoList({
type:'loan',
niceName:'Loan',
heading:'Loans',
fields:'group,title,code,description,loaner,initial,balance,note,status,time',
promptFn:$.promptForm,
promptFields:[
{name:'title'},
{name:'group'},
{name:'loaner'},
{name:'code'},
{name:'status'},
{name:'initial'},
{name:'balance'},
{name:'description', type:'text'},
{name:'note', question:'Note'},
],
labels:{
description:'Description',
description_la:'Description in Lao',
title_la:'Title in Lao'
}
})
}
$.fn.expensesList = function(){
$.preSaveCalls['t_expense'] = function(o){
if(o.code == '') o.code = o.title
return o
}
return $(this).autoList({
type:'expense',
niceName:'Expense',
heading:'Expenses',
fields:'photo,group,title,quantiy,total,code,description,note,time',
promptFn:$.promptForm,
promptFields:[
{name:'title'},
{name:'group'},
{name:'code'},
{name:'title_la', question:'Lao Title'},
{name:'quantity'},
{name:'total'},
{name:'description', type:'text'},
{name:'photo', type:'file'},
{name:'note', question:'Note'},
]
})
}
$.fn.resourcesList = function(){
$.preSaveCalls['t_resource'] = function(o){
if(o.code == '') o.code = o.title
return o
}
return $(this).autoList({
type:'resource',
niceName:'Resource',
heading:'Resources',
fields:'photo,group,title,code,description,note,time',
promptFn:$.promptForm,
promptFields:[
{name:'title'},
{name:'code'},
{name:'group'},
{name:'title_la', question:'Lao Title'},
{name:'description', type:'text'},
{name:'photo', type:'file'},
{name:'note', question:'Note'},
{name:'settings', type:'text'}
]
})
}
$.fn.customizersList = function(){
$.preSaveCalls['t_customizer'] = function(o){
if(o.code == '') o.code = o.title
o.updatenote = 'customizer'
return o
}
return $(this).autoList({
type:'customizer',
niceName:'Customizer',
heading:'Customizers',
fields:'group,position,title,code,title_la,color,price,comment,note,time',
promptFn:$.promptForm,
promptFields:[
{name:'title'},
{name:'title_la', question:'Lao Title'},
{name:'code'},
{name:'group'},
{name:'status'},
{name:'position'},
{name:'color', type:'color'},
{name:'price'},
{name:'percent'},
{name:'comment', type:'text'},
{name:'note'},
],
})
}
$.customizerLocalListLoad = function(items){
// store customizers into groups
delete $.localLists['customizers']
$.localLists['customizers'] = {}
// let c = _.padStart(o.group, 20, '_') + _.padStart(o.position, 3, '0')
let sortedItems = _.orderBy(items, ['position'])
_.forEach(sortedItems, function(item){
let key = item.group.toLowerCase()
if($.localLists['customizers'][key] == undefined)
$.localLists['customizers'][key] = []
//if(key == 'milk2') alert(item.position + ' ' + item.title)
$.localLists['customizers'][key].push(item)
})
}
$.fn.settingsList = function(){
$.preSaveCalls['t_setting'] = function(o){
if(o.code == '') o.code = o.title
return o
}
return $(this).autoList({
type:'setting',
niceName:'Setting',
heading:'Settings',
fields:'photo,group,title,code,value,description,note,time',
promptFn:$.promptForm,
promptFields:[
{name:'title'},
{name:'code'},
{name:'value'},
{name:'description', type:'text'},
{name:'note', question:'Note'},
{name:'photo', type:'file'}
]
})
}
$.fn.todosList = function(){
return $(this).autoList({
type:'todo',
niceName:'To Do',
heading:'To Dos',
fields:'group,description,status,time',
promptFn:$.promptForm,
promptFields:[
{name:'description', question:'Description', type:'text'},
{name:'group', question:'Group'},
{name:'status', question:'Status'}
]
})
}
$.fn.announcementsList = function(){
return $(this).autoList({
type:'announcement',
niceName:'Announcement',
heading:'Announcements',
fields:'group,code,note,status,time',
promptFn:$.promptForm,
promptFields:[
{name:'code'},
{name:'group'},
{name:'note', type:'text'},
]
})
}
$.fn.tasksList = function(){
return $(this).autoList({
type:'task',
niceName:'Task',
heading:'Tasks',
fields:'group,note,person,status,time',
promptFn:$.promptForm,
promptFields:[
{name:'note', type:'text'},
{name:'person'},
{name:'group'}
]
})
}
$.fn.recipesList = function(){
return $(this).autoList({
type:'recipe',
niceName:'Recipe',
heading:'Recipes',
fields:'group,title,description,time',
promptFn:$.promptForm,
promptFields:[
{name:'title'},
{name:'description'},
{name:'group'}
]
})
}
$.fn.checkList = function(){
$.preSaveCalls['t_stock'] = function(o){
if(o.code == '') o.code = o.title.toUpperCase()
return o
}
return $(this).autoList({
type:'stock',
niceName:'Stock',
heading:'Checklist',
fields:'photo,group,quantity,title,title_la,supplier,note',
imagewidth:60,
promptFn:$.promptForm,
promptFields:[
{name:'title'},
{name:'code'},
{name:'group'},
{name:'title_la', question:'Lao Title'},
{name:'supplier'},
{name:'price'},
{name:'quantity'},
{name:'description', type:'text'},
{name:'photo', type:'file'},
{name:'note', question:'Note'},
],
fn:{
build:function(o, div){
div
.button('Buy', function(){
$.promptChoice('How many?',
{1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9,
10:10, 11:11, 12:12},
function(quantity){
$.store.saveItem({
id:o.id,
status:'buy',
quantity:quantity,
updatenote:'stock'
})
}
)
}, 'buy-button')
.button('Note', ()=>{
$.editItemField({
inputType:'text',
question:'Note',
field:'note',
item:o, updatenote:'stock'
})}, 'note-button')
.button('Done', ()=>{
$.store.saveItem({
id:o.id,
status:'done',
updatenote:'stock'
})}, 'done-button')}
},
filters:{
'To Buy':{name:'To Buy', match:'"status":"buy"', groupfield:'supplier'},
'All':{name:'All', match:''},
}
})
}
$.fn.costsList = function(){
let costParams = {
type:'cost',
niceName:'Cost',
heading:'Costs',
fields:'group,name,code,total_cost,total_weight,'
+'cooked_weight,unit_price,sale_quantity,'
+'sale_cost,estimated_sale_price,profit_margin,'
+'note,details,instruction,updated',
labels:{
total_cost:'Total Cost',
total_weight:'Total Weight',
cooked_weight:'Cooked Weight',
unit_price:'Unit Price',
sale_quantity:'Sale Quantity',
sale_cost:'Sale Cost',
estimated_sale_price:'Estimated Sale Price',
profit_margin:'Profit Margin'
},
styles:{},
promptFn:$.promptForm,
promptFields:[
{name:'name'},
{name:'code'},
{name:'group'},
{name:'note'},
{name:'total_weight'},
{name:'cooked_weight'},
{name:'sale_quantity'},
{name:'estimated_sale_price'},
{name:'sum_only'},
],
promptControls:function(div){
div.button('Up%', ()=>{$.costsRescaleForm(0.2)})
div.button('Down%', ()=>{$.costsRescaleForm(-0.2)})
div.br()
},
fn:{
build:function(o, div){
div.button('Print', function(elem, o){
let t = o.name + "\n"
if(o.note) t += 'NOTE:' + o.note + "\n"
if(o.instruction) t += 'INSTRUCTION: ' + o.instruction + "\n\n"
for(i=1; i <= 15; i++){
if(o['mat'+i] == undefined) continue
t += o['mat'+i] + ': ' + o['qua'+i] + "g\n"
}
t += "\n\n"
$.printBT(t)
}, 'print-button', o)
}
}
}
$.costsRescaleForm = function(percent){
let total_weight = 0
$('[cost-quantity]').each(function(){
let val = _.trim($(this).val())
if(val == '') return
val = val * (100 + percent)/100
val = Math.round(val * 100) / 100
$(this).val(val)
total_weight += val
})
$('[name="total_weight"]').val(total_weight)
}
$.maxMaterialCount = 15
for(i=1; i <= $.maxMaterialCount; i++){
costParams.promptFields.push({name:'mat'+i, question:'Material Code '+i, attr:'short short2',
valp:{
vals:$.getCostCodes,
namefield:'code'
}
})
costParams.promptFields.push({name:'qua'+i, question:'Quantity', attr:'short short3 cost-quantity'})
costParams.promptFields.push({name:'note'+i, question:'Note', attr:'short short3 cost-mat-note'})
}
costParams.promptFields.push({name:'instruction', type:'text'})
$.localList('material')
$.localList('cost')
$.preSaveCalls['t_cost'] = function(item){
// calculate total cost
let total_cost = 0
let total_weight = 0
let key_quantity = 0
let key_ingredient
let details = ''
if(item.code == '') item.code = item.name.toLowerCase().trim()
for(i=1; i <= $.maxMaterialCount; i++){
let quantity = item['qua'+i] * 1
if(isNaN(quantity)) continue
total_weight += quantity
if(key_quantity == 0){
key_quantity = quantity
key_ingredient = item['mat'+i]
}
}
for(i=1; i <= $.maxMaterialCount; i++){
let quantity = item['qua'+i] * 1
if(isNaN(quantity)) continue
if(item.sum_only){
total_cost += quantity
continue
}
let code = item['mat'+i]
if(code == '' || code == undefined) continue
let note = item['note'+i]
let unit_price = 0
code = code.toLowerCase().trim()
if($.localLists[code])
unit_price = $.localLists[code].unit_price
let subtotal = unit_price * quantity
total_cost += subtotal
if(note) note = '/ '+ note
let roundedQuantity = Math.round(quantity)
let halfQuantity = roundedQuantity / 2
let percentKey = Math.round(quantity / key_quantity * 100)
let percentTotal = Math.round(quantity / total_weight * 100)
details += '> ' + code.toUpperCase() + ' '+note
+'\r\n ' + roundedQuantity.toLocaleString()
+ 'g\t\t x\t' + CUR(unit_price)
+ '\t=\t' + CUR(subtotal)
+ '\r\n\t\t\t' + percentKey+ '% of ' + key_ingredient
+ ' (' + percentTotal + '% of total)'
// + '\r\n\t\thalf ' + halfQuantity
+ '\r\n'
}
item.total_cost = Math.round(total_cost)
item.total_weight = Math.round(total_weight)
let final_weight = item.total_weight
if(item.cooked_weight) final_weight = item.cooked_weight
item.unit_price = Math.round(item.total_cost / final_weight)
item.sale_cost = Math.round(item.total_cost / final_weight * item.sale_quantity)
item.details = details
item.profit_margin = Math.floor(
(item.estimated_sale_price - item.sale_cost)
/ item.estimated_sale_price * 100
)
return item
}
return $(this).autoList(costParams)
}
$.fn.materialsList = function(){
$.preSaveCalls['t_material'] = function(item){
if(item.code == '') item.code = item.name.toUpperCase()
if(item.price) item.unit_price = item.price / item.quantity
item.updatenote = 'material'
return item
}
return $(this).autoList({
type:'material',
niceName:'Material',
heading:'Materials',
updatenote:'material',
fields:'group,subgroup,name,title_la,tobuy,price,unit_price,brand,code,time',
promptFn:$.promptForm,
promptFields:[
{name:'name'},
{name:'code', question:'Material Code'},
{name:'group', attr:'short'},
{name:'subgroup', attr:'short'},
{name:'title_la', question:'Lao Title'},
{name:'price', question:'Price (LAK)', attr:'short'},
{name:'quantity', question:'Quantity (g/pieces)', attr:'short'},
{name:'brand', attr:'short'},
{name:'supplier', attr:'short'},
{name:'note'},
{name:'photo', type:'file'}
],
fn:{
build:function(o, div){
div
.button('Buy', function(){
$.promptChoice('How many?',
{1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9,
10:10, 11:11, 12:12},
function(quantity){
$.store.saveItem({id:o.id, type:'t_material', status:'buy', tobuy:quantity})
}
)
}, 'buy-button')
.button('Note', ()=>{$.editItemField({inputType:'text', question:'Note', field:'note', item:o})}, 'note-button')
.button('Done', ()=>{$.store.saveItem({id:o.id, type:'t_material', status:'done'})}, 'done-button')}
},
filters:{
'To Buy':{name:'To Buy', match:'"status":"buy"', groupfield:'supplier'},
'All':{name:'All', match:''},
}
})
}
$.fn.pLine = function(text){
return $(this).ap('
'+text+'
')
}
$.fn.signMaker = function(){
$(this).button('Print', function(){
window.print()
})
.pLine('Closed Today')
.pLine('')
.pLine('')
.pLine('')
.pLine('')
return this
}