[MERGE] latest trunk-proto61.
bzr revid: kch@tinyerp.com-20110518055511-zg1wuyfv10xalnxm
This commit is contained in:
commit
cc41897bd2
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import base64
|
||||
import glob, os
|
||||
import pprint
|
||||
from xml.etree import ElementTree
|
||||
|
@ -346,12 +347,21 @@ class DataSet(openerpweb.Controller):
|
|||
:rtype: list
|
||||
"""
|
||||
Model = request.session.model(model)
|
||||
|
||||
ids = Model.search(domain or [], offset or 0, limit or False,
|
||||
sort or False, request.context)
|
||||
|
||||
if fields and fields == ['id']:
|
||||
# shortcut read if we only want the ids
|
||||
return map(lambda id: {'id': id}, ids)
|
||||
return Model.read(ids, fields or False, request.context)
|
||||
|
||||
reads = Model.read(ids, fields or False, request.context)
|
||||
reads.sort(key=lambda obj: ids.index(obj['id']))
|
||||
return reads
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def read(self, request, model, ids, fields=False):
|
||||
return self.do_search_read(request, model, ids, fields)
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def get(self, request, model, ids, fields=False):
|
||||
|
@ -376,7 +386,7 @@ class DataSet(openerpweb.Controller):
|
|||
:rtype: list
|
||||
"""
|
||||
Model = request.session.model(model)
|
||||
records = Model.read(ids, fields)
|
||||
records = Model.read(ids, fields, request.context)
|
||||
|
||||
record_map = dict((record['id'], record) for record in records)
|
||||
|
||||
|
@ -409,11 +419,29 @@ class DataSet(openerpweb.Controller):
|
|||
r = getattr(m, method)(ids, *args)
|
||||
return {'result': r}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def exec_workflow(self, req, model, id, signal):
|
||||
r = req.session.exec_workflow(model, id, signal)
|
||||
return {'result': r}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def default_get(self, req, model, fields, context={}):
|
||||
m = req.session.model(model)
|
||||
r = m.default_get(fields, context)
|
||||
return {'result': r}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def name_search(self, req, model, search_str, domain=[], context={}):
|
||||
m = req.session.model(model)
|
||||
r = m.name_search(search_str+'%', domain, '=ilike', context)
|
||||
return {'result': r}
|
||||
|
||||
class DataGroup(openerpweb.Controller):
|
||||
_cp_path = "/base/group"
|
||||
@openerpweb.jsonrequest
|
||||
def read(self, request, model, group_by_fields, domain=None, context=None):
|
||||
Model = request.session.model(model)
|
||||
return Model.read_group(domain or False, False, group_by_fields, 0, False, context or False)
|
||||
|
||||
class View(openerpweb.Controller):
|
||||
def fields_view_get(self, request, model, view_id, view_type,
|
||||
|
@ -527,6 +555,21 @@ class FormView(View):
|
|||
fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
|
||||
return {'fields_view': fields_view}
|
||||
|
||||
@openerpweb.httprequest
|
||||
def image(self, request, session_id, model, id, field):
|
||||
cherrypy.response.headers['Content-Type'] = 'image/png'
|
||||
Model = request.session.model(model)
|
||||
try:
|
||||
if not id:
|
||||
res = Model.default_get([field], request.context).get(field, '')
|
||||
else:
|
||||
res = Model.read([id], [field], request.context)[0].get(field, '')
|
||||
return base64.decodestring(res)
|
||||
except:
|
||||
return self.placeholder()
|
||||
def placeholder(self):
|
||||
return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
|
||||
|
||||
class ListView(View):
|
||||
_cp_path = "/base/listview"
|
||||
|
||||
|
@ -551,11 +594,11 @@ class ListView(View):
|
|||
|
||||
@openerpweb.jsonrequest
|
||||
def fill(self, request, model, id, domain,
|
||||
offset=0, limit=False):
|
||||
return self.do_fill(request, model, id, domain, offset, limit)
|
||||
offset=0, limit=False, sort=None):
|
||||
return self.do_fill(request, model, id, domain, offset, limit, sort)
|
||||
|
||||
def do_fill(self, request, model, id, domain,
|
||||
offset=0, limit=False):
|
||||
offset=0, limit=False, sort=None):
|
||||
""" Returns all information needed to fill a table:
|
||||
|
||||
* view with processed ``editable`` flag
|
||||
|
@ -573,19 +616,33 @@ class ListView(View):
|
|||
:param int limit: search limit, for pagination
|
||||
:returns: hell if I have any idea yet
|
||||
"""
|
||||
view = self.fields_view_get(request, model, id)
|
||||
view = self.fields_view_get(request, model, id, toolbar=True)
|
||||
|
||||
print sort
|
||||
rows = DataSet().do_search_read(request, model,
|
||||
offset=offset, limit=limit,
|
||||
domain=domain)
|
||||
domain=domain, sort=sort)
|
||||
eval_context = request.session.evaluation_context(
|
||||
request.context)
|
||||
return [
|
||||
{'data': dict((key, {'value': value})
|
||||
for key, value in row.iteritems()),
|
||||
'color': self.process_colors(view, row, eval_context)}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
if sort:
|
||||
sort_criteria = sort.split(',')[0].split(' ')
|
||||
print sort, sort_criteria
|
||||
view['sorted'] = {
|
||||
'field': sort_criteria[0],
|
||||
'reversed': sort_criteria[1] == 'DESC'
|
||||
}
|
||||
else:
|
||||
view['sorted'] = {}
|
||||
return {
|
||||
'view': view,
|
||||
'records': [
|
||||
{'data': dict((key, {'value': value})
|
||||
for key, value in row.iteritems()),
|
||||
'color': self.process_colors(view, row, eval_context)}
|
||||
for row in rows
|
||||
]
|
||||
}
|
||||
|
||||
def process_colors(self, view, row, context):
|
||||
colors = view['arch']['attrs'].get('colors')
|
||||
|
@ -618,5 +675,12 @@ class Action(openerpweb.Controller):
|
|||
_cp_path = "/base/action"
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def load(self, req, action_id):
|
||||
return {}
|
||||
def load(self, req, action_id, context={}):
|
||||
Actions = req.session.model('ir.actions.actions')
|
||||
value = False
|
||||
action_type = Actions.read([action_id], ['type'], context)
|
||||
if action_type:
|
||||
action = req.session.model(action_type[0]['type']).read([action_id], False, context)
|
||||
if action:
|
||||
value = action[0]
|
||||
return {'result': value}
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
* @version: 1.0 Alpha-1
|
||||
* @author: Coolite Inc. http://www.coolite.com/
|
||||
* @date: 2008-05-13
|
||||
* @copyright: Copyright (c) 2006-2008, Coolite Inc. (http://www.coolite.com/). All rights reserved.
|
||||
* @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
|
||||
* @website: http://www.datejs.com/
|
||||
*/
|
||||
Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|aft(er)?|from|hence)/i,subtract:/^(\-|bef(ore)?|ago)/i,yesterday:/^yes(terday)?/i,today:/^t(od(ay)?)?/i,tomorrow:/^tom(orrow)?/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^mn|min(ute)?s?/i,hour:/^h(our)?s?/i,week:/^w(eek)?s?/i,month:/^m(onth)?s?/i,day:/^d(ay)?s?/i,year:/^y(ear)?s?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a(?!u|p)|p)/i},timezones:[{name:"UTC",offset:"-000"},{name:"GMT",offset:"-000"},{name:"EST",offset:"-0500"},{name:"EDT",offset:"-0400"},{name:"CST",offset:"-0600"},{name:"CDT",offset:"-0500"},{name:"MST",offset:"-0700"},{name:"MDT",offset:"-0600"},{name:"PST",offset:"-0800"},{name:"PDT",offset:"-0700"}]};
|
||||
(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,p=function(s,l){if(!l){l=2;}
|
||||
return("000"+s).slice(l*-1);};$P.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};$P.setTimeToNow=function(){var n=new Date();this.setHours(n.getHours());this.setMinutes(n.getMinutes());this.setSeconds(n.getSeconds());this.setMilliseconds(n.getMilliseconds());return this;};$D.today=function(){return new Date().clearTime();};$D.compare=function(date1,date2){if(isNaN(date1)||isNaN(date2)){throw new Error(date1+" - "+date2);}else if(date1 instanceof Date&&date2 instanceof Date){return(date1<date2)?-1:(date1>date2)?1:0;}else{throw new TypeError(date1+" - "+date2);}};$D.equals=function(date1,date2){return(date1.compareTo(date2)===0);};$D.getDayNumberFromName=function(name){var n=$C.dayNames,m=$C.abbreviatedDayNames,o=$C.shortestDayNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s||o[i].toLowerCase()==s){return i;}}
|
||||
return-1;};$D.getMonthNumberFromName=function(name){var n=$C.monthNames,m=$C.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
|
||||
return-1;};$D.isLeapYear=function(year){return((year%4===0&&year%100!==0)||year%400===0);};$D.getDaysInMonth=function(year,month){return[31,($D.isLeapYear(year)?29:28),31,30,31,30,31,31,30,31,30,31][month];};$D.getTimezoneAbbreviation=function(offset){var z=$C.timezones,p;for(var i=0;i<z.length;i++){if(z[i].offset===offset){return z[i].name;}}
|
||||
return null;};$D.getTimezoneOffset=function(name){var z=$C.timezones,p;for(var i=0;i<z.length;i++){if(z[i].name===name.toUpperCase()){return z[i].offset;}}
|
||||
return null;};$P.clone=function(){return new Date(this.getTime());};$P.compareTo=function(date){return Date.compare(this,date);};$P.equals=function(date){return Date.equals(this,date||new Date());};$P.between=function(start,end){return this.getTime()>=start.getTime()&&this.getTime()<=end.getTime();};$P.isAfter=function(date){return this.compareTo(date||new Date())===1;};$P.isBefore=function(date){return(this.compareTo(date||new Date())===-1);};$P.isToday=function(){return this.isSameDay(new Date());};$P.isSameDay=function(date){return this.clone().clearTime().equals(date.clone().clearTime());};$P.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};$P.addSeconds=function(value){return this.addMilliseconds(value*1000);};$P.addMinutes=function(value){return this.addMilliseconds(value*60000);};$P.addHours=function(value){return this.addMilliseconds(value*3600000);};$P.addDays=function(value){this.setDate(this.getDate()+value);return this;};$P.addWeeks=function(value){return this.addDays(value*7);};$P.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,$D.getDaysInMonth(this.getFullYear(),this.getMonth())));return this;};$P.addYears=function(value){return this.addMonths(value*12);};$P.add=function(config){if(typeof config=="number"){this._orient=config;return this;}
|
||||
var x=config;if(x.milliseconds){this.addMilliseconds(x.milliseconds);}
|
||||
if(x.seconds){this.addSeconds(x.seconds);}
|
||||
if(x.minutes){this.addMinutes(x.minutes);}
|
||||
if(x.hours){this.addHours(x.hours);}
|
||||
if(x.weeks){this.addWeeks(x.weeks);}
|
||||
if(x.months){this.addMonths(x.months);}
|
||||
if(x.years){this.addYears(x.years);}
|
||||
if(x.days){this.addDays(x.days);}
|
||||
return this;};var $y,$m,$d;$P.getWeek=function(){var a,b,c,d,e,f,g,n,s,w;$y=(!$y)?this.getFullYear():$y;$m=(!$m)?this.getMonth()+1:$m;$d=(!$d)?this.getDate():$d;if($m<=2){a=$y-1;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=0;f=$d-1+(31*($m-1));}else{a=$y;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=s+1;f=$d+((153*($m-3)+2)/5)+58+s;}
|
||||
g=(a+b)%7;d=(f+g-e)%7;n=(f+3-d)|0;if(n<0){w=53-((g-s)/5|0);}else if(n>364+s){w=1;}else{w=(n/7|0)+1;}
|
||||
$y=$m=$d=null;return w;};$P.getISOWeek=function(){$y=this.getUTCFullYear();$m=this.getUTCMonth()+1;$d=this.getUTCDate();return p(this.getWeek());};$P.setWeek=function(n){return this.moveToDayOfWeek(1).addWeeks(n-this.getWeek());};$D._validate=function(n,min,max,name){if(typeof n=="undefined"){return false;}else if(typeof n!="number"){throw new TypeError(n+" is not a Number.");}else if(n<min||n>max){throw new RangeError(n+" is not a valid value for "+name+".");}
|
||||
return true;};$D.validateMillisecond=function(value){return $D._validate(value,0,999,"millisecond");};$D.validateSecond=function(value){return $D._validate(value,0,59,"second");};$D.validateMinute=function(value){return $D._validate(value,0,59,"minute");};$D.validateHour=function(value){return $D._validate(value,0,23,"hour");};$D.validateDay=function(value,year,month){return $D._validate(value,1,$D.getDaysInMonth(year,month),"day");};$D.validateMonth=function(value){return $D._validate(value,0,11,"month");};$D.validateYear=function(value){return $D._validate(value,0,9999,"year");};$P.set=function(config){if($D.validateMillisecond(config.millisecond)){this.addMilliseconds(config.millisecond-this.getMilliseconds());}
|
||||
if($D.validateSecond(config.second)){this.addSeconds(config.second-this.getSeconds());}
|
||||
if($D.validateMinute(config.minute)){this.addMinutes(config.minute-this.getMinutes());}
|
||||
if($D.validateHour(config.hour)){this.addHours(config.hour-this.getHours());}
|
||||
if($D.validateMonth(config.month)){this.addMonths(config.month-this.getMonth());}
|
||||
if($D.validateYear(config.year)){this.addYears(config.year-this.getFullYear());}
|
||||
if($D.validateDay(config.day,this.getFullYear(),this.getMonth())){this.addDays(config.day-this.getDate());}
|
||||
if(config.timezone){this.setTimezone(config.timezone);}
|
||||
if(config.timezoneOffset){this.setTimezoneOffset(config.timezoneOffset);}
|
||||
if(config.week&&$D._validate(config.week,0,53,"week")){this.setWeek(config.week);}
|
||||
return this;};$P.moveToFirstDayOfMonth=function(){return this.set({day:1});};$P.moveToLastDayOfMonth=function(){return this.set({day:$D.getDaysInMonth(this.getFullYear(),this.getMonth())});};$P.moveToNthOccurrence=function(dayOfWeek,occurrence){var shift=0;if(occurrence>0){shift=occurrence-1;}
|
||||
else if(occurrence===-1){this.moveToLastDayOfMonth();if(this.getDay()!==dayOfWeek){this.moveToDayOfWeek(dayOfWeek,-1);}
|
||||
return this;}
|
||||
return this.moveToFirstDayOfMonth().addDays(-1).moveToDayOfWeek(dayOfWeek,+1).addWeeks(shift);};$P.moveToDayOfWeek=function(dayOfWeek,orient){var diff=(dayOfWeek-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};$P.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};$P.getOrdinalNumber=function(){return Math.ceil((this.clone().clearTime()-new Date(this.getFullYear(),0,1))/86400000)+1;};$P.getTimezone=function(){return $D.getTimezoneAbbreviation(this.getUTCOffset());};$P.setTimezoneOffset=function(offset){var here=this.getTimezoneOffset(),there=Number(offset)*-6/10;return this.addMinutes(there-here);};$P.setTimezone=function(offset){return this.setTimezoneOffset($D.getTimezoneOffset(offset));};$P.hasDaylightSavingTime=function(){return(Date.today().set({month:0,day:1}).getTimezoneOffset()!==Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.isDaylightSavingTime=function(){return(this.hasDaylightSavingTime()&&new Date().getTimezoneOffset()===Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r.charAt(0)+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};$P.getElapsed=function(date){return(date||new Date())-this;};if(!$P.toISOString){$P.toISOString=function(){function f(n){return n<10?'0'+n:n;}
|
||||
return'"'+this.getUTCFullYear()+'-'+
|
||||
f(this.getUTCMonth()+1)+'-'+
|
||||
f(this.getUTCDate())+'T'+
|
||||
f(this.getUTCHours())+':'+
|
||||
f(this.getUTCMinutes())+':'+
|
||||
f(this.getUTCSeconds())+'Z"';};}
|
||||
$P._toString=$P.toString;$P.toString=function(format){var x=this;if(format&&format.length==1){var c=$C.formatPatterns;x.t=x.toString;switch(format){case"d":return x.t(c.shortDate);case"D":return x.t(c.longDate);case"F":return x.t(c.fullDateTime);case"m":return x.t(c.monthDay);case"r":return x.t(c.rfc1123);case"s":return x.t(c.sortableDateTime);case"t":return x.t(c.shortTime);case"T":return x.t(c.longTime);case"u":return x.t(c.universalSortableDateTime);case"y":return x.t(c.yearMonth);}}
|
||||
var ord=function(n){switch(n*1){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};return format?format.replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g,function(m){if(m.charAt(0)==="\\"){return m.replace("\\","");}
|
||||
x.h=x.getHours;switch(m){case"hh":return p(x.h()<13?(x.h()===0?12:x.h()):(x.h()-12));case"h":return x.h()<13?(x.h()===0?12:x.h()):(x.h()-12);case"HH":return p(x.h());case"H":return x.h();case"mm":return p(x.getMinutes());case"m":return x.getMinutes();case"ss":return p(x.getSeconds());case"s":return x.getSeconds();case"yyyy":return p(x.getFullYear(),4);case"yy":return p(x.getFullYear());case"dddd":return $C.dayNames[x.getDay()];case"ddd":return $C.abbreviatedDayNames[x.getDay()];case"dd":return p(x.getDate());case"d":return x.getDate();case"MMMM":return $C.monthNames[x.getMonth()];case"MMM":return $C.abbreviatedMonthNames[x.getMonth()];case"MM":return p((x.getMonth()+1));case"M":return x.getMonth()+1;case"t":return x.h()<12?$C.amDesignator.substring(0,1):$C.pmDesignator.substring(0,1);case"tt":return x.h()<12?$C.amDesignator:$C.pmDesignator;case"S":return ord(x.getDate());default:return m;}}):this._toString();};}());
|
||||
(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,$N=Number.prototype;$P._orient=+1;$P._nth=null;$P._is=false;$P._same=false;$P._isSecond=false;$N._dateElement="day";$P.next=function(){this._orient=+1;return this;};$D.next=function(){return $D.today().next();};$P.last=$P.prev=$P.previous=function(){this._orient=-1;return this;};$D.last=$D.prev=$D.previous=function(){return $D.today().last();};$P.is=function(){this._is=true;return this;};$P.same=function(){this._same=true;this._isSecond=false;return this;};$P.today=function(){return this.same().day();};$P.weekday=function(){if(this._is){this._is=false;return(!this.is().sat()&&!this.is().sun());}
|
||||
return false;};$P.at=function(time){return(typeof time==="string")?$D.parse(this.toString("d")+" "+time):this.set(time);};$N.fromNow=$N.after=function(date){var c={};c[this._dateElement]=this;return((!date)?new Date():date.clone()).add(c);};$N.ago=$N.before=function(date){var c={};c[this._dateElement]=this*-1;return((!date)?new Date():date.clone()).add(c);};var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),pxf=("Milliseconds Seconds Minutes Hours Date Week Month FullYear").split(/\s/),nth=("final first second third fourth fifth").split(/\s/),de;$P.toObject=function(){var o={};for(var i=0;i<px.length;i++){o[px[i].toLowerCase()]=this["get"+pxf[i]]();}
|
||||
return o;};$D.fromObject=function(config){config.week=null;return Date.today().set(config);};var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;}
|
||||
if(this._nth!==null){if(this._isSecond){this.addSeconds(this._orient*-1);}
|
||||
this._isSecond=false;var ntemp=this._nth;this._nth=null;var temp=this.clone().moveToLastDayOfMonth();this.moveToNthOccurrence(n,ntemp);if(this>temp){throw new RangeError($D.getDayName(n)+" does not occur "+ntemp+" times in the month of "+$D.getMonthName(temp.getMonth())+" "+temp.getFullYear()+".");}
|
||||
return this;}
|
||||
return this.moveToDayOfWeek(n,this._orient);};};var sdf=function(n){return function(){var t=$D.today(),shift=n-t.getDay();if(n===0&&$C.firstDayOfWeek===1&&t.getDay()!==0){shift=shift+7;}
|
||||
return t.addDays(shift);};};for(var i=0;i<dx.length;i++){$D[dx[i].toUpperCase()]=$D[dx[i].toUpperCase().substring(0,3)]=i;$D[dx[i]]=$D[dx[i].substring(0,3)]=sdf(i);$P[dx[i]]=$P[dx[i].substring(0,3)]=df(i);}
|
||||
var mf=function(n){return function(){if(this._is){this._is=false;return this.getMonth()===n;}
|
||||
return this.moveToMonth(n,this._orient);};};var smf=function(n){return function(){return $D.today().set({month:n,day:1});};};for(var j=0;j<mx.length;j++){$D[mx[j].toUpperCase()]=$D[mx[j].toUpperCase().substring(0,3)]=j;$D[mx[j]]=$D[mx[j].substring(0,3)]=smf(j);$P[mx[j]]=$P[mx[j].substring(0,3)]=mf(j);}
|
||||
var ef=function(j){return function(){if(this._isSecond){this._isSecond=false;return this;}
|
||||
if(this._same){this._same=this._is=false;var o1=this.toObject(),o2=(arguments[0]||new Date()).toObject(),v="",k=j.toLowerCase();for(var m=(px.length-1);m>-1;m--){v=px[m].toLowerCase();if(o1[v]!=o2[v]){return false;}
|
||||
if(k==v){break;}}
|
||||
return true;}
|
||||
if(j.substring(j.length-1)!="s"){j+="s";}
|
||||
return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k<px.length;k++){de=px[k].toLowerCase();$P[de]=$P[de+"s"]=ef(px[k]);$N[de]=$N[de+"s"]=nf(de);}
|
||||
$P._ss=ef("Second");var nthfn=function(n){return function(dayOfWeek){if(this._same){return this._ss(arguments[0]);}
|
||||
if(dayOfWeek||dayOfWeek===0){return this.moveToNthOccurrence(dayOfWeek,n);}
|
||||
this._nth=n;if(n===2&&(dayOfWeek===undefined||dayOfWeek===null)){this._isSecond=true;return this.addSeconds(this._orient);}
|
||||
return this;};};for(var l=0;l<nth.length;l++){$P[nth[l]]=(l===0)?nthfn(-1):nthfn(l);}}());
|
||||
(function(){Date.Parsing={Exception:function(s){this.message="Parse error at '"+s.substring(0,10)+" ...'";}};var $P=Date.Parsing;var _=$P.Operators={rtoken:function(r){return function(s){var mx=s.match(r);if(mx){return([mx[0],s.substring(mx[0].length)]);}else{throw new $P.Exception(s);}};},token:function(s){return function(s){return _.rtoken(new RegExp("^\s*"+s+"\s*"))(s);};},stoken:function(s){return _.rtoken(new RegExp("^"+s));},until:function(p){return function(s){var qx=[],rx=null;while(s.length){try{rx=p.call(this,s);}catch(e){qx.push(rx[0]);s=rx[1];continue;}
|
||||
break;}
|
||||
return[qx,s];};},many:function(p){return function(s){var rx=[],r=null;while(s.length){try{r=p.call(this,s);}catch(e){return[rx,s];}
|
||||
rx.push(r[0]);s=r[1];}
|
||||
return[rx,s];};},optional:function(p){return function(s){var r=null;try{r=p.call(this,s);}catch(e){return[null,s];}
|
||||
return[r[0],r[1]];};},not:function(p){return function(s){try{p.call(this,s);}catch(e){return[null,s];}
|
||||
throw new $P.Exception(s);};},ignore:function(p){return p?function(s){var r=null;r=p.call(this,s);return[null,r[1]];}:null;},product:function(){var px=arguments[0],qx=Array.prototype.slice.call(arguments,1),rx=[];for(var i=0;i<px.length;i++){rx.push(_.each(px[i],qx));}
|
||||
return rx;},cache:function(rule){var cache={},r=null;return function(s){try{r=cache[s]=(cache[s]||rule.call(this,s));}catch(e){r=cache[s]=e;}
|
||||
if(r instanceof $P.Exception){throw r;}else{return r;}};},any:function(){var px=arguments;return function(s){var r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
|
||||
try{r=(px[i].call(this,s));}catch(e){r=null;}
|
||||
if(r){return r;}}
|
||||
throw new $P.Exception(s);};},each:function(){var px=arguments;return function(s){var rx=[],r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
|
||||
try{r=(px[i].call(this,s));}catch(e){throw new $P.Exception(s);}
|
||||
rx.push(r[0]);s=r[1];}
|
||||
return[rx,s];};},all:function(){var px=arguments,_=_;return _.each(_.optional(px));},sequence:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;if(px.length==1){return px[0];}
|
||||
return function(s){var r=null,q=null;var rx=[];for(var i=0;i<px.length;i++){try{r=px[i].call(this,s);}catch(e){break;}
|
||||
rx.push(r[0]);try{q=d.call(this,r[1]);}catch(ex){q=null;break;}
|
||||
s=q[1];}
|
||||
if(!r){throw new $P.Exception(s);}
|
||||
if(q){throw new $P.Exception(q[1]);}
|
||||
if(c){try{r=c.call(this,r[1]);}catch(ey){throw new $P.Exception(r[1]);}}
|
||||
return[rx,(r?r[1]:s)];};},between:function(d1,p,d2){d2=d2||d1;var _fn=_.each(_.ignore(d1),p,_.ignore(d2));return function(s){var rx=_fn.call(this,s);return[[rx[0][0],r[0][2]],rx[1]];};},list:function(p,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return(p instanceof Array?_.each(_.product(p.slice(0,-1),_.ignore(d)),p.slice(-1),_.ignore(c)):_.each(_.many(_.each(p,_.ignore(d))),px,_.ignore(c)));},set:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return function(s){var r=null,p=null,q=null,rx=null,best=[[],s],last=false;for(var i=0;i<px.length;i++){q=null;p=null;r=null;last=(px.length==1);try{r=px[i].call(this,s);}catch(e){continue;}
|
||||
rx=[[r[0]],r[1]];if(r[1].length>0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;}
|
||||
if(!last&&q[1].length===0){last=true;}
|
||||
if(!last){var qx=[];for(var j=0;j<px.length;j++){if(i!=j){qx.push(px[j]);}}
|
||||
p=_.set(qx,d).call(this,q[1]);if(p[0].length>0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}}
|
||||
if(rx[1].length<best[1].length){best=rx;}
|
||||
if(best[1].length===0){break;}}
|
||||
if(best[0].length===0){return best;}
|
||||
if(c){try{q=c.call(this,best[1]);}catch(ey){throw new $P.Exception(best[1]);}
|
||||
best[1]=q[1];}
|
||||
return best;};},forward:function(gr,fname){return function(s){return gr[fname].call(this,s);};},replace:function(rule,repl){return function(s){var r=rule.call(this,s);return[repl,r[1]];};},process:function(rule,fn){return function(s){var r=rule.call(this,s);return[fn.call(this,r[0]),r[1]];};},min:function(min,rule){return function(s){var rx=rule.call(this,s);if(rx[0].length<min){throw new $P.Exception(s);}
|
||||
return rx;};}};var _generator=function(op){return function(){var args=null,rx=[];if(arguments.length>1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];}
|
||||
if(args){for(var i=0,px=args.shift();i<px.length;i++){args.unshift(px[i]);rx.push(op.apply(null,args));args.shift();return rx;}}else{return op.apply(null,arguments);}};};var gx="optional not ignore cache".split(/\s/);for(var i=0;i<gx.length;i++){_[gx[i]]=_generator(_[gx[i]]);}
|
||||
var _vector=function(op){return function(){if(arguments[0]instanceof Array){return op.apply(null,arguments[0]);}else{return op.apply(null,arguments);}};};var vx="each any all".split(/\s/);for(var j=0;j<vx.length;j++){_[vx[j]]=_vector(_[vx[j]]);}}());(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo;var flattenAndCompact=function(ax){var rx=[];for(var i=0;i<ax.length;i++){if(ax[i]instanceof Array){rx=rx.concat(flattenAndCompact(ax[i]));}else{if(ax[i]){rx.push(ax[i]);}}}
|
||||
return rx;};$D.Grammar={};$D.Translator={hour:function(s){return function(){this.hour=Number(s);};},minute:function(s){return function(){this.minute=Number(s);};},second:function(s){return function(){this.second=Number(s);};},meridian:function(s){return function(){this.meridian=s.slice(0,1).toLowerCase();};},timezone:function(s){return function(){var n=s.replace(/[^\d\+\-]/g,"");if(n.length){this.timezoneOffset=Number(n);}else{this.timezone=s.toLowerCase();}};},day:function(x){var s=x[0];return function(){this.day=Number(s.match(/\d+/)[0]);};},month:function(s){return function(){this.month=(s.length==3)?"jan feb mar apr may jun jul aug sep oct nov dec".indexOf(s)/4:Number(s)-1;};},year:function(s){return function(){var n=Number(s);this.year=((s.length>2)?n:(n+(((n+2000)<$C.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];for(var i=0;i<x.length;i++){if(x[i]){x[i].call(this);}}
|
||||
var now=new Date();if((this.hour||this.minute)&&(!this.month&&!this.year&&!this.day)){this.day=now.getDate();}
|
||||
if(!this.year){this.year=now.getFullYear();}
|
||||
if(!this.month&&this.month!==0){this.month=now.getMonth();}
|
||||
if(!this.day){this.day=1;}
|
||||
if(!this.hour){this.hour=0;}
|
||||
if(!this.minute){this.minute=0;}
|
||||
if(!this.second){this.second=0;}
|
||||
if(this.meridian&&this.hour){if(this.meridian=="p"&&this.hour<12){this.hour=this.hour+12;}else if(this.meridian=="a"&&this.hour==12){this.hour=0;}}
|
||||
if(this.day>$D.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");}
|
||||
var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});}
|
||||
return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;}
|
||||
for(var i=0;i<x.length;i++){if(typeof x[i]=="function"){x[i].call(this);}}
|
||||
var today=$D.today();if(this.now&&!this.unit&&!this.operator){return new Date();}else if(this.now){today=new Date();}
|
||||
var expression=!!(this.days&&this.days!==null||this.orient||this.operator);var gap,mod,orient;orient=((this.orient=="past"||this.operator=="subtract")?-1:1);if(!this.now&&"hour minute second".indexOf(this.unit)!=-1){today.setTimeToNow();}
|
||||
if(this.month||this.month===0){if("year day hour minute second".indexOf(this.unit)!=-1){this.value=this.month+1;this.month=null;expression=true;}}
|
||||
if(!expression&&this.weekday&&!this.day&&!this.days){var temp=Date[this.weekday]();this.day=temp.getDate();if(!this.month){this.month=temp.getMonth();}
|
||||
this.year=temp.getFullYear();}
|
||||
if(expression&&this.weekday&&this.unit!="month"){this.unit="day";gap=($D.getDayNumberFromName(this.weekday)-today.getDay());mod=7;this.days=gap?((gap+(orient*mod))%mod):(orient*mod);}
|
||||
if(this.month&&this.unit=="day"&&this.operator){this.value=(this.month+1);this.month=null;}
|
||||
if(this.value!=null&&this.month!=null&&this.year!=null){this.day=this.value*1;}
|
||||
if(this.month&&!this.day&&this.value){today.set({day:this.value*1});if(!expression){this.day=this.value*1;}}
|
||||
if(!this.month&&this.value&&this.unit=="month"&&!this.now){this.month=this.value;expression=true;}
|
||||
if(expression&&(this.month||this.month===0)&&this.unit!="year"){this.unit="month";gap=(this.month-today.getMonth());mod=12;this.months=gap?((gap+(orient*mod))%mod):(orient*mod);this.month=null;}
|
||||
if(!this.unit){this.unit="day";}
|
||||
if(!this.value&&this.operator&&this.operator!==null&&this[this.unit+"s"]&&this[this.unit+"s"]!==null){this[this.unit+"s"]=this[this.unit+"s"]+((this.operator=="add")?1:-1)+(this.value||0)*orient;}else if(this[this.unit+"s"]==null||this.operator!=null){if(!this.value){this.value=1;}
|
||||
this[this.unit+"s"]=this.value*orient;}
|
||||
if(this.meridian&&this.hour){if(this.meridian=="p"&&this.hour<12){this.hour=this.hour+12;}else if(this.meridian=="a"&&this.hour==12){this.hour=0;}}
|
||||
if(this.weekday&&!this.day&&!this.days){var temp=Date[this.weekday]();this.day=temp.getDate();if(temp.getMonth()!==today.getMonth()){this.month=temp.getMonth();}}
|
||||
if((this.month||this.month===0)&&!this.day){this.day=1;}
|
||||
if(!this.orient&&!this.operator&&this.unit=="week"&&this.value&&!this.day&&!this.month){return Date.today().setWeek(this.value);}
|
||||
if(expression&&this.timezone&&this.day&&this.days){this.day=this.days;}
|
||||
return(expression)?today.add(this):today.set(this);}};var _=$D.Parsing.Operators,g=$D.Grammar,t=$D.Translator,_fn;g.datePartDelimiter=_.rtoken(/^([\s\-\.\,\/\x27]+)/);g.timePartDelimiter=_.stoken(":");g.whiteSpace=_.rtoken(/^\s*/);g.generalDelimiter=_.rtoken(/^(([\s\,]|at|@|on)+)/);var _C={};g.ctoken=function(keys){var fn=_C[keys];if(!fn){var c=$C.regexPatterns;var kx=keys.split(/\s+/),px=[];for(var i=0;i<kx.length;i++){px.push(_.replace(_.rtoken(c[kx[i]]),kx[i]));}
|
||||
fn=_C[keys]=_.any.apply(null,px);}
|
||||
return fn;};g.ctoken2=function(key){return _.rtoken($C.regexPatterns[key]);};g.h=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2]|[1-9])/),t.hour));g.hh=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2])/),t.hour));g.H=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3]|[0-9])/),t.hour));g.HH=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3])/),t.hour));g.m=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.minute));g.mm=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.minute));g.s=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.second));g.ss=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.second));g.hms=_.cache(_.sequence([g.H,g.m,g.s],g.timePartDelimiter));g.t=_.cache(_.process(g.ctoken2("shortMeridian"),t.meridian));g.tt=_.cache(_.process(g.ctoken2("longMeridian"),t.meridian));g.z=_.cache(_.process(_.rtoken(/^((\+|\-)\s*\d\d\d\d)|((\+|\-)\d\d\:?\d\d)/),t.timezone));g.zz=_.cache(_.process(_.rtoken(/^((\+|\-)\s*\d\d\d\d)|((\+|\-)\d\d\:?\d\d)/),t.timezone));g.zzz=_.cache(_.process(g.ctoken2("timezone"),t.timezone));g.timeSuffix=_.each(_.ignore(g.whiteSpace),_.set([g.tt,g.zzz]));g.time=_.each(_.optional(_.ignore(_.stoken("T"))),g.hms,g.timeSuffix);g.d=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1]|\d)/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.dd=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1])/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.ddd=g.dddd=_.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"),function(s){return function(){this.weekday=s;};}));g.M=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d|\d)/),t.month));g.MM=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d)/),t.month));g.MMM=g.MMMM=_.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"),t.month));g.y=_.cache(_.process(_.rtoken(/^(\d\d?)/),t.year));g.yy=_.cache(_.process(_.rtoken(/^(\d\d)/),t.year));g.yyy=_.cache(_.process(_.rtoken(/^(\d\d?\d?\d?)/),t.year));g.yyyy=_.cache(_.process(_.rtoken(/^(\d\d\d\d)/),t.year));_fn=function(){return _.each(_.any.apply(null,arguments),_.not(g.ctoken2("timeContext")));};g.day=_fn(g.d,g.dd);g.month=_fn(g.M,g.MMM);g.year=_fn(g.yyyy,g.yy);g.orientation=_.process(g.ctoken("past future"),function(s){return function(){this.orient=s;};});g.operator=_.process(g.ctoken("add subtract"),function(s){return function(){this.operator=s;};});g.rday=_.process(g.ctoken("yesterday tomorrow today now"),t.rday);g.unit=_.process(g.ctoken("second minute hour day week month year"),function(s){return function(){this.unit=s;};});g.value=_.process(_.rtoken(/^\d\d?(st|nd|rd|th)?/),function(s){return function(){this.value=s.replace(/\D/g,"");};});g.expression=_.set([g.rday,g.operator,g.value,g.unit,g.orientation,g.ddd,g.MMM]);_fn=function(){return _.set(arguments,g.datePartDelimiter);};g.mdy=_fn(g.ddd,g.month,g.day,g.year);g.ymd=_fn(g.ddd,g.year,g.month,g.day);g.dmy=_fn(g.ddd,g.day,g.month,g.year);g.date=function(s){return((g[$C.dateElementOrder]||g.mdy).call(this,s));};g.format=_.process(_.many(_.any(_.process(_.rtoken(/^(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/),function(fmt){if(g[fmt]){return g[fmt];}else{throw $D.Parsing.Exception(fmt);}}),_.process(_.rtoken(/^[^dMyhHmstz]+/),function(s){return _.ignore(_.stoken(s));}))),function(rules){return _.process(_.each.apply(null,rules),t.finishExact);});var _F={};var _get=function(f){return _F[f]=(_F[f]||g.format(f)[0]);};g.formats=function(fx){if(fx instanceof Array){var rx=[];for(var i=0;i<fx.length;i++){rx.push(_get(fx[i]));}
|
||||
return _.any.apply(null,rx);}else{return _get(fx);}};g._formats=g.formats(["\"yyyy-MM-ddTHH:mm:ssZ\"","yyyy-MM-ddTHH:mm:ssZ","yyyy-MM-ddTHH:mm:ssz","yyyy-MM-ddTHH:mm:ss","yyyy-MM-ddTHH:mmZ","yyyy-MM-ddTHH:mmz","yyyy-MM-ddTHH:mm","ddd, MMM dd, yyyy H:mm:ss tt","ddd MMM d yyyy HH:mm:ss zzz","MMddyyyy","ddMMyyyy","Mddyyyy","ddMyyyy","Mdyyyy","dMyyyy","yyyy","Mdyy","dMyy","d"]);g._start=_.process(_.set([g.date,g.time,g.expression],g.generalDelimiter,g.whiteSpace),t.finish);g.start=function(s){try{var r=g._formats.call({},s);if(r[1].length===0){return r;}}catch(e){}
|
||||
return g._start.call({},s);};$D._parse=$D.parse;$D.parse=function(s){var r=null;if(!s){return null;}
|
||||
if(s instanceof Date){return s;}
|
||||
try{r=$D.Grammar.start.call({},s.replace(/^\s*(\S*(\s+\S+)*)\s*$/,"$1"));}catch(e){return null;}
|
||||
return((r[1].length===0)?r[0]:null);};$D.getParseFunction=function(fx){var fn=$D.Grammar.formats(fx);return function(s){var r=null;try{r=fn.call({},s);}catch(e){return null;}
|
||||
return((r[1].length===0)?r[0]:null);};};$D.parseExact=function(s,fx){return $D.getParseFunction(fx)(s);};}());
|
|
@ -0,0 +1,480 @@
|
|||
/*
|
||||
http://www.JSON.org/json2.js
|
||||
2011-02-23
|
||||
|
||||
Public Domain.
|
||||
|
||||
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
||||
|
||||
See http://www.JSON.org/js.html
|
||||
|
||||
|
||||
This code should be minified before deployment.
|
||||
See http://javascript.crockford.com/jsmin.html
|
||||
|
||||
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
|
||||
NOT CONTROL.
|
||||
|
||||
|
||||
This file creates a global JSON object containing two methods: stringify
|
||||
and parse.
|
||||
|
||||
JSON.stringify(value, replacer, space)
|
||||
value any JavaScript value, usually an object or array.
|
||||
|
||||
replacer an optional parameter that determines how object
|
||||
values are stringified for objects. It can be a
|
||||
function or an array of strings.
|
||||
|
||||
space an optional parameter that specifies the indentation
|
||||
of nested structures. If it is omitted, the text will
|
||||
be packed without extra whitespace. If it is a number,
|
||||
it will specify the number of spaces to indent at each
|
||||
level. If it is a string (such as '\t' or ' '),
|
||||
it contains the characters used to indent at each level.
|
||||
|
||||
This method produces a JSON text from a JavaScript value.
|
||||
|
||||
When an object value is found, if the object contains a toJSON
|
||||
method, its toJSON method will be called and the result will be
|
||||
stringified. A toJSON method does not serialize: it returns the
|
||||
value represented by the name/value pair that should be serialized,
|
||||
or undefined if nothing should be serialized. The toJSON method
|
||||
will be passed the key associated with the value, and this will be
|
||||
bound to the value
|
||||
|
||||
For example, this would serialize Dates as ISO strings.
|
||||
|
||||
Date.prototype.toJSON = function (key) {
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
return this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z';
|
||||
};
|
||||
|
||||
You can provide an optional replacer method. It will be passed the
|
||||
key and value of each member, with this bound to the containing
|
||||
object. The value that is returned from your method will be
|
||||
serialized. If your method returns undefined, then the member will
|
||||
be excluded from the serialization.
|
||||
|
||||
If the replacer parameter is an array of strings, then it will be
|
||||
used to select the members to be serialized. It filters the results
|
||||
such that only members with keys listed in the replacer array are
|
||||
stringified.
|
||||
|
||||
Values that do not have JSON representations, such as undefined or
|
||||
functions, will not be serialized. Such values in objects will be
|
||||
dropped; in arrays they will be replaced with null. You can use
|
||||
a replacer function to replace those with JSON values.
|
||||
JSON.stringify(undefined) returns undefined.
|
||||
|
||||
The optional space parameter produces a stringification of the
|
||||
value that is filled with line breaks and indentation to make it
|
||||
easier to read.
|
||||
|
||||
If the space parameter is a non-empty string, then that string will
|
||||
be used for indentation. If the space parameter is a number, then
|
||||
the indentation will be that many spaces.
|
||||
|
||||
Example:
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}]);
|
||||
// text is '["e",{"pluribus":"unum"}]'
|
||||
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
|
||||
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
|
||||
|
||||
text = JSON.stringify([new Date()], function (key, value) {
|
||||
return this[key] instanceof Date ?
|
||||
'Date(' + this[key] + ')' : value;
|
||||
});
|
||||
// text is '["Date(---current time---)"]'
|
||||
|
||||
|
||||
JSON.parse(text, reviver)
|
||||
This method parses a JSON text to produce an object or array.
|
||||
It can throw a SyntaxError exception.
|
||||
|
||||
The optional reviver parameter is a function that can filter and
|
||||
transform the results. It receives each of the keys and values,
|
||||
and its return value is used instead of the original value.
|
||||
If it returns what it received, then the structure is not modified.
|
||||
If it returns undefined then the member is deleted.
|
||||
|
||||
Example:
|
||||
|
||||
// Parse the text. Values that look like ISO date strings will
|
||||
// be converted to Date objects.
|
||||
|
||||
myData = JSON.parse(text, function (key, value) {
|
||||
var a;
|
||||
if (typeof value === 'string') {
|
||||
a =
|
||||
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
|
||||
if (a) {
|
||||
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
|
||||
+a[5], +a[6]));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
|
||||
var d;
|
||||
if (typeof value === 'string' &&
|
||||
value.slice(0, 5) === 'Date(' &&
|
||||
value.slice(-1) === ')') {
|
||||
d = new Date(value.slice(5, -1));
|
||||
if (d) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
|
||||
This is a reference implementation. You are free to copy, modify, or
|
||||
redistribute.
|
||||
*/
|
||||
|
||||
/*jslint evil: true, strict: false, regexp: false */
|
||||
|
||||
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
|
||||
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
|
||||
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
|
||||
lastIndex, length, parse, prototype, push, replace, slice, stringify,
|
||||
test, toJSON, toString, valueOf
|
||||
*/
|
||||
|
||||
|
||||
// Create a JSON object only if one does not already exist. We create the
|
||||
// methods in a closure to avoid creating global variables.
|
||||
|
||||
var JSON;
|
||||
if (!JSON) {
|
||||
JSON = {};
|
||||
}
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
if (typeof Date.prototype.toJSON !== 'function') {
|
||||
|
||||
Date.prototype.toJSON = function (key) {
|
||||
|
||||
return isFinite(this.valueOf()) ?
|
||||
this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z' : null;
|
||||
};
|
||||
|
||||
String.prototype.toJSON =
|
||||
Number.prototype.toJSON =
|
||||
Boolean.prototype.toJSON = function (key) {
|
||||
return this.valueOf();
|
||||
};
|
||||
}
|
||||
|
||||
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
gap,
|
||||
indent,
|
||||
meta = { // table of character substitutions
|
||||
'\b': '\\b',
|
||||
'\t': '\\t',
|
||||
'\n': '\\n',
|
||||
'\f': '\\f',
|
||||
'\r': '\\r',
|
||||
'"' : '\\"',
|
||||
'\\': '\\\\'
|
||||
},
|
||||
rep;
|
||||
|
||||
|
||||
function quote(string) {
|
||||
|
||||
// If the string contains no control characters, no quote characters, and no
|
||||
// backslash characters, then we can safely slap some quotes around it.
|
||||
// Otherwise we must also replace the offending characters with safe escape
|
||||
// sequences.
|
||||
|
||||
escapable.lastIndex = 0;
|
||||
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
|
||||
var c = meta[a];
|
||||
return typeof c === 'string' ? c :
|
||||
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
}) + '"' : '"' + string + '"';
|
||||
}
|
||||
|
||||
|
||||
function str(key, holder) {
|
||||
|
||||
// Produce a string from holder[key].
|
||||
|
||||
var i, // The loop counter.
|
||||
k, // The member key.
|
||||
v, // The member value.
|
||||
length,
|
||||
mind = gap,
|
||||
partial,
|
||||
value = holder[key];
|
||||
|
||||
// If the value has a toJSON method, call it to obtain a replacement value.
|
||||
|
||||
if (value && typeof value === 'object' &&
|
||||
typeof value.toJSON === 'function') {
|
||||
value = value.toJSON(key);
|
||||
}
|
||||
|
||||
// If we were called with a replacer function, then call the replacer to
|
||||
// obtain a replacement value.
|
||||
|
||||
if (typeof rep === 'function') {
|
||||
value = rep.call(holder, key, value);
|
||||
}
|
||||
|
||||
// What happens next depends on the value's type.
|
||||
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
return quote(value);
|
||||
|
||||
case 'number':
|
||||
|
||||
// JSON numbers must be finite. Encode non-finite numbers as null.
|
||||
|
||||
return isFinite(value) ? String(value) : 'null';
|
||||
|
||||
case 'boolean':
|
||||
case 'null':
|
||||
|
||||
// If the value is a boolean or null, convert it to a string. Note:
|
||||
// typeof null does not produce 'null'. The case is included here in
|
||||
// the remote chance that this gets fixed someday.
|
||||
|
||||
return String(value);
|
||||
|
||||
// If the type is 'object', we might be dealing with an object or an array or
|
||||
// null.
|
||||
|
||||
case 'object':
|
||||
|
||||
// Due to a specification blunder in ECMAScript, typeof null is 'object',
|
||||
// so watch out for that case.
|
||||
|
||||
if (!value) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
// Make an array to hold the partial results of stringifying this object value.
|
||||
|
||||
gap += indent;
|
||||
partial = [];
|
||||
|
||||
// Is the value an array?
|
||||
|
||||
if (Object.prototype.toString.apply(value) === '[object Array]') {
|
||||
|
||||
// The value is an array. Stringify every element. Use null as a placeholder
|
||||
// for non-JSON values.
|
||||
|
||||
length = value.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
partial[i] = str(i, value) || 'null';
|
||||
}
|
||||
|
||||
// Join all of the elements together, separated with commas, and wrap them in
|
||||
// brackets.
|
||||
|
||||
v = partial.length === 0 ? '[]' : gap ?
|
||||
'[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
|
||||
'[' + partial.join(',') + ']';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
|
||||
// If the replacer is an array, use it to select the members to be stringified.
|
||||
|
||||
if (rep && typeof rep === 'object') {
|
||||
length = rep.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
if (typeof rep[i] === 'string') {
|
||||
k = rep[i];
|
||||
v = str(k, value);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Otherwise, iterate through all of the keys in the object.
|
||||
|
||||
for (k in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
||||
v = str(k, value);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Join all of the member texts together, separated with commas,
|
||||
// and wrap them in braces.
|
||||
|
||||
v = partial.length === 0 ? '{}' : gap ?
|
||||
'{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
|
||||
'{' + partial.join(',') + '}';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
// If the JSON object does not yet have a stringify method, give it one.
|
||||
|
||||
if (typeof JSON.stringify !== 'function') {
|
||||
JSON.stringify = function (value, replacer, space) {
|
||||
|
||||
// The stringify method takes a value and an optional replacer, and an optional
|
||||
// space parameter, and returns a JSON text. The replacer can be a function
|
||||
// that can replace values, or an array of strings that will select the keys.
|
||||
// A default replacer method can be provided. Use of the space parameter can
|
||||
// produce text that is more easily readable.
|
||||
|
||||
var i;
|
||||
gap = '';
|
||||
indent = '';
|
||||
|
||||
// If the space parameter is a number, make an indent string containing that
|
||||
// many spaces.
|
||||
|
||||
if (typeof space === 'number') {
|
||||
for (i = 0; i < space; i += 1) {
|
||||
indent += ' ';
|
||||
}
|
||||
|
||||
// If the space parameter is a string, it will be used as the indent string.
|
||||
|
||||
} else if (typeof space === 'string') {
|
||||
indent = space;
|
||||
}
|
||||
|
||||
// If there is a replacer, it must be a function or an array.
|
||||
// Otherwise, throw an error.
|
||||
|
||||
rep = replacer;
|
||||
if (replacer && typeof replacer !== 'function' &&
|
||||
(typeof replacer !== 'object' ||
|
||||
typeof replacer.length !== 'number')) {
|
||||
throw new Error('JSON.stringify');
|
||||
}
|
||||
|
||||
// Make a fake root object containing our value under the key of ''.
|
||||
// Return the result of stringifying the value.
|
||||
|
||||
return str('', {'': value});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// If the JSON object does not yet have a parse method, give it one.
|
||||
|
||||
if (typeof JSON.parse !== 'function') {
|
||||
JSON.parse = function (text, reviver) {
|
||||
|
||||
// The parse method takes a text and an optional reviver function, and returns
|
||||
// a JavaScript value if the text is a valid JSON text.
|
||||
|
||||
var j;
|
||||
|
||||
function walk(holder, key) {
|
||||
|
||||
// The walk method is used to recursively walk the resulting structure so
|
||||
// that modifications can be made.
|
||||
|
||||
var k, v, value = holder[key];
|
||||
if (value && typeof value === 'object') {
|
||||
for (k in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
||||
v = walk(value, k);
|
||||
if (v !== undefined) {
|
||||
value[k] = v;
|
||||
} else {
|
||||
delete value[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reviver.call(holder, key, value);
|
||||
}
|
||||
|
||||
|
||||
// Parsing happens in four stages. In the first stage, we replace certain
|
||||
// Unicode characters with escape sequences. JavaScript handles many characters
|
||||
// incorrectly, either silently deleting them, or treating them as line endings.
|
||||
|
||||
text = String(text);
|
||||
cx.lastIndex = 0;
|
||||
if (cx.test(text)) {
|
||||
text = text.replace(cx, function (a) {
|
||||
return '\\u' +
|
||||
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
});
|
||||
}
|
||||
|
||||
// In the second stage, we run the text against regular expressions that look
|
||||
// for non-JSON patterns. We are especially concerned with '()' and 'new'
|
||||
// because they can cause invocation, and '=' because it can cause mutation.
|
||||
// But just to be safe, we want to reject all unexpected forms.
|
||||
|
||||
// We split the second stage into 4 regexp operations in order to work around
|
||||
// crippling inefficiencies in IE's and Safari's regexp engines. First we
|
||||
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
|
||||
// replace all simple value tokens with ']' characters. Third, we delete all
|
||||
// open brackets that follow a colon or comma or that begin the text. Finally,
|
||||
// we look to see that the remaining characters are only whitespace or ']' or
|
||||
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
|
||||
|
||||
if (/^[\],:{}\s]*$/
|
||||
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
|
||||
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
|
||||
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
|
||||
|
||||
// In the third stage we use the eval function to compile the text into a
|
||||
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
|
||||
// in JavaScript: it can begin a block or an object literal. We wrap the text
|
||||
// in parens to eliminate the ambiguity.
|
||||
|
||||
j = eval('(' + text + ')');
|
||||
|
||||
// In the optional fourth stage, we recursively walk the new structure, passing
|
||||
// each name/value pair to a reviver function for possible transformation.
|
||||
|
||||
return typeof reviver === 'function' ?
|
||||
walk({'': j}, '') : j;
|
||||
}
|
||||
|
||||
// If the text is not JSON parseable, then a SyntaxError is thrown.
|
||||
|
||||
throw new SyntaxError('JSON.parse');
|
||||
};
|
||||
}
|
||||
}());
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html style="height: 100%">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<script type="text/javascript" src="qweb.js"></script>
|
||||
<script type="text/javascript" src="qweb2.js"></script>
|
||||
<script type="text/javascript">
|
||||
var dict = {
|
||||
session : true,
|
||||
testing : 'yes',
|
||||
name : 'AGR'
|
||||
}
|
||||
console.time("Load template with QWeb");
|
||||
QWeb.add_template("qweb-benchmark.xml");
|
||||
console.timeEnd("Load template with QWeb");
|
||||
|
||||
console.time("Load template with QWeb2");
|
||||
var engine = new QWeb2.Engine("qweb-benchmark.xml")
|
||||
engine.debug = true;
|
||||
console.timeEnd("Load template with QWeb2")
|
||||
|
||||
var iter = 1000;
|
||||
console.log("Rendering...");
|
||||
console.time("Render " + iter + " templates with QWeb");
|
||||
for (var i = 0; i < iter; i++) {
|
||||
var qweb = QWeb.render('benchmark', dict);
|
||||
}
|
||||
console.timeEnd("Render " + iter + " templates with QWeb");
|
||||
|
||||
console.time("Render " + iter + " templates with QWeb2");
|
||||
for (var i = 0; i < iter; i++) {
|
||||
var qweb2 = engine.render('benchmark', dict);
|
||||
}
|
||||
console.timeEnd("Render " + iter + " templates with QWeb2");
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Please, check your console for results
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- vim:fdl=1:
|
||||
-->
|
||||
<templates id="template">
|
||||
<t t-name="benchmark"><div id="oe_notification" class="oe_notification">
|
||||
<div id="oe_notification_default">
|
||||
<a class="ui-notify-cross ui-notify-close" href="#">x</a>
|
||||
<h1>title</h1>
|
||||
<p>text</p>
|
||||
</div>
|
||||
<div id="oe_notification_alert" class="ui-state-error">
|
||||
<a class="ui-notify-cross ui-notify-close" href="#">x</a>
|
||||
<span style="float:left; margin:2px 5px 0 0;" class="ui-icon ui-icon-alert"></span>
|
||||
<h1>title</h1>
|
||||
<p>text</p>
|
||||
</div>
|
||||
</div>
|
||||
<t t-js="d">
|
||||
d.iter = 'one,two,three,four,five'.split(',')
|
||||
</t>
|
||||
<t t-foreach="iter" t-as="i">
|
||||
<t t-call="benchmark_call">
|
||||
+ <t t-esc="i"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-set="enplus">1</t>
|
||||
<t t-set="novar">true</t>
|
||||
<div t-attf-class="id_#{enplus}"/>
|
||||
<div t-if="testing || true" t-att-class="novar || 'yes'" style="display: none">
|
||||
<t t-set="novar"></t>
|
||||
<t t-set="style">height: 200px; border: 1px solid red;</t>
|
||||
<div t-att="{ 'style' : style, 'disabled' : 'false', 'readonly' : novar or undefined }" t-opentag="true"/>
|
||||
<t t-foreach="{'my': 'first', 'my2': 'second' }" t-as="v">
|
||||
* <t t-esc="v"/> : <t t-esc="v_value"/>
|
||||
</t>
|
||||
Ok this is good <t t-esc="name"/>!
|
||||
<t t-set="myvar">Hi there !</t>
|
||||
[<t t-raw="myvar"/>]
|
||||
<t t-set="myvar2" t-value="'a,b,c,d,e'.split(',')"/>
|
||||
<t t-foreach="myvar2" t-as="i">
|
||||
(<t t-esc="i"/>)
|
||||
</t>
|
||||
</div>
|
||||
<div id="oe_notification" class="oe_notification">
|
||||
<div id="oe_notification_default">
|
||||
<a class="ui-notify-cross ui-notify-close" href="#">x</a>
|
||||
<h1>title</h1>
|
||||
<p>text</p>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="benchmark_call">
|
||||
<div id="oe_notification_alert" class="ui-state-error">
|
||||
<a class="ui-notify-cross ui-notify-close" href="#">x</a>
|
||||
<span style="float:left; margin:2px 5px 0 0;" class="ui-icon ui-icon-alert"></span>
|
||||
<h1>Here's your value : (<t t-esc="0"/>) !!</h1>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<templates>
|
||||
<t t-name="jquery-extend">
|
||||
<ul><li>one</li></ul>
|
||||
</t>
|
||||
<t t-extend="jquery-extend">
|
||||
<t t-jquery="ul" t-operation="append"><li>3</li></t>
|
||||
</t>
|
||||
<t t-extend="jquery-extend">
|
||||
<t t-jquery="ul li:first-child" t-operation="replace"><li>2</li></t>
|
||||
</t>
|
||||
<t t-extend="jquery-extend">
|
||||
<t t-jquery="ul" t-operation="prepend"><li>1</li></t>
|
||||
<t t-jquery="ul" t-operation="before"><hr/></t>
|
||||
<t t-jquery="ul" t-operation="after"><hr/></t>
|
||||
</t>
|
||||
<t t-extend="jquery-extend">
|
||||
<t t-jquery="ul">
|
||||
this.attr('class', 'main');
|
||||
</t>
|
||||
</t>
|
||||
<t t-extend="jquery-extend">
|
||||
<t t-jquery="hr:eq(1)" t-operation="replace"><footer></footer></t>
|
||||
</t>
|
||||
<t t-extend="jquery-extend">
|
||||
<t t-jquery="footer" t-operation="inner"><b>END</b></t>
|
||||
</t>
|
||||
|
||||
<t t-name="jquery-extend-clone" t-extend="jquery-extend">
|
||||
<t t-jquery="ul" t-operation="append"><li>CLONED TEMPLATE</li></t>
|
||||
</t>
|
||||
|
||||
</templates>
|
|
@ -1,40 +1,17 @@
|
|||
<templates>
|
||||
<t t-name="repetition-text-content" t-trim="both">
|
||||
<t t-foreach="seq">*</t>
|
||||
</t>
|
||||
<t t-name="repetition-dom-content" t-trim="both">
|
||||
<t t-foreach="seq"><b/></t>
|
||||
</t>
|
||||
<t t-name="repetition-self" t-trim="both">
|
||||
<b t-foreach="seq"/>
|
||||
</t>
|
||||
<t t-name="repetition-text-content" t-foreach="seq">*</t>
|
||||
<t t-name="repetition-dom-content" t-foreach="seq"><b/></t>
|
||||
<b t-name="repetition-self" t-foreach="seq"/>
|
||||
|
||||
<t t-name="scope-self" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq"/></t>
|
||||
</t>
|
||||
<t t-name="scope-value" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_value"/></t>
|
||||
</t>
|
||||
<t t-name="scope-index" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_index"/></t>
|
||||
</t>
|
||||
<t t-name="scope-first" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_first"/> </t>
|
||||
</t>
|
||||
<t t-name="scope-last" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_last"/> </t>
|
||||
</t>
|
||||
<t t-name="scope-parity" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_parity"/> </t>
|
||||
</t>
|
||||
<t t-name="scope-size" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_size"/> </t>
|
||||
</t>
|
||||
<t t-name="scope-self" t-foreach="seq" t-esc="seq"/>
|
||||
<t t-name="scope-value" t-foreach="seq" t-esc="seq_value"/>
|
||||
<t t-name="scope-index" t-foreach="seq" t-esc="seq_index"/>
|
||||
<t t-name="scope-first" t-foreach="seq"><t t-esc="seq_first"/> </t>
|
||||
<t t-name="scope-last" t-foreach="seq"><t t-esc="seq_last"/> </t>
|
||||
<t t-name="scope-parity" t-foreach="seq"><t t-esc="seq_parity"/> </t>
|
||||
<t t-name="scope-size" t-foreach="seq"><t t-esc="seq_size"/> </t>
|
||||
|
||||
<t t-name="aliasing" t-trim="both">
|
||||
<t t-foreach="seq" t-as="item"><t t-esc="item"/></t>
|
||||
</t>
|
||||
<t t-name="loopvars-aliasing" t-trim="both">
|
||||
<t t-foreach="seq" t-as="item"><t t-esc="item_parity"/> </t>
|
||||
</t>
|
||||
<t t-name="aliasing" t-foreach="seq" t-as="item" t-esc="item"/>
|
||||
<t t-name="loopvars-aliasing"
|
||||
t-foreach="seq" t-as="item"><t t-esc="item_parity"/> </t>
|
||||
</templates>
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<templates>
|
||||
<t t-name="trim-content-both" t-trim="both">
|
||||
ok
|
||||
</t>
|
||||
<t t-name="trim-content-before" t-trim="left"> ok </t>
|
||||
<t t-name="trim-content-after" t-trim="right"> ok </t>
|
||||
<t t-name="trim-content-unknown" t-trim="stuff"> ok </t>
|
||||
|
||||
<t t-name="trim-children-text" t-trim="inner">o k</t>
|
||||
<t t-name="trim-children-elements" t-trim="inner">
|
||||
<foo/>
|
||||
<bar/>
|
||||
</t>
|
||||
<t t-name="trim-mixed-children" t-trim="inner">
|
||||
[ <foo/> ]
|
||||
</t>
|
||||
<t t-name='trim-children-left' t-trim="left inner"> <foo/> <bar/> </t>
|
||||
<t t-name='trim-children-right' t-trim="right inner"> <foo/> <bar/> </t>
|
||||
</templates>
|
|
@ -5,9 +5,11 @@
|
|||
<link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" type="text/css" media="screen"/>
|
||||
<script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script>
|
||||
|
||||
<script type="text/javascript" src="qweb.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/jquery/jquery-1.5.2.js"></script>
|
||||
<script type="text/javascript" src="qweb2.js"></script>
|
||||
|
||||
<script>
|
||||
QWeb = new QWeb2.Engine();
|
||||
function trim(s) {
|
||||
return s.replace(/(^\s+|\s+$)/g, '');
|
||||
}
|
||||
|
@ -193,39 +195,6 @@
|
|||
"Call with t-import (calls in current context)");
|
||||
});
|
||||
|
||||
module("Trim", {
|
||||
setup: function () {
|
||||
QWeb.add_template('qweb-test-trim.xml');
|
||||
},
|
||||
teardown: function () {
|
||||
QWeb.templates = [];
|
||||
QWeb.tag = {};
|
||||
QWeb.att = {};
|
||||
}
|
||||
});
|
||||
test('Trim content', function () {
|
||||
equals(QWeb.render('trim-content-both', {}), 'ok',
|
||||
"Trims before and after content");
|
||||
equals(QWeb.render('trim-content-before', {}), 'ok ',
|
||||
"Trims before content");
|
||||
equals(QWeb.render('trim-content-after', {}), ' ok',
|
||||
"Trims after content");
|
||||
raises(function () {QWeb.render('trim-content-unknown', {});},
|
||||
"Trims with an unknown mode");
|
||||
});
|
||||
test('Trim content *and* children', function () {
|
||||
equals(QWeb.render('trim-children-text', {}), 'o k',
|
||||
"Trims text children");
|
||||
equals(QWeb.render('trim-children-elements', {}), '<foo/><bar/>',
|
||||
"Full trim");
|
||||
equals(QWeb.render('trim-mixed-children', {}), '[<foo/>]',
|
||||
"Trims mixed content of text and elements");
|
||||
equals(QWeb.render('trim-children-left', {}), '<foo/><bar/> ',
|
||||
"Trims children and left of content");
|
||||
equals(QWeb.render('trim-children-right', {}), ' <foo/><bar/>',
|
||||
"Trims children and right of content");
|
||||
});
|
||||
|
||||
module("Foreach", {
|
||||
setup: function () {
|
||||
QWeb.add_template('qweb-test-foreach.xml');
|
||||
|
@ -252,21 +221,39 @@
|
|||
"each value of the sequence is also via the _value");
|
||||
equals(QWeb.render('scope-index', {seq:seq}), '01234',
|
||||
"the current 0-based index is available via _index");
|
||||
equals(QWeb.render('scope-first', {seq:seq}), 'true false false false false',
|
||||
equals(QWeb.render('scope-first', {seq:seq}), 'true false false false false ',
|
||||
"_first says whether the current item is the first of the sequence");
|
||||
equals(QWeb.render('scope-last', {seq:seq}), 'false false false false true',
|
||||
equals(QWeb.render('scope-last', {seq:seq}), 'false false false false true ',
|
||||
"_last says whether the current item is the last of the sequence");
|
||||
equals(QWeb.render('scope-parity', {seq:seq}), 'even odd even odd even',
|
||||
equals(QWeb.render('scope-parity', {seq:seq}), 'even odd even odd even ',
|
||||
"the parity (odd/even) of the current row is available via _parity");
|
||||
equals(QWeb.render('scope-size', {seq:seq}), '5 5 5 5 5',
|
||||
equals(QWeb.render('scope-size', {seq:seq}), '5 5 5 5 5 ',
|
||||
"the total length of the sequence is available through _size");
|
||||
});
|
||||
test('Name aliasing via t-as', function () {
|
||||
equals(QWeb.render('aliasing', {seq:seq}), '43210',
|
||||
"the inner value can be re-bound via t-as");
|
||||
equals(QWeb.render('loopvars-aliasing', {seq:seq}), 'even odd even odd even',
|
||||
equals(QWeb.render('loopvars-aliasing', {seq:seq}), 'even odd even odd even ',
|
||||
"inner loop variables should be rebound as well");
|
||||
});
|
||||
|
||||
module("Template inheritance tests", {
|
||||
setup: function () {
|
||||
QWeb.add_template('qweb-test-extend.xml');
|
||||
},
|
||||
teardown: function () {
|
||||
QWeb.templates = [];
|
||||
QWeb.tag = {};
|
||||
QWeb.att = {};
|
||||
}
|
||||
});
|
||||
|
||||
test("jQuery extend", function () {
|
||||
equals(render('jquery-extend', {}), '<hr/><ul class="main"><li>1</li><li>2</li><li>3</li></ul><footer><b>END</b></footer>',
|
||||
"Extend template with jQuery");
|
||||
equals(render('jquery-extend-clone', {}), '<ul><li>one</li><li>CLONED TEMPLATE</li></ul>',
|
||||
"Clone template");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -234,8 +234,11 @@ var QWeb = {
|
|||
return this.render(t_att["call"], d);
|
||||
},
|
||||
render_tag_js:function(e, t_att, g_att, v) {
|
||||
var dict_name = t_att["js"] || "dict";
|
||||
v[dict_name] = v;
|
||||
var r = this.eval_str(this.render_element(e, t_att, g_att, v), v);
|
||||
return t_att["js"] != "quiet" ? r : "";
|
||||
delete(v[dict_name]);
|
||||
return r || '';
|
||||
},
|
||||
/**
|
||||
* Renders a foreach loop (@t-foreach).
|
||||
|
|
|
@ -0,0 +1,691 @@
|
|||
// TODO: trim support
|
||||
// TODO: line number -> https://bugzilla.mozilla.org/show_bug.cgi?id=618650
|
||||
var QWeb2 = {
|
||||
expressions_cache: {},
|
||||
reserved_words: 'true,false,NaN,null,undefined,debugger,in,instanceof,new,function,return,this,typeof,eval,Math,RegExp,Array,Object'.split(','),
|
||||
actions_precedence: 'foreach,if,call,set,esc,escf,raw,rawf,js,debug,log'.split(','),
|
||||
word_replacement: {
|
||||
'and': '&&',
|
||||
'or': '||',
|
||||
'gt': '>',
|
||||
'gte': '>=',
|
||||
'lt': '<',
|
||||
'lte': '<='
|
||||
},
|
||||
tools: {
|
||||
exception: function(message, context) {
|
||||
context = context || {};
|
||||
var prefix = 'QWeb2';
|
||||
if (context.template) {
|
||||
prefix += " - template['" + context.template + "']";
|
||||
}
|
||||
throw prefix + ": " + message;
|
||||
},
|
||||
trim: function(s, mode) {
|
||||
switch (mode) {
|
||||
case "left":
|
||||
return s.replace(/^\s*/, "");
|
||||
case "right":
|
||||
return s.replace(/\s*$/, "");
|
||||
default:
|
||||
return s.replace(/^\s*|\s*$/g, "");
|
||||
}
|
||||
},
|
||||
js_escape: function(s, noquotes) {
|
||||
return (noquotes ? '' : "'") + s.replace(/\r?\n/g, "\\n").replace(/'/g, "\\'") + (noquotes ? '' : "'");
|
||||
},
|
||||
html_escape: function(s, attribute) {
|
||||
if (s === null || s === undefined) {
|
||||
return '';
|
||||
}
|
||||
s = (new String(s)).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
if (attribute) {
|
||||
s = s.replace(/"/g, '"');
|
||||
}
|
||||
return s;
|
||||
},
|
||||
gen_attribute: function(o) {
|
||||
if (o !== null && o !== undefined) {
|
||||
if (o.constructor === Array) {
|
||||
if (o[1] !== null && o[1] !== undefined) {
|
||||
return ' ' + o[0] + '="' + this.html_escape(o[1]) + '"';
|
||||
}
|
||||
} else if (typeof o === 'object') {
|
||||
var r = '';
|
||||
for (var k in o) {
|
||||
if (o.hasOwnProperty(k)) {
|
||||
r += this.gen_attribute([k, o[k]]);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
},
|
||||
extend: function(dst, src, exclude) {
|
||||
for (var p in src) {
|
||||
if (src.hasOwnProperty(p) && !(exclude && this.arrayIndexOf(exclude, p) !== -1)) {
|
||||
dst[p] = src[p];
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
},
|
||||
arrayIndexOf : function(array, item) {
|
||||
for (var i = 0, ilen = array.length; i < ilen; i++) {
|
||||
if (array[i] === item) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
xml_node_to_string : function(node, childs_only) {
|
||||
if (childs_only) {
|
||||
var childs = node.childNodes, r = [];
|
||||
for (var i = 0, ilen = childs.length; i < ilen; i++) {
|
||||
r.push(this.xml_node_to_string(childs[i]));
|
||||
}
|
||||
return r.join('');
|
||||
} else {
|
||||
if (node.xml !== undefined) {
|
||||
return node.xml;
|
||||
} else {
|
||||
return (new XMLSerializer()).serializeToString(node);
|
||||
}
|
||||
}
|
||||
},
|
||||
xpath_selectNodes : function(node, expr) {
|
||||
if (node.selectNodes !== undefined) {
|
||||
return node.selectNodes(expr);
|
||||
} else {
|
||||
var xpath = new XPathEvaluator();
|
||||
try {
|
||||
var result = xpath.evaluate(expr, node, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
|
||||
} catch(error) {
|
||||
this.exception("Error with XPath expression '" + expr + "' : " + error);
|
||||
}
|
||||
var r = [];
|
||||
if (result != null) {
|
||||
var element;
|
||||
while (element = result.iterateNext()) {
|
||||
r.push(element);
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
},
|
||||
call: function(context, template, old_dict, _import, callback) {
|
||||
var new_dict = this.extend({}, old_dict);
|
||||
new_dict['__caller__'] = old_dict['__template__'];
|
||||
if (callback) {
|
||||
new_dict['__content__'] = callback(context, new_dict);
|
||||
}
|
||||
var r = context.engine._render(template, new_dict);
|
||||
if (_import) {
|
||||
if (_import === '*') {
|
||||
this.extend(old_dict, new_dict, ['__caller__', '__template__']);
|
||||
} else {
|
||||
_import = _import.split(',');
|
||||
for (var i = 0, ilen = _import.length; i < ilen; i++) {
|
||||
var v = _import[i];
|
||||
old_dict[v] = new_dict[v];
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
},
|
||||
foreach: function(context, enu, as, old_dict, callback) {
|
||||
if (enu != null) {
|
||||
var size, new_dict = this.extend({}, old_dict);
|
||||
new_dict[as + "_all"] = enu;
|
||||
var as_value = as + "_value",
|
||||
as_index = as + "_index",
|
||||
as_first = as + "_first",
|
||||
as_last = as + "_last",
|
||||
as_parity = as + "_parity";
|
||||
if (size = enu.length) {
|
||||
new_dict[as + "_size"] = size;
|
||||
for (var j = 0, jlen = enu.length; j < jlen; j++) {
|
||||
var cur = enu[j];
|
||||
new_dict[as_value] = cur;
|
||||
new_dict[as_index] = j;
|
||||
new_dict[as_first] = j === 0;
|
||||
new_dict[as_last] = j + 1 === size;
|
||||
new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
|
||||
if (cur.constructor === Object) {
|
||||
this.extend(new_dict, cur);
|
||||
}
|
||||
new_dict[as] = cur;
|
||||
callback(context, new_dict);
|
||||
}
|
||||
} else if (enu.constructor == Number) {
|
||||
var _enu = [];
|
||||
for (var i = 0; i < enu; i++) {
|
||||
_enu.push(i);
|
||||
}
|
||||
this.foreach(context, enu, as, old_dict, callback);
|
||||
} else {
|
||||
var index = 0;
|
||||
for (var k in enu) {
|
||||
if (enu.hasOwnProperty(k)) {
|
||||
var v = enu[k];
|
||||
new_dict[as_value] = v;
|
||||
new_dict[as_index] = index;
|
||||
new_dict[as_first] = index === 0;
|
||||
new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
|
||||
new_dict[as] = k;
|
||||
callback(context, new_dict);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.exception("No enumerator given to foreach", context);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QWeb2.Engine = (function() {
|
||||
function Engine() {
|
||||
// TODO: handle prefix at template level : t-prefix="x"
|
||||
this.prefix = 't';
|
||||
this.debug = false;
|
||||
this.templates_resources = []; // TODO: implement this.reload()
|
||||
this.templates = {};
|
||||
this.compiled_templates = {};
|
||||
this.extend_templates = {};
|
||||
this.dict = {};
|
||||
this.tools = QWeb2.tools;
|
||||
this.jQuery = window.jQuery;
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
this.add_template(arguments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
QWeb2.tools.extend(Engine.prototype, {
|
||||
add_template : function(template) {
|
||||
this.templates_resources.push(template);
|
||||
if (template.constructor === String) {
|
||||
template = this.load_xml(template);
|
||||
}
|
||||
var ec = (template.documentElement && template.documentElement.childNodes) || template.childNodes || [];
|
||||
for (var i = 0; i < ec.length; i++) {
|
||||
var node = ec[i];
|
||||
if (node.nodeType === 1) {
|
||||
if (node.nodeName == 'parsererror') {
|
||||
return this.tools.exception(node.innerText);
|
||||
}
|
||||
var name = node.getAttribute(this.prefix + '-name');
|
||||
var extend = node.getAttribute(this.prefix + '-extend');
|
||||
if (name && extend) {
|
||||
// Clone template and extend it
|
||||
if (!this.templates[extend]) {
|
||||
return this.tools.exception("Can't clone undefined template " + extend);
|
||||
}
|
||||
this.templates[name] = this.templates[extend].cloneNode(true);
|
||||
extend = name;
|
||||
name = undefined;
|
||||
}
|
||||
if (name) {
|
||||
this.templates[name] = node;
|
||||
} else if (extend) {
|
||||
delete(this.compiled_templates[extend]);
|
||||
if (this.extend_templates[extend]) {
|
||||
this.extend_templates[extend].push(node);
|
||||
} else {
|
||||
this.extend_templates[extend] = [node];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
load_xml : function(s) {
|
||||
s = s.replace(/^\s*|\s*$/g, '');
|
||||
if (this.tools.trim(s)[0] === '<') {
|
||||
return this.load_xml_string(s);
|
||||
} else {
|
||||
var req = this.get_xhr();
|
||||
if (req) {
|
||||
// TODO: third parameter is async : https://developer.mozilla.org/en/XMLHttpRequest#open()
|
||||
// do an on_ready in QWeb2{} that could be passed to add_template
|
||||
req.open('GET', s, false);
|
||||
req.send(null);
|
||||
if (req.responseXML) {
|
||||
if (req.responseXML.documentElement.nodeName == "parsererror") {
|
||||
return this.tools.exception(req.responseXML.documentElement.childNodes[0].nodeValue);
|
||||
}
|
||||
return req.responseXML;
|
||||
} else {
|
||||
return this.load_xml_string(req.responseText);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
load_xml_string : function(s) {
|
||||
if (window.DOMParser) {
|
||||
var dp = new DOMParser();
|
||||
var r = dp.parseFromString(s, "text/xml");
|
||||
if (r.body && r.body.firstChild && r.body.firstChild.nodeName == 'parsererror') {
|
||||
return this.tools.exception(r.body.innerText);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
var xDoc;
|
||||
try {
|
||||
// new ActiveXObject("Msxml2.DOMDocument.4.0");
|
||||
xDoc = new ActiveXObject("MSXML2.DOMDocument");
|
||||
} catch (e) {
|
||||
return this.tools.exception("Could not find a DOM Parser");
|
||||
}
|
||||
xDoc.async = false;
|
||||
xDoc.preserveWhiteSpace = true;
|
||||
xDoc.loadXML(s);
|
||||
return xDoc;
|
||||
},
|
||||
get_xhr : function() {
|
||||
if (window.XMLHttpRequest) {
|
||||
return new window.XMLHttpRequest();
|
||||
}
|
||||
try {
|
||||
return new ActiveXObject('MSXML2.XMLHTTP.3.0');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
compile : function(node) {
|
||||
var e = new QWeb2.Element(this, node);
|
||||
var template = node.getAttribute(this.prefix + '-name');
|
||||
return " /* 'this' refers to Qweb2.Engine instance */\n" +
|
||||
" var context = { engine : this, template : " + (this.tools.js_escape(template)) + " };\n" +
|
||||
" dict = dict || {};\n" +
|
||||
" dict['__template__'] = '" + template + "';\n" +
|
||||
" var r = [];\n" +
|
||||
" /* START TEMPLATE */ try {\n" +
|
||||
(e.compile()) + "\n" +
|
||||
" /* END OF TEMPLATE */ } catch(error) {\n" +
|
||||
" context.engine.tools.exception('Runtime Error: ' + error, context);\n" +
|
||||
" }\n" +
|
||||
" return r.join('');";
|
||||
},
|
||||
render : function(template, dict) {
|
||||
if (this.debug && window['console'] !== undefined) {
|
||||
console.time("QWeb render template " + template);
|
||||
}
|
||||
var r = this._render(template, dict);
|
||||
if (this.debug && window['console'] !== undefined) {
|
||||
console.timeEnd("QWeb render template " + template);
|
||||
}
|
||||
return r;
|
||||
},
|
||||
_render : function(template, dict) {
|
||||
if (this.compiled_templates[template]) {
|
||||
return this.compiled_templates[template].apply(this, [dict || {}]);
|
||||
} else if (this.templates[template]) {
|
||||
var ext;
|
||||
if (ext = this.extend_templates[template]) {
|
||||
var extend_node;
|
||||
while (extend_node = ext.shift()) {
|
||||
this.extend(template, extend_node);
|
||||
}
|
||||
}
|
||||
var code = this.compile(this.templates[template]), tcompiled;
|
||||
try {
|
||||
tcompiled = new Function(['dict'], code);
|
||||
} catch (error) {
|
||||
if (this.debug && window.console) {
|
||||
console.log(code);
|
||||
}
|
||||
this.tools.exception("Error evaluating template: " + error, { template: name });
|
||||
}
|
||||
if (!tcompiled) {
|
||||
this.tools.exception("Error evaluating template: (IE?)" + error, { template: name });
|
||||
}
|
||||
this.compiled_templates[template] = tcompiled;
|
||||
return this.render(template, dict);
|
||||
} else {
|
||||
return this.tools.exception("Template '" + template + "' not found");
|
||||
}
|
||||
},
|
||||
extend : function(template, extend_node) {
|
||||
if (!this.jQuery) {
|
||||
return this.tools.exception("Can't extend template " + template + " without jQuery");
|
||||
}
|
||||
for (var i = 0, ilen = extend_node.childNodes.length; i < ilen; i++) {
|
||||
var child = extend_node.childNodes[i];
|
||||
if (child.nodeType === 1) {
|
||||
var xpath = child.getAttribute(this.prefix + '-xpath'),
|
||||
jquery = child.getAttribute(this.prefix + '-jquery'),
|
||||
operation = child.getAttribute(this.prefix + '-operation'),
|
||||
target,
|
||||
error_msg = "Error while extending template '" + template;
|
||||
if (jquery) {
|
||||
target = this.jQuery(jquery, this.templates[template]);
|
||||
} else if (xpath) {
|
||||
// NOTE: due to the XPath implementation, extending a template will only work once
|
||||
// when using XPath because XPathResult won't match objects with other constructor than 'Element'
|
||||
// As jQuery will create HTMLElements, xpath will ignore them then causing problems when a
|
||||
// template has already been extended. XPath selectors will probably be removed in QWeb.
|
||||
target = this.jQuery(this.tools.xpath_selectNodes(this.templates[template], xpath));
|
||||
} else {
|
||||
this.tools.exception(error_msg + "No expression given");
|
||||
}
|
||||
error_msg += "' (expression='" + (jquery || xpath) + "') : ";
|
||||
var inner = this.tools.xml_node_to_string(child, true);
|
||||
if (operation) {
|
||||
var allowed_operations = "append,prepend,before,after,replace,inner".split(',');
|
||||
if (this.tools.arrayIndexOf(allowed_operations, operation) == -1) {
|
||||
this.tools.exception(error_msg + "Invalid operation : '" + operation + "'");
|
||||
}
|
||||
operation = {'replace' : 'replaceWith', 'inner' : 'html'}[operation] || operation;
|
||||
target[operation]($(inner));
|
||||
} else {
|
||||
try {
|
||||
var f = new Function(['$'], inner);
|
||||
} catch(error) {
|
||||
return this.tools.exception("Parse " + error_msg + error);
|
||||
}
|
||||
try {
|
||||
f.apply(target, [this.jQuery]);
|
||||
} catch(error) {
|
||||
return this.tools.exception("Runtime " + error_msg + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return Engine;
|
||||
})();
|
||||
|
||||
QWeb2.Element = (function() {
|
||||
function Element(engine, node) {
|
||||
this.engine = engine;
|
||||
this.node = node;
|
||||
this.tag = node.tagName;
|
||||
this.actions = {};
|
||||
this.actions_done = [];
|
||||
this.attributes = {};
|
||||
this.children = [];
|
||||
this._top = [];
|
||||
this._bottom = [];
|
||||
this._indent = 1;
|
||||
this.process_children = true;
|
||||
var childs = this.node.childNodes;
|
||||
if (childs) {
|
||||
for (var i = 0, ilen = childs.length; i < ilen; i++) {
|
||||
this.children.push(new QWeb2.Element(this.engine, childs[i]));
|
||||
}
|
||||
}
|
||||
var attrs = this.node.attributes;
|
||||
if (attrs) {
|
||||
for (var j = 0, jlen = attrs.length; j < jlen; j++) {
|
||||
var attr = attrs[j];
|
||||
var name = attr.name;
|
||||
var m = name.match(new RegExp("^" + this.engine.prefix + "-(.+)"));
|
||||
if (m) {
|
||||
name = m[1];
|
||||
if (name === 'name') {
|
||||
continue;
|
||||
}
|
||||
this.actions[name] = attr.value;
|
||||
} else {
|
||||
this.attributes[name] = attr.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QWeb2.tools.extend(Element.prototype, {
|
||||
compile : function() {
|
||||
var r = [],
|
||||
instring = false,
|
||||
lines = this._compile().split('\n');
|
||||
for (var i = 0, ilen = lines.length; i < ilen; i++) {
|
||||
var m, line = lines[i];
|
||||
if (m = line.match(/^(\s*)\/\/@string=(.*)/)) {
|
||||
if (instring) {
|
||||
if (this.engine.debug) {
|
||||
// Split string lines in indented r.push arguments
|
||||
r.push((m[2].indexOf("\\n") != -1 ? "',\n\t" + m[1] + "'" : '') + m[2]);
|
||||
} else {
|
||||
r.push(m[2]);
|
||||
}
|
||||
} else {
|
||||
r.push(m[1] + "r.push('" + m[2]);
|
||||
instring = true;
|
||||
}
|
||||
} else {
|
||||
if (instring) {
|
||||
r.push("');\n");
|
||||
}
|
||||
instring = false;
|
||||
r.push(line + '\n');
|
||||
}
|
||||
}
|
||||
return r.join('');
|
||||
},
|
||||
_compile : function() {
|
||||
switch (this.node.nodeType) {
|
||||
case 3:
|
||||
case 4:
|
||||
this.top_string(this.node.data);
|
||||
break;
|
||||
case 1:
|
||||
this.compile_element();
|
||||
}
|
||||
var r = this._top.join('');
|
||||
if (this.process_children) {
|
||||
for (var i = 0, ilen = this.children.length; i < ilen; i++) {
|
||||
var child = this.children[i];
|
||||
child._indent = this._indent;
|
||||
r += child._compile();
|
||||
}
|
||||
}
|
||||
r += this._bottom.join('');
|
||||
return r;
|
||||
},
|
||||
format_expression : function(e) {
|
||||
/* Naive format expression builder. Replace reserved words and variables to dict[variable]
|
||||
* Does not handle spaces before dot yet, and causes problems for anonymous functions. Use t-js="" for that */
|
||||
if (QWeb2.expressions_cache[e]) {
|
||||
return QWeb2.expressions_cache[e];
|
||||
}
|
||||
var chars = e.split(''),
|
||||
instring = '',
|
||||
invar = '',
|
||||
invar_pos = 0,
|
||||
r = '';
|
||||
chars.push(' ');
|
||||
for (var i = 0, ilen = chars.length; i < ilen; i++) {
|
||||
var c = chars[i];
|
||||
if (instring.length) {
|
||||
if (c === instring && chars[i - 1] !== "\\") {
|
||||
instring = '';
|
||||
}
|
||||
} else if (c === '"' || c === "'") {
|
||||
instring = c;
|
||||
} else if (c.match(/[a-zA-Z_\$]/) && !invar.length) {
|
||||
invar = c;
|
||||
invar_pos = i;
|
||||
continue;
|
||||
} else if (c.match(/\W/) && invar.length) {
|
||||
// TODO: Should check for possible spaces before dot
|
||||
if (chars[invar_pos - 1] !== '.' && QWeb2.tools.arrayIndexOf(QWeb2.reserved_words, invar) < 0) {
|
||||
invar = QWeb2.word_replacement[invar] || ("dict['" + invar + "']");
|
||||
}
|
||||
r += invar;
|
||||
invar = '';
|
||||
} else if (invar.length) {
|
||||
invar += c;
|
||||
continue;
|
||||
}
|
||||
r += c;
|
||||
}
|
||||
r = r.slice(0, -1);
|
||||
QWeb2.expressions_cache[e] = r;
|
||||
return r;
|
||||
},
|
||||
string_interpolation : function(s) {
|
||||
if (!s) {
|
||||
return "''";
|
||||
}
|
||||
var regex = /^{(.*)}(.*)/,
|
||||
src = s.split(/#/),
|
||||
r = [];
|
||||
for (var i = 0, ilen = src.length; i < ilen; i++) {
|
||||
var val = src[i],
|
||||
m = val.match(regex);
|
||||
if (m) {
|
||||
r.push("(" + this.format_expression(m[1]) + ")");
|
||||
if (m[2]) {
|
||||
r.push(this.engine.tools.js_escape(m[2]));
|
||||
}
|
||||
} else if (!(i === 0 && val === '')) {
|
||||
r.push(this.engine.tools.js_escape((i === 0 ? '' : '#') + val));
|
||||
}
|
||||
}
|
||||
return r.join(' + ');
|
||||
},
|
||||
indent : function() {
|
||||
return this._indent++;
|
||||
},
|
||||
dedent : function() {
|
||||
if (this._indent !== 0) {
|
||||
return this._indent--;
|
||||
}
|
||||
},
|
||||
get_indent : function() {
|
||||
return new Array(this._indent + 1).join("\t");
|
||||
},
|
||||
top : function(s) {
|
||||
return this._top.push(this.get_indent() + s + '\n');
|
||||
},
|
||||
top_string : function(s) {
|
||||
return this._top.push(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
|
||||
},
|
||||
bottom : function(s) {
|
||||
return this._bottom.unshift(this.get_indent() + s + '\n');
|
||||
},
|
||||
bottom_string : function(s) {
|
||||
return this._bottom.unshift(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
|
||||
},
|
||||
compile_element : function() {
|
||||
for (var i = 0, ilen = QWeb2.actions_precedence.length; i < ilen; i++) {
|
||||
var a = QWeb2.actions_precedence[i];
|
||||
if (a in this.actions) {
|
||||
var value = this.actions[a];
|
||||
var key = 'compile_action_' + a;
|
||||
if (!this[key]) {
|
||||
this.engine.tools.exception("No handler method for action '" + a + "'");
|
||||
}
|
||||
this[key](value);
|
||||
}
|
||||
}
|
||||
if (this.tag !== this.engine.prefix) {
|
||||
var tag = "<" + this.tag;
|
||||
for (var a in this.attributes) {
|
||||
tag += this.engine.tools.gen_attribute([a, this.attributes[a]]);
|
||||
}
|
||||
this.top_string(tag);
|
||||
if (this.actions.att) {
|
||||
this.top("r.push(context.engine.tools.gen_attribute(" + (this.format_expression(this.actions.att)) + "));");
|
||||
}
|
||||
for (var a in this.actions) {
|
||||
var v = this.actions[a];
|
||||
var m = a.match(/att-(.+)/);
|
||||
if (m) {
|
||||
this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.format_expression(v)) + ")]));");
|
||||
}
|
||||
var m = a.match(/attf-(.+)/);
|
||||
if (m) {
|
||||
this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.string_interpolation(v)) + ")]));");
|
||||
}
|
||||
}
|
||||
if (this.children.length || this.actions.opentag === 'true') {
|
||||
this.top_string(">");
|
||||
this.bottom_string("</" + this.tag + ">");
|
||||
} else {
|
||||
this.top_string("/>");
|
||||
}
|
||||
}
|
||||
},
|
||||
compile_action_if : function(value) {
|
||||
this.top("if (" + (this.format_expression(value)) + ") {");
|
||||
this.bottom("}");
|
||||
this.indent();
|
||||
},
|
||||
compile_action_foreach : function(value) {
|
||||
var as = this.actions['as'] || value.replace(/[^a-zA-Z0-9]/g, '_');
|
||||
//TODO: exception if t-as not valid
|
||||
this.top("context.engine.tools.foreach(context, " + (this.format_expression(value)) + ", " + (this.engine.tools.js_escape(as)) + ", dict, function(context, dict) {");
|
||||
this.bottom("});");
|
||||
this.indent();
|
||||
},
|
||||
compile_action_call : function(value) {
|
||||
var _import = this.actions['import'] || '';
|
||||
if (this.children.length === 0) {
|
||||
return this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + "));");
|
||||
} else {
|
||||
this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + ", function(context, dict) {");
|
||||
this.bottom("}));");
|
||||
this.indent();
|
||||
this.top("var r = [];");
|
||||
return this.bottom("return r.join('');");
|
||||
}
|
||||
},
|
||||
compile_action_set : function(value) {
|
||||
if (this.actions['value']) {
|
||||
this.top("dict['" + value + "'] = (" + (this.format_expression(this.actions['value'])) + ");");
|
||||
this.process_children = false;
|
||||
} else {
|
||||
if (this.children.length === 0) {
|
||||
this.top("dict['" + value + "'] = '';");
|
||||
} else if (this.children.length === 1 && this.children[0].node.nodeType === 3) {
|
||||
this.top("dict['" + value + "'] = " + (this.engine.tools.js_escape(this.children[0].node.data)) + ";");
|
||||
this.process_children = false;
|
||||
} else {
|
||||
this.top("dict['" + value + "'] = (function(dict) {");
|
||||
this.bottom("})(dict);");
|
||||
this.indent();
|
||||
this.top("var r = [];");
|
||||
this.bottom("return r.join('');");
|
||||
}
|
||||
}
|
||||
},
|
||||
compile_action_esc : function(value) {
|
||||
this.top("r.push(context.engine.tools.html_escape(" + (this.format_expression(value)) + "));");
|
||||
},
|
||||
compile_action_escf : function(value) {
|
||||
this.top("r.push(context.engine.tools.html_escape(" + (this.string_interpolation(value)) + "));");
|
||||
},
|
||||
compile_action_raw : function(value) {
|
||||
this.top("r.push(" + (this.format_expression(value)) + ");");
|
||||
},
|
||||
compile_action_rawf : function(value) {
|
||||
this.top("r.push(" + (this.string_interpolation(value)) + ");");
|
||||
},
|
||||
compile_action_js : function(value) {
|
||||
this.top("(function(" + value + ") {");
|
||||
this.bottom("})(dict);");
|
||||
this.indent();
|
||||
if (this.children.length === 1) {
|
||||
var lines = this.children[0].node.data.split(/\r?\n/);
|
||||
for (var i = 0, ilen = lines.length; i < ilen; i++) {
|
||||
this.top(lines[i]);
|
||||
}
|
||||
} else {
|
||||
this.engine.tools.exception("'js' code block contains " + this.children.length + " nodes instead of 1");
|
||||
}
|
||||
// Maybe I could handle the children ?
|
||||
this.process_children = false;
|
||||
},
|
||||
compile_action_debug : function(value) {
|
||||
this.top("debugger;");
|
||||
},
|
||||
compile_action_log : function(value) {
|
||||
this.top("console.log(" + this.format_expression(value) + "});");
|
||||
}
|
||||
});
|
||||
return Element;
|
||||
})();
|
|
@ -8,7 +8,10 @@
|
|||
<script type="text/javascript" src="/base/static/lib/LABjs/LAB.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/underscore/underscore.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/underscore/underscore.strings.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/qweb/qweb2.js"></script>
|
||||
<!-- Uncomment in order to use legacy QWeb
|
||||
<script type="text/javascript" src="/base/static/lib/qweb/qweb.js"></script>
|
||||
-->
|
||||
<script type="text/javascript" src="/base/static/lib/jquery/jquery-1.5.2.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/jquery.ui/js/jquery-ui-1.8.9.custom.min.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/jquery.ui/js/jquery-ui-timepicker-addon.js"></script>
|
||||
|
@ -16,22 +19,30 @@
|
|||
<script type="text/javascript" src="/base/static/lib/jquery.superfish/js/hoverIntent.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/jquery.superfish/js/superfish.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/datejs/date-en-US.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/json/json2.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/base.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/dates.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/chrome.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/data.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/views.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/form.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/list.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/search.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/views.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/m2o.js"></script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/base/static/lib/jquery.ui/css/smoothness/jquery-ui-1.8.9.custom.css" />
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/base/static/lib/jquery.ui.notify/css/ui.notify.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/base/static/lib/jquery.superfish/css/superfish.css" media="screen">
|
||||
|
||||
<link rel="stylesheet" href="/base/static/src/css/base.css" type="text/css"/>
|
||||
<!--[if lte IE 7]>
|
||||
<link rel="stylesheet" href="/base/static/src/css/base-ie7.css" type="text/css"/>
|
||||
<![endif]-->
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
QWeb = window.QWeb || new QWeb2.Engine();
|
||||
var oe = openerp.init();
|
||||
oe.base.webclient("oe");
|
||||
});
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
.openerp .menu span {
|
||||
min-width: 0px;
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
/* TODO: seperate openerp webclient page css from openerp views css */
|
||||
/* TODO: separate openerp web client page css from openerp views css */
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: helvetica, sans, arial;
|
||||
font-family: helvetica, arial, sans-serif;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ body.openerp {
|
|||
|
||||
/* Menu */
|
||||
.openerp .sf-menu {
|
||||
margin-bottom: 0px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/*
|
||||
.sf-menu a {
|
||||
|
@ -99,53 +99,38 @@ body.openerp {
|
|||
white-space: nowrap;
|
||||
background: url(../img/menu-bg.png) repeat-x;
|
||||
}
|
||||
.openerp .menu ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.openerp .menu li {
|
||||
display: -moz-inline-stack;
|
||||
display: inline; /*IE7 */
|
||||
display: inline-block;
|
||||
margin: 6px 1px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.openerp .menu a {
|
||||
display: block;
|
||||
float: left;
|
||||
padding: 0 0 0 3px;
|
||||
background: url(../img/menu-item.png) no-repeat;
|
||||
color: #eee;
|
||||
text-shadow: #222 0 1px 0;
|
||||
text-decoration: none;
|
||||
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
margin: 2px 1px;
|
||||
height: 23px;
|
||||
}
|
||||
.openerp .menu span {
|
||||
display: block;
|
||||
float: left;
|
||||
min-width: 60px;
|
||||
height: 23px;
|
||||
padding: 0 9px 0 6px;
|
||||
background: url(../img/menu-item.png) 100% 0 no-repeat;
|
||||
line-height: 22px;
|
||||
line-height: 23px;
|
||||
font-weight: bold;
|
||||
font-size: 85%;
|
||||
}
|
||||
.openerp .menu ul li a:hover,
|
||||
.openerp .menu li a:focus,
|
||||
.openerp .menu ul li a.active {
|
||||
.openerp .menu a:hover,
|
||||
.openerp .menu a:focus,
|
||||
.openerp .menu a.active {
|
||||
background-position: 0 -23px;
|
||||
color: #fff;
|
||||
}
|
||||
.openerp .menu ul li a:hover span,
|
||||
.openerp .menu ul li a:focus span,
|
||||
.openerp .menu ul li a.active span {
|
||||
.openerp .menu a:hover span,
|
||||
.openerp .menu a:focus span,
|
||||
.openerp .menu a.active span {
|
||||
background-position: 100% -23px;
|
||||
}
|
||||
/* Secondary Menu */
|
||||
|
@ -169,10 +154,9 @@ body.openerp {
|
|||
border: none;
|
||||
}
|
||||
.openerp .secondary_menu h4 {
|
||||
padding: 0 0 2px;
|
||||
padding: 0 0 2px 10px;
|
||||
border: none;
|
||||
background: none;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.openerp .secondary_menu h3 span, .openerp .secondary_menu h4 span {
|
||||
left: 0 !important;
|
||||
|
@ -222,19 +206,21 @@ body.openerp {
|
|||
height: 63px;
|
||||
width: 200px;
|
||||
margin-right: 10px;
|
||||
line-height: 63px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid black;
|
||||
border-right: 1px solid black;
|
||||
border-top: 1px solid white;
|
||||
border-left: 1px solid white;
|
||||
border: 1px solid white;
|
||||
border-right-color: black;
|
||||
border-bottom-color: black;
|
||||
background: #FFFFFF;
|
||||
background: -moz-linear-gradient(top, #FFFFFF 0%, #CECECE 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#FFFFFF), color-stop(100%,#CECECE));
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#CECECE',GradientType=0 );
|
||||
}
|
||||
.openerp .company_logo {
|
||||
vertical-align: middle
|
||||
margin-top: 7px;
|
||||
margin-left: 10px;
|
||||
display: block;
|
||||
background: url(/base/static/src/img/logo.png);
|
||||
width:180px;
|
||||
height:46px;
|
||||
}
|
||||
.openerp .header_title {
|
||||
float: left;
|
||||
|
@ -301,7 +287,6 @@ body.openerp {
|
|||
right: 0;
|
||||
top: 5px;
|
||||
padding: 1px 4px 2px;
|
||||
background: #333;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
|
@ -316,9 +301,9 @@ body.openerp {
|
|||
overflow: hidden;
|
||||
padding: 5px 0;
|
||||
position: relative;
|
||||
-moz-box-shadow: inset 0px 3px 5px rgba(0, 0, 0, 0.4);
|
||||
-webkit-box-shadow: inset 0px 3px 5px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: inset 0px 3px 5px rgba(0, 0, 0, 0.4);
|
||||
-moz-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.4);
|
||||
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.openerp div.oe_footer p.oe_footer_powered {
|
||||
left: 50%;
|
||||
|
@ -336,8 +321,9 @@ body.openerp {
|
|||
}
|
||||
|
||||
/* Main Application */
|
||||
.openerp .application {
|
||||
padding: 0px;
|
||||
.openerp .oe-application {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.openerp h2.oe_view_title {
|
||||
|
@ -379,7 +365,7 @@ body.openerp {
|
|||
background: #AAAAAA;
|
||||
}
|
||||
.openerp .filter_icon {
|
||||
padding: 1px 2px 0px 2px;
|
||||
padding: 1px 2px 0 2px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
@ -442,6 +428,9 @@ body.openerp {
|
|||
.openerp .searchview_group_content {
|
||||
padding-left: 10px;
|
||||
}
|
||||
.openerp .searchview_group_content .oe-searchview-render-line {
|
||||
width:0;
|
||||
}
|
||||
|
||||
|
||||
.openerp .oe-searchview-render-line {
|
||||
|
@ -454,12 +443,12 @@ body.openerp {
|
|||
margin: 2px;
|
||||
}
|
||||
|
||||
.openerp .searchview_extended_add_proposition, .openerp .searchview_extended_add_group {
|
||||
.openerp .searchview_extended_add_proposition span, .openerp .searchview_extended_add_group span {
|
||||
background: url(../img/icons/gtk-add.png) repeat-y;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.openerp .searchview_extended_delete_group, .openerp .searchview_extended_delete_prop {
|
||||
.openerp .searchview_extended_delete_group span, .openerp .searchview_extended_delete_prop span {
|
||||
background: url(../img/icons/gtk-remove.png) repeat-y;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
@ -484,8 +473,16 @@ body.openerp {
|
|||
.openerp .oe-listview td,
|
||||
.openerp .oe-listview th {
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
}
|
||||
.openerp .oe-listview th.oe-sortable,
|
||||
.openerp .oe-listview th.oe-sortable .ui-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.openerp .oe-listview .oe-field-cell {
|
||||
cursor: pointer;
|
||||
}
|
||||
.openerp .oe-listview .oe-field-cell button {
|
||||
padding: 0;
|
||||
border: none;
|
||||
|
@ -535,17 +532,17 @@ body.openerp {
|
|||
|
||||
/* Notebook */
|
||||
.openerp .oe_form_notebook {
|
||||
padding: 0px;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border-width: 0px;
|
||||
border-width: 0;
|
||||
}
|
||||
.openerp .oe_form_notebook ul.ui-tabs-nav {
|
||||
padding-left: 0px;
|
||||
padding-left: 0;
|
||||
background: transparent;
|
||||
border-width: 0px 0px 1px 0px;
|
||||
border-radius: 0px;
|
||||
-moz-border-radius: 0px;
|
||||
-webkit-border-radius: 0px;
|
||||
border-width: 0 0 1px 0;
|
||||
border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
line-height: 0.5em;
|
||||
}
|
||||
.openerp .oe_form_notebook ul.ui-tabs-nav li {
|
||||
|
@ -553,7 +550,7 @@ body.openerp {
|
|||
}
|
||||
.openerp .oe_form_notebook .ui-tabs-panel {
|
||||
background: #f9f9f9;
|
||||
border-width: 0px 1px 1px 1px;
|
||||
border-width: 0 1px 1px 1px;
|
||||
}
|
||||
.openerp .oe_form_notebook .ui-tabs-selected {
|
||||
background: #f9f9f9;
|
||||
|
@ -562,7 +559,6 @@ body.openerp {
|
|||
/* Form */
|
||||
.openerp table.oe_frame td {
|
||||
color: #4c4c4c;
|
||||
font-weight: 80%;
|
||||
}
|
||||
.openerp .required.error {
|
||||
border: 1px solid #900;
|
||||
|
@ -605,6 +601,9 @@ body.openerp {
|
|||
min-width: 90px;
|
||||
color: #1f1f1f;
|
||||
}
|
||||
.openerp textarea {
|
||||
resize:vertical;
|
||||
}
|
||||
.openerp input[type="text"], .openerp input[type="password"], .openerp select, .openerp .button {
|
||||
height: 22px;
|
||||
}
|
||||
|
@ -636,10 +635,16 @@ body.openerp {
|
|||
.openerp td.required input, .openerp td.required select {
|
||||
background-color: #D2D2FF;
|
||||
}
|
||||
.openerp td.invalid input, .openerp td.invalid select {
|
||||
.openerp td.invalid input, .openerp td.invalid select, .openerp td.invalid textarea {
|
||||
background-color: #F66;
|
||||
border: 1px solid #D00;
|
||||
}
|
||||
.openerp div.oe-progressbar span {
|
||||
position: absolute;
|
||||
margin-left: 10px;
|
||||
margin-top: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* jQuery UI override */
|
||||
.openerp .ui-widget {
|
||||
|
@ -673,13 +678,14 @@ body.openerp {
|
|||
}
|
||||
|
||||
.openerp .view-manager-main-content {
|
||||
width: 100%;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.openerp .view-manager-main-sidebar {
|
||||
width:0;
|
||||
padding:0;
|
||||
margin:0;
|
||||
width: 180px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.openerp .sidebar-main-div {
|
||||
|
@ -690,9 +696,7 @@ body.openerp {
|
|||
padding:0;
|
||||
margin:0;
|
||||
width:180px;
|
||||
border-left-color: #D2CFCF;
|
||||
border-left-style: solid;
|
||||
border-left-width: 1px;
|
||||
border-left: 1px solid #D2CFCF;
|
||||
height:100%;
|
||||
font-family: Ubuntu, Helvetica, sans-serif;
|
||||
font-size: 0.9em;
|
||||
|
@ -720,17 +724,14 @@ body.openerp {
|
|||
font-family: Ubuntu, Helvetica, sans-serif;
|
||||
font-size: 1.15em;
|
||||
color: #8E8E8E;
|
||||
text-shadow: white 0px 1px 0px;
|
||||
text-shadow: white 0 1px 0;
|
||||
padding-left: 10px;
|
||||
padding-right: 21px;
|
||||
height: 21px;
|
||||
background: url(../img/sideheader-a-bg.png) repeat-x;
|
||||
border-color: #D2CFCF;
|
||||
border-style: solid;
|
||||
border-top-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 0;
|
||||
border: 1px solid #D2CFCF;
|
||||
border-right-width: 0;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.openerp .view-manager-main-sidebar ul {
|
||||
|
@ -746,9 +747,8 @@ body.openerp {
|
|||
}
|
||||
|
||||
.openerp .toggle-sidebar {
|
||||
border-color: #D2CFCF;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #D2CFCF;
|
||||
display: block;
|
||||
background: url(../img/toggle-a-bg.png);
|
||||
width: 21px;
|
||||
|
@ -761,15 +761,62 @@ body.openerp {
|
|||
background-position: 21px 0;
|
||||
}
|
||||
|
||||
.openerp .kitten-mode-activated {
|
||||
.openerp.kitten-mode-activated .main_table {
|
||||
background: url(http://placekitten.com/g/1500/800) repeat;
|
||||
}
|
||||
|
||||
.openerp .kitten-mode-activated .header {
|
||||
.openerp.kitten-mode-activated .header {
|
||||
background: url(http://placekitten.com/g/211/65) repeat;
|
||||
}
|
||||
|
||||
.openerp .kitten-mode-activated .secondary_menu {
|
||||
.openerp.kitten-mode-activated .secondary_menu {
|
||||
background: url(http://placekitten.com/g/212/100) repeat;
|
||||
}
|
||||
|
||||
.openerp.kitten-mode-activated .menu {
|
||||
background: #828282;
|
||||
background: -moz-linear-gradient(top, #828282 0%, #4D4D4D 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#828282), color-stop(100%,#4D4D4D));
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#828282', endColorstr='#4D4D4D',GradientType=0 );
|
||||
}
|
||||
.openerp.kitten-mode-activated .menu a {
|
||||
background: none;
|
||||
}
|
||||
.openerp.kitten-mode-activated .menu span {
|
||||
background: none;
|
||||
}
|
||||
.openerp.kitten-mode-activated .sidebar-displaying-div li a,
|
||||
.openerp.kitten-mode-activated .oe-application .view-manager-main-content h2.oe_view_title,
|
||||
.openerp.kitten-mode-activated .oe-application .view-manager-main-content a.searchview_group_string,
|
||||
.openerp.kitten-mode-activated .oe-application .view-manager-main-content label {
|
||||
color: white;
|
||||
}
|
||||
.openerp.kitten-mode-activated .menu,
|
||||
.openerp.kitten-mode-activated .header_corner,
|
||||
.openerp.kitten-mode-activated .header_title,
|
||||
.openerp.kitten-mode-activated .secondary_menu div,
|
||||
.openerp.kitten-mode-activated .oe-application,
|
||||
.openerp.kitten-mode-activated .oe_footer,
|
||||
.openerp.kitten-mode-activated .loading,
|
||||
.openerp.kitten-mode-activated .ui-dialog {
|
||||
opacity:0.8;
|
||||
filter:alpha(opacity=80);
|
||||
}
|
||||
.openerp.kitten-mode-activated .header .company_logo {
|
||||
background: url(http://placekitten.com/g/180/46);
|
||||
}
|
||||
.openerp.kitten-mode-activated .loading {
|
||||
background: #828282;
|
||||
border-color: #828282;
|
||||
}
|
||||
|
||||
/* Many2one Autosearch */
|
||||
.openerp .ui_combo {
|
||||
height: 19px;
|
||||
top: 6px;
|
||||
width: 20px;
|
||||
position: relative;
|
||||
margin-left: -26px;
|
||||
}
|
||||
/*------------- End autocomplete ------- */
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 473 B |
Binary file not shown.
After Width: | Height: | Size: 471 B |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -44,8 +44,11 @@
|
|||
// The dummy class constructor
|
||||
function Class() {
|
||||
// All construction is actually done in the init method
|
||||
if ( !initializing && this.init )
|
||||
this.init.apply(this, arguments);
|
||||
if ( !initializing && this.init ) {
|
||||
var ret = this.init.apply(this, arguments);
|
||||
if (ret) { return ret; }
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// Populate our constructed prototype object
|
||||
|
@ -120,11 +123,13 @@
|
|||
|
||||
/** @namespace */
|
||||
openerp.base = function(instance) {
|
||||
openerp.base.dates(instance);
|
||||
openerp.base.chrome(instance);
|
||||
openerp.base.data(instance);
|
||||
openerp.base.views(instance);
|
||||
openerp.base.search(instance);
|
||||
openerp.base.list(instance);
|
||||
openerp.base.m2o(instance);
|
||||
openerp.base.form(instance);
|
||||
};
|
||||
|
||||
|
|
|
@ -296,6 +296,9 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b
|
|||
// Construct a JSON-RPC2 request, method is currently unused
|
||||
params.session_id = this.session_id;
|
||||
params.context = typeof(params.context) != "undefined" ? params.context : this.context;
|
||||
if (!params.context.bin_size) {
|
||||
params.context.bin_size = true;
|
||||
}
|
||||
|
||||
// Use a default error handler unless defined
|
||||
error_callback = typeof(error_callback) != "undefined" ? error_callback : this.on_rpc_error;
|
||||
|
@ -790,10 +793,6 @@ openerp.base.Header = openerp.base.Controller.extend({
|
|||
this.do_update();
|
||||
},
|
||||
do_update: function() {
|
||||
if(jQuery.param != undefined &&
|
||||
jQuery.deparam(jQuery.param.querystring()).kitten != undefined) {
|
||||
this.kitten = 1;
|
||||
}
|
||||
this.$element.html(QWeb.render("Header", this));
|
||||
}
|
||||
});
|
||||
|
@ -897,7 +896,7 @@ openerp.base.WebClient = openerp.base.Controller.extend({
|
|||
var params = {};
|
||||
if(jQuery.param != undefined &&
|
||||
jQuery.deparam(jQuery.param.querystring()).kitten != undefined) {
|
||||
params = {kitten:1};
|
||||
this.$element.addClass("kitten-mode-activated");
|
||||
}
|
||||
this.$element.html(QWeb.render("Interface", params));
|
||||
|
||||
|
|
|
@ -3,15 +3,139 @@ openerp.base.data = function(openerp) {
|
|||
|
||||
openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.base.DataGroup# */{
|
||||
/**
|
||||
* Management interface between views and the collection of selected OpenERP
|
||||
* records (represents the view's state?)
|
||||
* Management interface between views and grouped collections of OpenERP
|
||||
* records.
|
||||
*
|
||||
* The root DataGroup is instantiated with the relevant information
|
||||
* (a session, a model, a domain, a context and a group_by sequence), the
|
||||
* domain and context may be empty. It is then interacted with via
|
||||
* :js:func:`~openerp.base.DataGroup.list`, which is used to read the
|
||||
* content of the current grouping level, and
|
||||
* :js:func:`~openerp.base.DataGroup.get`, which is used to fetch an item
|
||||
* of the current grouping level.
|
||||
*
|
||||
* @constructs
|
||||
* @extends openerp.base.Controller
|
||||
*
|
||||
* @param {openerp.base.Session} session Current OpenERP session
|
||||
* @param {String} model name of the model managed by this DataGroup
|
||||
* @param {Array} domain search domain for this DataGroup
|
||||
* @param {Object} context context of the DataGroup's searches
|
||||
* @param {Array} group_by sequence of fields by which to group
|
||||
*/
|
||||
init: function(session) {
|
||||
init: function(session, model, domain, context, group_by) {
|
||||
this._super(session, null);
|
||||
this.model = model;
|
||||
this.context = context;
|
||||
this.domain = domain;
|
||||
|
||||
this.group_by = group_by;
|
||||
|
||||
this.groups = null;
|
||||
},
|
||||
fetch: function () {
|
||||
// internal method
|
||||
var d = new $.Deferred();
|
||||
var self = this;
|
||||
|
||||
if (this.groups) {
|
||||
d.resolveWith(this, [this.groups]);
|
||||
} else {
|
||||
this.rpc('/base/group/read', {
|
||||
model: this.model,
|
||||
context: this.context,
|
||||
domain: this.domain,
|
||||
group_by_fields: this.group_by
|
||||
}, function () { }).then(function (response) {
|
||||
self.groups = response.result;
|
||||
// read_group results are annoying: they use the name of the
|
||||
// field grouped on to hold the value and the count, so no
|
||||
// generic access to those values is possible.
|
||||
// Alias them to `value` and `length`.
|
||||
d.resolveWith(self, [_(response.result).map(function (group) {
|
||||
var field_name = self.group_by[0];
|
||||
return _.extend({}, group, {
|
||||
// provide field used for grouping
|
||||
grouped_on: field_name,
|
||||
length: group[field_name + '_count'],
|
||||
value: group[field_name]
|
||||
});
|
||||
})]);
|
||||
}, function () {
|
||||
d.rejectWith.apply(d, self, [arguments]);
|
||||
});
|
||||
}
|
||||
return d.promise();
|
||||
},
|
||||
/**
|
||||
* Retrieves the content of an item in the DataGroup, which results in
|
||||
* either a DataSet or new DataGroup instance.
|
||||
*
|
||||
* Calling :js:func:`~openerp.base.DataGroup.get` without having called
|
||||
* :js:func:`~openerp.base.DataGroup.list` beforehand will likely result
|
||||
* in an error.
|
||||
*
|
||||
* The resulting :js:class:`~openerp.base.DataGroup` or
|
||||
* :js:class:`~openerp.base.DataSet` will be provided through the relevant
|
||||
* callback function. In both functions, the current DataGroup will be
|
||||
* provided as context (``this``)
|
||||
*
|
||||
* @param {Number} index the index of the group to open in the datagroup's collection
|
||||
* @param {Function} ifDataSet executed if the item results in a DataSet, provided with the new dataset as parameter
|
||||
* @param {Function} ifDataGroup executed if the item results in a DataSet, provided with the new datagroup as parameter
|
||||
*/
|
||||
get: function (index, ifDataSet, ifDataGroup) {
|
||||
var group = this.groups[index];
|
||||
if (!group) {
|
||||
throw new Error("No group at index " + index);
|
||||
}
|
||||
|
||||
var child_context = _.extend({}, this.context, group.__context);
|
||||
if (group.__context.group_by.length) {
|
||||
var datagroup = new openerp.base.DataGroup(
|
||||
this.session, this.model, group.__domain, child_context,
|
||||
group.__context.group_by);
|
||||
ifDataGroup.call(this, datagroup);
|
||||
} else {
|
||||
var dataset = new openerp.base.DataSetSearch(this.session, this.model);
|
||||
dataset.domain = group.__domain;
|
||||
dataset.context = child_context;
|
||||
ifDataSet.call(this, dataset);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Gathers the content of the current data group (the current grouping
|
||||
* level), and provides it via a promise object to which callbacks should
|
||||
* be added::
|
||||
*
|
||||
* datagroup.list().then(function (list) {
|
||||
* // manipulate list here
|
||||
* });
|
||||
*
|
||||
* The argument to the callback is the list of elements fetched, the
|
||||
* context (``this``) is the datagroup itself.
|
||||
*
|
||||
* The items of a list are the standard objects returned by ``read_group``
|
||||
* with three properties added:
|
||||
*
|
||||
* ``length``
|
||||
* the number of records contained in the group (and all of its
|
||||
* sub-groups). This does *not* provide the size of the "next level"
|
||||
* of the group, unless the group is terminal (no more groups within
|
||||
* it).
|
||||
* ``grouped_on``
|
||||
* the name of the field this level was grouped on, this is mostly
|
||||
* used for display purposes, in order to know the name of the current
|
||||
* level of grouping. The ``grouped_on`` should be the same for all
|
||||
* objects of the list.
|
||||
* ``value``
|
||||
* the value which led to this group (this is the value all contained
|
||||
* records have for the current ``grouped_on`` field name).
|
||||
*
|
||||
* @returns {$.Deferred}
|
||||
*/
|
||||
list: function () {
|
||||
return this.fetch();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -89,12 +213,12 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
|
|||
context: this.context
|
||||
}, callback);
|
||||
},
|
||||
create: function(data, callback) {
|
||||
create: function(data, callback, error_callback) {
|
||||
return this.rpc('/base/dataset/create', {
|
||||
model: this.model,
|
||||
data: data,
|
||||
context: this.context
|
||||
}, callback);
|
||||
}, callback, error_callback);
|
||||
},
|
||||
write: function (id, data, callback) {
|
||||
return this.rpc('/base/dataset/save', {
|
||||
|
@ -118,6 +242,22 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
|
|||
ids: ids,
|
||||
args: args
|
||||
}, callback);
|
||||
},
|
||||
name_search: function (search_str, callback) {
|
||||
search_str = search_str || '';
|
||||
return this.rpc('/base/dataset/name_search', {
|
||||
model: this.model,
|
||||
search_str: search_str,
|
||||
domain: this.domain || [],
|
||||
context: this.context
|
||||
}, callback);
|
||||
},
|
||||
exec_workflow: function (id, signal, callback) {
|
||||
return this.rpc('/base/dataset/exec_workflow', {
|
||||
model: this.model,
|
||||
id: id,
|
||||
signal: signal
|
||||
}, callback);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -137,7 +277,7 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
|
|||
init: function(session, model) {
|
||||
this._super(session, model);
|
||||
this.domain = [];
|
||||
this.sort = [];
|
||||
this._sort = [];
|
||||
this.offset = 0;
|
||||
// subset records[offset:offset+limit]
|
||||
// is it necessary ?
|
||||
|
@ -159,7 +299,7 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
|
|||
fields: fields,
|
||||
domain: this.domain,
|
||||
context: this.context,
|
||||
sort: this.sort,
|
||||
sort: this.sort(),
|
||||
offset: offset,
|
||||
limit: limit
|
||||
}, function (records) {
|
||||
|
@ -170,12 +310,50 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
|
|||
}
|
||||
callback(records);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Reads or changes sort criteria on the dataset.
|
||||
*
|
||||
* If not provided with any argument, serializes the sort criteria to
|
||||
* an SQL-like form usable by OpenERP's ORM.
|
||||
*
|
||||
* If given a field, will set that field as first sorting criteria or,
|
||||
* if the field is already the first sorting criteria, will reverse it.
|
||||
*
|
||||
* @param {String} [field] field to sort on, reverses it (toggle from ASC to DESC) if already the main sort criteria
|
||||
* @param {Boolean} [force_reverse=false] forces inserting the field as DESC
|
||||
* @returns {String|undefined}
|
||||
*/
|
||||
sort: function (field, force_reverse) {
|
||||
if (!field) {
|
||||
return _.map(this._sort, function (criteria) {
|
||||
if (criteria[0] === '-') {
|
||||
return criteria.slice(1) + ' DESC';
|
||||
}
|
||||
return criteria + ' ASC';
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
var reverse = force_reverse || (this._sort[0] === field);
|
||||
this._sort = _.without(this._sort, field, '-' + field);
|
||||
|
||||
this._sort.unshift((reverse ? '-' : '') + field);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.DataSetRelational = openerp.base.DataSet.extend( /** @lends openerp.base.DataSet# */{
|
||||
});
|
||||
|
||||
openerp.base.DataSetMany2Many = openerp.base.DataSetStatic.extend({
|
||||
/* should extend DataSetStatic instead, but list view still does not support it
|
||||
*/
|
||||
|
||||
unlink: function(ids) {
|
||||
// just do nothing
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
|
||||
openerp.base.dates = function(openerp) {
|
||||
|
||||
/**
|
||||
* Converts a string to a Date javascript object using OpenERP's
|
||||
* datetime string format (exemple: '2011-12-01 15:12:35').
|
||||
*
|
||||
* The timezone is assumed to be UTC (standard for OpenERP 6.1)
|
||||
* and will be converted to the browser's timezone.
|
||||
*
|
||||
* @param {String} str A string representing a datetime.
|
||||
* @returns {Date}
|
||||
*/
|
||||
openerp.base.parse_datetime = function(str) {
|
||||
if(!str) {
|
||||
return str;
|
||||
}
|
||||
var regex = /\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/;
|
||||
var res = regex.exec(str);
|
||||
if ( res[0] != str ) {
|
||||
throw "'" + str + "' is not a valid datetime";
|
||||
}
|
||||
var obj = Date.parse(str + " GMT");
|
||||
if (! obj) {
|
||||
throw "'" + str + "' is not a valid datetime";
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a string to a Date javascript object using OpenERP's
|
||||
* date string format (exemple: '2011-12-01').
|
||||
*
|
||||
* @param {String} str A string representing a date.
|
||||
* @returns {Date}
|
||||
*/
|
||||
openerp.base.parse_date = function(str) {
|
||||
if(!str) {
|
||||
return str;
|
||||
}
|
||||
var regex = /\d\d\d\d-\d\d-\d\d/;
|
||||
var res = regex.exec(str);
|
||||
if ( res[0] != str ) {
|
||||
throw "'" + str + "' is not a valid date";
|
||||
}
|
||||
var obj = Date.parse(str);
|
||||
if (! obj) {
|
||||
throw "'" + str + "' is not a valid date";
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a string to a Date javascript object using OpenERP's
|
||||
* time string format (exemple: '15:12:35').
|
||||
*
|
||||
* @param {String} str A string representing a time.
|
||||
* @returns {Date}
|
||||
*/
|
||||
openerp.base.parse_time = function(str) {
|
||||
if(!str) {
|
||||
return str;
|
||||
}
|
||||
var regex = /\d\d:\d\d:\d\d/;
|
||||
var res = regex.exec(str);
|
||||
if ( res[0] != str ) {
|
||||
throw "'" + str + "' is not a valid time";
|
||||
}
|
||||
var obj = Date.parse(str);
|
||||
if (! obj) {
|
||||
throw "'" + str + "' is not a valid time";
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
/*
|
||||
* Just a simple function to add some '0' if an integer it too small.
|
||||
*/
|
||||
var fts = function(str, size) {
|
||||
str = "" + str;
|
||||
var to_add = "";
|
||||
_.each(_.range(size - str.length), function() {
|
||||
to_add = to_add + "0";
|
||||
});
|
||||
return to_add + str;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a Date javascript object to a string using OpenERP's
|
||||
* datetime string format (exemple: '2011-12-01 15:12:35').
|
||||
*
|
||||
* The timezone of the Date object is assumed to be the one of the
|
||||
* browser and it will be converted to UTC (standard for OpenERP 6.1).
|
||||
*
|
||||
* @param {Date} obj
|
||||
* @returns {String} A string representing a datetime.
|
||||
*/
|
||||
openerp.base.format_datetime = function(obj) {
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
return fts(obj.getUTCFullYear(),4) + "-" + fts(obj.getUTCMonth() + 1,2) + "-"
|
||||
+ fts(obj.getUTCDate(),2) + " " + fts(obj.getUTCHours(),2) + ":"
|
||||
+ fts(obj.getUTCMinutes(),2) + ":" + fts(obj.getUTCSeconds(),2);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a Date javascript object to a string using OpenERP's
|
||||
* date string format (exemple: '2011-12-01').
|
||||
*
|
||||
* @param {Date} obj
|
||||
* @returns {String} A string representing a date.
|
||||
*/
|
||||
openerp.base.format_date = function(obj) {
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
return fts(obj.getFullYear(),4) + "-" + fts(obj.getMonth() + 1,2) + "-"
|
||||
+ fts(obj.getDate(),2);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a Date javascript object to a string using OpenERP's
|
||||
* time string format (exemple: '15:12:35').
|
||||
*
|
||||
* @param {Date} obj
|
||||
* @returns {String} A string representing a time.
|
||||
*/
|
||||
openerp.base.format_time = function(obj) {
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
return fts(obj.getHours(),2) + ":" + fts(obj.getMinutes(),2) + ":"
|
||||
+ fts(obj.getSeconds(),2);
|
||||
};
|
||||
|
||||
};
|
|
@ -2,7 +2,7 @@
|
|||
openerp.base.form = function (openerp) {
|
||||
|
||||
openerp.base.views.add('form', 'openerp.base.FormView');
|
||||
openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base.FormView# */{
|
||||
openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{
|
||||
/**
|
||||
* Indicates that this view is not searchable, and thus that no search
|
||||
* view should be displayed (if there is one active).
|
||||
|
@ -143,14 +143,21 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base
|
|||
var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/);
|
||||
if (call) {
|
||||
var method = call[1], args = [];
|
||||
var argument_replacement = {
|
||||
'False' : false,
|
||||
'True' : true,
|
||||
'None' : null
|
||||
}
|
||||
_.each(call[2].split(','), function(a) {
|
||||
var field = _.trim(a);
|
||||
if (self.fields[field]) {
|
||||
if (field in argument_replacement) {
|
||||
args.push(argument_replacement[field]);
|
||||
} else if (self.fields[field]) {
|
||||
var value = self.fields[field].value;
|
||||
args.push(value == null ? false : value);
|
||||
} else {
|
||||
args.push(false);
|
||||
this.log("warning : on_change can't find field " + field, onchange);
|
||||
self.log("warning : on_change can't find field " + field, onchange);
|
||||
}
|
||||
});
|
||||
var ajax = {
|
||||
|
@ -314,7 +321,7 @@ openerp.base.form.compute_domain = function(expr, fields) {
|
|||
var ex = expr[i];
|
||||
if (ex.length == 1) {
|
||||
var top = stack.pop();
|
||||
switch (ex[0]) {
|
||||
switch (ex) {
|
||||
case '|':
|
||||
stack.push(stack.pop() || top);
|
||||
continue;
|
||||
|
@ -325,7 +332,7 @@ openerp.base.form.compute_domain = function(expr, fields) {
|
|||
stack.push(!top);
|
||||
continue;
|
||||
default:
|
||||
throw new Error('Unknown domain operator ' + ex[0]);
|
||||
throw new Error('Unknown domain operator ' + ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -355,17 +362,18 @@ openerp.base.form.compute_domain = function(expr, fields) {
|
|||
stack.push(field >= val);
|
||||
break;
|
||||
case 'in':
|
||||
stack.push(_.indexOf(val, field) > -1);
|
||||
stack.push(_(val).contains(field));
|
||||
break;
|
||||
case 'not in':
|
||||
stack.push(_.indexOf(val, field) == -1);
|
||||
stack.push(!_(val).contains(field));
|
||||
break;
|
||||
default:
|
||||
this.log("Unsupported operator in attrs :", op);
|
||||
}
|
||||
}
|
||||
return _.indexOf(stack, false) == -1;
|
||||
},
|
||||
return _.all(stack);
|
||||
};
|
||||
|
||||
openerp.base.form.Widget = openerp.base.Controller.extend({
|
||||
init: function(view, node) {
|
||||
this.view = view;
|
||||
|
@ -457,16 +465,14 @@ openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
|
|||
}
|
||||
this.add_widget(widget);
|
||||
},
|
||||
add_widget: function(w) {
|
||||
if (!w.invisible) {
|
||||
var current_row = this.table[this.table.length - 1];
|
||||
if (current_row.length && (this.x + w.colspan) > this.columns) {
|
||||
current_row = this.add_row();
|
||||
}
|
||||
current_row.push(w);
|
||||
this.x += w.colspan;
|
||||
add_widget: function(widget) {
|
||||
var current_row = this.table[this.table.length - 1];
|
||||
if (current_row.length && (this.x + widget.colspan) > this.columns) {
|
||||
current_row = this.add_row();
|
||||
}
|
||||
return w;
|
||||
current_row.push(widget);
|
||||
this.x += widget.colspan;
|
||||
return widget;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -532,31 +538,14 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
|
|||
}
|
||||
},
|
||||
on_confirmed: function() {
|
||||
var attrs = this.node.attrs;
|
||||
if (attrs.special) {
|
||||
this.on_button_object({
|
||||
result : { type: 'ir.actions.act_window_close' }
|
||||
var self = this;
|
||||
|
||||
this.view.execute_action(
|
||||
this.node.attrs, this.view.dataset, this.session.action_manager,
|
||||
this.view.datarecord.id, function (result) {
|
||||
self.log("Button returned", result);
|
||||
self.view.reload();
|
||||
});
|
||||
} else {
|
||||
var type = attrs.type || 'workflow';
|
||||
var context = _.extend({}, this.view.dataset.context, attrs.context || {});
|
||||
switch(type) {
|
||||
case 'object':
|
||||
return this.view.dataset.call(attrs.name, [this.view.datarecord.id], [context], this.on_button_object);
|
||||
break;
|
||||
default:
|
||||
this.log(_.sprintf("Unsupported button type : %s", type));
|
||||
}
|
||||
}
|
||||
},
|
||||
on_button_object: function(r) {
|
||||
if (r.result === false) {
|
||||
this.log("Button object returns false");
|
||||
} else if (r.result.constructor == Object) {
|
||||
this.session.action_manager.do_action(r.result);
|
||||
} else {
|
||||
this.view.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -665,7 +654,24 @@ openerp.base.form.FieldChar = openerp.base.form.Field.extend({
|
|||
openerp.base.form.FieldEmail = openerp.base.form.FieldChar.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldEmail";
|
||||
this.validation_regex = /@/;
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.find('button').click(this.on_button_clicked);
|
||||
},
|
||||
on_button_clicked: function() {
|
||||
if (!this.value || this.invalid) {
|
||||
this.notification.warn("E-mail error", "Can't send email to invalid e-mail address");
|
||||
} else {
|
||||
location.href = 'mailto:' + this.value;
|
||||
}
|
||||
},
|
||||
set_value: function(value) {
|
||||
this._super.apply(this, arguments);
|
||||
var show_value = (value != null && value !== false) ? value : '';
|
||||
this.$element.find('a').attr('href', 'mailto:' + show_value);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -675,44 +681,91 @@ openerp.base.form.FieldUrl = openerp.base.form.FieldChar.extend({
|
|||
openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.validation_regex = /^\d+(\.\d+)?$/;
|
||||
this.validation_regex = /^-?\d+(\.\d+)?$/;
|
||||
},
|
||||
set_value: function(value) {
|
||||
this._super.apply(this, arguments);
|
||||
var show_value = (value != null && value !== false) ? value.toFixed(2) : '';
|
||||
if (!value) {
|
||||
// As in GTK client, floats default to 0
|
||||
value = 0;
|
||||
}
|
||||
this._super.apply(this, [value]);
|
||||
var show_value = value.toFixed(2);
|
||||
this.$element.find('input').val(show_value);
|
||||
},
|
||||
set_value_from_ui: function() {
|
||||
this.value = this.$element.find('input').val().replace(/,/g, '.');
|
||||
},
|
||||
validate: function() {
|
||||
this._super.apply(this, arguments);
|
||||
if (!this.invalid) {
|
||||
this.value = Number(this.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.form.FieldDate = openerp.base.form.FieldChar.extend({
|
||||
openerp.base.form.FieldDatetime = openerp.base.form.Field.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldDate";
|
||||
this.validation_regex = /^\d+-\d+-\d+$/;
|
||||
this.jqueryui_object = 'datetimepicker';
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.find('input').change(this.on_ui_change).datepicker({
|
||||
dateFormat: 'yy-mm-dd'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.form.FieldDatetime = openerp.base.form.FieldChar.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldDatetime";
|
||||
this.validation_regex = /^\d+-\d+-\d+( \d+:\d+(:\d+)?)?$/;
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.find('input').change(this.on_ui_change).datetimepicker({
|
||||
this.$element.find('input').change(this.on_ui_change)[this.jqueryui_object]({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
timeFormat: 'hh:mm:ss'
|
||||
});
|
||||
},
|
||||
set_value: function(value) {
|
||||
this._super.apply(this, arguments);
|
||||
if (value == null || value == false) {
|
||||
this.$element.find('input').val('');
|
||||
} else {
|
||||
this.value = this.parse(value);
|
||||
this.$element.find('input')[this.jqueryui_object]('setDate', this.value);
|
||||
}
|
||||
},
|
||||
set_value_from_ui: function() {
|
||||
this.value = this.$element.find('input')[this.jqueryui_object]('getDate') || false;
|
||||
if (this.value) {
|
||||
this.value = this.format(this.value);
|
||||
}
|
||||
},
|
||||
validate: function() {
|
||||
this.invalid = this.required && this.value === false;
|
||||
},
|
||||
parse: openerp.base.parse_datetime,
|
||||
format: openerp.base.format_datetime
|
||||
});
|
||||
|
||||
openerp.base.form.FieldDate = openerp.base.form.FieldDatetime.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.jqueryui_object = 'datepicker';
|
||||
},
|
||||
parse: openerp.base.parse_date,
|
||||
format: openerp.base.format_date
|
||||
});
|
||||
|
||||
openerp.base.form.FieldFloatTime = openerp.base.form.FieldChar.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.validation_regex = /^\d+:\d+$/;
|
||||
},
|
||||
set_value: function(value) {
|
||||
value = value || 0;
|
||||
this._super.apply(this, [value]);
|
||||
var show_value = _.sprintf("%02d:%02d", Math.floor(value), Math.round((value % 1) * 60));
|
||||
this.$element.find('input').val(show_value);
|
||||
},
|
||||
validate: function() {
|
||||
if (typeof(this.value) == "string") {
|
||||
this._super.apply(this, arguments);
|
||||
if (!this.invalid) {
|
||||
var time = this.value.split(':');
|
||||
this.value = parseInt(time[0], 10) + parseInt(time[1], 10) / 60;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -789,6 +842,14 @@ openerp.base.form.FieldProgressBar = openerp.base.form.Field.extend({
|
|||
value: this.value,
|
||||
disabled: this.readonly
|
||||
});
|
||||
},
|
||||
set_value: function(value) {
|
||||
this._super.apply(this, arguments);
|
||||
var show_value = Number(value);
|
||||
if (show_value === NaN) {
|
||||
show_value = 0;
|
||||
}
|
||||
this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(show_value + '%');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -825,10 +886,33 @@ openerp.base.form.FieldSelection = openerp.base.form.Field.extend({
|
|||
}
|
||||
});
|
||||
|
||||
openerp.base.form.FieldMany2OneDatasSet = openerp.base.DataSetStatic.extend({
|
||||
start: function() {
|
||||
},
|
||||
write: function (id, data, callback) {
|
||||
this._super(id, data, callback);
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.form.FieldMany2OneViewManager = openerp.base.ViewManager.extend({
|
||||
init: function(session, element_id, dataset, views) {
|
||||
this._super(session, element_id, dataset, views);
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldMany2One";
|
||||
this.is_field_m2o = true;
|
||||
},
|
||||
start: function() {
|
||||
this.$element = $('#' + this.element_id);
|
||||
this.dataset = new openerp.base.form.FieldMany2OneDatasSet(this.session, this.field.relation);
|
||||
var views = [ [false,"list"], [false,"form"] ];
|
||||
this.viewmanager = new openerp.base.form.FieldMany2OneViewManager(this.view.session, this.element_id, this.dataset, views);
|
||||
new openerp.base.m2o(this.viewmanager, this.$element, this.field.relation, this.dataset, this.session)
|
||||
this.$element.find('input').change(this.on_ui_change);
|
||||
},
|
||||
set_value: function(value) {
|
||||
this._super.apply(this, arguments);
|
||||
|
@ -838,6 +922,16 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
|
|||
this.value = value[0];
|
||||
}
|
||||
this.$element.find('input').val(show_value);
|
||||
this.$element.find('input').attr('m2o_id', this.value);
|
||||
},
|
||||
|
||||
get_value: function() {
|
||||
var val = this.$element.find('input').attr('m2o_id') || this.value
|
||||
return val;
|
||||
},
|
||||
|
||||
on_ui_change: function() {
|
||||
this.touched = this.view.touched = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -897,6 +991,189 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
|
|||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldMany2Many";
|
||||
this.list_id = _.uniqueId("many2many");
|
||||
this.is_started = false;
|
||||
this.is_setted = false;
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.dataset = new openerp.base.DataSetMany2Many(this.session, this.field.relation);
|
||||
this.list_view = new openerp.base.form.Many2ManyListView(undefined, this.view.session,
|
||||
this.list_id, this.dataset, false, {'selected': false, 'addable': 'Add'});
|
||||
var self = this;
|
||||
this.list_view.m2m_field = this;
|
||||
this.list_view.start();
|
||||
var hack = {loaded: false};
|
||||
this.list_view.on_loaded.add_last(function() {
|
||||
if (! hack.loaded) {
|
||||
self.is_started = true;
|
||||
self.check_load();
|
||||
hack.loaded = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
set_value: function(value) {
|
||||
if (value != false) {
|
||||
this.dataset.ids = value;
|
||||
this.dataset.count = value.length;
|
||||
this.is_setted = true;
|
||||
this.check_load();
|
||||
}
|
||||
},
|
||||
get_value: function() {
|
||||
return [[6,false,this.dataset.ids]];
|
||||
},
|
||||
check_load: function() {
|
||||
if(this.is_started && this.is_setted) {
|
||||
this.list_view.do_reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
|
||||
do_delete: function (ids) {
|
||||
this.dataset.ids = _.without.apply(null, [this.dataset.ids].concat(ids));
|
||||
this.dataset.count = this.dataset.ids.length;
|
||||
// there may be a faster way
|
||||
this.do_reload();
|
||||
|
||||
this.m2m_field.on_ui_change();
|
||||
},
|
||||
do_reload: function () {
|
||||
/* Dear xmo, according to your comments, this method's implementation in list view seems
|
||||
* to be a little bit bullshit.
|
||||
* I assume the list view will be changed later, so I hope it will support static datasets.
|
||||
*/
|
||||
return this.rpc('/base/listview/fill', {
|
||||
'model': this.dataset.model,
|
||||
'id': this.view_id,
|
||||
'domain': [["id", "in", this.dataset.ids]],
|
||||
'context': this.dataset.context
|
||||
}, this.do_fill_table);
|
||||
},
|
||||
do_add_record: function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
var pop = new openerp.base.form.Many2XSelectPopup(null, this.m2m_field.view.session);
|
||||
pop.select_element(this.model);
|
||||
var self = this;
|
||||
pop.on_select_element.add(function(element_id) {
|
||||
if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) {
|
||||
self.dataset.ids.push(element_id);
|
||||
self.dataset.count = self.dataset.ids.length;
|
||||
self.do_reload();
|
||||
}
|
||||
pop.stop();
|
||||
});
|
||||
},
|
||||
select_record: function(index) {
|
||||
var id = this.rows[index].data.id.value;
|
||||
if(! id) {
|
||||
return;
|
||||
}
|
||||
var action_manager = this.m2m_field.view.session.action_manager;
|
||||
action_manager.do_action({
|
||||
"res_model": this.dataset.model,
|
||||
"views":[[false,"form"]],
|
||||
"res_id": id,
|
||||
"type":"ir.actions.act_window",
|
||||
"view_type":"form",
|
||||
"view_mode":"form",
|
||||
"target":"new"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.form.Many2XSelectPopup = openerp.base.BaseWidget.extend({
|
||||
identifier_prefix: "many2xselectpopup",
|
||||
template: "Many2XSelectPopup",
|
||||
select_element: function(model, dataset) {
|
||||
this.model = model;
|
||||
this.dataset = dataset
|
||||
var html = this.render();
|
||||
jQuery(html).dialog({title: '',
|
||||
modal: true,
|
||||
minWidth: 800});
|
||||
this.start();
|
||||
},
|
||||
start: function() {
|
||||
this._super();
|
||||
if (!this.dataset) {
|
||||
this.dataset = new openerp.base.DataSetSearch(this.session, this.model);
|
||||
}
|
||||
this.setup_search_view();
|
||||
},
|
||||
setup_search_view: function() {
|
||||
var self = this;
|
||||
if (this.searchview) {
|
||||
this.searchview.stop();
|
||||
}
|
||||
this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search",
|
||||
this.dataset, false, {});
|
||||
this.searchview.on_search.add(function(domains, contexts, groupbys) {
|
||||
self.view_list.do_search.call(
|
||||
self, domains, contexts, groupbys);
|
||||
});
|
||||
this.searchview.on_loaded.add_last(function () {
|
||||
var $buttons = self.searchview.$element.find(".oe_search-view-buttons");
|
||||
$buttons.append(QWeb.render("Many2XSelectPopup.search.buttons"));
|
||||
var $nbutton = $buttons.find(".oe_many2xselectpopup-search-new");
|
||||
$nbutton.click(function() {
|
||||
self.new_object();
|
||||
});
|
||||
var $cbutton = $buttons.find(".oe_many2xselectpopup-search-close");
|
||||
$cbutton.click(function() {
|
||||
self.stop();
|
||||
});
|
||||
self.view_list = new openerp.base.form.Many2XPopupListView( null, self.session,
|
||||
self.element_id + "_view_list", self.dataset, false);
|
||||
self.view_list.popup = self;
|
||||
self.view_list.do_show();
|
||||
self.view_list.start();
|
||||
var tmphack = {"loaded": false};
|
||||
self.view_list.on_loaded.add_last(function() {
|
||||
if ( !tmphack.loaded ) {
|
||||
self.view_list.do_reload();
|
||||
tmphack.loaded = true;
|
||||
};
|
||||
});
|
||||
});
|
||||
this.searchview.start();
|
||||
},
|
||||
on_select_element: function(element_id) {
|
||||
},
|
||||
new_object: function() {
|
||||
var self = this;
|
||||
this.searchview.hide();
|
||||
this.view_list.$element.hide();
|
||||
this.dataset.index = null;
|
||||
this.view_form = new openerp.base.FormView({}, this.session,
|
||||
this.element_id + "_view_form", this.dataset, false);
|
||||
this.view_form.start();
|
||||
this.view_form.on_loaded.add_last(function() {
|
||||
var $buttons = self.view_form.$element.find(".oe_form_buttons");
|
||||
$buttons.html(QWeb.render("Many2XSelectPopup.form.buttons"));
|
||||
var $nbutton = $buttons.find(".oe_many2xselectpopup-form-save");
|
||||
$nbutton.click(function() {
|
||||
self.view_form.do_save();
|
||||
});
|
||||
var $cbutton = $buttons.find(".oe_many2xselectpopup-form-close");
|
||||
$cbutton.click(function() {
|
||||
self.stop();
|
||||
});
|
||||
});
|
||||
this.view_form.on_created.add_last(function(r, success) {
|
||||
if (r.result) {
|
||||
var id = arguments[0].result;
|
||||
self.on_select_element(id);
|
||||
}
|
||||
});
|
||||
this.view_form.do_show();
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.form.Many2XPopupListView = openerp.base.ListView.extend({
|
||||
switch_to_record: function(index) {
|
||||
this.popup.on_select_element(this.dataset.ids[index]);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -907,6 +1184,19 @@ openerp.base.form.FieldReference = openerp.base.form.Field.extend({
|
|||
}
|
||||
});
|
||||
|
||||
openerp.base.form.FieldImage = openerp.base.form.Field.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldImage";
|
||||
},
|
||||
set_value: function(value) {
|
||||
this._super.apply(this, arguments);
|
||||
var url = '/base/formview/image?session_id=' + this.session.session_id + '&model=' +
|
||||
this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name
|
||||
this.$element.find('img').show().attr('src', url);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Registry of form widgets, called by :js:`openerp.base.FormView`
|
||||
*/
|
||||
|
@ -933,7 +1223,8 @@ openerp.base.form.widgets = new openerp.base.Registry({
|
|||
'float' : 'openerp.base.form.FieldFloat',
|
||||
'integer': 'openerp.base.form.FieldFloat',
|
||||
'progressbar': 'openerp.base.form.FieldProgressBar',
|
||||
'float_time': 'openerp.base.form.FieldFloat'
|
||||
'float_time': 'openerp.base.form.FieldFloatTime',
|
||||
'image': 'openerp.base.form.FieldImage'
|
||||
});
|
||||
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
openerp.base.list = function (openerp) {
|
||||
openerp.base.views.add('list', 'openerp.base.ListView');
|
||||
openerp.base.ListView = openerp.base.Controller.extend(
|
||||
/** @lends openerp.base.ListView# */ {
|
||||
openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListView# */ {
|
||||
defaults: {
|
||||
// records can be selected one by one
|
||||
'selectable': true,
|
||||
|
@ -10,9 +9,23 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
// whether the column headers should be displayed
|
||||
'header': true,
|
||||
// display addition button, with that label
|
||||
'addable': "New"
|
||||
'addable': "New",
|
||||
// whether the list view can be sorted, note that once a view has been
|
||||
// sorted it can not be reordered anymore
|
||||
'sortable': true,
|
||||
// whether the view rows can be reordered (via vertical drag & drop)
|
||||
'reorderable': true
|
||||
},
|
||||
/**
|
||||
* Core class for list-type displays.
|
||||
*
|
||||
* As a view, needs a number of view-related parameters to be correctly
|
||||
* instantiated, provides options and overridable methods for behavioral
|
||||
* customization.
|
||||
*
|
||||
* See constructor parameters and method documentations for information on
|
||||
* the default behaviors and possible options for the list view.
|
||||
*
|
||||
* @constructs
|
||||
* @param view_manager
|
||||
* @param session An OpenERP session object
|
||||
|
@ -24,8 +37,13 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
* @param {Boolean} [options.header=true] should the list's header be displayed
|
||||
* @param {Boolean} [options.deletable=true] are the list rows deletable
|
||||
* @param {null|String} [options.addable="New"] should the new-record button be displayed, and what should its label be. Use ``null`` to hide the button.
|
||||
* @param {Boolean} [options.sortable=true] is it possible to sort the table by clicking on column headers
|
||||
* @param {Boolean} [options.reorderable=true] is it possible to reorder list rows
|
||||
*
|
||||
* @borrows openerp.base.ActionExecutor#execute_action as #execute_action
|
||||
*/
|
||||
init: function(view_manager, session, element_id, dataset, view_id, options) {
|
||||
var self = this;
|
||||
this._super(session, element_id);
|
||||
this.view_manager = view_manager;
|
||||
this.dataset = dataset;
|
||||
|
@ -36,12 +54,76 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
this.rows = [];
|
||||
|
||||
this.options = _.extend({}, this.defaults, options || {});
|
||||
|
||||
this.list = new openerp.base.ListView.List({
|
||||
options: this.options,
|
||||
columns: this.columns,
|
||||
rows: this.rows
|
||||
});
|
||||
this.groups = new openerp.base.ListView.Groups({
|
||||
options: this.options,
|
||||
columns: this.columns
|
||||
});
|
||||
$([this.list, this.groups]).bind({
|
||||
'selected': function (e, selection) {
|
||||
self.$element.find('#oe-list-delete')
|
||||
.toggle(!!selection.length);
|
||||
},
|
||||
'deleted': function (e, ids) {
|
||||
self.do_delete(ids);
|
||||
},
|
||||
'action': function (e, action_name, id) {
|
||||
var action = _.detect(self.columns, function (field) {
|
||||
return field.name === action_name;
|
||||
});
|
||||
if (!action) { return; }
|
||||
// TODO: not supposed to reload everything, I think
|
||||
self.execute_action(
|
||||
action, self.dataset, self.session.action_manager,
|
||||
id, self.do_reload);
|
||||
},
|
||||
'row_link': function (e, index) {
|
||||
self.select_record(index);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
/**
|
||||
* View startup method, the default behavior is to set the ``oe-listview``
|
||||
* class on its root element and to perform an RPC load call.
|
||||
*
|
||||
* @returns {$.Deferred} loading promise
|
||||
*/
|
||||
start: function() {
|
||||
this.$element.addClass('oe-listview');
|
||||
return this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id,
|
||||
toolbar:!!this.view_manager.sidebar}, this.on_loaded);
|
||||
return this.rpc("/base/listview/load", {
|
||||
model: this.model,
|
||||
view_id: this.view_id,
|
||||
toolbar: this.view_manager ? !!this.view_manager.sidebar : false
|
||||
}, this.on_loaded);
|
||||
},
|
||||
/**
|
||||
* Called after loading the list view's description, sets up such things
|
||||
* as the view table's columns, renders the table itself and hooks up the
|
||||
* various table-level and row-level DOM events (action buttons, deletion
|
||||
* buttons, selection of records, [New] button, selection of a given
|
||||
* record, ...)
|
||||
*
|
||||
* Sets up the following:
|
||||
*
|
||||
* * Processes arch and fields to generate a complete field descriptor for each field
|
||||
* * Create the table itself and allocate visible columns
|
||||
* * Hook in the top-level (header) [New|Add] and [Delete] button
|
||||
* * Sets up showing/hiding the top-level [Delete] button based on records being selected or not
|
||||
* * Sets up event handlers for action buttons and per-row deletion button
|
||||
* * Hooks global callback for clicking on a row
|
||||
* * Sets up its sidebar, if any
|
||||
*
|
||||
* @param {Object} data wrapped fields_view_get result
|
||||
* @param {Object} data.fields_view fields_view_get result (processed)
|
||||
* @param {Object} data.fields_view.fields mapping of fields for the current model
|
||||
* @param {Object} data.fields_view.arch current list view descriptor
|
||||
*/
|
||||
on_loaded: function(data) {
|
||||
var self = this;
|
||||
this.fields_view = data.fields_view;
|
||||
|
@ -50,7 +132,9 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
|
||||
var fields = this.fields_view.fields;
|
||||
var domain_computer = openerp.base.form.compute_domain;
|
||||
this.columns = _(this.fields_view.arch.children).chain()
|
||||
|
||||
this.columns.splice(0, this.columns.length);
|
||||
this.columns.push.apply(this.columns, _(this.fields_view.arch.children).chain()
|
||||
.map(function (field) {
|
||||
var name = field.attrs.name;
|
||||
var column = _.extend({id: name, tag: field.tag},
|
||||
|
@ -69,11 +153,14 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
column.attrs_for = function () { return {}; };
|
||||
}
|
||||
return column;
|
||||
}).value();
|
||||
}).value());
|
||||
|
||||
this.visible_columns = _.filter(this.columns, function (column) {
|
||||
return column.invisible !== '1';
|
||||
});
|
||||
|
||||
if (!this.fields_view.sorted) { this.fields_view.sorted = {}; }
|
||||
|
||||
this.$element.html(QWeb.render("ListView", this));
|
||||
|
||||
// Head hook
|
||||
|
@ -81,58 +168,40 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
this.$element.find('#oe-list-delete')
|
||||
.hide()
|
||||
.click(this.do_delete_selected);
|
||||
this.$element.find('thead').delegate('th[data-id]', 'click', function (e) {
|
||||
e.stopPropagation();
|
||||
|
||||
self.dataset.sort($(this).data('id'));
|
||||
|
||||
// TODO: should only reload content (and set the right column to a sorted display state)
|
||||
self.do_reload();
|
||||
});
|
||||
|
||||
var $table = this.$element.find('table');
|
||||
// Cell events
|
||||
$table.delegate(
|
||||
'th.oe-record-selector', 'click', function (e) {
|
||||
// TODO: ~linear performances, would a simple counter work?
|
||||
if ($table.find('th.oe-record-selector input:checked').length) {
|
||||
$table.find('#oe-list-delete').show();
|
||||
} else {
|
||||
$table.find('#oe-list-delete').hide();
|
||||
}
|
||||
// A click in the selection cell should not activate the
|
||||
// linking feature
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
$table.delegate(
|
||||
'td.oe-field-cell button', 'click', function (e) {
|
||||
var $cell = $(e.currentTarget).closest('td');
|
||||
var col_index = $cell.prevAll('td').length;
|
||||
var field = self.visible_columns[col_index];
|
||||
var action = field.name;
|
||||
|
||||
var $row = $cell.parent('tr');
|
||||
var row = self.rows[$row.prevAll().length];
|
||||
|
||||
var context = _.extend(
|
||||
{}, self.dataset.context, field.context || {});
|
||||
self.dataset.call(action, [row.data.id.value], [context],
|
||||
self.do_reload);
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
$table.delegate(
|
||||
'td.oe-record-delete button', 'click', this.do_delete);
|
||||
|
||||
// Global rows handlers
|
||||
$table.delegate(
|
||||
'tr', 'click', this.on_select_row);
|
||||
this.list.move_to($table);
|
||||
|
||||
// sidebar stuff
|
||||
if (this.view_manager.sidebar) {
|
||||
if (this.view_manager && this.view_manager.sidebar) {
|
||||
this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Fills the table with the provided records after emptying it
|
||||
*
|
||||
* @param {Array} records the records to fill the list view with
|
||||
* @returns {Promise} promise to the end of view rendering (list views are asynchronously filled for improved responsiveness)
|
||||
* TODO: should also re-load the table itself, as e.g. columns may have changed
|
||||
*
|
||||
* @param {Object} result filling result
|
||||
* @param {Array} [result.view] the new view (wrapped fields_view_get result)
|
||||
* @param {Array} result.records records the records to fill the list view with
|
||||
*/
|
||||
do_fill_table: function(records) {
|
||||
var $table = this.$element.find('table');
|
||||
this.rows = records;
|
||||
do_fill_table: function(result) {
|
||||
if (result.view) {
|
||||
this.on_loaded({fields_view: result.view});
|
||||
}
|
||||
var records = result.records;
|
||||
|
||||
this.rows.splice(0, this.rows.length);
|
||||
this.rows.push.apply(this.rows, records);
|
||||
|
||||
// Keep current selected record, if it's still in our new search
|
||||
var current_record_id = this.dataset.ids[this.dataset.index];
|
||||
|
@ -146,78 +215,36 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
|
||||
this.dataset.count = this.dataset.ids.length;
|
||||
var results = this.rows.length;
|
||||
$table.find('.oe-pager-last').text(results);
|
||||
$table.find('.oe-pager-total').text(results);
|
||||
this.$element.find('table')
|
||||
.find('.oe-pager-last').text(results).end()
|
||||
.find('.oe-pager-total').text(results);
|
||||
|
||||
|
||||
// remove all data lines
|
||||
var $old_body = $table.find('tbody');
|
||||
|
||||
// add new content
|
||||
var columns = this.columns,
|
||||
rows = this.rows,
|
||||
options = this.options;
|
||||
|
||||
// Paginate by groups of 50 for rendering
|
||||
var PAGE_SIZE = 50,
|
||||
bodies_count = Math.ceil(this.rows.length / PAGE_SIZE),
|
||||
body = 0,
|
||||
$body = $('<tbody class="ui-widget-content">').appendTo($table);
|
||||
|
||||
var rendered = $.Deferred();
|
||||
var render_body = function () {
|
||||
setTimeout(function () {
|
||||
$body.append(
|
||||
QWeb.render("ListView.rows", {
|
||||
columns: columns,
|
||||
rows: rows.slice(body*PAGE_SIZE, (body+1)*PAGE_SIZE),
|
||||
options: options
|
||||
}));
|
||||
++body;
|
||||
if (body < bodies_count) {
|
||||
render_body();
|
||||
} else {
|
||||
rendered.resolve();
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
render_body();
|
||||
|
||||
return rendered.promise().then(function () {
|
||||
$old_body.remove();
|
||||
});
|
||||
this.list.refresh();
|
||||
},
|
||||
/**
|
||||
* Asks the view manager to switch to a different view, using the provided
|
||||
* record index (within the current dataset).
|
||||
* Used to handle a click on a table row, if no other handler caught the
|
||||
* event.
|
||||
*
|
||||
* The default implementation asks the list view's view manager to switch
|
||||
* to a different view (by calling
|
||||
* :js:func:`~openerp.base.ViewManager.on_mode_switch`), using the
|
||||
* provided record index (within the current list view's dataset).
|
||||
*
|
||||
* If the index is null, ``switch_to_record`` asks for the creation of a
|
||||
* new record.
|
||||
*
|
||||
* @param {Number|null} index the record index (in the current dataset) to switch to
|
||||
* @param {String} [view="form"] the view to switch to
|
||||
* @param {String} [view="form"] the view type to switch to
|
||||
*/
|
||||
switch_to_record:function (index, view) {
|
||||
select_record:function (index, view) {
|
||||
view = view || 'form';
|
||||
this.dataset.index = index;
|
||||
_.delay(_.bind(function () {
|
||||
this.view_manager.on_mode_switch(view);
|
||||
if(this.view_manager) {
|
||||
this.view_manager.on_mode_switch(view);
|
||||
}
|
||||
}, this));
|
||||
},
|
||||
on_select_row: function (event) {
|
||||
var $target = $(event.currentTarget);
|
||||
if (!$target.parent().is('tbody')) {
|
||||
return;
|
||||
}
|
||||
// count number of preceding siblings to line clicked
|
||||
var row = this.rows[$target.prevAll().length];
|
||||
|
||||
var index = _.indexOf(this.dataset.ids, row.data.id.value);
|
||||
if (index == undefined || index === -1) {
|
||||
return;
|
||||
}
|
||||
this.switch_to_record(index);
|
||||
},
|
||||
do_show: function () {
|
||||
this.$element.show();
|
||||
if (this.hidden) {
|
||||
|
@ -243,9 +270,19 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
'model': this.dataset.model,
|
||||
'id': this.view_id,
|
||||
'context': this.dataset.context,
|
||||
'domain': this.dataset.domain
|
||||
'domain': this.dataset.domain,
|
||||
'sort': this.dataset.sort && this.dataset.sort()
|
||||
}, this.do_fill_table);
|
||||
},
|
||||
/**
|
||||
* Event handler for a search, asks for the computation/folding of domains
|
||||
* and contexts (and group-by), then reloads the view's content.
|
||||
*
|
||||
* @param {Array} domains a sequence of literal and non-literal domains
|
||||
* @param {Array} contexts a sequence of literal and non-literal contexts
|
||||
* @param {Array} groupbys a sequence of literal and non-literal group-by contexts
|
||||
* @returns {$.Deferred} fold request evaluation promise
|
||||
*/
|
||||
do_search: function (domains, contexts, groupbys) {
|
||||
var self = this;
|
||||
return this.rpc('/base/session/eval_domain_and_context', {
|
||||
|
@ -256,6 +293,14 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
// TODO: handle non-empty results.group_by with read_group
|
||||
self.dataset.context = results.context;
|
||||
self.dataset.domain = results.domain;
|
||||
if (results.group_by.length) {
|
||||
self.groups.datagroup = new openerp.base.DataGroup(
|
||||
self.session, self.dataset.model,
|
||||
results.domain, results.context,
|
||||
results.group_by);
|
||||
self.$element.html(self.groups.render());
|
||||
return;
|
||||
}
|
||||
return self.do_reload();
|
||||
});
|
||||
},
|
||||
|
@ -266,13 +311,35 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
/**
|
||||
* Handles the signal to delete a line from the DOM
|
||||
*
|
||||
* @param e
|
||||
* @param {Array} ids the id of the object to delete
|
||||
*/
|
||||
do_delete: function (e) {
|
||||
// don't link to forms
|
||||
e.stopImmediatePropagation();
|
||||
this.dataset.unlink(
|
||||
[this.rows[$(e.currentTarget).closest('tr').prevAll().length].data.id.value]);
|
||||
do_delete: function (ids) {
|
||||
if (!ids.length) {
|
||||
return;
|
||||
}
|
||||
var self = this;
|
||||
return $.when(this.dataset.unlink(ids)).then(function () {
|
||||
_(self.rows).chain()
|
||||
.map(function (row, index) {
|
||||
return {
|
||||
index: index,
|
||||
id: row.data.id.value
|
||||
};})
|
||||
.filter(function (record) {
|
||||
return _.contains(ids, record.id);
|
||||
})
|
||||
.sort(function (a, b) {
|
||||
// sort in reverse index order, so we delete from the end
|
||||
// and don't blow up the following indexes (leading to
|
||||
// removing the wrong records from the visible list)
|
||||
return b.index - a.index;
|
||||
})
|
||||
.each(function (record) {
|
||||
self.rows.splice(record.index, 1);
|
||||
});
|
||||
// TODO only refresh modified rows
|
||||
self.list.refresh();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Handles signal for the addition of a new record (can be a creation,
|
||||
|
@ -282,36 +349,177 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
*/
|
||||
do_add_record: function () {
|
||||
this.notification.notify('Add', "New record");
|
||||
this.switch_to_record(null);
|
||||
this.select_record(null);
|
||||
},
|
||||
/**
|
||||
* Handles deletion of all selected lines
|
||||
*/
|
||||
do_delete_selected: function () {
|
||||
var selection = this.get_selection();
|
||||
if (selection.length) {
|
||||
this.dataset.unlink(selection);
|
||||
}
|
||||
this.do_delete(
|
||||
this.list.get_selection());
|
||||
}
|
||||
// TODO: implement reorder (drag and drop rows)
|
||||
});
|
||||
openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List# */{
|
||||
/**
|
||||
* List display for the ListView, handles basic DOM events and transforms
|
||||
* them in the relevant higher-level events, to which the list view (or
|
||||
* other consumers) can subscribe.
|
||||
*
|
||||
* Events on this object are registered via jQuery.
|
||||
*
|
||||
* Available events:
|
||||
*
|
||||
* `selected`
|
||||
* Triggered when a row is selected (using check boxes), provides an
|
||||
* array of ids of all the selected records.
|
||||
* `deleted`
|
||||
* Triggered when deletion buttons are hit, provide an array of ids of
|
||||
* all the records being marked for suppression.
|
||||
* `action`
|
||||
* Triggered when an action button is clicked, provides two parameters:
|
||||
*
|
||||
* * The name of the action to execute (as a string)
|
||||
* * The id of the record to execute the action on
|
||||
* `row_link`
|
||||
* Triggered when a row of the table is clicked, provides the index (in
|
||||
* the rows array) and id of the selected record to the handle function.
|
||||
*
|
||||
* @constructs
|
||||
* @param {Object} opts display options, identical to those of :js:class:`openerp.base.ListView`
|
||||
*/
|
||||
init: function (opts) {
|
||||
var self = this;
|
||||
// columns, rows, options
|
||||
|
||||
this.options = opts.options;
|
||||
this.columns = opts.columns;
|
||||
this.rows = opts.rows;
|
||||
|
||||
this.$_element = $('<tbody class="ui-widget-content">')
|
||||
.appendTo(document.body)
|
||||
.delegate('th.oe-record-selector', 'click', function (e) {
|
||||
e.stopPropagation();
|
||||
$(self).trigger('selected', [self.get_selection()]);
|
||||
})
|
||||
.delegate('td.oe-record-delete button', 'click', function (e) {
|
||||
e.stopPropagation();
|
||||
var $row = $(e.target).closest('tr');
|
||||
$(self).trigger('deleted', [[self.row_id($row)]]);
|
||||
})
|
||||
.delegate('td.oe-field-cell button', 'click', function (e) {
|
||||
e.stopPropagation();
|
||||
var $target = $(e.currentTarget),
|
||||
field = $target.closest('td').data('field'),
|
||||
record_id = self.row_id($target.closest('tr'));
|
||||
|
||||
$(self).trigger('action', [field, record_id]);
|
||||
})
|
||||
.delegate('tr', 'click', function (e) {
|
||||
e.stopPropagation();
|
||||
$(self).trigger(
|
||||
'row_link',
|
||||
[self.row_position(e.currentTarget),
|
||||
self.row_id(e.currentTarget)]);
|
||||
});
|
||||
},
|
||||
move_to: function (element) {
|
||||
this.$current = this.$_element.clone(true).appendTo(element);
|
||||
this.render();
|
||||
return this;
|
||||
},
|
||||
render: function () {
|
||||
this.$current.empty().append($(QWeb.render('ListView.rows', this)));
|
||||
return this;
|
||||
},
|
||||
refresh: function () {
|
||||
this.render();
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Gets the ids of all currently selected records, if any
|
||||
* @returns a list of ids, empty if no record is selected (or the list view is not selectable
|
||||
* @returns {Array} empty if no record is selected (or the list view is not selectable)
|
||||
*/
|
||||
get_selection: function () {
|
||||
if (!this.options.selectable) {
|
||||
return [];
|
||||
}
|
||||
var rows = this.rows;
|
||||
return this.$element.find('th.oe-record-selector input:checked')
|
||||
return this.$current.find('th.oe-record-selector input:checked')
|
||||
.closest('tr').map(function () {
|
||||
return rows[$(this).prevAll().length].data.id.value;
|
||||
}).get();
|
||||
},
|
||||
/**
|
||||
* Returns the index of the row in the list of rows.
|
||||
*
|
||||
* @param {Object} row the selected row
|
||||
* @returns {Number} the position of the row in this.rows
|
||||
*/
|
||||
row_position: function (row) {
|
||||
return $(row).prevAll().length;
|
||||
},
|
||||
/**
|
||||
* Returns the identifier of the object displayed in the provided table
|
||||
* row
|
||||
*
|
||||
* @param {Object} row the selected table row
|
||||
* @returns {Number|String} the identifier of the row's object
|
||||
*/
|
||||
row_id: function (row) {
|
||||
return this.rows[this.row_position(row)].data.id.value;
|
||||
}
|
||||
// drag and drop
|
||||
// editable?
|
||||
});
|
||||
openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Groups# */{
|
||||
/**
|
||||
* Grouped display for the ListView. Handles basic DOM events and interacts
|
||||
* with the :js:class:`~openerp.base.DataGroup` bound to it.
|
||||
*
|
||||
* Provides events similar to those of
|
||||
* :js:class:`~openerp.base.ListView.List`
|
||||
*/
|
||||
init: function (opts) {
|
||||
this.options = opts.options;
|
||||
this.columns = opts.columns;
|
||||
this.datagroup = {};
|
||||
},
|
||||
make_level: function (datagroup) {
|
||||
var self = this, $root = $('<dl>');
|
||||
datagroup.list().then(function (list) {
|
||||
_(list).each(function (group, index) {
|
||||
var $title = $('<dt>')
|
||||
.text(group.grouped_on + ': ' + group.value + ' (' + group.length + ')')
|
||||
.appendTo($root);
|
||||
$title.click(function () {
|
||||
datagroup.get(index, function (new_dataset) {
|
||||
var $content = $('<ul>').appendTo(
|
||||
$('<dd>').insertAfter($title));
|
||||
new_dataset.read_slice([], null, null, function (records) {
|
||||
_(records).each(function (record) {
|
||||
$('<li>')
|
||||
.appendTo($content)
|
||||
.text(_(record).map(function (value, key) {
|
||||
return key + ': ' + value;
|
||||
}).join(', '));
|
||||
});
|
||||
});
|
||||
}, function (new_datagroup) {
|
||||
console.log(new_datagroup);
|
||||
$('<dd>')
|
||||
.insertAfter($title)
|
||||
.append(self.make_level(new_datagroup));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return $root;
|
||||
},
|
||||
render: function () {
|
||||
return this.make_level(this.datagroup);
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.TreeView = openerp.base.Controller.extend({
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
openerp.base.m2o = function(openerp){
|
||||
|
||||
openerp.base.m2o = openerp.base.Controller.extend({
|
||||
init: function(view_manager, element_id, model, dataset, session){
|
||||
this._super(element_id, model, dataset, session);
|
||||
|
||||
this.view_manager = view_manager
|
||||
this.session = session;
|
||||
this.element = element_id.find('input');
|
||||
this.button = element_id.find('div');
|
||||
this.dataset = dataset;
|
||||
var cache = {};
|
||||
var lastXhr;
|
||||
this.relation = model;
|
||||
this.result_ids = [];
|
||||
var self = this;
|
||||
|
||||
var $input = this.element.autocomplete({
|
||||
|
||||
source: function(request, response){
|
||||
var search_val = request.term;
|
||||
if (search_val in cache) {
|
||||
response(cache[search_val]);
|
||||
return;
|
||||
}
|
||||
//pass request to server
|
||||
lastXhr = self.dataset.name_search(search_val, function(obj, status, xhr){
|
||||
var result = obj.result;
|
||||
var values = [];
|
||||
if (!result.length) {
|
||||
values.push({'value': 'Create...', id: 'create', orig_val: ''});
|
||||
}
|
||||
|
||||
$.each(result, function(i, val){
|
||||
values.push({
|
||||
value: val[1],
|
||||
id: val[0],
|
||||
orig_val: val[1]
|
||||
});
|
||||
self.result_ids.push(result[i][0]);
|
||||
});
|
||||
|
||||
if (values.length > 7) {
|
||||
values = values.slice(0, 7);
|
||||
}
|
||||
values.push({'value': 'More...', id: 'more', orig_val:''});
|
||||
cache[search_val] = values;
|
||||
response(values);
|
||||
});
|
||||
return;
|
||||
},
|
||||
|
||||
select: function(event, ui){
|
||||
ui.item.value = ui.item.orig_val? ui.item.orig_val : self.element.data( "autocomplete" ).term;
|
||||
if (ui.item.id == 'more') {
|
||||
self.dataset.ids = self.result_ids;
|
||||
self.dataset.count = self.dataset.ids.length;
|
||||
self.dataset.domain = self.result_ids.length ? [["id", "in", self.dataset.ids]] : []
|
||||
self.element.val('');
|
||||
var pop = new openerp.base.form.Many2XSelectPopup(null, self.session);
|
||||
pop.select_element(self.relation, self.dataset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ui.item.id == 'create') {
|
||||
var val = self.element.val();
|
||||
self.dataset.create({'name': ui.item.value},
|
||||
function(r){}, function(r){
|
||||
var element_id = _.uniqueId("act_window_dialog");
|
||||
var dialog = jQuery('<div>',
|
||||
{'id': element_id
|
||||
}).dialog({
|
||||
modal: true,
|
||||
minWidth: 800
|
||||
});
|
||||
self.element.val('');
|
||||
var event_form = new openerp.base.FormView(self.view_manager, self.session, element_id, self.dataset, false);
|
||||
event_form.start();
|
||||
});
|
||||
$input.val(self.element.data( "autocomplete" ).term);
|
||||
return true;
|
||||
}
|
||||
self.element.attr('m2o_id', ui.item.id);
|
||||
},
|
||||
|
||||
minLength: 0,
|
||||
|
||||
focus: function(event, ui) {
|
||||
if (ui.item.id == ('create')) {
|
||||
return true;
|
||||
}
|
||||
ui.item.value = self.element.data("autocomplete").term.length ? self.element.val() + '[' + ui.item.orig_val.substring(self.element.data("autocomplete").term.length) + ']' : this.lastSearch;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
this.button.button({
|
||||
icons: {
|
||||
primary: "ui-icon-triangle-1-s"},
|
||||
text: false
|
||||
})
|
||||
.click(function() {
|
||||
// close if already visible
|
||||
if ($input.autocomplete("widget").is(":visible")) {
|
||||
$input.autocomplete( "close" );
|
||||
return;
|
||||
}
|
||||
$(this).blur();
|
||||
$input.autocomplete("search", "" );
|
||||
$input.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -121,7 +121,10 @@ openerp.base.SearchView = openerp.base.Controller.extend({
|
|||
'lines': lines,
|
||||
'defaults': this.defaults
|
||||
});
|
||||
this.$element.html(render);
|
||||
// We don't understand why the following commented line does not work in Chrome but
|
||||
// the non-commented line does. As far as we investigated, only God knows.
|
||||
//this.$element.html(render);
|
||||
jQuery(render).appendTo(this.$element);
|
||||
|
||||
var f = this.$element.find('form');
|
||||
this.$element.find('form')
|
||||
|
@ -146,7 +149,6 @@ openerp.base.SearchView = openerp.base.Controller.extend({
|
|||
do_search: function (e) {
|
||||
if (e && e.preventDefault) { e.preventDefault(); }
|
||||
|
||||
//debugger;
|
||||
var domains = [], contexts = [];
|
||||
|
||||
var errors = [];
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*---------------------------------------------------------
|
||||
* OpenERP base library
|
||||
*---------------------------------------------------------*/
|
||||
|
||||
openerp.base.view_tree = function(openerp) {
|
||||
|
||||
openerp.base.views.add('tree', 'openerp.base.TreeView');
|
||||
openerp.base.TreeView = openerp.base.Controller.extend({
|
||||
/**
|
||||
* Genuine tree view (the one displayed as a tree, not the list)
|
||||
*/
|
||||
start: function () {
|
||||
this._super();
|
||||
this.$element.append('Tree view');
|
||||
},
|
||||
do_show: function () {
|
||||
this.$element.show();
|
||||
},
|
||||
do_hide: function () {
|
||||
this.$element.hide();
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
|
|
@ -19,14 +19,21 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
|
|||
* Supported actions: act_window
|
||||
*/
|
||||
do_action: function(action) {
|
||||
this.log(action);
|
||||
var self = this;
|
||||
action.flags = _.extend({
|
||||
sidebar : true,
|
||||
search_view : true,
|
||||
new_window : false,
|
||||
views_switcher : true,
|
||||
toolbar : true,
|
||||
pager : true
|
||||
}, action.flags || {});
|
||||
// instantiate the right controllers by understanding the action
|
||||
switch (action.type) {
|
||||
case 'ir.actions.act_window':
|
||||
if (action.target == 'new') {
|
||||
var element_id = _.uniqueId("act_window_dialog");
|
||||
var dialog = $('<div id="'+element_id+'"></div>');
|
||||
var dialog = $('<div id="' + element_id + '"></div>');
|
||||
dialog.dialog({
|
||||
title: action.name,
|
||||
modal: true,
|
||||
|
@ -36,14 +43,21 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
|
|||
// When dialog is closed with ESC key or close manually, branch to act_window_close logic
|
||||
self.do_action({ type: 'ir.actions.act_window_close' });
|
||||
});
|
||||
var viewmanager = new openerp.base.ViewManagerAction(this.session ,element_id, action, false);
|
||||
var viewmanager = new openerp.base.ViewManagerAction(this.session, element_id, action);
|
||||
viewmanager.start();
|
||||
this.dialog_stack.push(viewmanager);
|
||||
} else if (action.target == "current") {
|
||||
} else if (action.flags.new_window) {
|
||||
action.flags.new_window = false;
|
||||
this.rpc("/base/session/save_session_action", { the_action : action}, function(key) {
|
||||
var url = window.location.protocol + "//" + window.location.host +
|
||||
window.location.pathname + "?" + jQuery.param({ s_action : "" + key });
|
||||
window.open(url);
|
||||
});
|
||||
} else {
|
||||
if (this.viewmanager) {
|
||||
this.viewmanager.stop();
|
||||
}
|
||||
this.viewmanager = new openerp.base.ViewManagerAction(this.session,this.element_id, action, true);
|
||||
this.viewmanager = new openerp.base.ViewManagerAction(this.session, this.element_id, action);
|
||||
this.viewmanager.start();
|
||||
}
|
||||
break;
|
||||
|
@ -58,11 +72,6 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Registry for all the main views
|
||||
*/
|
||||
openerp.base.views = new openerp.base.Registry();
|
||||
|
||||
openerp.base.ViewManager = openerp.base.Controller.extend({
|
||||
init: function(session, element_id, dataset, views) {
|
||||
this._super(session, element_id);
|
||||
|
@ -72,6 +81,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
|
|||
this.active_view = null;
|
||||
this.views_src = views;
|
||||
this.views = {};
|
||||
this.flags = this.flags || {};
|
||||
},
|
||||
/**
|
||||
* @returns {jQuery.Deferred} initial view loading promise
|
||||
|
@ -86,6 +96,9 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
|
|||
_.each(this.views_src, function(view) {
|
||||
self.views[view[1]] = { view_id: view[0], controller: null };
|
||||
});
|
||||
if (this.flags.views_switcher === false) {
|
||||
this.$element.find('.oe_vm_switch').hide();
|
||||
}
|
||||
// switch to the first one in sequence
|
||||
return this.on_mode_switch(this.views_src[0][1]);
|
||||
},
|
||||
|
@ -109,7 +122,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
|
|||
this.views[view_type].controller = controller;
|
||||
}
|
||||
|
||||
if(this.searchview) {
|
||||
if (this.searchview) {
|
||||
if (view.controller.searchable === false) {
|
||||
this.searchview.hide();
|
||||
} else {
|
||||
|
@ -145,6 +158,9 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
|
|||
this.searchview.stop();
|
||||
}
|
||||
this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search", this.dataset, view_id, search_defaults);
|
||||
if (this.flags.search_view === false) {
|
||||
this.searchview.hide();
|
||||
}
|
||||
this.searchview.on_search.add(function(domains, contexts, groupbys) {
|
||||
self.views[self.active_view].controller.do_search.call(
|
||||
self, domains.concat(self.domains()),
|
||||
|
@ -183,13 +199,21 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
|
|||
});
|
||||
|
||||
openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
|
||||
init: function(session, element_id, action, sidebar) {
|
||||
var dataset = new openerp.base.DataSetSearch(session, action.res_model);
|
||||
init: function(session, element_id, action) {
|
||||
var dataset;
|
||||
if(!action.res_id) {
|
||||
dataset = new openerp.base.DataSetSearch(session, action.res_model);
|
||||
} else {
|
||||
dataset = new openerp.base.DataSetStatic(session, action.res_model);
|
||||
dataset.ids = [action.res_id];
|
||||
dataset.count = 1;
|
||||
}
|
||||
this._super(session, element_id, dataset, action.views);
|
||||
this.action = action;
|
||||
this.sidebar = sidebar;
|
||||
if (sidebar)
|
||||
this.flags = this.action.flags || {};
|
||||
if (this.flags.sidebar) {
|
||||
this.sidebar = new openerp.base.Sidebar(null, this);
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
var inital_view_loaded = this._super();
|
||||
|
@ -282,7 +306,10 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({
|
|||
var i = $this.attr("data-i");
|
||||
var j = $this.attr("data-j");
|
||||
var action = self.sections[i].elements[j];
|
||||
(new openerp.base.ExternalActionManager(self.view_manager.session, null)) .handle_action(action);
|
||||
action.flags = {
|
||||
new_window : true
|
||||
}
|
||||
self.session.action_manager.do_action(action);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
@ -293,89 +320,51 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({
|
|||
}
|
||||
});
|
||||
|
||||
openerp.base.ExternalActionManager = openerp.base.Controller.extend({
|
||||
handle_action: function(action) {
|
||||
if(action.type=="ir.actions.act_window") {
|
||||
if(action.target=="new") {
|
||||
var element_id = _.uniqueId("act_window_dialog");
|
||||
var dialog = $('<div id="'+element_id+'"></div>');
|
||||
dialog.dialog({
|
||||
title: action.name
|
||||
});
|
||||
var viewmanager = new openerp.base.ViewManagerAction(this.session ,element_id, action, false);
|
||||
viewmanager.start();
|
||||
} else if (action.target == "current") {
|
||||
this.rpc("/base/session/save_session_action", {the_action:action}, function(key) {
|
||||
var url = window.location.protocol + "//" + window.location.host +
|
||||
window.location.pathname + "?" + jQuery.param({s_action:""+key});
|
||||
window.open(url);
|
||||
});
|
||||
openerp.base.View = openerp.base.Controller.extend({
|
||||
/**
|
||||
* Fetches and executes the action identified by ``action_data``.
|
||||
*
|
||||
* @param {Object} action_data the action descriptor data
|
||||
* @param {String} action_data.name the action name, used to uniquely identify the action to find and execute it
|
||||
* @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``)
|
||||
* @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'``
|
||||
* @param {Object} [action_data.context=null] additional action context, to add to the current context
|
||||
* @param {openerp.base.DataSet} dataset a dataset object used to communicate with the server
|
||||
* @param {openerp.base.ActionManager} action_manager object able to actually execute the action, if any is fetched
|
||||
* @param {Number} [record_id] the identifier of the object on which the action is to be applied
|
||||
* @param {Function} on_no_action callback to execute if the action does not generate any result (no new action)
|
||||
*/
|
||||
execute_action: function (action_data, dataset, action_manager, record_id, on_no_action) {
|
||||
var handler = function (r) {
|
||||
if (r.result && r.result.constructor == Object) {
|
||||
action_manager.do_action(r.result);
|
||||
} else {
|
||||
on_no_action(r.result);
|
||||
}
|
||||
};
|
||||
|
||||
if (action_data.special) {
|
||||
handler({
|
||||
result : { type: 'ir.actions.act_window_close' }
|
||||
});
|
||||
} else {
|
||||
var context = _.extend({}, dataset.context, action_data.context || {});
|
||||
switch(action_data.type) {
|
||||
case 'object':
|
||||
return dataset.call(action_data.name, [record_id], [context], handler);
|
||||
case 'action':
|
||||
return this.rpc('/base/action/load', { action_id: parseInt(action_data.name, 10) }, handler);
|
||||
default:
|
||||
return dataset.exec_workflow(record_id, action_data.name, handler);
|
||||
}
|
||||
}
|
||||
// TODO: show an error like "not implemented" here
|
||||
// since we don't currently have any way to handle errors do you have any better idea
|
||||
// than using todos?
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.views.add('calendar', 'openerp.base.CalendarView');
|
||||
openerp.base.CalendarView = openerp.base.Controller.extend({
|
||||
start: function () {
|
||||
this._super();
|
||||
this.$element.append('Calendar view');
|
||||
},
|
||||
do_show: function () {
|
||||
this.$element.show();
|
||||
},
|
||||
do_hide: function () {
|
||||
this.$element.hide();
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.views.add('gantt', 'openerp.base.GanttView');
|
||||
openerp.base.GanttView = openerp.base.Controller.extend({
|
||||
start: function () {
|
||||
this._super();
|
||||
this.$element.append('Gantt view');
|
||||
},
|
||||
do_show: function () {
|
||||
this.$element.show();
|
||||
},
|
||||
do_hide: function () {
|
||||
this.$element.hide();
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.views.add('tree', 'openerp.base.TreeView');
|
||||
openerp.base.TreeView = openerp.base.Controller.extend({
|
||||
/**
|
||||
* Genuine tree view (the one displayed as a tree, not the list)
|
||||
* Registry for all the main views
|
||||
*/
|
||||
start: function () {
|
||||
this._super();
|
||||
this.$element.append('Tree view');
|
||||
},
|
||||
do_show: function () {
|
||||
this.$element.show();
|
||||
},
|
||||
do_hide: function () {
|
||||
this.$element.hide();
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.views.add('graph', 'openerp.base.GraphView');
|
||||
openerp.base.GraphView = openerp.base.Controller.extend({
|
||||
start: function () {
|
||||
this._super();
|
||||
this.$element.append('Graph view');
|
||||
},
|
||||
do_show: function () {
|
||||
this.$element.show();
|
||||
},
|
||||
do_hide: function () {
|
||||
this.$element.hide();
|
||||
}
|
||||
});
|
||||
openerp.base.views = new openerp.base.Registry();
|
||||
|
||||
openerp.base.ProcessView = openerp.base.Controller.extend({
|
||||
});
|
||||
|
|
|
@ -18,11 +18,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="oe_login" class="login"></div>
|
||||
<div style="position: absolute; right: 2px; top: 38px;">
|
||||
<button onclick="QWeb.add_template('base.xml'); $('body').css('background-color', '#FFFF9C'); setTimeout(function() { $('body').css('background-color', '#FFF'); }, 500);">Reload QWEB</button>
|
||||
</div>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%"
|
||||
t-att-class="'main_table' + (typeof kitten == 'undefined' ? '' : ' kitten-mode-activated')">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%" class="main_table">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div id="oe_header" class="header"></div>
|
||||
|
@ -32,11 +28,8 @@
|
|||
<tr>
|
||||
<td valign="top" id="oe_secondary_menu" class="secondary_menu">
|
||||
</td>
|
||||
<td valign="top" width="100%" height="100%">
|
||||
<div id="oe_main" class="main">
|
||||
<div id="oe_app" class="application"></div>
|
||||
<div id="oe_sidebar"></div>
|
||||
</div>
|
||||
<td valign="top" height="100%">
|
||||
<div id="oe_app" class="oe-application"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -61,8 +54,7 @@
|
|||
</t>
|
||||
<t t-name="Header">
|
||||
<a href="/" class="company_logo_link">
|
||||
<img t-att-src="typeof kitten == 'undefined' ? '/base/static/src/img/logo.png' :
|
||||
'http://placekitten.com/g/179/46'" border="0" class="company_logo"/>
|
||||
<div class="company_logo" />
|
||||
</a>
|
||||
<h1 class="header_title" t-if="session.session_is_valid()">
|
||||
<span class="company">$company</span> - (<span class="database">$database</span>)<br/>
|
||||
|
@ -93,13 +85,17 @@
|
|||
</div>
|
||||
</t>
|
||||
<t t-name="Menu">
|
||||
<ul>
|
||||
<li t-foreach="data.children" t-as="menu">
|
||||
<a href="#" t-att-data-menu="menu.id">
|
||||
<span><t t-esc="menu.name"/></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<table align="center">
|
||||
<tr>
|
||||
<t t-foreach="data.children" t-as="menu">
|
||||
<td>
|
||||
<a href="#" t-att-data-menu="menu.id">
|
||||
<span><t t-esc="menu.name"/></span>
|
||||
</a>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
<t t-name="Menu.secondary">
|
||||
<div style="display: none" class="menu_accordion" t-att-data-menu-parent="menu.id">
|
||||
|
@ -135,23 +131,23 @@
|
|||
<t t-name="ViewManager">
|
||||
<table class="view-manager-main-table">
|
||||
<tr>
|
||||
<td class="view-manager-main-content">
|
||||
<!-- TODO prefix id with the element_id of the controller t-attf-id="#{prefix}_localid" -->
|
||||
<div class="oe_vm_switch">
|
||||
<t t-foreach="views" t-as="view">
|
||||
<button type="button" t-att-data-view-type="view[1]">
|
||||
<t t-esc="view[1]"/>
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
<div t-attf-id="#{prefix}_search"/>
|
||||
<t t-foreach="views" t-as="view">
|
||||
<div t-attf-id="#{prefix}_view_#{view[1]}"/>
|
||||
</t>
|
||||
</td>
|
||||
<td class="view-manager-main-sidebar">
|
||||
</td>
|
||||
</tr>
|
||||
<td class="view-manager-main-content">
|
||||
<!-- TODO prefix id with the element_id of the controller t-attf-id="#{prefix}_localid" -->
|
||||
<div class="oe_vm_switch">
|
||||
<t t-foreach="views" t-as="view">
|
||||
<button type="button" t-att-data-view-type="view[1]">
|
||||
<t t-esc="view[1]"/>
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
<div t-attf-id="#{prefix}_search" t-opentag="true"/>
|
||||
<t t-foreach="views" t-as="view">
|
||||
<div t-attf-id="#{prefix}_view_#{view[1]}"/>
|
||||
</t>
|
||||
</td>
|
||||
<td class="view-manager-main-sidebar">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
<table t-name="ListView">
|
||||
|
@ -187,9 +183,11 @@
|
|||
<tr t-if="options.header">
|
||||
<th t-if="options.selectable"/>
|
||||
<t t-foreach="columns" t-as="column">
|
||||
<th t-if="column.invisible !== '1'">
|
||||
<th t-if="column.invisible !== '1'" t-att-data-id="column.id"
|
||||
t-att-class="((options.sortable and column.tag !== 'button') ? 'oe-sortable' : null)">
|
||||
<t t-if="column.tag !== 'button'">
|
||||
<t t-esc="column.string"/>
|
||||
<span t-att-class="(fields_view.sorted.field === column.id) ? ('ui-icon' + (fields_view.sorted.reversed ? ' ui-icon-triangle-1-n' : ' ui-icon-triangle-1-s')) : ''"/>
|
||||
</t>
|
||||
</th>
|
||||
</t>
|
||||
|
@ -212,12 +210,13 @@
|
|||
<t t-foreach="columns" t-as="column">
|
||||
<t t-set="attrs" t-value="column.attrs_for(row.data)"/>
|
||||
<td t-if="column.invisible !== '1'" t-att-title="column.help"
|
||||
class="oe-field-cell">
|
||||
class="oe-field-cell" t-att-data-field="column.id">
|
||||
<t t-if="!attrs.invisible">
|
||||
<t t-set="is_button" t-value="column.tag === 'button'"/>
|
||||
<!-- TODO: get correct widget from form -->
|
||||
<t t-if="!is_button and row['data'][column.id].value">
|
||||
<t t-esc="row['data'][column.id].value"/>
|
||||
<t t-set="value" t-value="row['data'][column.id].value"/>
|
||||
<t t-esc="value instanceof Array ? value[1] : value"/>
|
||||
</t>
|
||||
<button type="button" t-att-title="column.help"
|
||||
t-if="is_button">
|
||||
|
@ -262,7 +261,7 @@
|
|||
<t t-foreach="row" t-as="td">
|
||||
<td t-att-colspan="td.colspan gt 1 ? td.colspan : undefined"
|
||||
t-att-width="td.width ? td.width : undefined"
|
||||
t-att-nowrap="td.is_field_label ? 'true' : undefined"
|
||||
t-att-nowrap="td.is_field_label or td.is_field_m2o? 'true' : undefined"
|
||||
t-att-valign="td.table ? 'top' : undefined"
|
||||
t-att-id="td.element_id"
|
||||
t-att-class="'oe_form_' + (td.is_field_label ? 'label' : (td.field ? 'field_' + td.type : td.type))"
|
||||
|
@ -299,7 +298,7 @@
|
|||
t-att-ondblclick="'console.log(\'' + widget.element_id + '\', openerp.screen.' + widget.element_id + ')'">
|
||||
<t t-esc="widget.string"/>
|
||||
<span t-if="widget.help">?</span>
|
||||
<t t-if="widget.string">:</t>
|
||||
<t t-if="widget.string and widget.node.tag != 'label'">:</t>
|
||||
</label>
|
||||
</t>
|
||||
<t t-name="FieldChar">
|
||||
|
@ -309,6 +308,20 @@
|
|||
t-att-class="'field_' + widget.type" style="width: 100%"
|
||||
/>
|
||||
</t>
|
||||
<t t-name="FieldEmail">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td width="100%">
|
||||
<t t-call="FieldChar"/>
|
||||
</td>
|
||||
<td width="16">
|
||||
<button class="button" title="Send an e-mail with your default e-mail client">
|
||||
<img src="/base/static/src/img/icons/terp-mail-message-new.png"/>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
<t t-name="FieldText">
|
||||
<textarea rows="6" style="width: 100%;"
|
||||
t-att-name="widget.name"
|
||||
|
@ -323,13 +336,6 @@
|
|||
t-att-class="'field_' + widget.type"
|
||||
/>
|
||||
</t>
|
||||
<t t-name="FieldDatetime">
|
||||
<input type="text" style="width: 100%"
|
||||
t-att-name="widget.name"
|
||||
t-att-id="widget.element_id + '_field'"
|
||||
t-att-class="'field_' + widget.type"
|
||||
/>
|
||||
</t>
|
||||
<t t-name="FieldSelection">
|
||||
<select
|
||||
t-att-name="widget.name"
|
||||
|
@ -348,7 +354,9 @@
|
|||
t-att-name="widget.name"
|
||||
t-att-id="widget.element_id + '_field'"
|
||||
t-att-class="'field_' + widget.type"
|
||||
t-att-type="widget.type"
|
||||
style="width: 100%;"/>
|
||||
<div type='button' class='ui_combo' tabindex="'-1'" title="'Show All Items'"></div>
|
||||
</t>
|
||||
<t t-name="FieldOne2Many">
|
||||
<div t-att-id="widget.element_id">
|
||||
|
@ -356,9 +364,7 @@
|
|||
</div>
|
||||
</t>
|
||||
<t t-name="FieldMany2Many">
|
||||
<div style="background: #ccc; padding: 20px">
|
||||
Many2Many widget
|
||||
</div>
|
||||
<div t-att-id="widget.list_id"></div>
|
||||
</t>
|
||||
<t t-name="FieldReference">
|
||||
<input type="text" t-att-name="widget.name" t-att-id="widget.element_id" t-att-class="'field_' + widget.type" style="width: 100%" placeholder="Widget Reference"/>
|
||||
|
@ -370,7 +376,18 @@
|
|||
t-att-class="'field_' + widget.type"/>
|
||||
</t>
|
||||
<t t-name="FieldProgressBar">
|
||||
<div t-opentag="true"></div>
|
||||
<div t-opentag="true" class="oe-progressbar">
|
||||
<span></span>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="FieldImage">
|
||||
<img align="left" border="1" style="display: none"
|
||||
t-att-id="widget.element_id + '_field'"
|
||||
t-att-name="widget.name"
|
||||
t-att-class="'field_' + widget.type"
|
||||
t-att-width="widget.node.attrs.img_width || widget.node.attrs.width"
|
||||
t-att-height="widget.node.attrs.img_height || widget.node.attrs.height"
|
||||
/>
|
||||
</t>
|
||||
<t t-name="WidgetButton">
|
||||
<button type="button"
|
||||
|
@ -385,7 +402,7 @@
|
|||
<h2 class="oe_view_title"><t t-esc="view.attrs['string']"/></h2>
|
||||
<form>
|
||||
<t t-call="SearchView.render_lines"/>
|
||||
<div style="text-align:right;">
|
||||
<div class="oe_search-view-buttons" style="text-align:right;">
|
||||
<input type="submit" value="Search"/>
|
||||
<input type="reset" value="Clear"/>
|
||||
</div>
|
||||
|
@ -421,7 +438,7 @@
|
|||
<span t-if="attrs.help">(?)</span>
|
||||
</label>
|
||||
<div style="white-space: nowrap;">
|
||||
<input type="text" t-att-name="attrs.name"
|
||||
<input type="text" size="15" t-att-name="attrs.name"
|
||||
t-att-autofocus="attrs.default_focus === '1' ? 'autofocus' : undefined"
|
||||
t-att-id="element_id"
|
||||
t-att-value="defaults[attrs.name] || ''"/>
|
||||
|
@ -493,9 +510,10 @@
|
|||
<t t-set="expand" t-value="false"/>
|
||||
<t t-set="label" t-value="'Custom Filters'"/>
|
||||
<t t-set="content">
|
||||
<div class="searchview_extended_groups_list"/>
|
||||
<div class="searchview_extended_groups_list">
|
||||
</div>
|
||||
<button class="searchview_extended_add_group"
|
||||
type="button">Add group of conditions</button>
|
||||
type="button"><span>Add group of conditions</span></button>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
@ -507,10 +525,10 @@
|
|||
<option value="none">None of the following conditions must match</option>
|
||||
</select>
|
||||
<button class="searchview_extended_delete_group"
|
||||
type="button">Delete this group of conditions</button>
|
||||
type="button"><span>Delete this group of conditions</span></button>
|
||||
<div class="searchview_extended_propositions_list">
|
||||
</div>
|
||||
<button class="searchview_extended_add_proposition" type="button">Add condition</button>
|
||||
<button class="searchview_extended_add_proposition" type="button"><span>Add condition</span></button>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="SearchView.extended_search.proposition">
|
||||
|
@ -526,7 +544,7 @@
|
|||
<select class="searchview_extended_prop_op"/>
|
||||
<span class="searchview_extended_prop_value"/>
|
||||
<button class="searchview_extended_delete_prop"
|
||||
type="button">Delete this condition</button>
|
||||
type="button"><span>Delete this condition</span></button>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="SearchView.extended_search.proposition.char">
|
||||
|
@ -538,18 +556,21 @@
|
|||
</div>
|
||||
</t>
|
||||
<t t-name="ViewManager.sidebar.internal">
|
||||
<t t-set="the_condition" t-value="sections.length > 0 && _.detect(sections, function(x)
|
||||
{return x.elements.length > 0;}) != undefined"/>
|
||||
<t t-js="d">
|
||||
d.the_condition = d.sections.length > 0 && d._.detect(d.sections, function(x) {
|
||||
return x.elements.length > 0;
|
||||
}) != undefined
|
||||
</t>
|
||||
<t t-if="the_condition">
|
||||
<a class="toggle-sidebar"></a>
|
||||
<div t-att-id="element_id" class="sidebar-sub-div">
|
||||
<div class="sidebar-displaying-div">
|
||||
<t t-set="i" t-value="0"/>
|
||||
<t t-set="i" t-value="1-1"/> <!-- al do stupid things -->
|
||||
<t t-foreach="sections" t-as="section">
|
||||
<t t-if="section.elements.length > 0">
|
||||
<h2><t t-esc="section.label"/></h2>
|
||||
<ul>
|
||||
<t t-set="j" t-value="0"/>
|
||||
<t t-set="j" t-value="1-1"/>
|
||||
<t t-foreach="section.elements" t-as="element">
|
||||
<li><a t-att-data-i="i" t-att-data-j="j" href="#"><t t-esc="element.name"/></a></li>
|
||||
<t t-set="j" t-value="j+1"/>
|
||||
|
@ -570,4 +591,19 @@
|
|||
</p>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="Many2XSelectPopup">
|
||||
<div t-att-id="element_id">
|
||||
<div t-att-id="element_id + '_search'"></div>
|
||||
<div t-att-id="element_id + '_view_list'"></div>
|
||||
<div t-att-id="element_id + '_view_form'"></div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="Many2XSelectPopup.search.buttons">
|
||||
<button type="button" class="oe_many2xselectpopup-search-new">New</button>
|
||||
<button type="button" class="oe_many2xselectpopup-search-close">Close</button>
|
||||
</t>
|
||||
<t t-name="Many2XSelectPopup.form.buttons">
|
||||
<button type="button" class="oe_many2xselectpopup-form-save">Save</button>
|
||||
<button type="button" class="oe_many2xselectpopup-form-close">Close</button>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
|
@ -7,6 +7,7 @@ $(document).ready(function () {
|
|||
// views loader stuff
|
||||
window.openerp.base.data(openerp);
|
||||
window.openerp.base.views(openerp);
|
||||
window.openerp.base.list(openerp);
|
||||
window.openerp.base.form(openerp);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -26,23 +26,23 @@ $(document).ready(function () {
|
|||
// views loader stuff
|
||||
window.openerp.base.data(openerp);
|
||||
window.openerp.base.views(openerp);
|
||||
window.openerp.base.form(openerp);
|
||||
window.openerp.base.list(openerp);
|
||||
window.openerp.base.form(openerp);
|
||||
}
|
||||
});
|
||||
|
||||
asyncTest('render selection checkboxes', 2, function () {
|
||||
var listview = new openerp.base.ListView(
|
||||
{}, null,
|
||||
'qunit-fixture', {model: null});
|
||||
'qunit-fixture', {model: null, ids: [null, null, null], index: 0});
|
||||
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([
|
||||
listview.do_fill_table({records: [
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}}
|
||||
]).then(function () {
|
||||
]}).then(function () {
|
||||
ok(are(listview.$element.find('tbody th'),
|
||||
'.oe-record-selector'));
|
||||
ok(are(listview.$element.find('tbody th input'),
|
||||
|
@ -53,30 +53,30 @@ $(document).ready(function () {
|
|||
asyncTest('render no checkbox if selectable=false', 1, function () {
|
||||
var listview = new openerp.base.ListView(
|
||||
{}, null,
|
||||
'qunit-fixture', {model: null}, false,
|
||||
'qunit-fixture', {model: null, ids: [null, null, null], index: 0}, false,
|
||||
{selectable: false});
|
||||
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([
|
||||
listview.do_fill_table({records: [
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}}
|
||||
]).then(function () {
|
||||
]}).then(function () {
|
||||
equal(listview.$element.find('tbody th').length, 0);
|
||||
start();
|
||||
});
|
||||
});
|
||||
asyncTest('select a bunch of records', 2, function () {
|
||||
var listview = new openerp.base.ListView(
|
||||
{}, null, 'qunit-fixture', {model: null});
|
||||
{}, null, 'qunit-fixture', {model: null, ids: [1, 2, 3], index: 0});
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([
|
||||
listview.do_fill_table({records: [
|
||||
{data: {id: {value: 1}}},
|
||||
{data: {id: {value: 2}}},
|
||||
{data: {id: {value: 3}}}
|
||||
]).then(function () {
|
||||
]}).then(function () {
|
||||
listview.$element.find('tbody th input:eq(2)')
|
||||
.attr('checked', true);
|
||||
deepEqual(listview.get_selection(), [3]);
|
||||
|
@ -88,15 +88,15 @@ $(document).ready(function () {
|
|||
});
|
||||
asyncTest('render deletion button if list is deletable', 1, function () {
|
||||
var listview = new openerp.base.ListView(
|
||||
{}, null, 'qunit-fixture', {model: null});
|
||||
{}, null, 'qunit-fixture', {model: null, ids: [null, null, null], index: 0});
|
||||
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([
|
||||
listview.do_fill_table({records: [
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}}
|
||||
]).then(function () {
|
||||
]}).then(function () {
|
||||
equal(
|
||||
listview.$element.find('tbody tr td.oe-record-delete button').length,
|
||||
3);
|
||||
|
@ -109,15 +109,15 @@ $(document).ready(function () {
|
|||
var listview = new openerp.base.ListView(
|
||||
{}, null, 'qunit-fixture', {model: null, unlink: function (ids) {
|
||||
deleted = ids;
|
||||
}});
|
||||
}, ids: [1, 2, 3], index: 0});
|
||||
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([
|
||||
listview.do_fill_table({records: [
|
||||
{data: {id: {value: 1}}},
|
||||
{data: {id: {value: 2}}},
|
||||
{data: {id: {value: 3}}}
|
||||
]).then(function () {
|
||||
]}).then(function () {
|
||||
listview.$element.find('tbody td.oe-record-delete:eq(2) button').click();
|
||||
deepEqual(deleted, [3]);
|
||||
listview.$element.find('tbody td.oe-record-delete:eq(0) button').click();
|
||||
|
@ -130,15 +130,15 @@ $(document).ready(function () {
|
|||
var listview = new openerp.base.ListView(
|
||||
{}, null, 'qunit-fixture', {model: null, unlink: function (ids) {
|
||||
deleted = ids;
|
||||
}});
|
||||
}, ids: [1, 2, 3], index: 0});
|
||||
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([
|
||||
listview.do_fill_table({records: [
|
||||
{data: {id: {value: 1}}},
|
||||
{data: {id: {value: 2}}},
|
||||
{data: {id: {value: 3}}}
|
||||
]).then(function () {
|
||||
]}).then(function () {
|
||||
listview.$element.find('tbody th input:eq(2)')
|
||||
.attr('checked', true);
|
||||
listview.$element.find('tbody th input:eq(1)')
|
||||
|
|
|
@ -17,10 +17,12 @@
|
|||
<script src="/base/static/lib/qweb/qweb.js"></script>
|
||||
|
||||
<script src="/base/static/src/js/base.js"></script>
|
||||
<script src="/base/static/src/js/dates.js"></script>
|
||||
<script src="/base/static/src/js/chrome.js"></script>
|
||||
<script src="/base/static/src/js/data.js"></script>
|
||||
<script src="/base/static/src/js/views.js"></script>
|
||||
<script src="/base/static/src/js/search.js"></script>
|
||||
<script src="/base/static/src/js/m2o.js"></script>
|
||||
<script src="/base/static/src/js/form.js"></script>
|
||||
<script src="/base/static/src/js/list.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
import controllers
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "Base Dashboard",
|
||||
"version": "2.0",
|
||||
"depends": ['base'],
|
||||
"js": [
|
||||
'static/lib/jquery.dashboard/js/jquery.dashboard.js',
|
||||
'static/src/js/dashboard.js'
|
||||
],
|
||||
"css": ['static/lib/jquery.dashboard/css/dashboardui.css'],
|
||||
'active': True
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
import main
|
|
@ -0,0 +1,17 @@
|
|||
from base.controllers.main import View, clean_action
|
||||
import openerpweb
|
||||
|
||||
class Dashboard(View):
|
||||
_cp_path = "/base_dashboard/dashboard"
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def load(self, req, node_attrs):
|
||||
|
||||
action_id = int(node_attrs['name'])
|
||||
actions = req.session.model('ir.actions.actions')
|
||||
result = actions.read([action_id],['type'], req.session.context)
|
||||
if not result:
|
||||
raise _('Action not found!')
|
||||
action = req.session.model(result[0]['type']).read([action_id], False, req.session.context)[0]
|
||||
clean_action(action, req.session)
|
||||
return {'action': action}
|
|
@ -0,0 +1 @@
|
|||
content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4
|
|
@ -0,0 +1,261 @@
|
|||
.ui-icon {
|
||||
cursor:default;
|
||||
}
|
||||
.widgetheader {
|
||||
cursor:move;
|
||||
}
|
||||
|
||||
#layout-dialog .selected {
|
||||
background: no-repeat scroll 0px -51px transparent;
|
||||
}
|
||||
#layout-dialog .layoutchoice {
|
||||
width: 82px;
|
||||
height: 51px;
|
||||
float:left;
|
||||
list-style-type:none;
|
||||
margin:5px;
|
||||
padding:0;
|
||||
}
|
||||
.selectedcolumn {
|
||||
border: 3px dashed #aaaaaa;
|
||||
}
|
||||
.emptycolumn {
|
||||
font-size:20px;
|
||||
font-weight:bold;
|
||||
color: #aaaaaa;
|
||||
padding: 5px 5px 5px 5px;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.right {
|
||||
float:right;
|
||||
}
|
||||
.widgetheader {
|
||||
padding:2px 2px 5px 5px;
|
||||
}
|
||||
.ui-icon {
|
||||
float:left;
|
||||
}
|
||||
.widgetcontent {
|
||||
padding:2px 2px 5px 5px;
|
||||
}
|
||||
.widget {
|
||||
margin-bottom:10px;
|
||||
}
|
||||
.column {
|
||||
float:left;
|
||||
margin:0 1% -1.5em;
|
||||
padding:0;
|
||||
width:47.5%;
|
||||
}
|
||||
|
||||
.layout-a .column {
|
||||
width:98%;
|
||||
}
|
||||
.layout-a .column.second, .layout-a .column.third {
|
||||
display:none;
|
||||
}
|
||||
.layout-aa .column {
|
||||
width:47.5%;
|
||||
}
|
||||
.layout-aa .third {
|
||||
display:none;
|
||||
}
|
||||
.layout-ba .column {
|
||||
width:68%;
|
||||
}
|
||||
.layout-ba .first {
|
||||
width:27%;
|
||||
}
|
||||
.layout-ba .third {
|
||||
display:none;
|
||||
}
|
||||
.layout-ab .column {
|
||||
width:27%;
|
||||
}
|
||||
.layout-ab .first {
|
||||
width:68%;
|
||||
}
|
||||
.layout-ab .third {
|
||||
display:none;
|
||||
}
|
||||
.layout-aaa .column {
|
||||
width:30.9%;
|
||||
}
|
||||
|
||||
/*body {
|
||||
margin:0;
|
||||
color:#333333;
|
||||
font:12px/1.4 arial,FreeSans,Helvetica,sans-serif;
|
||||
}*/
|
||||
|
||||
.headerlink {
|
||||
color:#ffffff;
|
||||
}
|
||||
|
||||
.headerlinks {
|
||||
text-align:right;
|
||||
margin-right:20px;
|
||||
line-height:24px;
|
||||
text-decoration:underline;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
.headerbox {
|
||||
height:148px;
|
||||
}
|
||||
|
||||
.ui-widget-overlay {
|
||||
opacity:0.5;
|
||||
}
|
||||
|
||||
/*.loading {
|
||||
padding: 50px;
|
||||
text-align: center;
|
||||
background-image:loading.gif;
|
||||
}*/
|
||||
|
||||
|
||||
#layout-dialog ul {
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
#layout-dialog ul li a, #layout-dialog ul li a:link, #layout-dialog ul li a:visited {
|
||||
border:1px solid #BBBBBB;
|
||||
display:block;
|
||||
float:left;
|
||||
margin:0 1em 1em 0;
|
||||
outline:medium none;
|
||||
padding:0.35em;
|
||||
width:auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.controls {
|
||||
border:1px solid #BBBBBB;
|
||||
float:none;
|
||||
margin:0;
|
||||
padding:4px 0;
|
||||
}
|
||||
|
||||
.controls {
|
||||
width:100px;
|
||||
background:none repeat scroll 0 0 #dddddd;
|
||||
border:1px solid #6A8EB3;
|
||||
color:#000000;
|
||||
margin-top:-1px;
|
||||
padding:4px 0;
|
||||
position:absolute;
|
||||
right:0;
|
||||
z-index:2003;
|
||||
}
|
||||
|
||||
.controls li {
|
||||
float:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-type:none;
|
||||
margin:0 0 0 0.2em;
|
||||
width:auto;
|
||||
position:static;
|
||||
}
|
||||
|
||||
.controls li a {
|
||||
color:#000000;
|
||||
font-weight:normal;
|
||||
float:none;
|
||||
margin:0;
|
||||
text-decoration:none;
|
||||
width:auto;
|
||||
padding-left:5px;
|
||||
}
|
||||
|
||||
.hiddenmenu {
|
||||
position:relative;
|
||||
}
|
||||
|
||||
.dialog .categories {
|
||||
list-style:none outside none;
|
||||
height: 414px;
|
||||
}
|
||||
|
||||
.dialog .categories li.selected button {
|
||||
color:#FFFFFF;
|
||||
font-weight:800;
|
||||
}
|
||||
.dialog .categories li button {
|
||||
background:none repeat scroll 0 0 transparent;
|
||||
border:medium none;
|
||||
color:#666666;
|
||||
font-family:"segoe ui",helvetica,arial,sans-serif;
|
||||
font-size:0.8em;
|
||||
padding:0.4em 1.2em;
|
||||
text-align:left;
|
||||
width:100%;
|
||||
}
|
||||
ul.categories button {
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.widgetitem {
|
||||
border:2px none white;
|
||||
float:left;
|
||||
font-size:0.77em;
|
||||
height:142px;
|
||||
margin:0;
|
||||
overflow:hidden;
|
||||
padding:0 20px 0 142px;
|
||||
width:152px;
|
||||
}
|
||||
|
||||
ol.widgets {
|
||||
float:left;
|
||||
list-style:none outside none;
|
||||
margin:-10px;
|
||||
padding:0;
|
||||
width:auto;
|
||||
}
|
||||
|
||||
.dialog .panel-body {
|
||||
overflow:auto;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
.dialog .categories {
|
||||
background:none repeat scroll 0 0 #FFFFFF;
|
||||
border-right:1px solid #F0F0F0;
|
||||
float:left;
|
||||
height:100%;
|
||||
list-style:none outside none;
|
||||
margin:0 1.17em 0 0;
|
||||
padding:10px 0 0;
|
||||
width:25%;
|
||||
}
|
||||
|
||||
.dialog .categories li.selected {
|
||||
background:none repeat scroll 0 0 #6699CC;
|
||||
color:#FFFFFF;
|
||||
}
|
||||
|
||||
.widgetitem .add-button {
|
||||
float:left;
|
||||
margin:81px 0 0 -131px;
|
||||
width:auto;
|
||||
}
|
||||
|
||||
.widgetitem h3 {
|
||||
margin:11px 0 0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
.widgetitem img {
|
||||
border:1px solid #999999;
|
||||
float:left;
|
||||
margin:10px 0 0 -132px;
|
||||
}
|
|
@ -0,0 +1,936 @@
|
|||
/*
|
||||
* dashboard 1.0
|
||||
* http://www.gxdeveloperweb.com/dashboard/
|
||||
*
|
||||
* Copyright (c) 2010 Mark Machielsen
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses (same as jQuery):
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) { // Create closure.
|
||||
|
||||
// Constructor for dashboard object.
|
||||
$.fn.dashboard = function(options) {
|
||||
// Public properties of dashboard.
|
||||
var dashboard = {};
|
||||
var loading;
|
||||
var widgetDirectoryUrl;
|
||||
dashboard.layout;
|
||||
dashboard.element = this;
|
||||
dashboard.id = this.attr("id");
|
||||
dashboard.widgets = {};
|
||||
dashboard.widgetsToAdd = {};
|
||||
dashboard.widgetCategories = {};
|
||||
dashboard.initialized = false;
|
||||
|
||||
// Public methods
|
||||
dashboard.serialize = function() {
|
||||
dashboard.log('entering serialize function',1);
|
||||
var r = '{"layout": "' + dashboard.layout.id + '", "data" : [';
|
||||
// add al widgets in the right order
|
||||
var i=0;
|
||||
if ($('.' + opts.columnClass).length == 0) dashboard.log(opts.columnClass + ' class not found',5);
|
||||
$('.' + opts.columnClass).each(function() {
|
||||
$(this).children().each(function() {
|
||||
if ($(this).hasClass(opts.widgetClass)) {
|
||||
if (i > 0) { r+= ','; }
|
||||
r+= (dashboard.getWidget($(this).attr("id"))).serialize();
|
||||
i++;
|
||||
}
|
||||
});
|
||||
});
|
||||
r+= ']}';
|
||||
return r;
|
||||
}
|
||||
|
||||
dashboard.log = function(msg, level) {
|
||||
if (level >= opts.debuglevel && typeof console != 'undefined') {
|
||||
var l = '';
|
||||
if (level == 1) l = 'INFO';
|
||||
if (level == 2) l = 'EVENT';
|
||||
if (level == 3) l = 'WARNING';
|
||||
if (level == 5) l = 'ERROR';
|
||||
console.log(l + ' - ' + msg);
|
||||
}
|
||||
}
|
||||
|
||||
dashboard.setLayout = function(layout) {
|
||||
if (layout != null) {
|
||||
dashboard.log('entering setLayout function with layout ' + layout.id,1);
|
||||
} else {
|
||||
dashboard.log('entering setLayout function with layout null',1);
|
||||
}
|
||||
dashboard.layout = layout;
|
||||
|
||||
loading.remove();
|
||||
if (dashboard.layout != null) {
|
||||
if (typeof opts.layoutClass != 'undefined') {
|
||||
this.element.find('.' + opts.layoutClass).addClass(dashboard.layout.classname);
|
||||
} else {
|
||||
this.element.html(dashboard.layout.html);
|
||||
}
|
||||
}
|
||||
|
||||
// make the columns sortable, see http://jqueryui.com/demos/sortable/ for explaination
|
||||
$('.' + opts.columnClass).sortable({
|
||||
connectWith: $('.' + opts.columnClass),
|
||||
opacity: opts.opacity,
|
||||
handle: '.' + opts.widgetHeaderClass,
|
||||
over: function(event, ui) {
|
||||
$(this).addClass("selectedcolumn");
|
||||
},
|
||||
out: function(event, ui) {
|
||||
$(this).removeClass("selectedcolumn");
|
||||
},
|
||||
receive: function(event, ui) {
|
||||
// update the column attribute for the widget
|
||||
var w = dashboard.getWidget(ui.item.attr("id"));
|
||||
w.column = getColumnIdentifier($(this).attr("class"));
|
||||
|
||||
dashboard.log('dashboardStateChange event thrown for widget ' + w.id,2);
|
||||
dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetMoved","widget":w});
|
||||
|
||||
dashboard.log('widgetDropped event thrown for widget ' + w.id,2);
|
||||
w.element.trigger("widgetDropped",{"widget":w});
|
||||
},
|
||||
deactivate: function(event, ui) {
|
||||
// This event is called for each column
|
||||
dashboard.log('Widget is dropped: check if the column is now empty.',1);
|
||||
var childLength = $(this).children().length;
|
||||
if (childLength == 0) {
|
||||
dashboard.log('adding the empty text to the column',1);
|
||||
$(this).html('<div class="emptycolumn">' + opts.emptyColumnHtml + '</div>');
|
||||
} else {
|
||||
if (childLength == 2) {
|
||||
// remove the empty column HTML
|
||||
$(this).find('.emptycolumn').remove();
|
||||
}
|
||||
}
|
||||
},
|
||||
start: function(event, ui) {
|
||||
ui.item.find('.' + opts.widgetTitleClass).addClass('noclick');
|
||||
},
|
||||
stop: function(event, ui) {
|
||||
//sorting changed (within one list)
|
||||
setTimeout(function(){
|
||||
ui.item.find('.' + opts.widgetTitleClass).removeClass('noclick');
|
||||
}, 300);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
fixSortableColumns();
|
||||
|
||||
// trigger the dashboardLayoutLoaded event
|
||||
dashboard.log('dashboardLayoutLoaded event thrown',2);
|
||||
dashboard.element.trigger("dashboardLayoutLoaded");
|
||||
}
|
||||
|
||||
// This is a workaround for the following problem: when I drag a widget from column2 to column1, sometimes the widget is
|
||||
// moved to column3, which is not visible
|
||||
function fixSortableColumns() {
|
||||
dashboard.log('entering fixSortableColumns function',1);
|
||||
$('.nonsortablecolumn').removeClass('nonsortablecolumn').addClass(opts.columnClass);
|
||||
$('.' + opts.columnClass).filter(function() {return $(this).css("display") == 'none'}).addClass('nonsortablecolumn').removeClass(opts.columnClass);
|
||||
}
|
||||
|
||||
function getColumnIdentifier(classes) {
|
||||
dashboard.log('entering getColumnIdentifier function',1);
|
||||
var r;
|
||||
var s = classes.split(" ");
|
||||
for (var i = 0;i < s.length;i++) {
|
||||
if (s[i].indexOf(opts.columnPrefix) === 0) { r = s[i] };
|
||||
};
|
||||
return r.replace(opts.columnPrefix,'');
|
||||
}
|
||||
|
||||
dashboard.loadLayout = function() {
|
||||
dashboard.log('entering loadLayout function',1);
|
||||
if (typeof opts.json_data.url != 'undefined' && opts.json_data.url.length > 0) {
|
||||
// ajax option
|
||||
dashboard.log('Getting JSON feed : ' + opts.json_data.url,1);
|
||||
$.getJSON(opts.json_data.url, function(json) {
|
||||
if (json == null) {
|
||||
alert('Unable to get json. If you are using chrome: there is an issue with loading json with local files. It works on a server :-)',5);
|
||||
return;
|
||||
}
|
||||
// set the layout
|
||||
var currentLayout = (typeof dashboard.layout != 'undefined') ? dashboard.layout : getLayout(json.layout);
|
||||
dashboard.setLayout(currentLayout);
|
||||
dashboard.loadWidgets(json.data);
|
||||
});
|
||||
} else {
|
||||
// set the layout
|
||||
var currentLayout = (typeof dashboard.layout != 'undefined') ? dashboard.layout : getLayout(opts.json_data.layout);
|
||||
dashboard.setLayout(currentLayout);
|
||||
dashboard.loadWidgets(opts.json_data.data);
|
||||
}
|
||||
};
|
||||
|
||||
dashboard.addWidget = function(obj, column) {
|
||||
dashboard.log('entering addWidget function',1);
|
||||
// add the widget to the column
|
||||
var wid = obj.id;
|
||||
|
||||
// check if the widget is already registered and available in the dom
|
||||
if (typeof dashboard.widgets[wid] != 'undefined' && $('#' + wid).length > 0) {
|
||||
var wi = $('#' + wid);
|
||||
column = dashboard.widgets[wid].column;
|
||||
|
||||
// add it to the column
|
||||
wi.appendTo(column);
|
||||
|
||||
} else {
|
||||
// build the widget
|
||||
dashboard.log('Applying template : ' + opts.widgetTemplate,1);
|
||||
if ($('#' + opts.widgetTemplate).length == 0) dashboard.log('Template "' + opts.widgetTemplate + ' not found',5);
|
||||
var widgetStr = tmpl($('#' + opts.widgetTemplate).html(), obj);
|
||||
var wi = $(widgetStr);
|
||||
|
||||
// add it to the column
|
||||
wi.appendTo(column);
|
||||
|
||||
if(column.find('.emptycolumn').length)
|
||||
column.find('.emptycolumn').remove();
|
||||
|
||||
dashboard.widgets[wid] = widget({
|
||||
id: wid,
|
||||
element: wi,
|
||||
column: obj.column,
|
||||
url: (typeof obj.url != 'undefined' ? obj.url : null),
|
||||
editurl: obj.editurl,
|
||||
title: obj.title,
|
||||
open: obj.open,
|
||||
metadata: obj.metadata
|
||||
});
|
||||
}
|
||||
|
||||
dashboard.log('widgetAdded event thrown for widget ' + wid,2);
|
||||
dashboard.widgets[wid].element.trigger("widgetAdded", {"widget":dashboard.widgets[wid]});
|
||||
|
||||
if (dashboard.initialized) {
|
||||
dashboard.log('dashboardStateChange event thrown for widget ' + wid,2);
|
||||
dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetAdded","widget":wi});
|
||||
}
|
||||
}
|
||||
|
||||
dashboard.loadWidgets = function(data) {
|
||||
dashboard.log('entering loadWidgets function',1);
|
||||
dashboard.element.find('.' + opts.columnClass).empty();
|
||||
|
||||
|
||||
// This is for the manual feed
|
||||
$(data).each(function() {
|
||||
var column = this.column;
|
||||
dashboard.addWidget(this, dashboard.element.find('.' + opts.columnPrefix + column));
|
||||
}); // end loop for widgets
|
||||
|
||||
// check if there are widgets in the temp dashboard which needs to be moved
|
||||
// this is not the correct place, but otherwise we are too late
|
||||
|
||||
// check if there are still widgets in the temp
|
||||
$('#tempdashboard').find('.' + opts.widgetClass).each(function() {
|
||||
// append it to the first column
|
||||
var firstCol = dashboard.element.find('.' + opts.columnClass + ':first');
|
||||
$(this).appendTo(firstCol);
|
||||
|
||||
// set the new column
|
||||
dashboard.getWidget($(this).attr("id")).column = firstCol.attr("id");
|
||||
});
|
||||
$('#tempdashboard').remove();
|
||||
|
||||
// add the text to the empty columns
|
||||
$('.' + opts.columnClass).each(function() {
|
||||
if ($(this).children().length == 0) {
|
||||
$(this).html('<div class="emptycolumn">' + opts.emptyColumnHtml + '</div>');
|
||||
}
|
||||
});
|
||||
|
||||
dashboard.initialized = true;
|
||||
};
|
||||
|
||||
dashboard.init = function() {
|
||||
dashboard.log('entering init function',1);
|
||||
// load the widgets as fast as we can. After that add the binding
|
||||
dashboard.loadLayout();
|
||||
}
|
||||
|
||||
dashboard.getWidget = function(id) {
|
||||
dashboard.log('entering getWidget function',1);
|
||||
var wi = dashboard.widgets[id];
|
||||
if (typeof wi != 'undefined') {
|
||||
return wi;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Merge in the caller's options with the defaults.
|
||||
var opts = $.extend({}, $.fn.dashboard.defaults, options);
|
||||
var addOpts = $.extend({}, $.fn.dashboard.defaults.addWidgetSettings, options.addWidgetSettings);
|
||||
var layoutOpts = $.extend({}, $.fn.dashboard.defaults.editLayoutSettings, options.editLayoutSettings);
|
||||
|
||||
// Execution 'forks' here and restarts in init(). Tell the user we're busy with a loading.
|
||||
var loading = $(opts.loadingHtml).appendTo(dashboard.element);
|
||||
|
||||
/**
|
||||
* widget object
|
||||
* Private sub-class of dashboard
|
||||
* Constructor starts
|
||||
*/
|
||||
function widget(widget) {
|
||||
|
||||
dashboard.log('entering widget constructor',1);
|
||||
// Merge default options with the options defined for this widget.
|
||||
widget = $.extend({}, $.fn.dashboard.widget.defaults, widget);
|
||||
|
||||
// public functions
|
||||
widget.openContent = function() {
|
||||
// hide the open link, show the close link
|
||||
widget.element.find('.widgetOpen').hide();
|
||||
widget.element.find('.widgetClose').show();
|
||||
|
||||
dashboard.log('entering openContent function',1);
|
||||
widget.open = true;
|
||||
if (!widget.loaded) {
|
||||
// load the content in the widget if the state == open
|
||||
if (this.url != '' && this.url != null && typeof this.url != 'undefined') {
|
||||
// add the loading
|
||||
$(opts.loadingHtml).appendTo(widget.element.find('.' + opts.widgetContentClass));
|
||||
|
||||
dashboard.log('widgetShow event thrown for widget ' + widget.id,2);
|
||||
widget.element.trigger("widgetShow", {"widget":widget});
|
||||
|
||||
widget.element.find('.' + opts.widgetContentClass).load(this.url, function(response, status, xhr) {
|
||||
if (status == "error") {
|
||||
widget.element.find('.' + opts.widgetContentClass).html(opts.widgetNotFoundHtml);
|
||||
}
|
||||
widget.loaded = true;
|
||||
dashboard.log('widgetLoaded event thrown for widget ' + widget.id,2);
|
||||
widget.element.trigger("widgetLoaded", {"widget":widget});
|
||||
});
|
||||
} else {
|
||||
dashboard.log('widgetShow event thrown for widget ' + widget.id,2);
|
||||
widget.element.trigger("widgetShow", {"widget":widget});
|
||||
|
||||
dashboard.log('widgetLoaded event thrown',2);
|
||||
widget.element.trigger("widgetLoaded", {"widget":widget});
|
||||
}
|
||||
} else {
|
||||
dashboard.log('widgetShow event thrown for widget ' + widget.id,2);
|
||||
widget.element.trigger("widgetShow", {"widget":widget});
|
||||
}
|
||||
if (dashboard.initialized) {
|
||||
dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);
|
||||
dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetOpened","widget":widget});
|
||||
}
|
||||
};
|
||||
widget.refreshContent = function() {
|
||||
dashboard.log('entering refreshContent function',1);
|
||||
widget.loaded = false;
|
||||
if (widget.open) {
|
||||
widget.openContent();
|
||||
}
|
||||
}
|
||||
widget.setTitle = function(newTitle){
|
||||
dashboard.log('entering setTitle function',1);
|
||||
widget.title=newTitle;
|
||||
widget.element.find('.' + opts.widgetTitleClass).html(newTitle);
|
||||
if (dashboard.initialized) {
|
||||
dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);
|
||||
dashboard.element.trigger("dashboardStateChange",{"stateChange":"titleChanged","widget":widget});
|
||||
}
|
||||
}
|
||||
widget.closeContent = function() {
|
||||
dashboard.log('entering closeContent function',1);
|
||||
widget.open = false;
|
||||
|
||||
dashboard.log('widgetHide event thrown for widget ' + widget.id,2);
|
||||
widget.element.trigger("widgetHide", {"widget":widget});
|
||||
|
||||
// show the open link, hide the close link
|
||||
widget.element.find('.widgetOpen').show();
|
||||
widget.element.find('.widgetClose').hide();
|
||||
|
||||
dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);
|
||||
dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetClosed","widget":widget});
|
||||
};
|
||||
widget.addMetadataValue = function(name, value) {
|
||||
dashboard.log('entering addMetadataValue function',1);
|
||||
if (typeof widget.metadata == 'undefined') {
|
||||
widget.metadata = {};
|
||||
}
|
||||
widget.metadata[name] = value;
|
||||
dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);
|
||||
dashboard.element.trigger("dashboardStateChange",{"stateChange":"metadataChanged","widget":widget});
|
||||
};
|
||||
widget.openMenu = function() {
|
||||
dashboard.log('entering openMenu function',1);
|
||||
widget.element.find('.' + opts.menuClass).show();
|
||||
};
|
||||
widget.closeMenu = function() {
|
||||
dashboard.log('entering closeMenu function',1);
|
||||
widget.element.find('.' + opts.menuClass).hide();
|
||||
};
|
||||
widget.remove = function() {
|
||||
dashboard.log('entering remove function',1);
|
||||
widget.element.remove();
|
||||
dashboard.log('widgetDeleted event thrown for widget ' + widget.id,2);
|
||||
widget.element.trigger('widgetDeleted', {"widget":widget});
|
||||
|
||||
dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);
|
||||
dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetRemoved","widget":widget});
|
||||
};
|
||||
widget.serialize = function() {
|
||||
dashboard.log('entering serialize function',1);
|
||||
var r = '{"title" : "' + widget.title + '", "id" : "' + widget.id + '", "column" : "' + widget.column + '","editurl" : "' + widget.editurl + '","open" : ' + widget.open + ',"url" : "' + widget.url + '"';
|
||||
|
||||
if (typeof widget.metadata != 'undefined') {
|
||||
r+= ',"metadata":{'
|
||||
var obj = widget.metadata;
|
||||
var i=0;
|
||||
for(var item in obj) {
|
||||
if (i > 0) { r+= ',' };
|
||||
|
||||
// FIXME: support for more than string, eg numbers subobjects
|
||||
r+= '"' + item + '":"' + obj[item] + '"';
|
||||
i++;
|
||||
}
|
||||
r+= '}'
|
||||
|
||||
}
|
||||
|
||||
r += '}';
|
||||
return r;
|
||||
};
|
||||
widget.openFullscreen = function() {
|
||||
dashboard.log('entering openFullscreen function',1);
|
||||
widget.fullscreen = true;
|
||||
|
||||
// create a clone
|
||||
var clone = widget.element.clone();
|
||||
|
||||
// move the dashboard
|
||||
var temp = $('<div style="display:none" id="tempdashboard_' + dashboard.id + '"></div>');
|
||||
temp.appendTo($("body"));
|
||||
dashboard.element.children().appendTo(temp);
|
||||
|
||||
// add the clone to the dashboard
|
||||
var fs = $('<ul id="fullscreen_' + dashboard.id + '"></ul>');
|
||||
fs.appendTo(dashboard.element);
|
||||
clone.appendTo(fs);
|
||||
|
||||
};
|
||||
widget.closeFullscreen = function() {
|
||||
dashboard.log('entering closeFullscreen function',1);
|
||||
widget.fullscreen = false;
|
||||
|
||||
// remove the fullscreen
|
||||
$('#fullscreen_' + dashboard.id).remove();
|
||||
|
||||
// move the content from the tempdashboard back
|
||||
$('#tempdashboard_' + dashboard.id).children().appendTo(dashboard.element);
|
||||
$('#tempdashboard_' + dashboard.id).remove();
|
||||
|
||||
};
|
||||
widget.openSettings = function() {
|
||||
dashboard.log('entering openSettings function',1);
|
||||
widget.element.find('.' + opts.widgetContentClass).load(widget.editurl);
|
||||
};
|
||||
|
||||
// called when widget is initialized
|
||||
if (widget.open) {
|
||||
widget.openContent();
|
||||
}
|
||||
|
||||
widget.initialized = true;
|
||||
|
||||
dashboard.log('widgetInitialized event thrown',2);
|
||||
widget.element.trigger("widgetInitialized", {"widget":widget});
|
||||
|
||||
return widget;
|
||||
};
|
||||
|
||||
|
||||
// FIXME: can this be done easier??
|
||||
function getLayout(id) {
|
||||
dashboard.log('entering getLayout function',1);
|
||||
var r = null;
|
||||
var first = null;
|
||||
if (typeof opts.layouts != 'undefined') {
|
||||
|
||||
$.each(opts.layouts,function(i, item) {
|
||||
if (i == 0) { first = item; }
|
||||
if (item.id == id) {
|
||||
r = item;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (r == null) { r = first }
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
$('#' + dashboard.id + ' .menutrigger').live('click', function() {
|
||||
dashboard.log('widgetOpenMenu event thrown for widget ' + widget.id,2);
|
||||
var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
|
||||
|
||||
wi.element.trigger('widgetOpenMenu', {"widget":wi});
|
||||
return false;
|
||||
});
|
||||
|
||||
// add event handlers to the menu
|
||||
$('#' + dashboard.id + ' .' + opts.widgetFullScreenClass).live('click',function(e) {
|
||||
// close the menu
|
||||
dashboard.log('widgetCloseMenu event thrown for widget ' + widget.id,2);
|
||||
var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
|
||||
wi.element.trigger('widgetCloseMenu', {"widget":wi});
|
||||
|
||||
if (wi.fullscreen) {
|
||||
dashboard.log('widgetCloseFullScreen event thrown for widget ' + wi.id,2);
|
||||
wi.element.trigger('widgetCloseFullScreen', {"widget":wi});
|
||||
} else {
|
||||
dashboard.log('widgetOpenFullScreen event thrown for widget ' + wi.id,2);
|
||||
wi.element.trigger('widgetOpenFullScreen', {"widget":wi});
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .controls li').live('click',function(e) {
|
||||
// close the menu
|
||||
dashboard.log('widgetCloseMenu event thrown for widget ' + widget.id,2);
|
||||
|
||||
var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
|
||||
wi.element.trigger('widgetCloseMenu', {"widget":wi});
|
||||
|
||||
// use the class on the li to determine what action to trigger
|
||||
dashboard.log($(this).attr('class') + ' event thrown for widget ' + widget.id,2);
|
||||
|
||||
var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
|
||||
wi.element.trigger($(this).attr('class'), {"widget":wi});
|
||||
return false;
|
||||
});
|
||||
|
||||
// add the menu events (by default triggers are connected in dashboard_jsonfeed)
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetCloseMenu',function(e,o) {
|
||||
dashboard.log("Closing menu " + $(this).attr("id"),1);
|
||||
o.widget.closeMenu();
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpenMenu',function(e,o) {
|
||||
dashboard.log("Opening menu " + $(this).attr("id"),1);
|
||||
o.widget.openMenu();
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetDelete',function(e,o) {
|
||||
if (confirm(opts.deleteConfirmMessage)) {
|
||||
dashboard.log("Removing widget " + $(this).attr("id"),1);
|
||||
o.widget.remove();
|
||||
}
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetRefresh',function(e,o) {
|
||||
o.widget.refreshContent();
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetSetTitle',function(event, o) {
|
||||
o.widget.setTitle(o.title);
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetClose',function(e,o) {
|
||||
dashboard.log("Closing widget " + $(this).attr("id"),1);
|
||||
o.widget.closeContent();
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpen',function(e,o) {
|
||||
dashboard.log("Opening widget " + $(this).attr("id"),1);
|
||||
o.widget.openContent();
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetShow',function() {
|
||||
$(this).find('.' + opts.widgetContentClass).show();
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetHide',function() {
|
||||
$(this).find('.' + opts.widgetContentClass).hide();
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetAddMetadataValue',function(e,o) {
|
||||
dashboard.log("Changing metadata for widget " + $(this).attr("id") + ", metadata name: " + o.name + ", value: " + o.value, 1);
|
||||
o.widget.addMetadataValue(o.name, o.value);
|
||||
});
|
||||
|
||||
// Define a toggle event when clicking at the header
|
||||
$('#' + dashboard.id + ' .' + opts.widgetTitleClass).live('click',function(e) {
|
||||
dashboard.log("Click on the header detected for widget " + $(this).attr("id"),1);
|
||||
if (!$(this).hasClass('noclick')) {
|
||||
var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
|
||||
if (wi.open) {
|
||||
dashboard.log('widgetClose event thrown for widget ' + wi,2);
|
||||
wi.element.trigger('widgetClose', {"widget":wi});
|
||||
} else {
|
||||
dashboard.log('widgetOpen event thrown for widget ' + wi,2);
|
||||
wi.element.trigger('widgetOpen', {"widget":wi});
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetHeaderClass).live('mouseover',function () {
|
||||
$(this).find('.' + opts.iconsClass).removeClass("hidden");
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetHeaderClass).live('mouseout', function () {
|
||||
$(this).find('.' + opts.iconsClass).addClass("hidden");
|
||||
});
|
||||
|
||||
$('body').click(function() {
|
||||
$('.' + opts.menuClass).hide();
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpenFullScreen',function(e,o) {
|
||||
o.widget.openFullscreen();
|
||||
});
|
||||
|
||||
$('.' + opts.widgetClass).live('widgetCloseFullScreen',function(e,o) {
|
||||
o.widget.closeFullscreen();
|
||||
});
|
||||
|
||||
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetEdit',function(e,o) {
|
||||
o.widget.openSettings();
|
||||
});
|
||||
|
||||
if ($('#' + addOpts.dialogId).length == 0) dashboard.log('Unable to find ' + addOpts.dialogId,5);
|
||||
$('#' + addOpts.dialogId).dialog({
|
||||
autoOpen: false,
|
||||
height: 414,
|
||||
width: 550,
|
||||
modal: true,
|
||||
buttons: {
|
||||
Cancel: function() {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
close: function() {
|
||||
//close
|
||||
}
|
||||
});
|
||||
|
||||
if ($('#' + layoutOpts.dialogId).length == 0) dashboard.log('Unable to find ' + layoutOpts.dialogId,5);
|
||||
$('#' + layoutOpts.dialogId).dialog({
|
||||
autoOpen: false,
|
||||
height: 300,
|
||||
width: 600,
|
||||
modal: true
|
||||
});
|
||||
|
||||
$('.' + layoutOpts.openDialogClass).live('click', function(){
|
||||
dashboard.log('dashboardOpenLayoutDialog event thrown',2);
|
||||
dashboard.element.trigger("dashboardOpenLayoutDialog");
|
||||
return false;
|
||||
});
|
||||
|
||||
dashboard.element.live('dashboardOpenLayoutDialog', function(){
|
||||
dashboard.log('Opening dialog ' + layoutOpts.dialogId,1);
|
||||
$('#' + layoutOpts.dialogId).dialog('open');
|
||||
|
||||
// add the layout images
|
||||
var h = $('#' + layoutOpts.dialogId).find('.' + layoutOpts.layoutClass);
|
||||
h.empty();
|
||||
if (h.children().length == 0) {
|
||||
dashboard.log('Number of layouts : ' + opts.layouts.length,1);
|
||||
$.each(opts.layouts,function(i, item) {
|
||||
dashboard.log('Applying template : ' + layoutOpts.layoutTemplate,1);
|
||||
if ($('#' + layoutOpts.layoutTemplate).length == 0) dashboard.log('Template "' + layoutOpts.layoutTemplate + ' not found',5);
|
||||
h.append(tmpl($('#' + layoutOpts.layoutTemplate).html(), item));
|
||||
});
|
||||
}
|
||||
|
||||
// set the selected class for the selected layout
|
||||
$('.' + layoutOpts.selectLayoutClass).removeClass(layoutOpts.selectedLayoutClass);
|
||||
$('#' + dashboard.layout.id).addClass(layoutOpts.selectedLayoutClass);
|
||||
|
||||
bindSelectLayout();
|
||||
});
|
||||
|
||||
|
||||
dashboard.element.live('dashboardStateChange', function(){
|
||||
if (typeof opts.stateChangeUrl != 'undefined' && opts.stateChangeUrl != null && opts.stateChangeUrl != '') {
|
||||
$.ajax({type: 'POST',
|
||||
url: opts.stateChangeUrl,
|
||||
data: { dashboard: dashboard.element.attr("id"), settings: dashboard.serialize() },
|
||||
success: function(data){
|
||||
if (data == "NOK" || data.indexOf('<response>NOK</response>') != -1){
|
||||
dashboard.log('dashboardSaveFailed event thrown',2);
|
||||
dashboard.element.trigger("dashboardSaveFailed");
|
||||
} else {
|
||||
dashboard.log('dashboardSuccessfulSaved event thrown',2);
|
||||
dashboard.element.trigger("dashboardSuccessfulSaved");
|
||||
}
|
||||
},
|
||||
error: function(XMLHttpRequest, textStatus, errorThrown){
|
||||
dashboard.log('dashboardSaveFailed event thrown',2);
|
||||
dashboard.element.trigger("dashboardSaveFailed");
|
||||
},
|
||||
dataType: "text"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
dashboard.element.live('dashboardCloseLayoutDialog', function(){
|
||||
// close the dialog
|
||||
$('#' + layoutOpts.dialogId).dialog('close');
|
||||
});
|
||||
|
||||
// FIXME: why doesn't the live construct work in this case
|
||||
function bindSelectLayout() {
|
||||
if ($('.' + layoutOpts.selectLayoutClass).length == 0) dashboard.log('Unable to find ' + layoutOpts.selectLayoutClass,5);
|
||||
$('.' + layoutOpts.selectLayoutClass).bind('click', function(e){
|
||||
var currentLayout = dashboard.layout;
|
||||
|
||||
dashboard.log('dashboardCloseLayoutDialog event thrown',2);
|
||||
dashboard.element.trigger('dashboardCloseLayoutDialog');
|
||||
|
||||
// Now set the new layout
|
||||
var newLayout = getLayout($(this).attr("id"));
|
||||
dashboard.layout = newLayout;
|
||||
|
||||
// remove the class of the old layout
|
||||
if (typeof opts.layoutClass != 'undefined') {
|
||||
dashboard.element.find('.' + opts.layoutClass).removeClass(currentLayout.classname).addClass(newLayout.classname);
|
||||
|
||||
fixSortableColumns();
|
||||
|
||||
// check if there are widgets in hidden columns, move them to the first column
|
||||
if ($('.' + opts.columnClass).length == 0) dashboard.log('Unable to find ' + opts.columnClass,5);
|
||||
dashboard.element.find('.' + opts.columnClass).each(function() {
|
||||
if ($(this).css("display") == "none") {
|
||||
// move the widgets to the first column
|
||||
$(this).children().appendTo(dashboard.element.find('.' + opts.columnClass + ':first'));
|
||||
}
|
||||
|
||||
$('.emptycolumn').remove();
|
||||
// add the text to the empty columns
|
||||
$('.' + opts.columnClass).each(function() {
|
||||
if ($(this).children().length == 0) {
|
||||
$(this).html('<div class="emptycolumn">' + opts.emptyColumnHtml + '</div>');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
} else {
|
||||
// set the new layout, but first move the dashboard to a temp
|
||||
var temp = $('<div style="display:none" id="tempdashboard"></div>');
|
||||
temp.appendTo($("body"));
|
||||
|
||||
dashboard.element.children().appendTo(temp);
|
||||
|
||||
// reload the dashboard
|
||||
dashboard.init();
|
||||
}
|
||||
|
||||
// throw an event upon changing the layout.
|
||||
dashboard.log('dashboardChangeLayout event thrown',2);
|
||||
dashboard.element.trigger('dashboardLayoutChanged');
|
||||
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
$('.' + addOpts.selectCategoryClass).live('click', function(){
|
||||
dashboard.log('addWidgetDialogSelectCategory event thrown',2);
|
||||
dashboard.element.trigger('addWidgetDialogSelectCategory', {"category":$(this)});
|
||||
return false;
|
||||
});
|
||||
|
||||
dashboard.element.live('addWidgetDialogSelectCategory', function(e, obj){
|
||||
// remove the category selection
|
||||
$('.' + addOpts.selectCategoryClass).removeClass(addOpts.selectedCategoryClass);
|
||||
|
||||
// empty the widgets div
|
||||
$('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).empty();
|
||||
|
||||
// select the category
|
||||
$(obj.category).addClass(addOpts.selectedCategoryClass);
|
||||
|
||||
// get the widgets
|
||||
url = dashboard.widgetCategories[$(obj.category).attr("id")];
|
||||
|
||||
dashboard.log('Getting JSON feed : ' + url,1);
|
||||
$.getJSON(url, {"cache":true}, function(json) {
|
||||
// load the widgets from the category
|
||||
if (json.data == 0) dashboard.log('Empty data returned',3);
|
||||
$.each(json.data, function(i,item){
|
||||
dashboard.widgetsToAdd[item.id] = item;
|
||||
|
||||
dashboard.log('Applying template : ' + addOpts.widgetTemplate,1);
|
||||
if ($('#' + addOpts.widgetTemplate).length == 0) dashboard.log('Template "' + addOpts.widgetTemplate + ' not found',5);
|
||||
var html = tmpl($('#' + addOpts.widgetTemplate).html(), item);
|
||||
$('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).append(html);
|
||||
});
|
||||
});
|
||||
|
||||
dashboard.log('addWidgetDialogWidgetsLoaded event thrown',2);
|
||||
dashboard.element.trigger('addWidgetDialogWidgetsLoaded');
|
||||
});
|
||||
|
||||
|
||||
$('.' + addOpts.addWidgetClass).live('click', function(){
|
||||
var widget = dashboard.widgetsToAdd[$(this).attr("id").replace('addwidget','')];
|
||||
dashboard.log('dashboardAddWidget event thrown',2);
|
||||
dashboard.element.trigger('dashboardAddWidget', {"widget":widget});
|
||||
|
||||
dashboard.log('dashboardCloseWidgetDialog event thrown',2);
|
||||
dashboard.element.trigger('dashboardCloseWidgetDialog');
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.' + addOpts.openDialogClass).live('click', function(){
|
||||
dashboard.log('dashboardOpenWidgetDialog event thrown',2);
|
||||
dashboard.element.trigger('dashboardOpenWidgetDialog');
|
||||
return false;
|
||||
});
|
||||
|
||||
dashboard.element.live('dashboardCloseWidgetDialog', function(){
|
||||
// close the dialog
|
||||
$('#' + addOpts.dialogId).dialog('close');
|
||||
});
|
||||
|
||||
dashboard.element.live('dashboardOpenWidgetDialog', function(){
|
||||
|
||||
//remove existing categories/widgets from the DOM, to prevent duplications
|
||||
$('#' + addOpts.dialogId).find('.' + addOpts.categoryClass).empty();
|
||||
$('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).empty();
|
||||
|
||||
dashboard.log('Opening dialog ' + addOpts.dialogId,1);
|
||||
$('#' + addOpts.dialogId).dialog('open');
|
||||
|
||||
dashboard.log('Getting JSON feed : ' + addOpts.widgetDirectoryUrl,1);
|
||||
$.getJSON(addOpts.widgetDirectoryUrl, function(json) {
|
||||
if (json.category == 0) dashboard.log('Empty data returned',3);
|
||||
$.each(json.category, function(i,item){
|
||||
// Add the categories to the dashboard
|
||||
dashboard.widgetCategories[item.id] = item.url;
|
||||
|
||||
dashboard.log('Applying template : ' + addOpts.categoryTemplate,1);
|
||||
if ($('#' + addOpts.categoryTemplate).length == 0) dashboard.log('Template "' + addOpts.categoryTemplate + ' not found',5);
|
||||
var html = tmpl($('#' + addOpts.categoryTemplate).html(),item);
|
||||
$('#' + addOpts.dialogId).find('.' + addOpts.categoryClass).append(html);
|
||||
});
|
||||
dashboard.log('addWidgetDialogCategoriesLoaded event thrown',2);
|
||||
dashboard.element.trigger('addWidgetDialogCategoriesLoaded');
|
||||
|
||||
dashboard.log('addWidgetDialogSelectCategory event thrown',2);
|
||||
dashboard.element.trigger('addWidgetDialogSelectCategory', {"category":$('#' + addOpts.dialogId).find('.' + addOpts.categoryClass + '>li:first')});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return dashboard;
|
||||
};
|
||||
|
||||
|
||||
// Public static properties of dashboard. Default settings.
|
||||
$.fn.dashboard.defaults = {
|
||||
debuglevel:3,
|
||||
json_data: {},
|
||||
loadingHtml: '<div class="loading"><img alt="Loading, please wait" src="../themes/default/loading.gif" /><p>Loading...</p></div>',
|
||||
emptyColumnHtml: 'Drag your widgets here',
|
||||
widgetTemplate: 'widgettemplate',
|
||||
columnPrefix: 'column-',
|
||||
opacity:"0.2",
|
||||
deleteConfirmMessage: "Are you sure you want to delete this widget?",
|
||||
widgetNotFoundHtml: "The content of this widget is not available anymore. You may remove this widget.",
|
||||
columnClass: 'column',
|
||||
widgetClass: 'widget',
|
||||
menuClass: 'controls',
|
||||
widgetContentClass: 'widgetcontent',
|
||||
widgetTitleClass: 'widgettitle',
|
||||
widgetHeaderClass: 'widgetheader',
|
||||
widgetFullScreenClass: 'widgetopenfullscreen',
|
||||
iconsClass: 'icons',
|
||||
stateChangeUrl: '',
|
||||
|
||||
addWidgetSettings: {
|
||||
openDialogClass: 'openaddwidgetdialog',
|
||||
addWidgetClass: 'addwidget',
|
||||
selectCategoryClass: 'selectcategory',
|
||||
selectedCategoryClass: 'selected',
|
||||
categoryClass: 'categories',
|
||||
widgetClass: 'widgets',
|
||||
|
||||
dialogId: 'addwidgetdialog',
|
||||
|
||||
categoryTemplate: 'categorytemplate',
|
||||
widgetTemplate: 'addwidgettemplate'
|
||||
},
|
||||
editLayoutSettings: {
|
||||
dialogId: 'editLayout',
|
||||
layoutClass: 'layoutselection',
|
||||
selectLayoutClass: 'layoutchoice',
|
||||
selectedLayoutClass: 'selected',
|
||||
openDialogClass: 'editlayout',
|
||||
layoutTemplate: 'selectlayouttemplate'
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
// Default widget settings.
|
||||
$.fn.dashboard.widget = {
|
||||
defaults: {
|
||||
open: true,
|
||||
fullscreen: false,
|
||||
loaded: false,
|
||||
url: '',
|
||||
metadata: {}
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery); // end of closure
|
||||
|
||||
|
||||
// Simple JavaScript Templating
|
||||
// John Resig - http://ejohn.org/ - MIT Licensed
|
||||
(function(){
|
||||
var cache = {};
|
||||
|
||||
this.tmpl = function tmpl(str, data){
|
||||
// Figure out if we're getting a template, or if we need to
|
||||
// load the template - and be sure to cache the result.
|
||||
|
||||
var fn = !/\W/.test(str) ?
|
||||
cache[str] = cache[str] ||
|
||||
tmpl(document.getElementById(str).innerHTML) :
|
||||
|
||||
// Generate a reusable function that will serve as a template
|
||||
// generator (and which will be cached).
|
||||
new Function("obj",
|
||||
"var p=[],print=function(){p.push.apply(p,arguments);};" +
|
||||
|
||||
// Introduce the data as local variables using with(){}
|
||||
"with(obj){p.push('" +
|
||||
|
||||
// Convert the template into pure JavaScript
|
||||
str
|
||||
.replace(/[\r\t\n]/g, " ")
|
||||
.split("<%").join("\t")
|
||||
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
|
||||
.replace(/\t=(.*?)%>/g, "',$1,'")
|
||||
.split("\t").join("');")
|
||||
.split("%>").join("p.push('")
|
||||
.split("\r").join("\\'")
|
||||
+ "');}return p.join('');");
|
||||
|
||||
// Provide some basic currying to the user
|
||||
return data ? fn( data ) : fn;
|
||||
};
|
||||
})();
|
|
@ -0,0 +1,85 @@
|
|||
<script type="text/html" id="categorytemplate">
|
||||
<li id="<%= id %>" class="selectcategory"><button><%= title %> (<%= amount %>)</button></li>
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/html" id="widgettemplate">
|
||||
<div class="ui-widget ui-corner-all ui-widget-content widget" id="<%= id %>" title="<%= title %>">
|
||||
<div class="ui-widget-header ui-corner-all widgetheader">
|
||||
<span class="widgettitle"><%= title %></span>
|
||||
<span class="right icons hidden">
|
||||
<span class="ui-icon ui-icon-newwin widgetopenfullscreen"></span>
|
||||
<span class="ui-icon ui-icon-arrowthickstop-1-s menutrigger"></span>
|
||||
<span class="hiddenmenu">
|
||||
<ul style="top: 13px;" class="hidden controls ui-widget-header">
|
||||
<li class="widgetClose">
|
||||
<span class="ui-icon ui-icon-minus"></span>
|
||||
<a class="minimization" href="#">Minimize</a>
|
||||
</li>
|
||||
<li class="widgetOpen">
|
||||
<span class="ui-icon ui-icon-extlink"></span>
|
||||
<a class="minimization" href="#">Maximize</a>
|
||||
</li>
|
||||
<li class="widgetDelete">
|
||||
<span class="ui-icon ui-icon-close"></span>
|
||||
<a class="delete" href="#">Delete</a>
|
||||
</li>
|
||||
<!-- This could be implemented -->
|
||||
<!--
|
||||
<li class="widgetEdit">
|
||||
<span class="ui-icon ui-icon-tag"></span>
|
||||
<a class="no_target" href="#">Edit</a>
|
||||
</li>
|
||||
-->
|
||||
<li class="widgetRefresh">
|
||||
<span class="ui-icon ui-icon-arrowrefresh-1-w"></span>
|
||||
<a class="no_target" href="#">Refresh</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="widgetcontent">
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="selectlayouttemplate">
|
||||
<li class="layoutchoice" id="<%= id %>" style="background-image: url('<%= image %>')"></li>
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="addwidgettemplate">
|
||||
|
||||
<li class="widgetitem">
|
||||
<img src="<%= image %>" alt="" height="60" width="120">
|
||||
<div class="add-button">
|
||||
<input class="macro-button-add addwidget" id="addwidget<%= id %>" value="Add it Now" type="button"><br>
|
||||
<input class="macro-hidden-uri" value="<%= url %>" type="hidden">
|
||||
</div>
|
||||
<!-- // .add-button -->
|
||||
<h3><a href=""><%= title %></a></h3>
|
||||
|
||||
<p>By <%= creator %></p>
|
||||
<p><%= description %></p>
|
||||
</li>
|
||||
|
||||
</script>
|
||||
|
||||
<div class="dialog" id="addwidgetdialog" title="Widget Directory">
|
||||
<ul class="categories">
|
||||
</ul>
|
||||
|
||||
<div class="panel-body">
|
||||
<ol id="category-all" class="widgets">
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="dialog" id="editLayout" title="Edit layout">
|
||||
<div class="panel-body" id="layout-dialog">
|
||||
<p><strong>Choose dashboard layout</strong></p>
|
||||
<ul class="layoutselection">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,105 @@
|
|||
openerp.base_dashboard = function(openerp){
|
||||
|
||||
QWeb.add_template('/base_dashboard/static/src/xml/base_dashboard.xml');
|
||||
|
||||
openerp.base.form.Board = openerp.base.form.Widget.extend({
|
||||
init: function(view, node) {
|
||||
|
||||
this._super(view, node);
|
||||
this.template = "Board";
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.html(QWeb.render(this.template));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var self = this;
|
||||
jQuery('body').append(
|
||||
jQuery('<div>', {'id': 'dashboard_template'}).load('/base_dashboard/static/src/dashboard_template.html',self.on_loaded).hide()
|
||||
)
|
||||
},
|
||||
|
||||
on_loaded: function() {
|
||||
var children = this.node.children;
|
||||
var board = jQuery('#dashboard').dashboard({
|
||||
layoutClass:'layout'
|
||||
});
|
||||
board.init();
|
||||
for(var ch = 0; ch < children.length; ch++) {
|
||||
var ch_widgets = children[ch].children;
|
||||
for(var chld = 0; chld < ch_widgets.length; chld++) {
|
||||
var widget_type = ch_widgets[chld].tag;
|
||||
var child_index = widget_type == 'action' ? chld : ch;
|
||||
var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, ch_widgets[chld], board, child_index);
|
||||
widget.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.form.Action = openerp.base.form.Widget.extend({
|
||||
init: function(view, node, board, child_index) {
|
||||
this._super(view, node, board, child_index);
|
||||
this.board = board;
|
||||
this.child_index = child_index;
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.rpc('/base_dashboard/dashboard/load',{
|
||||
node_attrs: this.node.attrs
|
||||
},
|
||||
this.on_load_action);
|
||||
},
|
||||
|
||||
on_load_action: function(result) {
|
||||
var action = result.action;
|
||||
|
||||
action.flags = {
|
||||
search_view : false,
|
||||
sidebar : false,
|
||||
views_switcher : false
|
||||
}
|
||||
var node_attrs = this.node.attrs;
|
||||
var get_column = ['first', 'second', 'third'];
|
||||
var board_element = this.board.element.find('[id=column-'+get_column[this.child_index]+']');
|
||||
|
||||
this.board.addWidget({
|
||||
'id': node_attrs.name,
|
||||
'title': node_attrs.string,
|
||||
}, board_element);
|
||||
|
||||
var content_id = node_attrs.name+'-widgetcontent';
|
||||
this.board.getWidget(node_attrs.name).element.find('.widgetcontent').attr('id',content_id)
|
||||
|
||||
action_manager = new openerp.base.ActionManager(this.session, content_id);
|
||||
action_manager.start();
|
||||
this.board.getWidget(node_attrs.name).url = action_manager.do_action(action);
|
||||
}
|
||||
})
|
||||
|
||||
openerp.base.form.Vpaned = openerp.base.form.Widget.extend({
|
||||
init: function(view, node, board, child_index) {
|
||||
|
||||
this._super(view, node, board, child_index);
|
||||
this.board = board;
|
||||
this.child_index = child_index;
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
var children = this.node.children;
|
||||
for(var chld=0; chld<children.length; chld++) {
|
||||
var ch_widget = children[chld].children;
|
||||
for(var ch=0; ch<ch_widget.length; ch++) {
|
||||
var widget_type = ch_widget[ch].tag;
|
||||
var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, ch_widget[ch], this.board, this.child_index);
|
||||
widget.start();
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
openerp.base.form.widgets.add('hpaned', 'openerp.base.form.Board');
|
||||
openerp.base.form.widgets.add('vpaned', 'openerp.base.form.Vpaned');
|
||||
openerp.base.form.widgets.add('action', 'openerp.base.form.Action');
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<t t-name="Board">
|
||||
<div id="dashboard" class="dashboard">
|
||||
<!-- this HTML covers all layouts. The 5 different layouts are handled by setting another layout classname -->
|
||||
<div class="layout">
|
||||
<div id="column-first" class="column first column-first"></div>
|
||||
<div id="column-second" class="column second column-second"></div>
|
||||
<div id="column-third" class="column third column-third"></div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
|
@ -3,8 +3,8 @@
|
|||
"version": "2.0",
|
||||
"depends": ['base'],
|
||||
"js": [
|
||||
'static/lib/dhtmlxGantt/codebase/dhtmlxcommon.js',
|
||||
'static/lib/dhtmlxGantt/codebase/dhtmlxgantt.js',
|
||||
'static/lib/dhtmlxGantt/sources/dhtmlxcommon.js',
|
||||
'static/lib/dhtmlxGantt/sources/dhtmlxgantt.js',
|
||||
'static/src/js/gantt.js'
|
||||
],
|
||||
"css": ['static/lib/dhtmlxGantt/codebase/dhtmlxgantt.css'],
|
||||
|
|
|
@ -1,184 +1,11 @@
|
|||
import glob, os
|
||||
from xml.etree import ElementTree
|
||||
import math
|
||||
import simplejson
|
||||
import openerpweb
|
||||
import time
|
||||
import datetime
|
||||
from base.controllers.main import Xml2Json
|
||||
|
||||
from base.controllers.main import View
|
||||
|
||||
COLOR_PALETTE = ['#f57900', '#cc0000', '#d400a8', '#75507b', '#3465a4', '#73d216', '#c17d11', '#edd400',
|
||||
'#fcaf3e', '#ef2929', '#ff00c9', '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f',
|
||||
'#ff8e00', '#ff0000', '#b0008c', '#9000ff', '#0078ff', '#00ff00', '#e6ff00', '#ffff00',
|
||||
'#905000', '#9b0000', '#840067', '#510090', '#0000c9', '#009b00', '#9abe00', '#ffc900', ]
|
||||
|
||||
_colorline = ['#%02x%02x%02x' % (25 + ((r + 10) % 11) * 23, 5 + ((g + 1) % 11) * 20, 25 + ((b + 4) % 11) * 23) for r in range(11) for g in range(11) for b in range(11) ]
|
||||
|
||||
def choice_colors(n):
|
||||
if n > len(COLOR_PALETTE):
|
||||
return _colorline[0:-1:len(_colorline) / (n + 1)]
|
||||
elif n:
|
||||
return COLOR_PALETTE[:n]
|
||||
return []
|
||||
|
||||
class GanttView(openerpweb.Controller):
|
||||
class GanttView(View):
|
||||
_cp_path = "/base_gantt/ganttview"
|
||||
|
||||
date_start = None
|
||||
date_delay = None
|
||||
date_stop = None
|
||||
color_field = None
|
||||
|
||||
day_length = 8
|
||||
fields = {}
|
||||
events = []
|
||||
calendar_fields = {}
|
||||
ids = []
|
||||
model = ''
|
||||
domain = []
|
||||
context = {}
|
||||
event_res = []
|
||||
colors = {}
|
||||
color_values = []
|
||||
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def load(self, req, model, view_id):
|
||||
m = req.session.model(model)
|
||||
r = m.fields_view_get(view_id, 'gantt')
|
||||
r["arch"] = Xml2Json.convert_to_structure(r["arch"])
|
||||
return {'fields_view':r}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def get_events(self, req, **kw):
|
||||
fields_view = self.fields_view_get(req, model, view_id, 'gantt')
|
||||
return {'fields_view':fields_view}
|
||||
|
||||
self.model = kw['model']
|
||||
self.fields = kw['fields']
|
||||
self.day_length = kw['day_length']
|
||||
self.calendar_fields = kw['calendar_fields']
|
||||
self.color_field = kw.get('color_field') or self.color_field or None
|
||||
self.fields[self.color_field] = ""
|
||||
self.colors = kw.get('colors') or {}
|
||||
self.text = self.calendar_fields['text']['name']
|
||||
|
||||
self.date_start = self.calendar_fields['date_start']['name']
|
||||
self.fields[self.date_start] = ""
|
||||
|
||||
if self.calendar_fields.get('date_stop'):
|
||||
self.date_stop = self.calendar_fields['date_stop']['name']
|
||||
self.fields[self.date_stop] = ""
|
||||
if self.calendar_fields.get('date_delay'):
|
||||
self.date_delay = self.calendar_fields['date_delay']['name']
|
||||
self.fields[self.date_delay] = ""
|
||||
if self.calendar_fields.get('parent'):
|
||||
self.parent = self.calendar_fields['parent']['name']
|
||||
self.fields[self.parent] = ""
|
||||
|
||||
model = req.session.model(self.model)
|
||||
event_ids = model.search([])
|
||||
|
||||
return self.create_event(event_ids, model)
|
||||
|
||||
def create_event(self, event_ids, model):
|
||||
|
||||
self.events = model.read(event_ids, self.fields.keys())
|
||||
result = []
|
||||
for evt in self.events:
|
||||
|
||||
event_res = {}
|
||||
key = evt[self.color_field]
|
||||
name = key
|
||||
value = key
|
||||
if isinstance(key, list): # M2O, XMLRPC returns List instead of Tuple
|
||||
name = key[0]
|
||||
value = key[-1]
|
||||
evt[self.color_field] = key = key[-1]
|
||||
if isinstance(key, tuple): # M2O
|
||||
value, name = key
|
||||
|
||||
self.colors[key] = (name, value, None)
|
||||
|
||||
st_date = evt.get(self.date_start)
|
||||
if st_date:
|
||||
self.set_format(st_date)
|
||||
if self.date_delay:
|
||||
duration = evt.get(self.date_delay)
|
||||
else:
|
||||
en_date = evt.get(self.date_stop)
|
||||
|
||||
duration = (time.mktime(time.strptime(en_date, self.format))-\
|
||||
time.mktime(time.strptime(st_date, self.format)))/ (60 * 60)
|
||||
|
||||
if duration > self.day_length :
|
||||
d = math.floor(duration / 24)
|
||||
h = duration % 24
|
||||
duration = d * self.day_length + h
|
||||
|
||||
event_res = {}
|
||||
event_res['start_date'] = st_date
|
||||
event_res['duration'] = duration
|
||||
event_res['text'] = evt.get(self.text)
|
||||
event_res['id'] = evt['id']
|
||||
event_res['parent'] = evt.get(self.parent)
|
||||
result.append(event_res)
|
||||
|
||||
colors = choice_colors(len(self.colors))
|
||||
for i, (key, value) in enumerate(self.colors.items()):
|
||||
self.colors[key] = [value[0], value[1], colors[i]]
|
||||
|
||||
return {'result': result,'sidebar': self.colors}
|
||||
|
||||
def set_format(self, st_date):
|
||||
if len(st_date) == 10 :
|
||||
self.format = "%Y-%m-%d"
|
||||
else :
|
||||
self.format = "%Y-%m-%d %H:%M:%S"
|
||||
return
|
||||
|
||||
def check_format(self, date):
|
||||
if self.format == "%Y-%m-%d %H:%M:%S":
|
||||
date = date + " 00:00:00"
|
||||
return date
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def on_event_resize(self, req, **kw):
|
||||
if self.date_delay:
|
||||
key = self.date_delay
|
||||
value = kw['duration']
|
||||
else:
|
||||
key = self.date_stop
|
||||
value = self.check_format(kw['end_date'])
|
||||
try:
|
||||
model = req.session.model(self.model)
|
||||
res = model.write(kw['id'], {key : value})
|
||||
except Exception, e:
|
||||
print "eeeeeeeeeeeeeeeeeeeeeeeeeee",e
|
||||
return True
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def on_event_drag(self, req, **kw):
|
||||
start_date = self.check_format(kw['start_date'])
|
||||
if self.date_delay:
|
||||
key = self.date_delay
|
||||
value = kw['duration']
|
||||
else:
|
||||
key = self.date_stop
|
||||
value = self.check_format(kw['end_date'])
|
||||
try:
|
||||
model = req.session.model(self.model)
|
||||
res = model.write(kw['id'], {self.date_start : start_date, key : value})
|
||||
except Exception, e:
|
||||
print "eeeeeeeeeeeeeeeeeeeeeeeeeee",e
|
||||
return True
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def reload_gantt(self, req, **kw):
|
||||
|
||||
model = req.session.model(kw['model'])
|
||||
if (kw['domain']):
|
||||
domain = (kw['color_field'],'in', kw['domain'])
|
||||
event_ids = model.search([domain])
|
||||
else:
|
||||
event_ids = model.search([])
|
||||
return self.create_event(event_ids, model)
|
||||
|
|
@ -173,8 +173,9 @@ GanttProjectInfo.prototype.getTaskByIdInTree = function(parentTask, id)
|
|||
* @type: public
|
||||
* @topic: 0
|
||||
*/
|
||||
function GanttTaskInfo(id, name, est, duration, percentCompleted, predecessorTaskId)
|
||||
function GanttTaskInfo(id, name, est, duration, percentCompleted, predecessorTaskId, color)
|
||||
{
|
||||
this.color = color || "white";
|
||||
this.Id = id;
|
||||
this.Name = name;
|
||||
this.EST = est;
|
||||
|
@ -348,7 +349,7 @@ GanttProject.prototype.create = function()
|
|||
* @type: public
|
||||
* @topic: 0
|
||||
*/
|
||||
function GanttChart()
|
||||
function GanttChart(day_length)
|
||||
{
|
||||
this.Error = new GanttError();
|
||||
this.dhtmlXMLSenderObject = new dhtmlXMLSenderObject(this);
|
||||
|
@ -356,7 +357,7 @@ function GanttChart()
|
|||
//settings
|
||||
this.heightTaskItem = 12;
|
||||
this.dayInPixels = 24;
|
||||
this.hoursInDay = 8;
|
||||
this.hoursInDay = day_length;
|
||||
this._showTreePanel = true;
|
||||
this._showTooltip = true;
|
||||
this.isShowDescTask = false;
|
||||
|
@ -4869,11 +4870,13 @@ GanttTask.prototype.createTaskItem = function()
|
|||
cellTblTask.height = this.Chart.heightTaskItem + "px";
|
||||
cellTblTask.width = this.TaskInfo.PercentCompleted + "%";
|
||||
cellTblTask.style.lineHeight = "1px";
|
||||
cellTblTask.style.backgroundColor = self.TaskInfo.color;
|
||||
|
||||
var imgPr = document.createElement("img");
|
||||
imgPr.style.width = (this.TaskInfo.PercentCompleted * this.TaskInfo.Duration * this.Chart.hourInPixelsWork) / 100 + "px";
|
||||
imgPr.style.height = this.Chart.heightTaskItem + "px";
|
||||
cellTblTask.appendChild(imgPr);
|
||||
imgPr.src = this.Chart.imgs + "progress_filled.png";
|
||||
//imgPr.src = this.Chart.imgs + "progress_filled.png";
|
||||
}
|
||||
|
||||
if (this.TaskInfo.PercentCompleted != 100)
|
||||
|
@ -4943,9 +4946,9 @@ GanttTask.prototype.createTaskItem = function()
|
|||
}
|
||||
|
||||
var taskClick = function() {
|
||||
self.Chart.callEvent("onTaskClick", [self]);
|
||||
self.Chart.callEvent("onTaskDblClick", [self]);
|
||||
};
|
||||
this.addEvent(divMove, 'click', taskClick, false);
|
||||
this.addEvent(divMove, 'dblclick', taskClick, false);
|
||||
|
||||
if (this.Chart.isEditable)
|
||||
{
|
||||
|
|
|
@ -7,7 +7,8 @@ QWeb.add_template('/base_gantt/static/src/xml/base_gantt.xml');
|
|||
openerp.base.views.add('gantt', 'openerp.base_gantt.GanttView');
|
||||
openerp.base_gantt.GanttView = openerp.base.Controller.extend({
|
||||
|
||||
init: function(view_manager, session, element_id, dataset, view_id) {
|
||||
init: function(view_manager, session, element_id, dataset, view_id) {
|
||||
|
||||
this._super(session, element_id);
|
||||
this.view_manager = view_manager;
|
||||
this.dataset = dataset;
|
||||
|
@ -16,207 +17,326 @@ openerp.base_gantt.GanttView = openerp.base.Controller.extend({
|
|||
this.fields_views = {};
|
||||
this.widgets = {};
|
||||
this.widgets_counter = 0;
|
||||
this.fields = {};
|
||||
this.datarecord = {};
|
||||
this.fields = this.dataset.fields ? this.dataset.fields: {};
|
||||
this.ids = this.dataset.ids;
|
||||
this.name = "";
|
||||
this.date_start = "";
|
||||
this.date_delay = "";
|
||||
this.date_stop = "";
|
||||
this.color_field = "";
|
||||
this.day_lenth = 8;
|
||||
this.colors = [];
|
||||
this.color_values = [];
|
||||
this.calendar_fields = {};
|
||||
this.info_fields = [];
|
||||
this.domain = this.dataset._domain ? this.dataset._domain: [];
|
||||
this.context = {};
|
||||
},
|
||||
do_show: function () {
|
||||
// TODO: re-trigger search
|
||||
this.$element.show();
|
||||
},
|
||||
do_hide: function () {
|
||||
this.$element.hide();
|
||||
},
|
||||
|
||||
start: function() {
|
||||
this.rpc("/base_gantt/ganttview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
|
||||
this.rpc("/base_gantt/ganttview/load",
|
||||
{"model": this.model, "view_id": this.view_id}, this.on_loaded);
|
||||
},
|
||||
|
||||
on_loaded: function(data) {
|
||||
this.fields_view = data.fields_view;
|
||||
|
||||
var self = this;
|
||||
this.fields_view = data.fields_view;
|
||||
|
||||
this.name = this.fields_view.name || this.fields_view.arch.attrs.string;
|
||||
this.view_id = this.fields_view.view_id;
|
||||
|
||||
this.date_start = this.fields_view.arch.attrs.date_start;
|
||||
this.date_delay = this.fields_view.arch.attrs.date_delay;
|
||||
this.date_stop = this.fields_view.arch.attrs.date_stop;
|
||||
this.color_field = this.fields_view.arch.attrs.color;
|
||||
|
||||
this.color_field = this.fields_view.arch.attrs.color;
|
||||
this.day_length = this.fields_view.arch.attrs.day_length || 8;
|
||||
this.colors = this.fields_view.arch.attrs.colors;
|
||||
this.fields = this.fields_view.fields;
|
||||
|
||||
this.text = this.fields_view.arch.children[0].children[0].attrs.name;
|
||||
this.parent = this.fields_view.arch.children[0].attrs.link;
|
||||
|
||||
this.calendar_fields['parent'] = {'name': this.parent};
|
||||
this.calendar_fields['date_start'] = {'name': this.date_start};
|
||||
this.calendar_fields['text'] = {'name': this.text};
|
||||
if(this.date_delay)
|
||||
this.calendar_fields['date_delay'] = {'name': this.date_delay};
|
||||
if(this.date_stop)
|
||||
this.calendar_fields['date_stop'] = {'name': this.date_stop};
|
||||
this.format = "yyyy-MM-dd";
|
||||
|
||||
self.create_gantt();
|
||||
self.get_events();
|
||||
|
||||
this.calendar_fields['day_length'] = this.day_length;
|
||||
this.rpc('/base_gantt/ganttview/get_events',
|
||||
{'model': this.model,
|
||||
'fields': this.fields,
|
||||
'color_field': this.color_field,
|
||||
'day_length': this.day_length,
|
||||
'calendar_fields': this.calendar_fields,
|
||||
'colors': this.colors,
|
||||
'info_fields': this.info_fields
|
||||
},
|
||||
function(res) {
|
||||
self.create_gantt();
|
||||
self.load_event(res);
|
||||
})
|
||||
this.$element.html(QWeb.render("GanttView", {"view": this, "fields_view": this.fields_view}));
|
||||
|
||||
},
|
||||
convert_date_format: function(date) {
|
||||
date=date+"";
|
||||
if(typeof (date)!="string"||date.length===0){
|
||||
return null;
|
||||
}
|
||||
var iso=date.split("-");
|
||||
if(iso.length===0){
|
||||
return null;
|
||||
}
|
||||
var day = iso[2];
|
||||
var iso_hours = day.split(' ');
|
||||
|
||||
if (iso_hours.length > 1) {
|
||||
day = iso_hours[0];
|
||||
var iso_date_hours = iso_hours[1].split(':')
|
||||
var new_date = new Date(iso[0], iso[1] - 1, day);
|
||||
new_date.setHours(iso_date_hours[0]);
|
||||
new_date.setMinutes(iso_date_hours[1]);
|
||||
new_date.setSeconds(iso_date_hours[2]);
|
||||
}
|
||||
else {
|
||||
var new_date = new Date(iso[0], iso[1] - 1, day);
|
||||
}
|
||||
new_date.setFullYear(iso[0]);
|
||||
new_date.setMonth(iso[1]-1);
|
||||
new_date.setDate(day);
|
||||
return new_date;
|
||||
},
|
||||
|
||||
create_gantt: function() {
|
||||
ganttChartControl = new GanttChart();
|
||||
|
||||
ganttChartControl = new GanttChart(this.day_length);
|
||||
ganttChartControl.setImagePath("/base_gantt/static/lib/dhtmlxGantt/codebase/imgs/");
|
||||
ganttChartControl.setEditable(true);
|
||||
ganttChartControl.showTreePanel(true);
|
||||
ganttChartControl.showContextMenu(true);
|
||||
ganttChartControl.showDescTask(true,'d,s-f');
|
||||
ganttChartControl.showDescProject(true,'n,d');
|
||||
},
|
||||
load_event: function(res) {
|
||||
var self = this
|
||||
var result = res.result;
|
||||
var sidebar = res.sidebar;
|
||||
var project_id = new Array();
|
||||
var project = new Array();
|
||||
var j = -1;
|
||||
var self = this;
|
||||
for (i in result) {
|
||||
|
||||
var parent_id = result[i]['parent'][0];
|
||||
var parent_name = result[i]['parent'][1];
|
||||
|
||||
if (jQuery.inArray(parent_id, project_id) == -1){
|
||||
if (parent_id == undefined){
|
||||
parent_name = "";
|
||||
}
|
||||
j = j + 1;
|
||||
project[j] = new GanttProjectInfo(parent_id, parent_name, new Date(2011, 1, 1));
|
||||
project_id[j] = parent_id;
|
||||
}
|
||||
|
||||
var id = result[i]['id'];
|
||||
var text = result[i]['text'];
|
||||
var start_date = this.convert_date_format(result[i]['start_date']);
|
||||
var duration = result[i]['duration'];
|
||||
|
||||
var task = new GanttTaskInfo(id, text, start_date, duration, 100, "");
|
||||
|
||||
k = project_id.indexOf(parent_id);
|
||||
project[k].addTask(task);
|
||||
|
||||
}
|
||||
for (i in project_id){
|
||||
ganttChartControl.addProject(project[i]);
|
||||
}
|
||||
ganttChartControl.create("GanttDiv");
|
||||
ganttChartControl.attachEvent("onTaskEndResize", function(task) {self.on_task_end_resize(task);})
|
||||
ganttChartControl.attachEvent("onTaskEndDrag", function(task) {self.on_task_end_drag(task);})
|
||||
|
||||
//Create Sidebar
|
||||
if (jQuery('#cal-sidebar-option').length == 0){
|
||||
jQuery('#gantt-sidebar').append(
|
||||
jQuery('<table>',{'width':'100%','cellspacing': 0, 'cellpadding': 0, 'id':'cal-sidebar-option'})
|
||||
)
|
||||
for(s in sidebar) {
|
||||
jQuery('#cal-sidebar-option').append(
|
||||
jQuery('<tr>').append(
|
||||
jQuery('<td>').append(
|
||||
jQuery('<div>')
|
||||
.append(
|
||||
jQuery('<input>',
|
||||
{
|
||||
'type': 'checkbox',
|
||||
'id':sidebar[s][0],
|
||||
'value':sidebar[s][0]
|
||||
}).bind('click',function(){
|
||||
self.reload_gantt(self.color_field,self.model)
|
||||
}),
|
||||
sidebar[s][1]
|
||||
)
|
||||
.css('background-color',sidebar[s][sidebar[s].length-1])
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
get_events: function() {
|
||||
|
||||
var self = this;
|
||||
this.dataset.read_ids(this.dataset.ids, {}, function(result) {
|
||||
self.load_event(result);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
load_event: function(events) {
|
||||
|
||||
var self = this;
|
||||
|
||||
var result = events;
|
||||
var project = {};
|
||||
var project_smalldate = {};
|
||||
var proj_id = [];
|
||||
var proj_id_text = [];
|
||||
|
||||
COLOR_PALETTE = ['#ccccff', '#cc99ff', '#75507b', '#3465a4', '#73d216', '#c17d11', '#edd400',
|
||||
'#fcaf3e', '#ef2929', '#ff00c9', '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f',
|
||||
'#ff8e00', '#ff0000', '#b0008c', '#9000ff', '#0078ff', '#00ff00', '#e6ff00', '#ffff00',
|
||||
'#905000', '#9b0000', '#840067', '#510090', '#0000c9', '#009b00', '#9abe00', '#ffc900']
|
||||
|
||||
//Smallest date of child is parent start date
|
||||
for (i in result){
|
||||
var res = result[i];
|
||||
|
||||
if (res[this.date_start] != false){
|
||||
|
||||
var parent_id = res[this.parent][0] || res[this.parent];
|
||||
var parent_name = res[this.parent][1];
|
||||
var start_date = this.convert_str_date(res[this.date_start]);
|
||||
|
||||
if (project_smalldate[parent_id] == undefined){
|
||||
project_smalldate[parent_id] = start_date;
|
||||
proj_id.push(parent_id);
|
||||
|
||||
if (parent_name != undefined){
|
||||
proj_id_text.push(res[this.parent]);
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (start_date < project_smalldate[parent_id]){
|
||||
project_smalldate[parent_id] = start_date;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//get parent text
|
||||
if (parent_name == undefined){
|
||||
|
||||
var ajax = {
|
||||
url: '/base/dataset/call',
|
||||
async: false
|
||||
};
|
||||
|
||||
this.rpc(ajax, {
|
||||
model: this.dataset.model,
|
||||
method: "name_get",
|
||||
ids: proj_id,
|
||||
args: ""
|
||||
}, function(response) {
|
||||
proj_id_text = response.result;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
//create parents
|
||||
for (i in proj_id){
|
||||
|
||||
var id = proj_id_text[i][0];
|
||||
var text = proj_id_text[i][1];
|
||||
|
||||
project[id] = new GanttProjectInfo(id, text, project_smalldate[id]);
|
||||
}
|
||||
|
||||
//create childs
|
||||
var k = 0;
|
||||
var color_box = {};
|
||||
for (i in result) {
|
||||
|
||||
var res = result[i];
|
||||
if (res[this.date_start] != false){
|
||||
|
||||
var parent_id = res[this.parent][0] || res[this.parent];
|
||||
var id = res['id'];
|
||||
var text = res[this.text];
|
||||
|
||||
var start_date = this.convert_str_date(res[this.date_start]);
|
||||
|
||||
var color = res[this.color_field][0] || res[this.color_field];
|
||||
if (color_box[color] == undefined){
|
||||
color_box[color] = COLOR_PALETTE[k];
|
||||
k = k + 1;
|
||||
}
|
||||
|
||||
if (this.date_stop != undefined){
|
||||
if (res[this.date_stop] != false){
|
||||
var stop_date = this.convert_str_date(res[this.date_stop]);
|
||||
var duration= self.hours_between(start_date, stop_date);
|
||||
}
|
||||
else{
|
||||
var duration = 0;
|
||||
}
|
||||
}
|
||||
else{
|
||||
var duration = res[this.date_delay];
|
||||
}
|
||||
if (duration == false)
|
||||
duration = 0
|
||||
task = new GanttTaskInfo(id, text, start_date, duration, 100, "", color_box[color]);
|
||||
project[parent_id].addTask(task);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Add parent
|
||||
for (i in proj_id){
|
||||
ganttChartControl.addProject(project[proj_id[i]]);
|
||||
}
|
||||
|
||||
ganttChartControl.create("GanttDiv");
|
||||
ganttChartControl.attachEvent("onTaskStartDrag", function(task) {self.on_drag_start(task);});
|
||||
ganttChartControl.attachEvent("onTaskEndResize", function(task) {self.on_resize_drag_end(task, "resize");});
|
||||
ganttChartControl.attachEvent("onTaskEndDrag", function(task) {self.on_resize_drag_end(task, "drag");});
|
||||
ganttChartControl.attachEvent("onTaskDblClick", function(task) {self.open_popup(task);});
|
||||
|
||||
},
|
||||
reload_gantt: function(color_field, model) {
|
||||
var domain = [];
|
||||
|
||||
hours_between: function(date1, date2) {
|
||||
|
||||
var ONE_DAY = 1000 * 60 * 60 * 24;
|
||||
var date1_ms = date1.getTime();
|
||||
var date2_ms = date2.getTime();
|
||||
var difference_ms = Math.abs(date1_ms - date2_ms);
|
||||
|
||||
d = Math.round(difference_ms / ONE_DAY);
|
||||
h = Math.round((difference_ms % ONE_DAY)/(1000 * 60 * 60));
|
||||
|
||||
return (d * this.day_length) + h;
|
||||
|
||||
},
|
||||
|
||||
open_popup : function(task) {
|
||||
var event_id = task.getId();
|
||||
if (event_id) {
|
||||
event_id = parseInt(event_id, 10);
|
||||
var dataset_event_index = jQuery.inArray(event_id, this.ids);
|
||||
} else {
|
||||
var dataset_event_index = null;
|
||||
}
|
||||
this.dataset.index = dataset_event_index;
|
||||
var element_id = _.uniqueId("act_window_dialog");
|
||||
var dialog = jQuery('<div>',
|
||||
{'id': element_id
|
||||
}).dialog({
|
||||
title: 'Gantt Chart',
|
||||
modal: true,
|
||||
minWidth: 800,
|
||||
position: 'top'
|
||||
});
|
||||
var event_form = new openerp.base.FormView(this.view_manager, this.session, element_id, this.dataset, false);
|
||||
event_form.start();
|
||||
},
|
||||
|
||||
on_drag_start : function(task){
|
||||
st_date = task.getEST();
|
||||
if(st_date.getHours()){
|
||||
self.hh = st_date.getHours();
|
||||
self.mm = st_date.getMinutes();
|
||||
}
|
||||
},
|
||||
|
||||
on_resize_drag_end : function(task, evt){
|
||||
|
||||
var event_id = task.getId();
|
||||
var data = {};
|
||||
|
||||
if (evt == "drag"){
|
||||
full_date = task.getEST().set({hour: self.hh, minute : self.mm, second:0});
|
||||
data[this.date_start] = this.convert_date_str(full_date);
|
||||
}
|
||||
if (this.date_stop != undefined){
|
||||
tm = (task.getDuration() % this.day_length);
|
||||
stp = task.getFinishDate().add(tm).hour();
|
||||
data[this.date_stop] = this.convert_date_str(stp);
|
||||
}else{
|
||||
data[this.date_delay] = task.getDuration();
|
||||
}
|
||||
this.dataset.write(event_id, data, function(result) {});
|
||||
|
||||
},
|
||||
|
||||
do_show: function () {
|
||||
this.$element.show();
|
||||
},
|
||||
|
||||
do_hide: function () {
|
||||
this.$element.hide();
|
||||
},
|
||||
|
||||
convert_str_date: function (str){
|
||||
if (str.length == 19){
|
||||
this.format = "yyyy-MM-dd HH:mm:ss";
|
||||
return openerp.base.parse_datetime(str);
|
||||
} else if (str.length == 10){
|
||||
this.format = "yyyy-MM-dd";
|
||||
return openerp.base.parse_date(str);
|
||||
} else if (str.length == 8){
|
||||
this.format = "HH:mm:ss";
|
||||
return openerp.base.parse_time(str);
|
||||
}
|
||||
throw "Unrecognized date/time format";
|
||||
},
|
||||
|
||||
convert_date_str: function(full_date) {
|
||||
if (this.format == "yyyy-MM-dd HH:mm:ss"){
|
||||
return openerp.base.format_datetime(full_date);
|
||||
} else if (this.format == "yyyy-MM-dd"){
|
||||
return openerp.base.format_date(full_date);
|
||||
} else if (this.format == "HH:mm:ss"){
|
||||
return openerp.base.format_time(full_date);
|
||||
}
|
||||
throw "Unrecognized date/time format";
|
||||
},
|
||||
|
||||
reload_gantt: function(domain) {
|
||||
var self = this;
|
||||
jQuery('input[type=checkbox]:checked','#cal-sidebar-option').each(function() {
|
||||
domain.push(parseInt(jQuery(this).attr('id')))
|
||||
var ajax = {
|
||||
url: '/base/dataset/search_read',
|
||||
async: false
|
||||
};
|
||||
this.rpc(ajax, {
|
||||
model: this.dataset.model,
|
||||
domain: self.dataset.domain,
|
||||
context :self.dataset.context
|
||||
}, function(response) {
|
||||
ganttChartControl.clearAll();
|
||||
jQuery("#GanttDiv").children().remove();
|
||||
self.load_event(response);
|
||||
});
|
||||
},
|
||||
|
||||
do_search: function (domains, contexts, groupbys) {
|
||||
|
||||
var self = this;
|
||||
|
||||
return this.rpc('/base/session/eval_domain_and_context', {
|
||||
domains: domains,
|
||||
contexts: contexts,
|
||||
group_by_seq: groupbys
|
||||
}, function (results) {
|
||||
self.dataset.context = results.context;
|
||||
self.dataset.domain = results.domain;
|
||||
return self.reload_gantt(self.dataset.domain);
|
||||
});
|
||||
this.rpc('/base_gantt/ganttview/reload_gantt',{
|
||||
'domain':domain,
|
||||
'color_field':color_field,
|
||||
'model': model
|
||||
},function(res) {
|
||||
ganttChartControl.clearAll();
|
||||
jQuery("#GanttDiv").children().remove();
|
||||
self.load_event(res);
|
||||
});
|
||||
},
|
||||
reverse_convert_date_format: function(date) {
|
||||
return date.getFullYear()+"-"+(date.getMonth()+1)+"-"+date.getDate();
|
||||
},
|
||||
|
||||
on_task_end_resize : function(task) {
|
||||
this.rpc('/base_gantt/ganttview/on_event_resize',
|
||||
{'id' : task.getId(),
|
||||
'end_date' : this.reverse_convert_date_format(task.getFinishDate()),
|
||||
'duration' : task.getDuration()
|
||||
},
|
||||
function(result) {
|
||||
})
|
||||
},
|
||||
on_task_end_drag : function(task) {
|
||||
this.rpc('/base_gantt/ganttview/on_event_drag',
|
||||
{'id' : task.getId(),
|
||||
'start_date' : this.reverse_convert_date_format(task.getEST()),
|
||||
'end_date' : this.reverse_convert_date_format(task.getFinishDate()),
|
||||
'duration' : task.getDuration()
|
||||
},
|
||||
function(result) {
|
||||
})
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -224,5 +344,4 @@ openerp.base_gantt.GanttView = openerp.base.Controller.extend({
|
|||
// here you may tweak globals object, if any, and play with on_* or do_* callbacks on them
|
||||
|
||||
};
|
||||
|
||||
// vim:et fdc=0 fdl=0:
|
||||
|
|
|
@ -3,12 +3,9 @@
|
|||
<h3 class="title"><t t-esc="view.fields_view.arch.attrs.string"/></h3>
|
||||
<table class="gantt-view" width="100%" height="100%" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td style="width:85%">
|
||||
<td>
|
||||
<div style="width:100%;height:300px;position:relative" id="GanttDiv"/>
|
||||
</td>
|
||||
<td valign = "top">
|
||||
<div id="gantt-sidebar"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
|
|
|
@ -111,7 +111,6 @@ openerp.base_graph.GraphView = openerp.base.Controller.extend({
|
|||
if(this.chart == 'bar') {
|
||||
return this.schedule_bar(result);
|
||||
} else if(this.chart == "pie") {
|
||||
console.log("test");
|
||||
return this.schedule_pie(result);
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +124,7 @@ openerp.base_graph.GraphView = openerp.base.Controller.extend({
|
|||
var res = [];
|
||||
|
||||
|
||||
var COLOR_PALETTE = ['#ff8e00', '#ff0000', '#b0008c', '#9000ff', '#0078ff', '#00ff00', '#e6ff00', '#ffff00',
|
||||
var COLOR_PALETTE = ['#cc99ff', '#75507b', '#ccccff', '#b0008c', '#ff0000', '#ff8e00', '#9000ff', '#0078ff', '#00ff00', '#e6ff00', '#ffff00',
|
||||
'#905000', '#9b0000', '#840067', '#9abe00', '#ffc900', '#510090', '#0000c9', '#009b00',
|
||||
'#75507b', '#3465a4', '#73d216', '#c17d11', '#edd400', '#fcaf3e', '#ef2929', '#ff00c9',
|
||||
'#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f', '#f57900', '#cc0000', '#d400a8'];
|
||||
|
@ -245,8 +244,8 @@ openerp.base_graph.GraphView = openerp.base.Controller.extend({
|
|||
var val = obj[self.operator_field] / sum * 100 ;
|
||||
return Math.round(val * 10)/10 + "%";
|
||||
},
|
||||
label:"<b>#"+self.chart_info_fields[0]+"#</b>",
|
||||
gradient:"3d",
|
||||
height: 20,
|
||||
legend: {
|
||||
width: 300,
|
||||
align:"right",
|
||||
|
|
|
@ -10,5 +10,6 @@
|
|||
'static/src/js/web_chat.js'
|
||||
],
|
||||
"css": [],
|
||||
# 'active': True,
|
||||
'active': False,
|
||||
}
|
||||
|
|
|
@ -307,8 +307,6 @@ Javascript
|
|||
|
||||
:returns: the dataset's current active id
|
||||
|
||||
.. js:class:: openerp.base.DataRecord(session, model, fields, values)
|
||||
|
||||
Ad-hoc objects and structural types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ Events
|
|||
Event triggered after a user asked for a search. The search view
|
||||
fires this event after collecting all input data (contexts, domains
|
||||
and group_by contexts). Note that the search view does *not* merge
|
||||
those (or therwise evaluate them), they are returned as provided by
|
||||
those (or otherwise evaluate them), they are returned as provided by
|
||||
the various inputs within the view.
|
||||
|
||||
``on_clear``
|
||||
|
@ -71,7 +71,7 @@ An important concept in the search view is that of input. It is both
|
|||
an informal protocol and an abstract type that can be inherited from.
|
||||
|
||||
Inputs are widgets which can contain user data (a char widget for
|
||||
instance, or a selection box). They are able of action and of
|
||||
instance, or a selection box). They are capable of action and of
|
||||
reaction:
|
||||
|
||||
.. _views-search-registration:
|
||||
|
@ -93,9 +93,9 @@ reaction:
|
|||
* Return a context (an object), this is the "normal" response if the
|
||||
input holds a value.
|
||||
|
||||
* Return a falsy value (generally ``null``). This value indicates
|
||||
the input does not contain any value and will not take part in the
|
||||
research.
|
||||
* Return a value that evaluates as false (generally ``null``). This
|
||||
value indicates the input does not contain any value and will not
|
||||
affect the results of the search.
|
||||
|
||||
* Raise :js:class:`openerp.base.search.Invalid` to indicate that it
|
||||
holds a value but this value can not be used in the search
|
||||
|
@ -104,7 +104,7 @@ reaction:
|
|||
the search process.
|
||||
|
||||
:js:class:`~openerp.base.search.Invalid` takes three mandatory
|
||||
arguments: an indentifier (a name for instance), the invalid value
|
||||
arguments: an identifier (a name for instance), the invalid value,
|
||||
and a validation message indicating the issue.
|
||||
|
||||
``get_domain``
|
||||
|
@ -114,11 +114,11 @@ reaction:
|
|||
|
||||
The :js:class:`openerp.base.search.Input` type implements registration
|
||||
on its own, but its implementations of ``get_context`` and
|
||||
``get_domain`` simply raise errors and *have* to be overridden.
|
||||
``get_domain`` simply raise errors and *must* be overridden.
|
||||
|
||||
One last action is for filters, as an activation order has to be kept
|
||||
on them for some controls (establish the correct grouping sequence for
|
||||
instance).
|
||||
on them for some controls (to establish the correct grouping sequence,
|
||||
for instance).
|
||||
|
||||
To that end, filters can call
|
||||
:js:func:`openerp.base.Search.do_toggle_filter`, providing themselves
|
||||
|
@ -137,7 +137,7 @@ Life cycle
|
|||
|
||||
The search view has a pretty simple and linear life cycle, in three main steps:
|
||||
|
||||
:js:class:`init <openerp.base.SearchView>`
|
||||
:js:class:`~openerp.base.SearchView.init`
|
||||
|
||||
Nothing interesting happens here
|
||||
|
||||
|
@ -211,7 +211,7 @@ called in this order:
|
|||
Gives the widget the opportunity to unbind its events, remove itself
|
||||
from the DOM and perform any other cleanup task it may have.
|
||||
|
||||
Event if the widget does not do anything itself, it is responsible
|
||||
Even if the widget does not do anything itself, it is responsible
|
||||
for shutting down its children.
|
||||
|
||||
An abstract type is available and can be inherited from, to simplify
|
||||
|
@ -232,7 +232,8 @@ abstract types, used to implement input widgets:
|
|||
<views-search-registration>`.
|
||||
|
||||
If inherited from, descendant classes should not call its
|
||||
implementations of ``get_context`` and ``get_domain``.
|
||||
implementations of :js:func:`~openerp.base.search.Input.get_context`
|
||||
and :js:func:`~openerp.base.search.Input.get_domain`.
|
||||
|
||||
* :js:class:`openerp.base.search.Field` is used to implement more
|
||||
"field" widgets (which allow the user to input potentially complex
|
||||
|
@ -251,7 +252,7 @@ abstract types, used to implement input widgets:
|
|||
:js:func:`~openerp.base.search.Widget.make_id`.
|
||||
|
||||
* It sets up a basic (overridable)
|
||||
:js:attr:`~opererp.base.search.Field.template` attribute, combined
|
||||
:js:attr:`~openerp.base.search.Field.template` attribute, combined
|
||||
with the previous tasks, this makes subclasses of
|
||||
:js:class:`~openerp.base.search.Field` render themselves "for
|
||||
free".
|
||||
|
|
|
@ -31,7 +31,7 @@ Guides and main documentation
|
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The meat and most important part of all documentation. Should be
|
||||
written in plain english, using reStructuredText_ and taking advantage
|
||||
written in plain English, using reStructuredText_ and taking advantage
|
||||
of `Sphinx's extensions`_, especially `cross-references`_.
|
||||
|
||||
Python API Documentation
|
||||
|
@ -65,7 +65,7 @@ RST, using Sphinx's `Python domain`_ [#]_:
|
|||
and ``__new__``), using the `info fields`_ as well.
|
||||
|
||||
* Attributes (class and instance) should be documented in their
|
||||
class's docstrin via the ``.. attribute::`` directiveg, following
|
||||
class's docstring via the ``.. attribute::`` directive, following
|
||||
the class's own documentation.
|
||||
|
||||
* The relation between modules and module-level attributes is similar:
|
||||
|
@ -220,7 +220,7 @@ Roadmap
|
|||
Release notes
|
||||
+++++++++++++
|
||||
|
||||
.. [#] because Python is the default domain, the ``py:`` markup prefix
|
||||
.. [#] Because Python is the default domain, the ``py:`` markup prefix
|
||||
is optional and should be left out.
|
||||
|
||||
.. [#] Resig's Class still uses prototypes under the hood, it doesn't
|
||||
|
|
|
@ -1,27 +1,3 @@
|
|||
[global]
|
||||
server.environment = "development"
|
||||
# Some server parameters that you may want to tweak
|
||||
server.socket_host = "0.0.0.0"
|
||||
server.socket_port = 8080
|
||||
# Sets the number of threads the server uses
|
||||
server.thread_pool = 10
|
||||
|
||||
tools.sessions.on = True
|
||||
tools.sessions.persistent = False
|
||||
|
||||
# logging
|
||||
#log.access_file = "/var/log/openerp-web/access.log"
|
||||
#log.error_file = "/var/log/openerp-web/error.log"
|
||||
log.access_level = "INFO"
|
||||
log.error_level = "INFO"
|
||||
|
||||
# OpenERP Server
|
||||
openerp.server.host = 'localhost'
|
||||
openerp.server.port = '8070'
|
||||
openerp.server.protocol = 'socket'
|
||||
openerp.server.timeout = 450
|
||||
|
||||
# Regex for dbname %{subdomain}s for the subdomain
|
||||
#openerp.web.database.list = "%{subdomain}s_.*"
|
||||
#openerp.web.database.manager = False
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import datetime
|
||||
|
||||
DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
|
||||
DEFAULT_SERVER_TIME_FORMAT = "%H:%M:%S"
|
||||
DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % (
|
||||
DEFAULT_SERVER_DATE_FORMAT,
|
||||
DEFAULT_SERVER_TIME_FORMAT)
|
||||
|
||||
def parse_datetime(str):
|
||||
"""
|
||||
Converts a string to a datetime object using OpenERP's
|
||||
datetime string format (exemple: '2011-12-01 15:12:35').
|
||||
|
||||
No timezone information is added, the datetime is a naive instance, but
|
||||
according to OpenERP 6.1 specification the timezone is always UTC.
|
||||
"""
|
||||
if not str:
|
||||
return str
|
||||
return datetime.datetime.strptime(str, DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
def parse_date(str):
|
||||
"""
|
||||
Converts a string to a date object using OpenERP's
|
||||
date string format (exemple: '2011-12-01').
|
||||
"""
|
||||
if not str:
|
||||
return str
|
||||
return datetime.datetime.strptime(str, DEFAULT_SERVER_DATE_FORMAT).date()
|
||||
|
||||
def parse_time(str):
|
||||
"""
|
||||
Converts a string to a time object using OpenERP's
|
||||
time string format (exemple: '15:12:35').
|
||||
"""
|
||||
if not str:
|
||||
return str
|
||||
return datetime.datetime.strptime(str, DEFAULT_SERVER_TIME_FORMAT).time()
|
||||
|
||||
def format_datetime(obj):
|
||||
"""
|
||||
Converts a datetime object to a string using OpenERP's
|
||||
datetime string format (exemple: '2011-12-01 15:12:35').
|
||||
|
||||
The datetime instance should not have an attached timezone and be in UTC.
|
||||
"""
|
||||
if not obj:
|
||||
return False
|
||||
return obj.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
def format_date(obj):
|
||||
"""
|
||||
Converts a date object to a string using OpenERP's
|
||||
date string format (exemple: '2011-12-01').
|
||||
"""
|
||||
if not obj:
|
||||
return False
|
||||
return obj.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
||||
def format_time(obj):
|
||||
"""
|
||||
Converts a time object to a string using OpenERP's
|
||||
time string format (exemple: '15:12:35').
|
||||
"""
|
||||
if not obj:
|
||||
return False
|
||||
return obj.strftime(DEFAULT_SERVER_TIME_FORMAT)
|
|
@ -80,10 +80,6 @@ class Domain(object):
|
|||
def get_domain_string(self):
|
||||
""" Retrieves the domain string linked to this non-literal domain in
|
||||
the provided session.
|
||||
|
||||
:param session: the OpenERP Session used to store the domain string in
|
||||
the first place.
|
||||
:type session: openerpweb.openerpweb.OpenERPSession
|
||||
"""
|
||||
return self.session.domains_store[self.key]
|
||||
|
||||
|
@ -129,10 +125,6 @@ class Context(object):
|
|||
def get_context_string(self):
|
||||
""" Retrieves the context string linked to this non-literal context in
|
||||
the provided session.
|
||||
|
||||
:param session: the OpenERP Session used to store the context string in
|
||||
the first place.
|
||||
:type session: openerpweb.openerpweb.OpenERPSession
|
||||
"""
|
||||
return self.session.contexts_store[self.key]
|
||||
|
||||
|
|
|
@ -10,14 +10,12 @@ import time
|
|||
import traceback
|
||||
import uuid
|
||||
import xmlrpclib
|
||||
import pytz
|
||||
|
||||
import cherrypy
|
||||
import cherrypy.lib.static
|
||||
import simplejson
|
||||
|
||||
import nonliterals
|
||||
import xmlrpctimeout
|
||||
import logging
|
||||
|
||||
#-----------------------------------------------------------
|
||||
|
@ -94,7 +92,7 @@ class OpenERPSession(object):
|
|||
self.client_timezone = False
|
||||
|
||||
def proxy(self, service):
|
||||
s = xmlrpctimeout.TimeoutServerProxy('http://%s:%s/xmlrpc/%s' % (self._server, self._port, service), timeout=5)
|
||||
s = xmlrpclib.ServerProxy('http://%s:%s/xmlrpc/%s' % (self._server, self._port, service))
|
||||
return s
|
||||
|
||||
def bind(self, db, uid, password):
|
||||
|
@ -116,6 +114,12 @@ class OpenERPSession(object):
|
|||
r = self.proxy('object').execute(self._db, self._uid, self._password, model, func, *l, **d)
|
||||
return r
|
||||
|
||||
def exec_workflow(self, model, id, signal):
|
||||
if not (self._db and self._uid and self._password):
|
||||
raise OpenERPUnboundException()
|
||||
r = self.proxy('object').exec_workflow(self._db, self._uid, self._password, model, signal, id)
|
||||
return r
|
||||
|
||||
def model(self, model):
|
||||
""" Get an RPC proxy for the object ``model``, bound to this session.
|
||||
|
||||
|
@ -136,8 +140,9 @@ class OpenERPSession(object):
|
|||
self.context = self.model('res.users').context_get(self.context)
|
||||
|
||||
self.client_timezone = self.context.get("tz", False)
|
||||
if self.client_timezone:
|
||||
self.remote_timezone = self.execute('common', 'timezone_get')
|
||||
# invalid code, anyway we decided the server will be in UTC
|
||||
#if self.client_timezone:
|
||||
# self.remote_timezone = self.execute('common', 'timezone_get')
|
||||
|
||||
self._locale = self.context.get('lang','en_US')
|
||||
lang_ids = self.execute('res.lang','search', [('code', '=', self._locale)])
|
||||
|
@ -289,13 +294,34 @@ class JsonRequest(object):
|
|||
* the json string is passed as a form parameter named "request"
|
||||
* method is currently ignored
|
||||
|
||||
Sucessful request:
|
||||
--> {"jsonrpc": "2.0", "method": "call", "params": {"session_id": "SID", "context": {}, "arg1": "val1" }, "id": null}
|
||||
<-- {"jsonrpc": "2.0", "result": { "res1": "val1" }, "id": null}
|
||||
Sucessful request::
|
||||
|
||||
Request producing a error:
|
||||
--> {"jsonrpc": "2.0", "method": "call", "params": {"session_id": "SID", "context": {}, "arg1": "val1" }, "id": null}
|
||||
<-- {"jsonrpc": "2.0", "error": {"code": 1, "message": "End user error message.", "data": {"code": "codestring", "debug": "traceback" } }, "id": null}
|
||||
--> {"jsonrpc": "2.0",
|
||||
"method": "call",
|
||||
"params": {"session_id": "SID",
|
||||
"context": {},
|
||||
"arg1": "val1" },
|
||||
"id": null}
|
||||
|
||||
<-- {"jsonrpc": "2.0",
|
||||
"result": { "res1": "val1" },
|
||||
"id": null}
|
||||
|
||||
Request producing a error::
|
||||
|
||||
--> {"jsonrpc": "2.0",
|
||||
"method": "call",
|
||||
"params": {"session_id": "SID",
|
||||
"context": {},
|
||||
"arg1": "val1" },
|
||||
"id": null}
|
||||
|
||||
<-- {"jsonrpc": "2.0",
|
||||
"error": {"code": 1,
|
||||
"message": "End user error message.",
|
||||
"data": {"code": "codestring",
|
||||
"debug": "traceback" } },
|
||||
"id": null}
|
||||
|
||||
"""
|
||||
|
||||
|
@ -396,6 +422,8 @@ class HttpRequest(object):
|
|||
self.applicationsession = applicationsession
|
||||
self.httpsession_id = "cookieid"
|
||||
self.httpsession = cherrypy.session
|
||||
self.context = kw.get('context', {})
|
||||
self.session = self.httpsession.setdefault(kw.get('session_id', None), OpenERPSession())
|
||||
self.result = ""
|
||||
print "GET/POST --> %s.%s %s %r" % (controller.__class__.__name__, f.__name__, request, kw)
|
||||
r = f(controller, self, **kw)
|
||||
|
@ -471,30 +499,45 @@ class Root(object):
|
|||
default.exposed = True
|
||||
|
||||
def main(argv):
|
||||
# Parse config
|
||||
op = optparse.OptionParser()
|
||||
op.add_option("-p", "--port", dest="socket_port", help="listening port", metavar="NUMBER", default=8002)
|
||||
op.add_option("-s", "--session-path", dest="storage_path",
|
||||
help="directory used for session storage", metavar="DIR",
|
||||
default=os.path.join(tempfile.gettempdir(), "cpsessions"))
|
||||
(o, args) = op.parse_args(argv[1:])
|
||||
|
||||
# Prepare cherrypy config from options
|
||||
if not os.path.exists(o.storage_path):
|
||||
os.mkdir(o.storage_path, 0700)
|
||||
config = {
|
||||
'server.socket_port': int(o.socket_port),
|
||||
# change the timezone of the program to the OpenERP server's assumed timezone
|
||||
os.environ["TZ"] = "UTC"
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
'server.socket_port': 8002,
|
||||
'server.socket_host': '0.0.0.0',
|
||||
#'server.thread_pool' = 10,
|
||||
'tools.sessions.on': True,
|
||||
'tools.sessions.storage_type': 'file',
|
||||
'tools.sessions.storage_path': o.storage_path,
|
||||
'tools.sessions.storage_path': os.path.join(tempfile.gettempdir(), "cpsessions"),
|
||||
'tools.sessions.timeout': 60
|
||||
}
|
||||
|
||||
# Parse config
|
||||
op = optparse.OptionParser()
|
||||
op.add_option("-p", "--port", dest="server.socket_port", help="listening port",
|
||||
type="int", metavar="NUMBER")
|
||||
op.add_option("-s", "--session-path", dest="tools.sessions.storage_path",
|
||||
help="directory used for session storage", metavar="DIR")
|
||||
(o, args) = op.parse_args(argv[1:])
|
||||
o = vars(o)
|
||||
for k in o.keys():
|
||||
if o[k] == None:
|
||||
del(o[k])
|
||||
|
||||
# Setup and run cherrypy
|
||||
cherrypy.tree.mount(Root())
|
||||
cherrypy.config.update(config)
|
||||
|
||||
cherrypy.config.update(config=DEFAULT_CONFIG)
|
||||
if os.path.exists(os.path.join(os.path.dirname(
|
||||
os.path.dirname(__file__)),'openerp-web.cfg')):
|
||||
cherrypy.config.update(os.path.join(os.path.dirname(
|
||||
os.path.dirname(__file__)),'openerp-web.cfg'))
|
||||
if os.path.exists(os.path.expanduser('~/.openerp_webrc')):
|
||||
cherrypy.config.update(os.path.expanduser('~/.openerp_webrc'))
|
||||
cherrypy.config.update(o)
|
||||
|
||||
if not os.path.exists(cherrypy.config['tools.sessions.storage_path']):
|
||||
os.mkdir(cherrypy.config['tools.sessions.storage_path'], 0700)
|
||||
|
||||
cherrypy.server.subscribe()
|
||||
cherrypy.engine.start()
|
||||
cherrypy.engine.block()
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import xmlrpclib
|
||||
import httplib
|
||||
|
||||
class TimeoutHTTPConnection(httplib.HTTPConnection):
|
||||
def connect(self):
|
||||
httplib.HTTPConnection.connect(self)
|
||||
self.sock.settimeout(self.timeout)
|
||||
|
||||
class TimeoutHTTP(httplib.HTTP):
|
||||
_connection_class = TimeoutHTTPConnection
|
||||
def set_timeout(self, timeout):
|
||||
self._conn.timeout = timeout
|
||||
|
||||
class TimeoutTransport(xmlrpclib.Transport):
|
||||
def __init__(self, timeout=10, *l, **kw):
|
||||
xmlrpclib.Transport.__init__(self,*l,**kw)
|
||||
self.timeout=timeout
|
||||
def make_connection(self, host):
|
||||
conn = TimeoutHTTP(host)
|
||||
conn.set_timeout(self.timeout)
|
||||
return conn
|
||||
|
||||
class TimeoutServerProxy(xmlrpclib.ServerProxy):
|
||||
def __init__(self,uri,timeout=10,*l,**kw):
|
||||
kw['transport']=TimeoutTransport(timeout=timeout, use_datetime=kw.get('use_datetime',0))
|
||||
xmlrpclib.ServerProxy.__init__(self,uri,*l,**kw)
|
||||
|
||||
if __name__ == "__main__":
|
||||
s=TimeoutServerProxy('http://127.0.0.1:9090',timeout=2)
|
||||
s.dummy()
|
Loading…
Reference in New Issue