diff --git a/src/main/java/com/digmia/maven/plugin/extjsbuilder/BuildWriter.java b/src/main/java/com/digmia/maven/plugin/extjsbuilder/BuildWriter.java new file mode 100644 index 0000000..8660905 --- /dev/null +++ b/src/main/java/com/digmia/maven/plugin/extjsbuilder/BuildWriter.java @@ -0,0 +1,41 @@ +package com.digmia.maven.plugin.extjsbuilder; + +import com.digmia.maven.plugin.extjsbuilder.util.FileHelper; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; + +/** + * + * @author fk + */ +public class BuildWriter { + private FileHelper fileHelper; + private Global global; + + public BuildWriter(Global global) { + fileHelper = new FileHelper(true, false, "UTF-8"); + this.global = global; + } + + public void buildFromOrderedList(List list) throws IOException { + + global.getOutputFile().createNewFile(); + FileWriter writer = new FileWriter(global.getOutputFile()); + + try { + for(JsClass klass: list) { + File f = klass.getFile(); + String s = fileHelper.readFileToString(f); + writer.append(s); + + //append newline after each file + writer.append(String.format("%n")); + } + } finally { + writer.close(); + } + } + +} diff --git a/src/main/java/com/digmia/maven/plugin/extjsbuilder/DependencyCollector.java b/src/main/java/com/digmia/maven/plugin/extjsbuilder/DependencyCollector.java new file mode 100644 index 0000000..2555665 --- /dev/null +++ b/src/main/java/com/digmia/maven/plugin/extjsbuilder/DependencyCollector.java @@ -0,0 +1,121 @@ +package com.digmia.maven.plugin.extjsbuilder; + +import com.digmia.maven.plugin.extjsbuilder.extract.DefaultRegexBasedExtractor.ExtractionError; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.FileUtils; + +/** + * @author fk + */ +public class DependencyCollector { + + private Global global; + private DefaultJsClassParser classParser; + + private static class OrderingSession { + private Map unorderedMap; + private JsClass root; + private Collection bootstrapClasses; + private Map orderedMap = new LinkedHashMap(); + + public OrderingSession(JsClass root, Map unorderedMap) { + this.root = root; this.unorderedMap = unorderedMap; + } + + public List getOrderedClassList() { + List orderedList = new LinkedList(); + if(orderedMap.isEmpty()) { + orderedMap.put(root.getClassName(), root); + traverse(root, new LinkedList()); + } + //the root dep should be last, again, remove and add + orderedMap.remove(root.getClassName()); + + orderedList.addAll(orderedMap.values()); + if(bootstrapClasses != null) orderedList.addAll(bootstrapClasses); + Collections.reverse(orderedList); + orderedList.add(root); + return orderedList; + } + + private void traverse(JsClass currentClass, List parentVector) { + String currentClassName = currentClass.getClassName(); + + //if the vector of parent class names already contains this class name + //it effectively means there is a circular dependency somewhere along the chain + //a file cannot depend on itself, throw an assertion error + assert !parentVector.contains(currentClassName): "Circular dependency, exiting."; + parentVector.add(currentClassName); + + //if this dependency exists in the map, it means it was already required by a class up in the dependency chain + //we need to push it lower in the chain + + //so we remove it if it exists (if it does not, nothing happens on removal + orderedMap.remove(currentClassName); + + //and put it back again, which effectively pushed it forward + orderedMap.put(currentClassName, currentClass); + + for(String dependencyName: currentClass.getDependencies()) { + JsClass dependency = unorderedMap.get(dependencyName); + assert dependency instanceof JsClass: String.format("Dependency %s required by %s not found!", dependencyName, currentClassName); + traverse(dependency, parentVector); + } + //remove the current class from parent vector after taking care of it's dependency chain + parentVector.remove(currentClassName); + } + + public OrderingSession setBootstrapClasses(Collection bootstrapClasses) { + this.bootstrapClasses = bootstrapClasses; + return this; + } + } + + public DependencyCollector(Global global) { + this.global = global; + classParser = new DefaultJsClassParser(this.global); + } + + + public List collectDependencies() { + String appRootClassName = classParser.getClassNameFromFilePath(global.getAppRootFile()); + Map unorderedDependencyMap = createClassMap(collectFiles()); + JsClass rootClass = unorderedDependencyMap.get(appRootClassName); + List orderedDependencyList = new OrderingSession(rootClass, unorderedDependencyMap) + .setBootstrapClasses( + createClassMap(global.getBootstrapFileList(), false).values()) + .getOrderedClassList(); + return orderedDependencyList; + } + + + private Collection collectFiles() { + return FileUtils.listFiles(global.getAppDirectory(), new String[] {"js"}, true); + } + + private Map createClassMap(Collection files) { + return createClassMap(files, true); + } + + private Map createClassMap(Collection files, boolean parseDependencies) { + + Map out = new LinkedHashMap(); + + try { + for(File f: files) { + JsClass jsClass = classParser.parse(f, parseDependencies); + out.put(jsClass.getClassName(), jsClass); + } + } catch(ExtractionError ex) { + throw new RuntimeException(ex.getMessage(), ex); + } + return out; + } + +} diff --git a/src/main/java/com/digmia/maven/plugin/extjsbuilder/extract/DefaultRegexBasedExtractor.java b/src/main/java/com/digmia/maven/plugin/extjsbuilder/extract/DefaultRegexBasedExtractor.java new file mode 100644 index 0000000..ab297a4 --- /dev/null +++ b/src/main/java/com/digmia/maven/plugin/extjsbuilder/extract/DefaultRegexBasedExtractor.java @@ -0,0 +1,159 @@ +package com.digmia.maven.plugin.extjsbuilder.extract; + +import com.digmia.maven.plugin.extjsbuilder.Global; +import com.digmia.maven.plugin.extjsbuilder.JsClass; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.io.FileUtils; + +/** + * + * @author fk + */ +public class DefaultRegexBasedExtractor implements Extractor { + + private Global global; + + private static final Pattern PATTERN_ARRAY = Pattern.compile("((requires|extend|controllers|views|models|stores)\\s*:\\s*\\[([A-Za-z0-9_.',\\s\\/]*?)\\]\\s*)", Pattern.DOTALL); + private static final Pattern PATTERN_STRING = Pattern.compile("((requires|extend|controllers|views|models|stores)\\s*:\\s*([A-Za-z0-9_.']+)\\s*)", Pattern.DOTALL); + + private static final Pattern PATTERN_BLOCKCOMMENTS = Pattern.compile("/\\*.*?\\*/", Pattern.DOTALL); + private static final Pattern PATTERN_LINECOMMENTS = Pattern.compile("//.*", Pattern.MULTILINE); + + private static final List extractionMappings = new LinkedList() {{ + add(new TypeExtractionMapping(JsClass.Type.CONTROLLER, "controllers", "controller")); + add(new TypeExtractionMapping(JsClass.Type.MODEL, "models", "model")); + add(new TypeExtractionMapping(JsClass.Type.STORE, "stores", "store")); + add(new TypeExtractionMapping(JsClass.Type.VIEW, "views", "view")); + }}; + + + private static class TypeExtractionMapping { + + public final JsClass.Type type; + public final String dependencyProperty; + public final String folderName; + + TypeExtractionMapping(JsClass.Type type, String dependencyProperty, String folderName) { + this.type = type; this.dependencyProperty = dependencyProperty; this.folderName = folderName; + } + } + public static class ExtractionError extends Exception { + public ExtractionError(String msg) { super(msg); } + public ExtractionError(String msg, Throwable ex) { super(msg, ex); } + } + + public DefaultRegexBasedExtractor(Global global) { + this.global = global; + } + + + @Override + public List extractDependencies(JsClass klass) throws ExtractionError { + try { + String contents = FileUtils.readFileToString(klass.getFile()); + contents = clean(contents); + return runMatchers(contents); + } catch (IOException ex) { + throw new ExtractionError(ex.getMessage(), ex); + } + } + + private String clean(String contents) { + return PATTERN_LINECOMMENTS.matcher( + PATTERN_BLOCKCOMMENTS.matcher(contents).replaceAll("") + ).replaceAll(""); + } + + private List runMatchers(String contents) { + + Map> acc = createAcc(); + List out = new LinkedList(); + + Matcher arrayMatcher = PATTERN_ARRAY.matcher(contents); + collectDependenciesFromArrayMatcher(arrayMatcher, acc); + + Matcher stringMatcher = PATTERN_STRING.matcher(contents); + collectDependenciesFromStringMatcher(stringMatcher, acc); + + for(Listlist: acc.values()) { + Collections.reverse(list); + out.addAll(list); + } + + return out; + } + + + private Map> createAcc() { + + Map> acc = new LinkedHashMap>(); + + + //in reversed order - first load required libs, the controllers, then etc. etc. + acc.put(JsClass.Type.VIEW, new LinkedList()); + acc.put(JsClass.Type.STORE, new LinkedList()); + acc.put(JsClass.Type.MODEL, new LinkedList()); + acc.put(JsClass.Type.CONTROLLER, new LinkedList()); + acc.put(JsClass.Type.LIB, new LinkedList()); + + return acc; + } + + private void collectDependenciesFromStringMatcher(Matcher m, Map> acc) { + while(m.find()) { + String key = m.group(2); + String rawValue = m.group(3); + TypeExtractionMapping tem = findTypeExtractionMapping(key); + String path = processClassPath(rawValue, tem); + if(!isExcluded(path) && !path.isEmpty()) acc.get(tem.type).add(path); + } + } + + private void collectDependenciesFromArrayMatcher(Matcher m, Map> acc) { + while(m.find()) { + String key = m.group(2); + String values = m.group(3); + for(String rawValue: values.split(",")) { + TypeExtractionMapping tem = findTypeExtractionMapping(key); + String path = processClassPath(rawValue, tem); + if(!isExcluded(path) && !path.isEmpty()) acc.get(tem.type).add(path); + } + } + } + + private TypeExtractionMapping findTypeExtractionMapping(String key) { + for(TypeExtractionMapping tem: extractionMappings) { + if(tem.dependencyProperty.equals(key)) return tem; + } + + return new TypeExtractionMapping(JsClass.Type.LIB, "", "lib"); + } + + + private String processClassPath(String rawPath, TypeExtractionMapping tem) { + String path = rawPath.replace("'", "").replace("\"", "").trim(); + if (tem.type != JsClass.Type.LIB) { + // if this has a mapping, add the mapping prefix + return tem.folderName + "." + path; + } else { + // if not, just delete the global app prefix + return path.replace(global.getAppPrefix() + ".", ""); + } + + } + + private Boolean isExcluded(String path) { + for(String prefix: global.getExternalPackagePrefixes()) { + if(path.startsWith(prefix)) return true; + } + return false; + } + +} diff --git a/src/main/java/com/digmia/maven/plugin/extjsbuilder/extract/Extractor.java b/src/main/java/com/digmia/maven/plugin/extjsbuilder/extract/Extractor.java new file mode 100644 index 0000000..7a94246 --- /dev/null +++ b/src/main/java/com/digmia/maven/plugin/extjsbuilder/extract/Extractor.java @@ -0,0 +1,13 @@ +package com.digmia.maven.plugin.extjsbuilder.extract; + +import com.digmia.maven.plugin.extjsbuilder.JsClass; +import com.digmia.maven.plugin.extjsbuilder.extract.DefaultRegexBasedExtractor.ExtractionError; +import java.util.List; + +/** + * + * @author fk + */ +public interface Extractor { + public List extractDependencies(JsClass klass) throws ExtractionError; +} diff --git a/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/ContextWrapper.java b/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/ContextWrapper.java new file mode 100644 index 0000000..e7cf7ca --- /dev/null +++ b/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/ContextWrapper.java @@ -0,0 +1,24 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.digmia.maven.plugin.extjsbuilder.future; + +import com.digmia.maven.plugin.extjsbuilder.extract.Extractor; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +/** + * @author fk + */ +public interface ContextWrapper { + + void initialize(Extractor civ); + void initialize(); + + void destroy(); + Scriptable getScope(); + Context getContext(); + + Boolean isInitialized(); +} diff --git a/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/DefaultContextWrapper.java b/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/DefaultContextWrapper.java new file mode 100644 index 0000000..34e8459 --- /dev/null +++ b/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/DefaultContextWrapper.java @@ -0,0 +1,53 @@ +package com.digmia.maven.plugin.extjsbuilder.future; + +import com.digmia.maven.plugin.extjsbuilder.extract.Extractor; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +/** + * Default context initializer. + * @author fk + */ +public class DefaultContextWrapper implements ContextWrapper { + + private Context context; + private Scriptable scope; + private Boolean isInitialized = false; + + + @Override + public void initialize(Extractor ext) { + if(!isInitialized) { + context = Context.enter(); + scope = context.initStandardObjects(); + isInitialized = true; + } + } + + @Override + public void initialize() { + initialize((Extractor) null); + } + + @Override + public Scriptable getScope() { + return scope; + } + + @Override + public Context getContext() { + return context; + } + + @Override + public void destroy() { + Context.exit(); + } + + @Override + public Boolean isInitialized() { + return isInitialized; + } + + +} diff --git a/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/LegacyConvertor.java b/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/LegacyConvertor.java new file mode 100644 index 0000000..43e8ff4 --- /dev/null +++ b/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/LegacyConvertor.java @@ -0,0 +1,56 @@ +package com.digmia.maven.plugin.extjsbuilder.future; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.mozilla.javascript.NativeArray; +import org.mozilla.javascript.NativeObject; + +/** + * A simple helper class to convert native datatypes to java types. + * (Since in older versions of Rhino native datatypes need conversion). + * @author fk + */ +public class LegacyConvertor { + + public static List nativeArrayToStringList(NativeArray input) { + + ArrayList output = new ArrayList(); + for (Object o : input.getIds()) { + Integer idx = (Integer) o; + output.add(input.get(idx, null).toString()); + } + return output; + } + + public static Map nativeObjectToMap(NativeObject input) { + Map output = new HashMap(); + + for(Object o: input.getIds()) { + String key = (String) o; + output.put(key, input.get(key, null)); + } + + return output; + } + +// +// +// public static Scriptable getNodePath(List nodes, Scriptable scope) { +// Object current = scope.get(nodes.get(0), scope); +// if(nodes.size() > 1 && current instanceof Scriptable) { +// return getNodePath(nodes.subList(1, nodes.size()), (Scriptable) current); +// } +// +// if(current instanceof Scriptable) { return (Scriptable) current; } +// return null; +// } +// +// public static Scriptable getNodePath(String path, Scriptable scope) { +// Scriptable value = getNodePath(Arrays.asList(path.split("\\.")), scope); +// if(value != null) return value; +// +// throw new NoSuchNodeExists(path); +// } +} diff --git a/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/Node.java b/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/Node.java new file mode 100644 index 0000000..722cd97 --- /dev/null +++ b/src/main/java/com/digmia/maven/plugin/extjsbuilder/future/Node.java @@ -0,0 +1,78 @@ +package com.digmia.maven.plugin.extjsbuilder.future; + +import java.util.Arrays; +import java.util.List; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +/** + * Simple API for Scriptable traversal. + * Ex: Node.in(scope, context).path("global.someproperty").get("SomeComplex.Prop") + * @author fk + */ + +public class Node { + + private Scriptable root; + private Context context; + private Boolean strict; + + public static class NoSuchNodeExists extends RuntimeException { + NoSuchNodeExists(String msg) { super(msg); } + }; + + private Node(Scriptable root, Context context, Boolean strict) { + this.root = root; this.context = context; this.strict = strict != null ? strict:false; + } + + public static Node in(Scriptable root, Context context) { + return new Node(root, context, false); + }; + + public static Node in(Scriptable root, Context context, Boolean strict) { + return new Node(root, context, strict); + }; + + /** + * Splits the path by dots and tries to recursively find the node value. + * + * @param path + * @return + */ + public Node path(String path) { + Scriptable value = recurseNodePath(Arrays.asList(path.split("\\.")), root); + if(value != null) return new Node(value, context, strict); + + if (strict) throw new NoSuchNodeExists(path); + else return null; + } + + /** + * Finds the scriptable object by name in current scope. + * + * @param name + * @return + */ + public Node name(String name) { + Object value = root.get(name, root); + if(value instanceof Scriptable) return new Node((Scriptable) value, context, strict); + + if (strict) throw new NoSuchNodeExists(name); + else return null; + } + + public Scriptable get() { + return root; + } + + private Scriptable recurseNodePath(List nodes, Scriptable scope) { + Object current = scope.get(nodes.get(0), scope); + if(nodes.size() > 1 && current instanceof Scriptable) { + return recurseNodePath(nodes.subList(1, nodes.size()), (Scriptable) current); + } + + if(current instanceof Scriptable) { return (Scriptable) current; } + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/com/digmia/maven/plugin/extjsbuilder/util/FileHelper.java b/src/main/java/com/digmia/maven/plugin/extjsbuilder/util/FileHelper.java new file mode 100644 index 0000000..6d63f8f --- /dev/null +++ b/src/main/java/com/digmia/maven/plugin/extjsbuilder/util/FileHelper.java @@ -0,0 +1,328 @@ +package com.digmia.maven.plugin.extjsbuilder.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.Collection; +import java.util.Vector; +import java.util.regex.Pattern; + +// changing to non-static type class +// is because adding last BOM member make potentially thread safe +// one instance of this class per thread, each thread using its own instance only + +public class FileHelper { + + public boolean verbose = false; + public boolean debug = false; + public String encoding = null; + + public int lastBOM = 0; + public int lastSizeBOM = 0; + public String lastENC = null; + + public boolean lastWinLE = false; + public boolean lastUnixLE = false; + public int lastStride = 1; + + public String sysEncoding = null; + + private byte[] fileCopyBuf = null; + + private static long tstart = 0; + static long taccum = 0; + public static void tstart() { + tstart = System.currentTimeMillis(); + } + public static long tstop() { + return taccum += (System.currentTimeMillis() - tstart); + } + + + public FileHelper(boolean verbose, boolean debug, String encoding) { + this.verbose = verbose; + this.debug = debug; + this.encoding = ("".equals(encoding)) ? null : encoding; // only enforced on copyFile, rest is manual + + sysEncoding = new java.io.OutputStreamWriter(new java.io.ByteArrayOutputStream()).getEncoding(); + System.out.println("System default encoding is " + sysEncoding + "."); + } + + // BOM and encoding helpers, yes these look ugly... nmf --Beeaar + public byte[] getBufEnc(byte[] a) { + return getBufEnc(a, 0); + } + public byte[] getBufEnc(byte[] a, int i) { + lastENC = null; + lastBOM = lastSizeBOM = 0; + if (a==null) return a; + + int x=a[i]&0xFF, y=a[i+1]&0xFF, z=a[i+2]&0xFF; i=0; + if ( (x==0xFE && y==0xFF) || (x==0xFF && y==0xFE) || // utf 16 BE or LE + (x==0xEF && y==0xBB && z==0xBF) ) // utf 8 + i += (x==0xEF) ? 3 : 2; // mark bytes to skip + + if (i > 0) { // found BOM + lastSizeBOM = i; + lastBOM = x<<16 + y<<8 + z; + lastENC = (i==3) ? "UTF-8" : (x==0xFE) ? "UTF-16BE" : "UTF-16LE"; + if (verbose) { + System.out.println("Detected BOM: " + Integer.toHexString(lastBOM) + " " + lastENC); + } + } else { // scan bytes for clues + if (x==0 && y!=0) lastENC = "UTF-16BE"; // after testing for BOM, we don't care if its ascii or utf8 + else if (x!=0 && y==0) lastENC = "UTF-16LE"; // is the same for us, so long as the compressor only gets valid ascii + } // if compressor gets other than valid chars it throws exception + + return a; + } + public String toString(byte[] a) { + String s = null; + if (a==null) return null; + try { + getBufEnc(a); + s = new String(a, lastSizeBOM, a.length - lastSizeBOM, (lastENC==null) ? sysEncoding : lastENC); + } catch (Exception e) { e.printStackTrace(); } + return s; + } + public byte[] toBuf(String s, String enc) { + byte[] a = null; + if (s==null) return null; + try { + a = s.getBytes( (enc==null) ? sysEncoding : enc ); + } catch (Exception e) { e.printStackTrace(); } + return a; + } + public byte[] transCode(byte[] a, String enc) { + a = getBufEnc(a); // seed the buffer encoding values + if (enc != null && !enc.equals(lastENC)) { + if (debug) System.out.println("Transcode detected: " + enc + " from " + ((lastENC==null) ? sysEncoding : lastENC)); + lastBOM = lastSizeBOM = 0; // clear the bom size for the buffer, if you dont want this cleared, save or do toString toBuf manually + a = toBuf(toString(a), enc); // this converts from its natural encoding to java utf16, then back to forced encoding + } + return a; + } + // this scans the whole file, this is inherently slow + // and no, regex path would order of magnitude slower + // the problem is that the JS files have gotten so big in general + // the added stride correction has nearly no negative performance impact, btw. i tested + // also taking out the both w and u true short circuit is slower on the Ext dist + // there are lots of files in the Ext dist with mixed LE + public byte[] getBufLE(byte[] a, int length) { + boolean w = false, u = false; + int i=0, o=1, al=length; + for (i=((0==a[0])?0:1); i(o-1) && a[i-o]=='\r') w = true; else u = true; + } + lastWinLE = w; lastUnixLE = u; + return a; + } + public byte[] getBufLE(byte[] a) { + return getBufLE(a, a.length); + } + // ironically line-endings are one of the most code intensive + // this should work on utf16 etc.. now + public byte[] transCodeLE(byte[] a, int length, boolean isUnix) { + a = getBufLE(a); // seed the buffer encoding values + boolean e=isUnix, w = lastWinLE, u = lastUnixLE; + if (!(e && w) && !(!e && u)) return a; // to unix, has windows || to win, has unix + if (debug) System.out.println("Transcode detected: unix=" + isUnix + " from unix=" + lastUnixLE + " win=" + lastWinLE + ""); + + int i=0, j=0, o=lastStride, al=length; + if (!e) for (i=0,j=0; i 0) + out.write(buf, 0, len); + } finally { + try { if (out != null) out.close(); } catch (Exception e) { /* don't care */ } + try { if (in != null) in.close(); } catch (Exception e) {/* don't care */ } + } + } + + + public String getExtention(String name) { + String s = null; + int i = name.lastIndexOf('.'); // needs to search from the right end + if (i > 0) s = name.substring(i); // needs to be aware of files that start with '.' + return s; + } + public String insertFileSuffix(String name, String suffix) throws Exception { + int i = name.lastIndexOf("."); + if (i == -1) { + // this technically should never happen, because this is only called when ther is a known extention + // however this really isnt an exception, its ok for files to nto have an extention + // also a new try catch frame is expensive + throw new Exception("No period in the target file output."); + } + return name.substring(0, i) + suffix + name.substring(i); + } + + //http://www.java-tips.org/java-se-tips/java.io/how-to-copy-a-directory-from-one-location-to-another-loc.html + public void copyDirectory(File sourceLocation , File targetLocation, final String regExPattern) throws IOException { + if (sourceLocation.isDirectory()) { + if (!targetLocation.exists()) { + targetLocation.mkdir(); + } + + String[] children = sourceLocation.list(new FilenameFilter() { + private Pattern pattern = Pattern.compile(regExPattern); + public boolean accept(File dir, String name) { + File newFile = new File(dir.getAbsolutePath() + File.separatorChar + name); + Boolean isSvn = (newFile.getAbsolutePath().indexOf(".svn") != -1); + Boolean isHidden = newFile.isHidden(); + Boolean isDir = newFile.isDirectory(); + Boolean matches = pattern.matcher(name).matches(); + if (isSvn || isHidden) { + return false; + } else if (isDir) { + return true; + } else { + return matches; + } + } + }); + + for (int i=0; i files = listFiles(directory, filter, recurse); + + File[] arr = new File[files.size()]; + return files.toArray(arr); + } + + public Collection listFiles(File directory, FilenameFilter filter, boolean recurse) { + Vector files = new Vector(); + + // Get files / directories in the directory + File[] entries = directory.listFiles(); + + // Go over entries + for (File entry : entries) { + // If there is no filter or the filter accepts the + // file / directory, add it to the list + if (filter == null || filter.accept(directory, entry.getName())) { + files.add(entry); + } + + // If the file is a directory and the recurse flag + // is set, recurse into the directory + if (recurse && entry.isDirectory()) { + files.addAll(listFiles(entry, filter, recurse)); + } + } + // Return collection of files + return files; + } + + void deleteTree(File f) throws IOException { + if (f.isDirectory()) { + for (File c : f.listFiles()) + deleteTree(c); + } + if (!f.delete()) throw new FileNotFoundException("Failed to delete file: " + f); + } + + void deleteEmptyDirs(File f) throws IOException { + if (f.isDirectory()) { + for (File c : f.listFiles()) + if (f.isDirectory()) deleteEmptyDirs(c); + if (f.listFiles().length == 0 && !f.delete()) + throw new FileNotFoundException("Failed to delete file: " + f); + } + } + +} diff --git a/src/main/java/com/digmia/maven/plugin/extjsbuilder/util/FilenameListFilter.java b/src/main/java/com/digmia/maven/plugin/extjsbuilder/util/FilenameListFilter.java new file mode 100644 index 0000000..4f80352 --- /dev/null +++ b/src/main/java/com/digmia/maven/plugin/extjsbuilder/util/FilenameListFilter.java @@ -0,0 +1,30 @@ +package com.digmia.maven.plugin.extjsbuilder.util; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.Arrays; +import java.util.Collection; +import org.apache.commons.io.filefilter.IOFileFilter; + +/** + * Case-sensitive file name filter, which compares the file names with a list of allowed names. + * @author fk + */ +public class FilenameListFilter implements IOFileFilter { + + private Collection files; + + public FilenameListFilter(String... fileNames) { + this.files = Arrays.asList(fileNames); + } + + @Override + public boolean accept(File file, String string) { + return files.contains(file.getName()); + } + + @Override + public boolean accept(File file) { + return files.contains(file.getName()); + } +} diff --git a/src/main/java/com/digmia/maven/plugin/extjsbuilder/util/NodeSelector.java b/src/main/java/com/digmia/maven/plugin/extjsbuilder/util/NodeSelector.java new file mode 100644 index 0000000..6a9c46b --- /dev/null +++ b/src/main/java/com/digmia/maven/plugin/extjsbuilder/util/NodeSelector.java @@ -0,0 +1,13 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.digmia.maven.plugin.extjsbuilder.util; + +/** + * + * @author fk + */ +public interface NodeSelector { + +} diff --git a/src/test/java/com/digmia/maven/plugin/extjsbuilder/PatternTest.java b/src/test/java/com/digmia/maven/plugin/extjsbuilder/PatternTest.java new file mode 100644 index 0000000..31c3602 --- /dev/null +++ b/src/test/java/com/digmia/maven/plugin/extjsbuilder/PatternTest.java @@ -0,0 +1,62 @@ +package com.digmia.maven.plugin.extjsbuilder; + +import com.digmia.maven.plugin.extjsbuilder.future.ParsingFacadeTest; +import bsh.StringUtil; +import java.io.File; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.io.FileUtils; +import org.testng.Reporter; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * + * @author fk + */ +public class PatternTest { + + + @DataProvider(name = "stringData") + public static Object[][] stringData() throws IOException { + String appFile = FileUtils.readFileToString(new File(ParsingFacadeTest.class.getResource("/app.js").getFile()), "utf-8"); + String includeFile = FileUtils.readFileToString(new File(ParsingFacadeTest.class.getResource("/testInclude.js").getFile()), "utf-8"); + + return new Object[][] {{ appFile, includeFile }}; + } + + //@Test(dataProvider = "stringData") + public void classDefinition(String app, String include) { + Pattern.compile("^Ext\\.define\\((.*),(.*)\\);", Pattern.DOTALL); + } + + //@Test(dataProvider = "stringData") + public void allReq(String app, String include) { + Pattern arrayPattern = Pattern.compile("((requires|extend|controllers|views|models|stores)\\s*:\\s*\\[(.*?)\\]\\s*)", Pattern.DOTALL); + Pattern singleStringPattern = Pattern.compile("((requires|extend|controllers|views|models|stores)\\s*:\\s*([A-Za-z0-9_.']+)\\s*)", Pattern.DOTALL); + Pattern excludeBlockComments = Pattern.compile("/\\*.*?\\*/", Pattern.DOTALL); + Pattern excludeLineComments = Pattern.compile("//.*", Pattern.MULTILINE); + app = excludeBlockComments.matcher(app).replaceAll(""); + + //include = excludeBlockComments.matcher(include).replaceAll(""); + Matcher m1 = singleStringPattern.matcher(include); + Matcher m2 = arrayPattern.matcher(app); + + +// while(m2.find()) { +// Reporter.log(String.format("Match start: %d, end: %d, group: '%s'.", m2.start(), m2.end(), m2.group(3)), true); +// } + + while(m2.find()) { + String s = m2.group(3); + //Reporter.log(s, true); + + String replaced = excludeLineComments.matcher(s).replaceAll(""); + + //Reporter.log(replaced, true); + //Reporter.log(String.format("Match start: %d, end: %d, group: '%s'.", m1.start(), m1.end(), m1.group(3)), true); + } + } + +} diff --git a/src/test/java/com/digmia/maven/plugin/extjsbuilder/future/ParsingFacadeTest.java b/src/test/java/com/digmia/maven/plugin/extjsbuilder/future/ParsingFacadeTest.java new file mode 100644 index 0000000..a62ab6d --- /dev/null +++ b/src/test/java/com/digmia/maven/plugin/extjsbuilder/future/ParsingFacadeTest.java @@ -0,0 +1,69 @@ +package com.digmia.maven.plugin.extjsbuilder.future; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.testng.Reporter; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +/** + * Default Builder mojo testcase. + * @author fk + */ +public class ParsingFacadeTest { + + @DataProvider(name = "context") + public static Object[][] testJsFileProvider() throws IOException { + String contextInitFile = FileUtils.readFileToString(new File(ParsingFacadeTest.class.getResource("/contextInit.js").getFile()), "utf-8"); + String appFile = FileUtils.readFileToString(new File(ParsingFacadeTest.class.getResource("/app.js").getFile()), "utf-8"); + String includeFile = FileUtils.readFileToString(new File(ParsingFacadeTest.class.getResource("/testInclude.js").getFile()), "utf-8"); + + Context ctx = Context.enter(); + Scriptable root = ctx.initStandardObjects(); + + // first we need to initialize the context with dummy constructs + ctx.evaluateString(root, contextInitFile, "", 1, null); + + // fill the context with the actual app file + ctx.evaluateString(root, appFile, "", 1, null); + + // + ctx.evaluateString(root, includeFile, "", 1, null); + + + return new Object[][] { + {ctx, root} + }; + } + + @Test(dataProvider = "context") + public void testAppRequires(Context ctx, Scriptable root) { + Node global = Node.in(root, ctx).name("global"); + + Scriptable requires = global.path("defines.app.requires").get(); + NativeObject defines = (NativeObject) global.path("defines").get(); + List req = LegacyConvertor.nativeArrayToStringList((NativeArray) requires); + Map xtends = LegacyConvertor.nativeObjectToMap(defines); + + for(String s: xtends.keySet()) { + Reporter.log(s, true); + } + + Reporter.log(req.toString(), true); + } + + @Test + public void fileBoilerplate() { + File test = new File(ParsingFacadeTest.class.getResource("/contextInit.js").getFile()); + Reporter.log(String.valueOf(test.getAbsolutePath().replace(File.separator, "%").split("%").length), true); + Reporter.log(test.getAbsolutePath().substring(test.getAbsolutePath().lastIndexOf("target") + "target".length()), true); + } + + + + +} diff --git a/src/test/resources/app/app-manual.jsb2 b/src/test/resources/app/app-manual.jsb2 new file mode 100644 index 0000000..634a943 --- /dev/null +++ b/src/test/resources/app/app-manual.jsb2 @@ -0,0 +1,302 @@ +{ + "projectName": "DanteFrontend", + "licenseText": "", + "pkgs": [{ + "name": "App", "file": "app-build.js", + "fileIncludes": [{ + "path": "/", "text": "global.js" + },{ + "path": "/lib/", "text": "Util.js" + },{ + "path": "/lib/ux/", "text": "Growl.js" + },{ + "path": "/lib/base/", "text": "JsonReader.js" + },{ + "path": "/lib/base/", "text": "JsonWriter.js" + },{ + "path": "/lib/base/", "text": "Store.js" + },{ + "path": "/lib/model/", "text": "Conf.js" + },{ + "path": "/lib/resolver/", "text": "Resolver.js" + },{ + "path": "/lib/resolver/", "text": "Viewport.js" + },{ + "path": "/lib/resolver/", "text": "Modal.js" + },{ + "path": "/lib/resolver/", "text": "Handler.js" + },{ + "path": "/lib/view/", "text": "Combobox.js" + },{ + "path": "/lib/view/", "text": "ComboboxWithAdd.js" + },{ + "path": "/lib/view/", "text": "Editor.js" + },{ + "path": "/lib/view/", "text": "ExportTools.js" + },{ + "path": "/lib/view/", "text": "Grid.js" + },{ + "path": "/lib/view/", "text": "AddingGrid.js" + },{ + "path": "/lib/view/", "text": "FileGrid.js" + },{ + "path": "/lib/view/", "text": "Filter.js" + },{ + "path": "/lib/view/", "text": "Form.js" + },{ + "path": "/lib/view/", "text": "RowEditing.js" + },{ + "path": "/lib/view/", "text": "TextField.js" + },{ + "path": "/lib/view/", "text": "Window.js" + },{ + "path": "/lib/view/", "text": "StatePanel.js" + },{ + "path": "/lib/view/multistateeditor/", "text": "MultiStateEditor.js" + },{ + "path": "/lib/", "text": "Command.js" + },{ + "path": "/lib/", "text": "CommandBus.js" + },{ + "path": "/lib/", "text": "Controller.js" + },{ + "path": "/lib/", "text": "Notify.js" + },{ + "path": "/lib/", "text": "PartialController.js" + },{ + "path": "/lib/", "text": "Router.js" + },{ + "path": "/model/", "text": "Attachment.js" + },{ + "path": "/model/", "text": "Customer.js" + },{ + "path": "/model/", "text": "CustomerAddresses.js" + },{ + "path": "/model/", "text": "CustomerContact.js" + },{ + "path": "/model/", "text": "CustomerContactAndUser.js" + },{ + "path": "/model/", "text": "EmailQueue.js" + },{ + "path": "/model/", "text": "Employee.js" + },{ + "path": "/model/", "text": "Month.js" + },{ + "path": "/model/", "text": "Project.js" + },{ + "path": "/model/", "text": "Task.js" + },{ + "path": "/model/", "text": "TimeLine.js" + },{ + "path": "/model/", "text": "User.js" + },{ + "path": "/model/order/", "text": "Category.js" + },{ + "path": "/model/order/", "text": "DeliveryStatus.js" + },{ + "path": "/model/order/", "text": "ItemType.js" + },{ + "path": "/model/order/", "text": "Order.js" + },{ + "path": "/model/order/", "text": "OrderItem.js" + },{ + "path": "/model/order/", "text": "Unit.js" + },{ + "path": "/model/budget/", "text": "Balance.js" + },{ + "path": "/model/budget/", "text": "BalanceItem.js" + },{ + "path": "/model/budget/", "text": "BalanceItemUser.js" + },{ + "path": "/model/budget/", "text": "Budget.js" + },{ + "path": "/model/budget/", "text": "BudgetUser.js" + },{ + "path": "/model/budget/", "text": "ConfirmableBalanceItem.js" + },{ + "path": "/store/account/", "text": "CustomerAdditionalInfo.js" + },{ + "path": "/store/account/", "text": "CustomerAddresses.js" + },{ + "path": "/store/account/", "text": "CustomerContactsAndUsers.js" + },{ + "path": "/store/account/", "text": "LoggedCustomerContactAndUser.js" + },{ + "path": "/store/dashboard/", "text": "CurrentProjects.js" + },{ + "path": "/store/dashboard/", "text": "CurrentTasks.js" + },{ + "path": "/store/dashboard/", "text": "HotlineEmailQueues.js" + },{ + "path": "/store/order/", "text": "Attachments.js" + },{ + "path": "/store/order/", "text": "Categories.js" + },{ + "path": "/store/order/", "text": "DeliveryStatuses.js" + },{ + "path": "/store/order/", "text": "ItemTypes.js" + },{ + "path": "/store/order/", "text": "OrderItems.js" + },{ + "path": "/store/order/", "text": "Orders.js" + },{ + "path": "/store/order/", "text": "Units.js" + },{ + "path": "/store/budget/", "text": "Budgets.js" + },{ + "path": "/store/budget/", "text": "BudgetUsers.js" + },{ + "path": "/store/budget/", "text": "BalanceRenewalFrequencies.js" + },{ + "path": "/store/budget/", "text": "BalanceRenewalStrategies.js" + },{ + "path": "/store/budget/", "text": "ConfirmationUsersAmounts.js" + },{ + "path": "/store/budget/", "text": "Purposes.js" + },{ + "path": "/store/budget/", "text": "Balances.js" + },{ + "path": "/store/budget/", "text": "BalanceItems.js" + },{ + "path": "/store/budget/", "text": "ConfirmableBalanceItems.js" + },{ + "path": "/store/budget/", "text": "CurrentBalanceItems.js" + },{ + "path": "/store/budget/", "text": "BalanceItemDirections.js" + },{ + "path": "/store/budget/", "text": "BalanceItemEmployees.js" + },{ + "path": "/store/budget/", "text": "BalanceItemTypes.js" + },{ + "path": "/store/budget/", "text": "ConfirmationReasons.js" + },{ + "path": "/store/budget/", "text": "ConfirmationStatuses.js" + },{ + "path": "/store/budget/", "text": "ConfirmingUsers.js" + },{ + "path": "/store/worklog/", "text": "TimeLine.js" + },{ + "path": "/store/", "text": "Customers.js" + },{ + "path": "/store/", "text": "Employees.js" + },{ + "path": "/store/", "text": "Months.js" + },{ + "path": "/store/", "text": "Projects.js" + },{ + "path": "/store/", "text": "Suppliers.js" + },{ + "path": "/view/", "text": "Renderers.js" + },{ + "path": "/view/", "text": "Root.js" + },{ + "path": "/view/account/", "text": "EditCompanyAdditionalInfo.js" + },{ + "path": "/view/account/", "text": "EditCompanyAddresses.js" + },{ + "path": "/view/account/", "text": "EditCustomerContact.js" + },{ + "path": "/view/account/", "text": "EditCustomerContactDetails.js" + },{ + "path": "/view/account/", "text": "EditCustomerUser.js" + },{ + "path": "/view/account/", "text": "ListCustomerContacts.js" + },{ + "path": "/view/account/", "text": "ManageCompanyDetails.js" + },{ + "path": "/view/account/", "text": "ManageCustomerContacts.js" + },{ + "path": "/view/account/", "text": "ManageLoggedCustomerContactAndUser.js" + },{ + "path": "/view/dashboard/", "text": "AccountManager.js" + },{ + "path": "/view/dashboard/", "text": "CurrentProjects.js" + },{ + "path": "/view/dashboard/", "text": "CurrentTasks.js" + },{ + "path": "/view/dashboard/", "text": "HotlineEmail.js" + },{ + "path": "/view/dashboard/", "text": "HotlinePhone.js" + },{ + "path": "/view/dashboard/", "text": "Root.js" + },{ + "path": "/view/main/", "text": "Footer.js" + },{ + "path": "/view/main/", "text": "Toolbar.js" + },{ + "path": "/view/main/", "text": "Panel.js" + },{ + "path": "/view/worklog/", "text": "Filter.js" + },{ + "path": "/view/worklog/", "text": "TimeLine.js" + },{ + "path": "/view/worklog/", "text": "Root.js" + },{ + "path": "/view/worklog/", "text": "RootWithExportTools.js" + },{ + "path": "/view/order/details/", "text": "AttachmentsList.js" + },{ + "path": "/view/order/details/", "text": "BasicForm.js" + },{ + "path": "/view/order/details/", "text": "ItemsList.js" + },{ + "path": "/view/order/details/", "text": "Root.js" + },{ + "path": "/view/order/", "text": "Filter.js" + },{ + "path": "/view/order/", "text": "Root.js" + },{ + "path": "/view/budget/", "text": "BriefList.js" + },{ + "path": "/view/budget/", "text": "BalanceInfo.js" + },{ + "path": "/view/budget/balanceitems/", "text": "ConfirmationGrid.js" + },{ + "path": "/view/budget/balanceitems/", "text": "ExpenseDetailsForm.js" + },{ + "path": "/view/budget/balanceitems/", "text": "IncomeDetailsForm.js" + },{ + "path": "/view/budget/balanceitems/", "text": "DetailsForm.js" + },{ + "path": "/view/budget/balanceitems/", "text": "Filter.js" + },{ + "path": "/view/budget/balanceitems/", "text": "List.js" + },{ + "path": "/view/budget/balanceitems/", "text": "Root.js" + },{ + "path": "/view/budget/dashboard/", "text": "CurrentBalanceItemsList.js" + },{ + "path": "/view/budget/dashboard/", "text": "ExpenseConfirmationFilter.js" + },{ + "path": "/view/budget/dashboard/", "text": "ExpenseConfirmation.js" + },{ + "path": "/view/budget/dashboard/", "text": "Overview.js" + },{ + "path": "/view/budget/dashboard/", "text": "RefillForm.js" + },{ + "path": "/view/budget/dashboard/", "text": "Root.js" + },{ + "path": "/view/budget/manage/", "text": "DetailsForm.js" + },{ + "path": "/view/budget/manage/", "text": "Root.js" + },{ + "path": "/controller/account/", "text": "CompanyDetails.js" + },{ + "path": "/controller/account/", "text": "CustomerContacts.js" + },{ + "path": "/controller/account/", "text": "CustomerUserPrefs.js" + },{ + "path": "/controller/order/", "text": "Root.js" + },{ + "path": "/controller/dashboard/", "text": "Root.js" + },{ + "path": "/controller/worklog/", "text": "Root.js" + },{ + "path": "/controller/", "text": "Root.js" + },{ + "path": "/", "text": "app.js" + }] + }], + "resources": [], + "deployDir": "." +} \ No newline at end of file diff --git a/src/test/resources/app/app.js b/src/test/resources/app/app.js new file mode 100644 index 0000000..e2d38f9 --- /dev/null +++ b/src/test/resources/app/app.js @@ -0,0 +1,151 @@ +/** + * app + * App bootstrap class. + * + * @author fk + * @version 0.1 + * @date May 27, 2011 + */ + +//Ext.require([ +// 'DanteFrontend.view.Renderers', 'DanteFrontend.lib.Util' +//]); + +DanteFrontend = { + common: {} +}; + + +DanteFrontend.common.jsonDateFormat = "Y-m-d H:i:s"; +DanteFrontend.common.defaultDateFormat = "d.m.Y H:i:s"; + +/** + * todo: A role-dependent packager would be a nice addition, which would build + * a package for each role, and then these packages would be loaded by the client + * browser as necessary - only those parts of the client code would be published, + * which are necessary to run the app for that particular role. For now, + * everything is served. + * should be probably driven by comment annotations. + */ + + +DanteFrontend.appConf = { + + name: 'DanteFrontend', + + appFolder: '../static/js/app', + + requires: [ + 'DanteFrontend.lib.Util', + 'DanteFrontend.lib.Router', + 'DanteFrontend.lib.resolver.Viewport', + 'DanteFrontend.lib.resolver.Modal', + 'DanteFrontend.lib.resolver.Handler', + 'DanteFrontend.lib.Notify', + 'DanteFrontend.lib.Command', + 'DanteFrontend.lib.CommandBus', + 'DanteFrontend.lib.base.JsonReader', + 'DanteFrontend.lib.base.JsonWriter', + 'DanteFrontend.lib.ux.SchemaValidator', + 'DanteFrontend.lib.view.Window', + 'DanteFrontend.lib.view.Grid', + //'DanteFrontend.lib.view.Form', + 'DanteFrontend.lib.view.Filter', + 'DanteFrontend.lib.view.FileGrid', + /*'DanteFrontend.lib.view.Combobox', + 'DanteFrontend.lib.view.ComboboxWithAdd',*/ + 'DanteFrontend.lib.view.TextField', + 'DanteFrontend.lib.view.AddingGrid', + 'DanteFrontend.lib.view.StatePanel', + 'DanteFrontend.view.customer.SupplierDetails', + 'DanteFrontend.view.customer.SupplierDetails', + 'DanteFrontend.lib.base.Store', + 'DanteFrontend.lib.model.Conf', + 'DanteFrontend.lib.view.multistateeditor.MultiStateEditor', + 'DanteFrontend.test.Fixtures', 'DanteFrontend.test.MemoryProxy', + 'DanteFrontend.test.FileProxy', + ], + + controllers: [ + 'Root', + 'dashboard.Root', + 'worklog.Root', + 'worklog.RootWithExportTools', + 'account.CustomerContacts', + 'account.CompanyDetails', + 'account.CustomerUserPrefs', + 'order.Root', + 'budget.Dashboard', + 'budget.Manage', + 'budget.BalanceItems' + ], + + //actionResolvers: ['modal', 'default'], + + launch: function() { + this.addEvents('rootviewloaded'); + this.setCurrentController(null); this.setCurrentRootView(null); + + var lib = DanteFrontend.lib; + + Ext.create('Ext.container.Viewport', { + layout: 'fit', + items: { + xtype: 'df-root' + } + }); + + this.router = Ext.create('DanteFrontend.lib.Router'); + lib.CommandBus.init(this.router); + + //register routes to resolvers + this.router.registerRoutes(DanteFrontend.routes, this); + + //use resolve, as the history may not be changed (when reloading the same viewport) + + this.loadStartingViewport(); + }, + + loadStartingViewport: function() { + var initUrl = res('url.init'); + if(window.location.toString().indexOf(initUrl) !== -1) { + this.router.resolve(initUrl); + } else { + this.router.route(initUrl); + } + }, + + getViewport: function() { + return this.getController('Root').getContent(); + }, + + getHandler: function(handlerName) { + return this[handlerName]; + }, + + + //handlers should be probably separated to a singleton class, like renderers + logout: function() { + window.location.href = res('url.logout'); + }, + + + getCurrentController: function() { + return this._currentController; + }, + + setCurrentController: function(controller) { + this._currentController = controller; + }, + + getCurrentRootView: function() { + return this._currentRootView; + }, + + setCurrentRootView: function(rootViewInstance) { + this._currentRootView = rootViewInstance; + } +}; + +Ext.application(DanteFrontend.appConf); + diff --git a/src/test/resources/app/boiler.js b/src/test/resources/app/boiler.js new file mode 100644 index 0000000..e168b51 --- /dev/null +++ b/src/test/resources/app/boiler.js @@ -0,0 +1,98 @@ +/** + * boiler + * boilerplate testing ground + * + * @author fk + * @version 0.1 + * @date Aug 18, 2011 + */ + + +Ext.Loader.setConfig({ + enabled: true, + paths: { + 'DanteFrontend': '../static/js/app' + } +}); + +Ext.syncRequire(['DanteFrontend.test.Fixtures', 'DanteFrontend.test.MemoryProxy']); +Ext.syncRequire(['DanteFrontend.lib.base.JsonReader','DanteFrontend.lib.base.JsonWriter', 'DanteFrontend.lib.base.Store', 'DanteFrontend.model.Customer', 'DanteFrontend.store.Suppliers']); + + +Ext.application({ + name: 'DanteFrontend', + + appFolder: '../static/js/app', + + requires: [ + 'DanteFrontend.lib.Util', + 'DanteFrontend.lib.Router', + 'DanteFrontend.lib.resolver.Viewport', + 'DanteFrontend.lib.resolver.Modal', + 'DanteFrontend.lib.resolver.Handler', + 'DanteFrontend.lib.Notify', + 'DanteFrontend.lib.Command', + 'DanteFrontend.lib.CommandBus', + 'DanteFrontend.lib.ux.SchemaValidator', + 'DanteFrontend.lib.view.Window', + 'DanteFrontend.lib.view.Grid', + 'DanteFrontend.lib.view.Form', + 'DanteFrontend.lib.view.Filter', + 'DanteFrontend.lib.view.FileGrid', + 'DanteFrontend.lib.view.Combobox', + 'DanteFrontend.lib.view.ComboboxWithAdd', + 'DanteFrontend.view.customer.SupplierDetails', +// 'DanteFrontend.model.Customer', +// 'DanteFrontend.store.Suppliers', + 'DanteFrontend.test.Fixtures', + 'DanteFrontend.test.MemoryProxy', + 'DanteFrontend.test.FileProxy' + ], + + stores: ['Suppliers'], + models: ['Customer'], + + launch: function() { + + var store = Ext.data.StoreManager.lookup('Suppliers'); + store.load(Ext.bind(function() { + Ext.create('Ext.container.Viewport', { + layout: 'anchor', + items: { + xtype: 'df-combo-add', + width: 120, + store: 'Suppliers', + valueField: 'id', + displayField: 'name', + queryMode: 'local', + + windowTitle: g('Add supplier'), + addForm: { + xtype: 'df-customer-supplier-details' + } + } + }); + }, this)); + +// var fileGrid = Ext.widget('df-grid-file', { +// width: 300, +// height: 200, +// store: store, +// ref: 'attachments', +// margin: '0 0 6 0', +// api: { +// submit: orderController.attachToOrder +// } +// }); +// +// fileGrid.setFilter({ +// property: 'orderId', +// value: 1 +// }); +// + +// +// store.load(); + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/Root.js b/src/test/resources/app/controller/Root.js new file mode 100644 index 0000000..1811eaf --- /dev/null +++ b/src/test/resources/app/controller/Root.js @@ -0,0 +1,48 @@ +/** + * Root + * The root controller. Handles viewport state changes - popups, main panel state changes, etc. + * + * @author fk + * @version 0.1 + * @date May 27, 2011 + */ + +//pre-init libs which are referenced as singleton classes in +Ext.require([ + 'DanteFrontend.view.Renderers', 'DanteFrontend.lib.Util' +]); + +Ext.define('DanteFrontend.controller.Root', { + extend: 'Ext.app.Controller', + + views: [ + 'Root', + 'main.Panel', + 'main.Footer', + 'main.Toolbar' + ], + + refs: [{ + ref: 'content', selector: 'df-main-panel' + }], + + init: function() { + //xpath-like comma-separation does not work with component queries (yet?) + this.control({ + 'df-main-toolbar button': { + click: this.clickHandler + }, + 'df-main-toolbar menuitem': { + click: this.clickHandler + } + }); + + }, + + clickHandler: function(invoker) { + if(Ext.isDefined(invoker.action)) { + if(this.application.router) + this.application.router.route(invoker.action, invoker.text); + } + } +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/account/CompanyDetails.js b/src/test/resources/app/controller/account/CompanyDetails.js new file mode 100644 index 0000000..942d21d --- /dev/null +++ b/src/test/resources/app/controller/account/CompanyDetails.js @@ -0,0 +1,77 @@ +/** + * CompanyDetails + * + * @author fk + * @version 0.1 + * @date Jun 23, 2011 + */ + +Ext.define('DanteFrontend.controller.account.CompanyDetails', { + extend: 'DanteFrontend.lib.Controller', + + stores: ['account.CustomerAddresses', 'account.CustomerAdditionalInfo'], + models: ['Customer','CustomerAddresses'], + views: ['account.EditCompanyAdditionalInfo', 'account.EditCompanyAddresses', + 'account.ManageCompanyDetails' + ], + + editors: [{ + editor: 'account.EditCompanyAddresses', + store: 'account.CustomerAddresses', + handler: 'saveCompanyAddress' + },{ + editor: 'account.EditCompanyAdditionalInfo', + store: 'account.CustomerAdditionalInfo' + }], + + refs: [{ + selector: 'df-account-editcompanyaddr', + ref: 'companyAddressEditor' + },{ + selector: 'df-account-editcompanyaddinfo', + ref: 'companyAddInfoEditor' + },{ + selector: 'df-account-editcompanyaddr fieldset[ref=billingAddressPanel]', + ref: 'billingAddressFieldset' + }], + + rootView: 'df-account-managecompanydetails', + + init: function() { + this.callParent(arguments); + }, + + saveCompanyAddress: function() { + var editor = this.getCompanyAddressEditor(); + var store = this.getStore('account.CustomerAddresses'); + if(editor.validateAndUpdate()) { + if (!this.getBillingAddressFieldset().collapsed) { + editor.accessRecord().set('hasBillingAddress', true); + } + store.sync(); + } + }, + + onStoresLoaded: function() { + + this.getCompanyAddressEditor().setRecordAccessor(function() { + return this.getStore('account.CustomerAddresses').getAt(0); + }, this); + + this.getCompanyAddInfoEditor().setRecordAccessor(function() { + return this.getStore('account.CustomerAdditionalInfo').getAt(0); + }, this); + + + //var addressRecord = this.getCompanyAddressEditor().accessAndLoad(); + var addressRecord = this.getCompanyAddressEditor().accessAndLoad(); + this.getCompanyAddInfoEditor().accessAndLoad(); + + if(addressRecord.get('hasBillingAddress') == true) + this.getBillingAddressFieldset().expand(); + } + + + + +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/account/CustomerContacts.js b/src/test/resources/app/controller/account/CustomerContacts.js new file mode 100644 index 0000000..73280e2 --- /dev/null +++ b/src/test/resources/app/controller/account/CustomerContacts.js @@ -0,0 +1,127 @@ +/** + * Customer contacts editor controller. + * + * TODO: + * - refactor most of this functionality to an 'editor' component. This should be done if there's + * more 'editor' components to be deployed, it's unnecessary now. + * + * @author fk + * @version 0.1 + * @date Jun 16, 2011 + */ + +//Ext.require('DanteFrontend.view.dashboard.Root'); +Ext.define('DanteFrontend.controller.account.CustomerContacts', { + extend: 'DanteFrontend.lib.Controller', + + stores: ['account.CustomerContactsAndUsers'], + models: ['CustomerContactAndUser','CustomerContact', 'User'], + views: ['account.EditCustomerContactDetails', 'account.EditCustomerUser', + 'account.EditCustomerContact', 'account.ListCustomerContacts', + 'account.ManageCustomerContacts' + ], + + config: { + currentIndex: null + }, + + editors: [{ + editor: 'account.EditCustomerContact', + store: 'account.CustomerContactsAndUsers', + handler: 'save' + }], + + rootView: 'df-account-managecc', + + refs: [{ + selector: 'df-account-managecc', + ref: 'root' + },{ + selector: 'df-account-managecc df-account-listcc', + ref: 'list' + },{ + selector: 'df-account-managecc df-account-editcc', + ref: 'editor' + },{ + selector: 'df-account-managecc df-account-editccuser', + ref: 'userEditor' + },{ + selector: 'df-account-editcc button', + ref: 'createUserButton' + }], + + init: function() { + this.control({ + 'df-account-managecc': { + afterrender: this.onRootViewRender + }, + 'df-account-managecc df-account-listcc': { + select: this.onListRowSelect, + itemremoved: this.onListItemRemoved + } + }); + this.callParent(arguments); + }, + + + onRootViewRender: function() { + this.getList().down('headercontainer').on('sortchange', function() { + var rec = DanteFrontend.lib.Util.first(this.getList().getSelectionModel().getSelection()); + if(rec) { + this.setCurrentIndex(rec.index); + } + }, this); + + var store = this.getAccountCustomerContactsAndUsersStore(); + store.on('aftersync', function() { + this.getList().getSelectionModel().select(this.getCurrentIndex()); + }, this); + + this.getEditor().hide(); + this.getRoot().setContactText(false); + + var first = store.getAt(0); + if(first) this.loadRecord(first); + + }, + + onListRowSelect: function(cmp, record) { + this.loadRecord(record); + }, + + onListItemRemoved: function(item, grid) { + var hasRecords = grid.getStore().count() > 0; + if(hasRecords) { + selModel.select(0); + } else { + this.getRoot().setContactText(false); + this.getEditor().hide(); + } + }, + + loadRecord: function(record) { + this.setCurrentIndex(!Ext.isEmpty(record.index) ? record.index: + this.getAccountCustomerContactsAndUsersStore().lastIndex); + + this.getEditor().loadRecord(record); + this.getEditor().show(); + this.getRoot().setContactText(record.data.name); + }, + +// setCurrentIndex: function(index) { +// this.currentIndex = Ext.isEmpty(index) ? this.getList().getSelectionModel().getLastSelected() +// }, + + save: function() { + var store = this.getAccountCustomerContactsAndUsersStore(); + if(this.getEditor().updateAccessorRecord()) { + store.sync(); + } + }, + + onStoresLoaded: function() { + this.getEditor().setRecordAccessor(function() { + return this.getAccountCustomerContactsAndUsersStore().getAt(this.getCurrentIndex()); + }, this); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/account/CustomerUserPrefs.js b/src/test/resources/app/controller/account/CustomerUserPrefs.js new file mode 100644 index 0000000..39694b2 --- /dev/null +++ b/src/test/resources/app/controller/account/CustomerUserPrefs.js @@ -0,0 +1,41 @@ +/** + * UserPreferences + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 23, 2011 + */ + +Ext.define('DanteFrontend.controller.account.CustomerUserPrefs', { + extend: 'DanteFrontend.lib.Controller', + + stores: ['account.LoggedCustomerContactAndUser'], + //models: ['Customer','CustomerAddresses'], + views: ['account.ManageLoggedCustomerContactAndUser','account.EditCustomerContact'], + + editors: [{ + editor: 'account.EditCustomerContact', + store: 'account.LoggedCustomerContactAndUser' + }], + + refs: [{ + selector: 'df-account-manageloggedccu df-account-editcc', + ref: 'editor' + }], + + rootView: 'df-account-manageloggedccu', + + init: function() { + this.callParent(arguments); + }, + + onStoresLoaded: function() { + var e = this.getEditor(); + e.setRecordAccessor(function() { + return this.getAccountLoggedCustomerContactAndUserStore().getAt(0); + }, this); + + e.accessAndLoad(); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/budget/BalanceItems.js b/src/test/resources/app/controller/budget/BalanceItems.js new file mode 100644 index 0000000..6545449 --- /dev/null +++ b/src/test/resources/app/controller/budget/BalanceItems.js @@ -0,0 +1,232 @@ +/** + * BalanceItems + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 4, 2011 + */ + +Ext.define('DanteFrontend.controller.budget.BalanceItems', { + extend: 'DanteFrontend.lib.Controller', + + stores: ['budget.Budgets', 'budget.BudgetUsers', 'budget.Balances', 'budget.BalanceItems', + 'budget.Purposes', 'Months', 'budget.ConfirmableBalanceItems', 'budget.ConfirmingUsers', + 'budget.BalanceItemEmployees', 'budget.BalanceItemDirections', + 'budget.BalanceItemTypes', 'Employees', 'budget.ConfirmationStatuses', 'budget.ConfirmationReasons'], + + models: ['budget.Budget','budget.BalanceItem', 'budget.ConfirmableBalanceItem','budget.BalanceItemUser'], + + views: ['budget.balanceitems.Root', 'budget.balanceitems.List', + 'budget.balanceitems.Filter', 'budget.balanceitems.ExpenseDetailsForm', + 'budget.balanceitems.IncomeDetailsForm', 'budget.balanceitems.DetailsForm', + 'budget.balanceitems.ConfirmationGrid'], + + refs: [{ + selector: 'df-budget-balanceitems df-statepanel[ref=details]', + ref: 'details' + },{ + selector: 'df-budget-balanceitems *[ref=expenseForm]', + ref: 'expenseForm' + },{ + selector: 'df-budget-balanceitems *[ref=incomeForm]', + ref: 'incomeForm' + },{ + selector: 'df-budget-balanceitems df-budget-balanceitems-details', + ref: 'editor' + },{ + selector: 'df-budget-balanceitems df-budget-balanceitems-filter', + ref: 'filter' + },{ + selector: 'df-budget-balanceitems df-budget-balanceitems-list', + ref: 'list' + },{ + selector: 'df-budget-balanceitems *[ref=expenseForm] *[ref=confirmation] df-grid', + ref: 'confirmationGrid' + },{ + selector: 'df-budget-balanceitems *[ref=expenseForm] *[ref=associatedEmployees] df-grid-adding', + ref: 'associatedEmployeesGrid' + }], + + rootView: 'df-budget-balanceitems', + + init: function() { + this.control({ + + 'df-budget-balanceitems df-msedit': { + selectitem: this.loadItem, + createitem: this.loadItem + + }, + 'df-budget-balanceitems df-budget-balanceitems-filter': { + render: function(v) { + v.down('df-combo[name=balance_id]').hide(); + } + }, + 'df-budget-balanceitems df-budget-balanceitems-filter df-combo[name=budget_id]': { + select: function(v, rec) { + var bc = this.getFilter().down('df-combo[name=balance_id]'), + bs = bc.store; + + //bs.clearFilter(); + bs.filter({ + property: 'budget_id', value: rec[0].getId() + }); + + bc.show(); + bc.select(rec[0].get('currentBalance_id')); + + this.getFilter().down('df-combo[name=receivedOn]').hide(); + }, + + clear: function(v) { + var bc = this.getFilter().down('df-combo[name=balance_id]'); + bc.hide(); + this.getFilter().down('df-combo[name=receivedOn]').show(); + } + }, + 'df-budget-balanceitems df-budget-balanceitems-list button[action=addExpense]': { + click: this.addExpense + }, + + 'df-budget-balanceitems df-budget-balanceitems-list button[action=addIncome]': { + click: this.addIncome + }, + 'df-budget-balanceitems *[ref=expenseForm]': { + save: this.saveItem + }, + 'df-budget-balanceitems *[ref=incomeForm]': { + save: this.saveItem + }, + + 'df-budget-balanceitems *[ref=expenseForm] *[ref=associatedEmployees] df-grid-adding': { + itemadded: this.attachEmployeeToExpense + }, + + 'df-budget-balanceitems *[ref=expenseForm] *[ref=confirmation] button[action=confirm]': { + click: this.confirmExpenseItem + }, + + 'df-budget-balanceitems *[ref=expenseForm] *[ref=confirmation] button[action=unconfirm]': { + click: this.unconfirmExpenseItem + } + }); + + }, + + + loadItem: function(r) { + r.isExpense() ? this.showExpenseForm(r):this.showIncomeForm(r); + this.getEditor().accessAndLoad(); + }, + + showExpenseForm: function(r) { + this.getEditor().showExpenseForm(); + var f = this.getExpenseForm(); + if(r.phantom) { + f.down('*[ref=confirmation]').disable(); + f.down('*[ref=associatedEmployees]').disable(); + } else { + r.get('confirmationStatus_id') > 0 ? + f.down('*[ref=confirmation]').enable(): + f.down('*[ref=confirmation]').disable(); + + f.down('*[ref=associatedEmployees]').enable(); + + var cgs = this.getConfirmationGrid().getStore(), + aes = this.getAssociatedEmployeesGrid().getStore(), + flt = { + property: 'balanceItem', + value: r.getId() + }; + + //cgs.clearFilter(); + cgs.filter(flt); + //aes.clearFilter(); + aes.filter(flt); + + this.switchConfirmationButtons(r); + } + + }, + + + showIncomeForm: function() { + this.getEditor().showIncomeForm(); + }, + + + switchConfirmationButtons: function(r) { + this.getConfirmationGrid().switchButtons( + this.getBudgetConfirmableBalanceItemsStore().getById(r.getId()) + ); + }, + + + confirmExpenseItem: function() { + var rec = this.getExpenseForm().accessRecord(), + me = this; + + budgetController.confirmBalanceItem(rec.getId(), function() { + me.getBudgetBalanceItemsStore().load({ + callback: function() { + var newRec = me.getBudgetBalanceItemsStore().getById(rec.getId()); + me.getList().getSelectionModel().select(newRec); + me.loadItem(newRec); + } + }) + }); + }, + + unconfirmExpenseItem: function() { + var rec = this.getExpenseForm().accessRecord(), + me = this; + + budgetController.unconfirmBalanceItem(rec.getId(), function() { + me.getBudgetBalanceItemsStore().load({ + callback: function() { + var newRec = me.getBudgetBalanceItemsStore().getById(rec.getId()); + me.getList().getSelectionModel().select(newRec); + me.loadItem(newRec); + } + }) + }); + }, + + addExpense: function() { + var g = this.getList(), + bs = this.getBudgetBudgetsStore(); + + g.addItem(Ext.create('DanteFrontend.model.budget.BalanceItem', Ext.apply( + DanteFrontend.model.budget.BalanceItem.instanceDefaults, { + direction_id: 1, + budget_id: bs.getAt(0).getId() + }))); + }, + + addIncome: function() { + var g = this.getList(), + bs = this.getBudgetBudgetsStore(); + + g.addItem(Ext.create('DanteFrontend.model.budget.BalanceItem', Ext.apply( + DanteFrontend.model.budget.BalanceItem.instanceDefaults, { + direction_id: 0, + budget_id: bs.getAt(0).getId() + }))); + }, + + saveItem: function() { + if(this.getEditor().validateAndUpdate()) { + this.getBudgetBalanceItemsStore().sync(); + } + }, + + attachEmployeeToExpense: function(v, r) { + var s = v.getStore(); + s.add(Ext.create('DanteFrontend.model.budget.BalanceItemUser', { + user_id: r.getId(), + balanceItem_id: this.getEditor().accessRecord().getId() + })); + s.sync(); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/budget/Dashboard.js b/src/test/resources/app/controller/budget/Dashboard.js new file mode 100644 index 0000000..0691bc3 --- /dev/null +++ b/src/test/resources/app/controller/budget/Dashboard.js @@ -0,0 +1,271 @@ +/** + * Overview + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.controller.budget.Dashboard', { + extend: 'DanteFrontend.lib.Controller', + + stores: ['budget.Budgets', 'budget.Balances', 'budget.CurrentBalanceItems', + 'budget.BalanceRenewalFrequencies', 'budget.BalanceRenewalStrategies', + 'budget.Purposes', 'budget.ConfirmableBalanceItems', 'budget.ConfirmationReasons', + 'budget.BalanceItemTypes', 'budget.ConfirmationStatuses', 'Months', 'budget.BalanceItemEmployees', 'Employees'], + + models: ['budget.Budget', 'budget.Balance', 'budget.BalanceItem', 'budget.ConfirmableBalanceItem'], + + views: ['budget.dashboard.Root', 'budget.BriefList', + 'budget.dashboard.Overview', 'budget.BalanceInfo', + 'budget.dashboard.CurrentBalanceItemsList', 'budget.dashboard.RefillForm', + 'budget.dashboard.ExpenseConfirmation', + 'budget.dashboard.ExpenseConfirmationFilter' + ], + + refs: [{ + selector: 'df-budget-dashboard df-budget-list', + ref: 'budgetList' + },{ + selector: 'df-budget-dashboard df-budget-balanceinfo', + ref: 'balanceInfo' + },{ + selector: 'df-budget-dashboard df-budget-dashboard-overview *[ref=balancePanel]', + ref: 'balancePanel' + },{ + selector: 'df-budget-dashboard df-budget-dashboard-currentbalanceitems', + ref: 'currentBalanceItemsList' + },{ + selector: 'df-budget-dashboard df-budget-expenseconfirmation', + ref: 'expenseConfirmationPanel' + },{ + selector: 'df-budget-dashboard df-budget-dashboard-overview button[ref=nextBalance]', + ref: 'nextButton' + },{ + selector: 'df-budget-dashboard df-budget-dashboard-overview button[ref=prevBalance]', + ref: 'prevButton' + },{ + selector: 'df-budget-dashboard df-budget-dashboard-overview *[ref=newBalance]', + ref: 'newBalanceForm' + },{ + selector: 'df-budget-dashboard df-budget-dashboard-refill *[ref=refill]', + ref: 'refillForm' + }], + + config: { currentBalanceId: null, currentBudgetId: null }, + + rootView: 'df-budget-dashboard', + + init: function() { + this.control({ + 'df-budget-dashboard df-budget-list': { + select: this.loadBudget + }, + + 'df-budget-dashboard df-budget-dashboard-overview button[ref=nextBalance]': { + click: this.loadNextBalance + }, + + 'df-budget-dashboard df-budget-dashboard-overview button[ref=prevBalance]': { + click: this.loadPreviousBalance + }, + + 'df-budget-dashboard df-budget-dashboard-overview combobox[name=balance_id]': { + select: function(v, rs) { + this.setBalance(rs[0].getId()); + } + }, + + + 'df-budget-dashboard df-budget-expenseconfirmation df-grid': { + selectionchange: function(sm, data) { + if(!Ext.isEmpty(data)) { + this.updateExpenseConfirmationDetails(data[0]); + } + } + }, + 'df-budget-dashboard df-budget-expenseconfirmation panel[ref=detail]': { + afterrender: function() { + var g = this.getExpenseConfirmationPanel().down('df-grid'); + var rec = g.getStore().getAt(0); + + //if(rec) this.updateExpenseConfirmationDetails(rec); + + this.attachConfirmationHandlers(); + } + }, + + 'df-budget-dashboard df-budget-dashboard-refill panel[ref=newBalance] button': { + click: this.renewBalance + }, + + 'df-budget-dashboard df-budget-dashboard-refill panel[ref=refill] button': { + click: this.refill + } + + }); + }, + + + attachConfirmationHandlers: function() { + this.getExpenseConfirmationPanel().setHandlerScope(this); + this.getExpenseConfirmationPanel().setConfirmHandler(this.confirmExpenseItem); + this.getExpenseConfirmationPanel().setUnconfirmHandler(this.unconfirmExpenseItem); + }, + + confirmExpenseItem: function(v, rowIndex) { + var rec = v.getRecord(v.getNode(rowIndex)); + //v.setLoading(true); + budgetController.confirmBalanceItem(rec.getId(), function() { + v.getStore().load(); + //v.setLoading(false); + }); + }, + + unconfirmExpenseItem: function(v, rowIndex) { + var rec = v.getRecord(v.getNode(rowIndex)); + //v.setLoading(true); + budgetController.unconfirmBalanceItem(rec.getId(), function() { + v.getStore().load(); + //v.setLoading(false); + }); + }, + + updateExpenseConfirmationDetails: function(rec) { + var s = this.getBudgetBalanceItemEmployeesStore(), + es = this.getEmployeesStore(), + d = this.getExpenseConfirmationPanel(), + data = rec.data; + + s.filter({ property: 'balanceItem', value: rec.getId()}, null, { + callback: function() { + var employees = []; + s.each(function(balanceItemEmployee) { + var foundEmployee = es.getById(balanceItemEmployee.get('user_id')); + if(foundEmployee) employees.push(foundEmployee.get('name')); + }); + + data.associatedEmployees = employees.join(", "); + d.updateDetails.apply(d, [data]); + } + }); + + }, + + //budget overview logic (separate controller?) + + loadBudget: function(v, r) { + this.setCurrentBudgetId(r.getId()); + + //this.getBalancePanel().setLoading(true); + this.getBalancePanel().setTitle(r.get('title')); + + this.constrainBalanceStoreToBudgetId(r); + + r.get('balanceRenewalStrategy_id') == 0 ? this.getNewBalanceForm().show():this.getNewBalanceForm().hide(); + }, + + constrainBalanceStoreToBudgetId: function(r) { + if(r != null) { + var bs = this.getBudgetBalancesStore(); + //bs.clearFilter(); + bs.filter({ property: 'budget_id', value: r.getId() }); + console.log(bs.filters); + this.setBalance(r.get('currentBalance_id'), r); + bs.sort('from', 'DESC'); + } + }, + + setBalance: function(balanceId, budgetRecord) { + if(balanceId) { + var balance = this.getBudgetBalancesStore().getById(balanceId), + budget = budgetRecord ? budgetRecord:this.getBudgetBudgetsStore().getById(balance.get('budget_id')), + bis = this.getCurrentBalanceItemsList().getStore(); + + //bis.clearFilter(); + + bis.filter({ property: 'balance', value: balanceId} , null, { + callback: function() { + this.getBalanceInfo().loadRecord(budget, balance); + }, + scope: this + }); + + this.setCurrentBalanceId(balanceId); + this.toggleNavButtons(balanceId); + console.log( this.getBudgetBalancesStore()); + this.getBalancePanel().down('toolbar combobox').select(balanceId); + } + //this.getBalancePanel().setLoading(false); + }, + + toggleNavButtons: function(id) { + this.getNextButton().setDisabled(!this.getNextBalanceId(id)); + this.getPrevButton().setDisabled(!this.getPrevBalanceId(id)); + }, + + getNextBalanceId: function(id) { + var s = this.getBudgetBalancesStore(); + var nextBalance = s.getAt(s.find('previousBalance_id', id)); + return nextBalance ? nextBalance.get('id'):null; + }, + + getPrevBalanceId: function(id) { + var b = this.getBudgetBalancesStore().getById(id); + return b ? b.get('previousBalance_id'):null; + }, + + loadNextBalance: function() { + var balanceId = this.getNextBalanceId(this.getCurrentBalanceId()); + if(balanceId) this.setBalance(balanceId); + }, + + loadPreviousBalance: function() { + var balanceId = this.getPrevBalanceId(this.getCurrentBalanceId()); + if(balanceId) this.setBalance(balanceId); + }, + + renewBalance: function() { + var balStore = this.getBudgetBalancesStore(); + + balStore.add(Ext.create('DanteFrontend.model.budget.Balance', + { budget_id: this.getCurrentBudgetId() } + )); + + balStore.sync({ + callback: function() { + this.loadBudget(null, + this.getBudgetBudgetsStore().getById(this.getCurrentBudgetId()) + ); + }, + scope: this + }); + }, + + refill: function() { + + var balanceItemStore = this.getBudgetCurrentBalanceItemsStore(), + refillField = this.getRefillForm().down('numberfield[name=sum]'); + + balanceItemStore.add(Ext.create('DanteFrontend.model.budget.BalanceItem', { + balance_id: this.getCurrentBalanceId(), + budget_id: this.getCurrentBudgetId(), + direction_id: 0, + type_id: 4, + grandTotal: refillField.getValue(), + amount: 1, + description: g("Refill") + })); + + + //this.getBalancePanel().setLoading(true); + balanceItemStore.sync({ + callback: function() { + this.setBalance(this.getCurrentBalanceId()) + }, + scope: this + }); + + } +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/budget/History.js b/src/test/resources/app/controller/budget/History.js new file mode 100644 index 0000000..9ab2b3b --- /dev/null +++ b/src/test/resources/app/controller/budget/History.js @@ -0,0 +1,25 @@ +/** + * History + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 10, 2011 + */ + +Ext.define('DanteFrontend.controller.budget.History', { + extend: 'DanteFrontend.lib.Controller', + + stores: ['budget.Budgets','budget.BalanceItems', 'budget.Balances'], + + models: ['budget.Budget','budget.BalanceItem','budget.Balance'], + + views: ['budget.history.Root', 'budget.dashboard.CurrentBalanceItemsList', + 'budget.BalanceInfo', 'budget.BalanceList'], + + rootView: 'df-budget-history', + + init: function() { + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/budget/Manage.js b/src/test/resources/app/controller/budget/Manage.js new file mode 100644 index 0000000..778422a --- /dev/null +++ b/src/test/resources/app/controller/budget/Manage.js @@ -0,0 +1,82 @@ +/** + * Overview + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.controller.budget.Manage', { + extend: 'DanteFrontend.lib.Controller', + + stores: ['budget.Budgets', 'budget.Balances', + 'budget.BalanceRenewalFrequencies', 'budget.BalanceRenewalStrategies', + 'budget.ConfirmationUsersAmounts', 'budget.Purposes', 'budget.BudgetUsers', 'Employees'], + + models: ['budget.Budget', 'budget.Balance', 'budget.BalanceItem', 'budget.BudgetUser'], + + views: ['budget.manage.Root', 'budget.BriefList', 'budget.manage.DetailsForm'], + + refs: [{ + selector: 'df-budget-manage df-budget-manage-details', + ref: 'editor' + },{ + selector: 'df-budget-manage df-budget-manage-details df-grid-adding', + ref: 'confirmingUsersGrid' + }], + + + rootView: 'df-budget-manage', + + init: function() { + this.control({ + 'df-budget-manage df-msedit': { + selectitem: this.loadBudget, + createitem: this.loadBudget + }, + 'df-budget-manage df-budget-manage-details': { + save: this.saveBudget + }, + 'df-budget-manage df-budget-manage-details df-grid-adding': { + itemadded: this.attachUser + } + }); + }, + + loadBudget: function(r) { + this.getEditor().accessAndLoad(); + (r.phantom) ? this.loadPhantom(r):this.loadExisting(r); + }, + + loadPhantom: function(r) { + this.getConfirmingUsersGrid().disable(); + }, + + loadExisting: function(r) { + var g = this.getConfirmingUsersGrid(), + flt = { + property: 'budget', + value: r.getId() + }; + + g.getStore().filter(flt); + g.enable(); + }, + + saveBudget: function(v, r) { + if(this.getEditor().validateAndUpdate()) { + this.getBudgetBudgetsStore().sync(); + } + }, + + attachUser: function(v, r) { + var s = v.getStore(); + r.set('budget_id', this.getEditor().accessRecord().getId()); + r.set('canConfirm', 1); + s.add(r); + s.sync(); + } + + +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/dashboard/CurrentProjects.js b/src/test/resources/app/controller/dashboard/CurrentProjects.js new file mode 100644 index 0000000..50ceb3b --- /dev/null +++ b/src/test/resources/app/controller/dashboard/CurrentProjects.js @@ -0,0 +1,18 @@ +/** + * CurrentProjects + * + * @author fk + * @version 0.1 + * @date Jun 3, 2011 + */ + +//store: DanteFrontend.dashboard.CurrentProjectsStore, + + +Ext.define('DanteFrontend.controller.dashboard.CurrentProjects', { + extend: 'Ext.app.Controller', + requires: ['DanteFrontend.view.Renderers', 'DanteFrontend.view.dashboard.Renderers'], + views: ['dashboard.CurrentProjects'], + stores: ['dashboard.CurrentProjects'], + models: ['Project'] +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/dashboard/CurrentTasks.js b/src/test/resources/app/controller/dashboard/CurrentTasks.js new file mode 100644 index 0000000..72a4596 --- /dev/null +++ b/src/test/resources/app/controller/dashboard/CurrentTasks.js @@ -0,0 +1,16 @@ +/** + * CurrentTasks + * + * @author fk + * @version 0.1 + * @date Jun 3, 2011 + */ + + +Ext.define('DanteFrontend.controller.dashboard.CurrentTasks', { + extend: 'Ext.app.Controller', + requires: ['DanteFrontend.view.Renderers', 'DanteFrontend.view.dashboard.Renderers'], + views: ['dashboard.CurrentTasks'], + stores: ['dashboard.CurrentTasks'], + models: ['Task'] +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/dashboard/Root.js b/src/test/resources/app/controller/dashboard/Root.js new file mode 100644 index 0000000..47cf790 --- /dev/null +++ b/src/test/resources/app/controller/dashboard/Root.js @@ -0,0 +1,46 @@ +/** + * Root dashboard panel controller. + * + * @author fk + * @version 0.1 + * @date Jun 2, 2011 + */ + +Ext.define('DanteFrontend.controller.dashboard.Root', { + extend: 'DanteFrontend.lib.Controller', + models: ['Task','Project','EmailQueue'], + stores: ['dashboard.CurrentTasks', 'dashboard.CurrentProjects', 'dashboard.HotlineEmailQueues'], + views: ['dashboard.Root', + 'dashboard.CurrentProjects', 'dashboard.CurrentTasks', + 'dashboard.AccountManager', + 'dashboard.HotlineEmail','dashboard.HotlinePhone'], + refs: [{ + selector: 'df-dashboard-hotlineemail', + ref: 'hotlineForm' + }], + + rootView: 'df-dashboard-root', + + init: function() { + this.control({ + 'button[action=sendRequest]': { + click: this.sendHotlineEmail + } + }); + + this.callParent(arguments); + }, + + sendHotlineEmail: function(){ + this.getHotlineForm().getForm().submit({ + waitTitle: g("Hotline Contact"), + waitMsg: g("Please wait, sending e-mail to specified queue..."), + success: function(f, a) { + DanteFrontend.lib.Notify.success.hotlineEmail(); + }, + failure: function(f, a) { + DanteFrontend.lib.Notify.error.hotlineEmail(); + } + }); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/main/Panel.js b/src/test/resources/app/controller/main/Panel.js new file mode 100644 index 0000000..38ca57a --- /dev/null +++ b/src/test/resources/app/controller/main/Panel.js @@ -0,0 +1,20 @@ +/** + * MainPanel + * Main content panel controller. + * + * @author fk + * @version 0.1 + * @date May 27, 2011 + */ + +Ext.define('DanteFrontend.controller.main.Panel', { + extend: 'Ext.app.Controller', + + views: ['main.Panel'], + refs: [{ ref: 'content', selector: 'df-main-panel'}], + + loadController: function(controller) { + console.log(controller.getContent()); + //this.getContent().add(controller.getContent()); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/order/Details.js b/src/test/resources/app/controller/order/Details.js new file mode 100644 index 0000000..5ba78e5 --- /dev/null +++ b/src/test/resources/app/controller/order/Details.js @@ -0,0 +1,8 @@ +/** + * Details + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 8, 2011 + */ diff --git a/src/test/resources/app/controller/order/Root.js b/src/test/resources/app/controller/order/Root.js new file mode 100644 index 0000000..2716693 --- /dev/null +++ b/src/test/resources/app/controller/order/Root.js @@ -0,0 +1,126 @@ +/** + * Root orders controller. + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 3, 2011 + */ + +//Ext.require('DanteFrontend.model.order.Attachment'); +Ext.define('DanteFrontend.controller.order.Root', { + extend: 'DanteFrontend.lib.Controller', + //, + + stores: ['order.Orders', 'order.OrderItems', 'order.DeliveryStatuses', + 'order.ItemTypes', 'order.Units', 'order.Attachments', + 'Customers', 'Suppliers'], + models: ['order.Order', 'order.OrderItem', 'order.DeliveryStatus', + 'order.ItemType', 'order.Unit', 'Attachment', + 'Customer'], + + views: ['order.Root', + 'order.details.Root', + 'order.details.BasicForm', + 'order.details.ItemsList', + 'order.Filter' + ], + +// editors: [{ +// editor: 'order.details.Root', +// store: 'order.Orders', +// handler: 'saveOrder' +// }], + + refs: [{ + ref: 'orderItemGrid', + selector: 'df-order-details-items' + },{ + ref: 'attGrid', + selector: 'df-order-details df-grid-file[ref=attachments]' + },{ + ref: 'editor', + selector: 'df-order-details' + },{ + ref: 'multiStateEditor', + selector: 'df-order-root df-msedit' + }], + + rootView: 'df-order-root', + + init: function() { + this.control({ + 'df-order-root df-msedit': { + selectitem: this.loadOrder, + createitem: this.addOrder + }, + + 'df-order-details-items': { + edit: this.saveOrderItem + }, + + 'df-order-details': { + save: this.saveOrder + } + }); + + this.getOrderOrderItemsStore().on('aftersync', function() { + var mse = this.getMultiStateEditor(); + var index = mse.getCurrentIndex(); + mse.getView('briefGrid').getStore().load({ + callback: function() { + mse.getView('briefGrid').getView().select(index); + }, + scope: this + }); + }, this); + + this.callParent(arguments); + }, + + onStoresLoaded: function() {}, + + addOrder: function(r) { + this.loadOrder(r); + }, + + loadOrder: function(r) { + this.getEditor().accessAndLoad(); + (r.phantom) ? this.loadPhantom(r):this.loadExisting(r); + }, + + loadExisting: function(r) { + var flt = { + property: 'order_id', + value: r.getId() + }; + + this.getAttGrid().up('fieldset').enable(); + this.getOrderItemGrid().enable(); + + this.getAttGrid().setFilter(flt); + this.getAttGrid().getStore().load(); + this.getOrderItemGrid().getStore().filter(flt); + }, + + loadPhantom: function(r) { + this.getAttGrid().up('fieldset').disable(); + this.getAttGrid().getStore().removeAll(); + this.getOrderItemGrid().disable(); + this.getOrderItemGrid().getStore().removeAll(); + }, + + saveOrder: function() { + if(this.getEditor().validateAndUpdate()) { + this.getOrderOrdersStore().sync(); + } + }, + + saveOrderItem: function(ctx) { + var record = ctx.record; + record.set('order_id', this.getEditor().accessRecord().getId()); + this.getOrderOrderItemsStore().sync(); + } + + +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/worklog/Root.js b/src/test/resources/app/controller/worklog/Root.js new file mode 100644 index 0000000..4e8206d --- /dev/null +++ b/src/test/resources/app/controller/worklog/Root.js @@ -0,0 +1,74 @@ +/** + * Root worklog panel controller. + * + * @author fk + * @version 0.1 + * @date Jun 16, 2011 + */ + +Ext.define('DanteFrontend.controller.worklog.Root', { + extend: 'DanteFrontend.lib.Controller', + + stores: ['Employees', 'Months', 'Customers', 'Projects', 'worklog.TimeLine'], + models: ['Employee', 'Month', 'Customer', 'Project', 'TimeLine'], + views: ['worklog.Root', 'worklog.Filter', 'worklog.TimeLine'], + + rootView: 'df-worklog-root', + api: { + preload: worklogController.preload + }, + + refs: [{ + selector: 'df-worklog-filter', + ref: 'filter' + },{ + selector: 'df-worklog-timeline', + ref: 'timeline' + }], + + init: function() { + this.on('storesloaded', function() { + this.getFilter().load({ + success: this.filterLoadHandler, + scope: this + }); + }, this); + + this.control({ + 'button[action=applyFilters]': { + click: function() { + this.getFilter().submit({ + success: this.filterSubmitHandler, + scope: this + }); + } + } + }) + + this.callParent(arguments); + }, + + handlePreload: function(callback, resolver, args) { + this.getApi().preload(this.getProjectId(args[1]), Ext.bind(function(res) { + this.loadStores(); + }, this)); + }, + + getPreloadParam: function(args) { + return this.getProjectId(args[1]); + }, + + getProjectId: function(o) { + return (!Ext.isEmpty(o)) ? o['project']: null; + }, + + getRootViewInstance: function() { + return this.callParent(arguments); + }, + + filterLoadHandler: function() {}, + + filterSubmitHandler: function() { + this.getWorklogTimeLineStore().load(); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/controller/worklog/RootWithExportTools.js b/src/test/resources/app/controller/worklog/RootWithExportTools.js new file mode 100644 index 0000000..b4ca92b --- /dev/null +++ b/src/test/resources/app/controller/worklog/RootWithExportTools.js @@ -0,0 +1,110 @@ +/** + * RootWithExportTools + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 2, 2011 + */ + +Ext.define('DanteFrontend.controller.worklog.RootWithExportTools', { + extend: 'DanteFrontend.controller.worklog.Root', + + views: ['worklog.RootWithExportTools', 'worklog.Filter', 'worklog.TimeLine'], + rootView: 'df-worklog-root-et', + + init: function() { + this.callParent(arguments); + }, + + filterLoadHandler: function() { + this.switchExportTools(); + }, + + filterSubmitHandler: function() { + this.switchExportTools(); + this.getWorklogTimeLineStore().load(); + }, + + switchExportTools: function() { + this.getFilter().getForm().findField('customer').getValue() == 0 ? + this.getTimeline().down('df-exporttools').disableExport(): + this.getTimeline().down('df-exporttools').enableExport(); + + } + + +}); + +/** + * Root worklog panel controller. + * + * @author fk + * @version 0.1 + * @date Jun 16, 2011 + */ + +//Ext.define('DanteFrontend.controller.worklog.RootWithExportTools', { +// extend: 'DanteFrontend.lib.Controller', +// +// stores: ['worklog.Employees', 'worklog.Months', 'worklog.Customers', 'worklog.Projects', 'worklog.TimeLine'], +// models: ['Employee', 'Month', 'Customer', 'Project', 'TimeLine'], +// views: ['worklog.Root', 'worklog.Filter', 'worklog.TimeLine'], +// +// rootView: 'df-worklog-root', +// api: { +// preload: worklogController.preload +// }, +// +// refs: [{ +// selector: 'df-worklog-filter', +// ref: 'filter' +// },{ +// selector: 'df-worklog-timeline', +// ref: 'timeline' +// }], +// +// init: function() { +// this.on('storesloaded', function() { +// this.getFilter().load({ +// success: this.filterLoadHandler, +// scope: this +// }); +// }, this); +// +// this.control({ +// 'button[action=applyFilters]': { +// click: function() { +// this.getFilter().submit({ +// success: this.filterSubmitHandler, +// scope: this +// }); +// } +// } +// }) +// +// this.callParent(arguments); +// }, +// +// beforeLoad: function(callback, resolver, args) { +// this.getApi().preload(this.getProjectId(args[1]), function(res) { +// if(res) { +// callback.apply(resolver, args); +// } +// }); +// }, +// +// getProjectId: function(o) { +// return (!Ext.isEmpty(o)) ? o['project']: null; +// }, +// +// getRootViewInstance: function() { +// return this.callParent(arguments); +// }, +// +// filterLoadHandler: function() {}, +// +// filterSubmitHandler: function() { +// this.getWorklogTimeLineStore().load(); +// } +//}); \ No newline at end of file diff --git a/src/test/resources/app/global.js b/src/test/resources/app/global.js new file mode 100644 index 0000000..d8cfbeb --- /dev/null +++ b/src/test/resources/app/global.js @@ -0,0 +1,36 @@ +/** + * Global functions + * + * @author fk + * @version 0.1 + * @date Jul 12, 2011 + */ + +var g = function(text) { + return text; +} + +var approot = function(str) { + return '/dante_frontend_spring' + str; +} + +var media = function(str) { + return approot('/static' + str); +} + +var res = function(str) { + try { + return eval("_resources." + str); + } catch (e) { + console.error('Cannot resolve identifier: ' + str); + } +}; + +//console = console != undefined ? console :{ +// log: function() {}, +// info: function() {}, +// warn: function() {} +//}; + +//fixed some stuff +//other diff --git a/src/test/resources/app/lib/Command.js b/src/test/resources/app/lib/Command.js new file mode 100644 index 0000000..fc2e393 --- /dev/null +++ b/src/test/resources/app/lib/Command.js @@ -0,0 +1,21 @@ +/** + * Command + * Command pattern implementation. + * + * @author fk + * @version 0.1 + * @date Jun 2, 2011 + */ + + +Ext.define('DanteFrontend.lib.Command', { + config: { + token: '' + }, + + constructor: function(config) { + Ext.apply(this, config); + }, + + execute: Ext.emptyFn +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/CommandBus.js b/src/test/resources/app/lib/CommandBus.js new file mode 100644 index 0000000..2c95864 --- /dev/null +++ b/src/test/resources/app/lib/CommandBus.js @@ -0,0 +1,58 @@ +/** + * CommandBus + * A simple bus which contains all executed commands. + * Uses LocalStorageProvider to store the state. + * + * @author fk + * @version 0.1 + * @date Jun 2, 2011 + */ + +Ext.require([ + 'Ext.util.History', + 'Ext.state.Manager', + 'Ext.state.LocalStorageProvider' +]); + +Ext.define('DanteFrontend.lib.CommandBus', { + singleton: true, + + MAPKEY: 'CommandMap', + + mixins: { + 'observable': 'Ext.util.Observable' + }, + + init: function(router) { + Ext.state.Manager.setProvider(new Ext.state.LocalStorageProvider()); + Ext.state.Manager.set(DanteFrontend.lib.CommandBus.MAPKEY, {}); + Ext.util.History.init(); + this.router = router; + Ext.util.History.on('change', Ext.bind(this.onHistoryChanged, this)); + }, + + add: function(command) { + var token = command.getToken(); + this.getCommandMap()[token] = command; + Ext.util.History.add(token); + return command; + }, + + + getCommandMap: function(bus) { + return Ext.state.Manager.get(DanteFrontend.lib.CommandBus.MAPKEY, {}); + }, + + onHistoryChanged: function(token) { + if(token) { + if(!this.hasCommand(token)) { + this.router.route(token); + } + this.getCommandMap()[token].execute(); + } + }, + + hasCommand: function(token) { + return !Ext.isEmpty(this.getCommandMap()[token]); + } +}) \ No newline at end of file diff --git a/src/test/resources/app/lib/Controller.js b/src/test/resources/app/lib/Controller.js new file mode 100644 index 0000000..416e313 --- /dev/null +++ b/src/test/resources/app/lib/Controller.js @@ -0,0 +1,204 @@ +/** + * Controller + * An app-specific extension of the base ext controller class. + * + * TODO: + * - move some of the editor functionality to the Editor component. + * @author fk + * @version 0.1 + * @date Jun 16, 2011 + */ + +Ext.define('DanteFrontend.lib.Controller', { + extend: 'Ext.app.Controller', + + config: { + rootView: '', + defaultSize: { + width: 300, + height: 200 + }, + api: null, + editors: null, + urlParams: {} + }, + + constructor: function() { + this.callParent(arguments); + this.addEvents('storesloaded','rootviewcreated'); + this.on('storesloaded', function() { + this.loadCallback(); + this.onStoresLoaded(); + }, this); + }, + + init: function() { + Ext.each(this.controllers, function(c) { + this.getController(c).partialInit(this); + }, this); + this.initEditors(); + }, + + getRootViewInstance: function() { + //if(!Ext.isDefined(this.rootViewInstance)) { + rootViewInstance = Ext.widget(this.rootView); + this.fireEvent('rootviewcreated', rootViewInstance); +// } + return rootViewInstance; + }, + + onStoresLoaded: Ext.emptyFn, + + /** + * private + */ + + /** + * Default pre-load event listener. Before the resolver loads this + * controller, beforeLoad calls a Direct api method, if specified. + * If there's no api method in the config to call, the resolver load + * callback is called right away. + */ + + /** consider loading stores only once, then just sync **/ + load: function(callback, resolver, args) { + this.loadCallback = function() { + callback.apply(resolver, args); + }; + + if(!Ext.isEmpty(this.getApi()) && Ext.isFunction(this.getApi().preload)) { + this.handlePreload(callback, resolver, args); + } else { + this.loadStores(); + } + + }, + + /** + * Default after-load event listener. Loads all stores required by this + * controller. + */ + handlePreload: function(callback, resolver, args) { + this.getApi().preload(null, Ext.bind(function(res) { + this.loadStores(); + }, this)); + }, + + loadStores: function() { + var storesToLoad = []; + Ext.Array.each(this.stores, + function(storeName) { + var store = this.getStore(storeName); + if(Ext.isEmpty(store.dependencyFilter)) { + storesToLoad.push(store); + } + }, this); + + this.storesRemaining = Ext.clone(storesToLoad); + + Ext.each(storesToLoad, function(store) { + if(store) { + var callback = { callback: this.confirmStoreLoad(store), scope: this }; + if(store.filters && store.filters.getCount() > 0) { + store.clearFilter(false, callback); + if(!store.remoteFilter) store.load(callback); + } else { + store.load(callback); + } + } + }, this); + }, + + confirmStoreLoad: function(store) { + return function() { + Ext.Array.remove(this.storesRemaining, store); + if(Ext.isEmpty(this.storesRemaining)) { + this.fireEvent('storesloaded'); + } + } + }, + + initEditors: function() { + if(!Ext.isEmpty(this.getEditors())) { + var editorConfig = this.getEditors(); + Ext.each(editorConfig, function(obj) { + this.initEditor(obj); + }, this); + } + }, + + initEditor: function(conf) { + if(Ext.isString(conf.editor)) { + var buttonSelector = this.rootView + ' ' + + this.getEditorAlias(conf.editor) + + ' toolbar button[action=save]'; + var controlObj = {}; + var handler = Ext.emptyFn; + + if(Ext.isString(conf.store)) { + handler = this.createDefaultHandler(conf.editor, conf.store); + } + //this.bindEditorToStore(conf.editor, conf.store); + + if(Ext.isFunction(this[conf.handler])) { + handler = Ext.bind(this[conf.handler], this); + } + + controlObj[buttonSelector] = { + click: handler + } + + //workaround for now, until the editor component gets refactored + + this.getStore(conf.store).on('beforesync', function() { + var editor = this.getEditorInstanceByName(conf.editor); + if(editor) editor.onBeforeSync(); + }, this); + + this.getStore(conf.store).on('aftersync', function() { + var editor = this.getEditorInstanceByName(conf.editor); + if(editor) editor.onAfterSync(); + }, this); + + this.control(controlObj); + } + }, + + createDefaultHandler: function(editorName, storeName) { + return Ext.bind(function() { + var editor = this.getEditorInstanceByName(editorName); + var store = this.getStore(storeName); + + if(editor.validateAndUpdate()) { + store.sync(); + } + }, this); + }, + +// bindEditorToStore: function(editorName, storeName) { +// var store = this.getStore(storeName); +// store.on('afterSync', Ext.bind(function(boundStore, operation) { +// +// var editor = this.getEditorInstanceByName(editorName); +// var updatedRecord = DanteFrontend.lib.Util.first(operation.records); +// editor.loadRecord(boundStore.getById(updatedRecord.getId())); +// +// }, this)); +// }, + + getEditorAlias: function(configName) { + var alias = DanteFrontend.lib.Util.first(Ext.ClassManager.getAliasesByName('DanteFrontend.view.' + + configName)); + if(alias) { + return alias.replace('widget.',''); + } + return false; + }, + + getEditorInstanceByName: function(configName) { + return DanteFrontend.lib.Util.first( + Ext.ComponentQuery.query(this.rootView + + ' ' + this.getEditorAlias(configName))); + + } +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/Notify.js b/src/test/resources/app/lib/Notify.js new file mode 100644 index 0000000..353264b --- /dev/null +++ b/src/test/resources/app/lib/Notify.js @@ -0,0 +1,56 @@ +/** + * Notify + * Basic notification shortcuts + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ +Ext.require('DanteFrontend.lib.ux.Growl'); + +Ext.define('DanteFrontend.lib.Notify', { + singleton: true, + + base: { + success: function(title, message) { + DanteFrontend.lib.ux.Growl.notify({ + title: title, + message: message, + iconCls: "x-growl-success" + }); + }, + error: function(title, message) { + //console.error(message, title); + DanteFrontend.lib.ux.Growl.notify({ + title: title, + message: message, + iconCls: "x-growl-error" + }); + } + }, + + + error: { + formSave: function() { + DanteFrontend.lib.Notify.base.error(g("Save unsuccessful"), g("The form has not been saved.")); + }, + formValidation: function() { + DanteFrontend.lib.Notify.base.error(g("Validation problem"), g("Invalid form values are present, please correct them.")); + }, + hotlineEmail: function() { + DanteFrontend.lib.Notify.base.error(g("Error"), g("Your email to the specified queue has not been sent.")); + } + }, + + success: { + formSave: function() { + DanteFrontend.lib.Notify.base.success(g("Save successful"), g("The form has been saved.")); + }, + hotlineEmail: function() { + DanteFrontend.lib.Notify.base.success(g("Success"), g("Your email to the specified queue has been sent.")); + }, + storeUpdate: function() { + DanteFrontend.lib.Notify.base.success(g("Update successful"), g("Your data has been saved.")); + } + } +}); diff --git a/src/test/resources/app/lib/Router.js b/src/test/resources/app/lib/Router.js new file mode 100644 index 0000000..1b75f72 --- /dev/null +++ b/src/test/resources/app/lib/Router.js @@ -0,0 +1,69 @@ +/** + * Router + * Basic routing + * + * @author fk + * @version 0.1 + * @date Jun 28, 2011 + */ + +Ext.define('DanteFrontend.lib.Router', { + extend: 'Ext.util.Observable', + + config: { + registeredRoutes: [], + resolverCache: Ext.create('Ext.util.MixedCollection') + }, + + constructor: function(cfg) { + this.initConfig(cfg); + this.callParent(arguments); + }, + + registerRoutes: function(arr, ref) { + Ext.each(arr, function(r) { + r.resolver = this.getResolver(r.resolver, ref); + }, this); + this.setRegisteredRoutes(Ext.Array.merge(this.getRegisteredRoutes(), arr)); + }, + + route: function(url) { + var cmd = Ext.create('DanteFrontend.lib.Command', { + token: url, + execute: Ext.bind(this.resolve, this, arguments) + }); + + DanteFrontend.lib.CommandBus.add(cmd); + }, + + resolve: function(url) { + var r = this.getMatchingRoute(url); + if(!Ext.isEmpty(r)) { + r.resolver.resolve(r.path, url, Ext.isEmpty(r.target) ? null:r.target); + } else { + console.error('This url cannot be resolved: ' + url); + } + }, + + getMatchingRoute: function(url) { + var ret = null; + Ext.each(this.getRegisteredRoutes(), function(r) { + if(r.path.test(url)) { + ret = r; + return false; + } + return true; + }); + return ret; + }, + + getResolver: function(className, ref) { + className = 'DanteFrontend.lib.resolver.' + className; + var instance = this.getResolverCache().get(className); + if(Ext.isEmpty(instance)) { + instance = Ext.create(className, ref); + this.getResolverCache().add(className, instance); + } + return instance; + } +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/Util.js b/src/test/resources/app/lib/Util.js new file mode 100644 index 0000000..f284236 --- /dev/null +++ b/src/test/resources/app/lib/Util.js @@ -0,0 +1,56 @@ +/** + * Util + * Utilities under a singleton class. + * + * @author fk + * @version 0.1 + * @date Jun 16, 2011 + */ + +Ext.define('DanteFrontend.lib.Util', { + singleton: true, + + + cache: function(obj) { + console.log(arguments.caller); + }, + + location: function(url) { + return function() { + window.location = url; + } + }, + + exportLocation: function(gridName, format) { + return DanteFrontend.lib.Util.location(approot('/' + gridName + '/report/' + format)); + }, + + createJsonFromString: function(str, root, value) { + var namespace = str.split("."); + if(namespace.length == 1) { + root[namespace[0]] = value; + return; + } + + var current = root[namespace[0]] = root[namespace[0]] || {}; + + Ext.each(namespace.slice(1), function(nested, i){ + if(i > 1) { + current = current[nested] = current[nested] || {}; + } + else { + current[nested] = value; + } + }); + }, + + first: function(arr) { + if(!Ext.isEmpty(arr)) { + return arr[0]; + } + return false; + } + + //verify: function() + +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/base/JsonReader.js b/src/test/resources/app/lib/base/JsonReader.js new file mode 100644 index 0000000..c9341b7 --- /dev/null +++ b/src/test/resources/app/lib/base/JsonReader.js @@ -0,0 +1,46 @@ +/** + * JsonReader + * Custom jsonreader implementation. + * + * @author fk + * @version 0.1 + * @date Jun 29, 2011 + */ + + +Ext.define('DanteFrontend.lib.base.JsonReader', { + extend: 'Ext.data.reader.Json', + alias: 'reader.df-reader-json', + + createAccessor : function(){ + var re = /[\[\.]/; + return function(expr) { + if(Ext.isEmpty(expr)){ + return Ext.emptyFn; + } + if(Ext.isFunction(expr)){ + return expr; + } + var i = String(expr).search(re); + if(i >= 0){ + return function(obj) { + access = function(o, f) { + if(o) return o[f]; + return null; + }; + + Ext.each(expr.split('.'), function(field) { + obj = access(obj, field); + }); + + return obj; + } + //return new Function('obj', 'return (obj != null) ? obj' + (i > 0 ? '.' : '') + expr +':null'); + } + return function(obj){ + return obj[expr]; + }; + + }; + }() +}); diff --git a/src/test/resources/app/lib/base/JsonWriter.js b/src/test/resources/app/lib/base/JsonWriter.js new file mode 100644 index 0000000..4ab1e7c --- /dev/null +++ b/src/test/resources/app/lib/base/JsonWriter.js @@ -0,0 +1,69 @@ +/** + * JsonWriter + * Custom jsonwriter implementation. + * + * @author fk + * @version 0.1 + * @date Jun 30, 2011 + */ + + +Ext.define('DanteFrontend.lib.base.JsonWriter', { + extend: 'Ext.data.writer.Json', + alias: 'writer.df-writer-json', + + constructor: function(config) { + this.callParent(arguments); + }, + + getRecordData: function(record) { + + var remap = function(incoming, field, record, value) { + name = field[this.nameProperty] || field.name; + if(Ext.isEmpty(field['mapping'])) { + data[name] = record.get(field.name); + } else { + DanteFrontend.lib.Util.createJsonFromString(field['mapping'], incoming, record.get(field.name)); + } + return incoming; + }; + + var isPhantom = record.phantom === true, + writeAll = this.writeAllFields || isPhantom, + nameProperty = this.nameProperty, + fields = record.fields, + data = {}, + changes, + name, + field, + key; + + if (writeAll) { + fields.each(function(field){ + if (field.persist) { + //name = field[nameProperty] || field.name; + + //data[name] = record.get(field.name); + + data = remap(data, field, record); + } + }); + } else { + // Only write the changes + changes = record.getChanges(); + for (key in changes) { + if (changes.hasOwnProperty(key)) { + field = fields.get(key); + name = field[nameProperty] || field.name; + data[name] = changes[key]; + //data = remap(data, field, record, changes); + } + } + if (!isPhantom) { + // always include the id for non phantoms + data[record.idProperty] = record.getId(); + } + } + return data; + } +}); diff --git a/src/test/resources/app/lib/base/Store.js b/src/test/resources/app/lib/base/Store.js new file mode 100644 index 0000000..5b80828 --- /dev/null +++ b/src/test/resources/app/lib/base/Store.js @@ -0,0 +1,158 @@ +/** + * Store + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.lib.base.Store', { + extend: 'Ext.data.Store', + + config: { + lastIndex: 0 + }, + + constructor: function(config) { + this.model = Ext.ModelManager.getModel(this.model); + config = Ext.applyIf(config, { + paramsAsHash: true, + autoSync: false, + autoLoad: false, + proxy: this.proxy || { + type: 'direct', + api: this.api || this.model.getProxy().api, + reader: { + type: 'df-reader-json' + }, + writer: { + type: 'df-writer-json', + writeAllFields: true + } + } + }); + this.addEvents('aftersync') + + this.callParent([config]); + + //always reload content after any sync. + this.on('write', function(store, operation) { + this.load({ + callback: function() { + switch(operation.action) { + case 'create': + DanteFrontend.lib.Notify.base.success(g('Store updated'), g('Your entry has been added.')); + break; + case 'update': + DanteFrontend.lib.Notify.base.success(g('Store updated'), g('Your entry has been updated.')); + break; + case 'destroy': + DanteFrontend.lib.Notify.base.success(g('Store updated'), g('Your entry has been removed.')); + break; + default: break; + } + this.fireEvent('aftersync', store, operation); + }, + scope: this + }); + }, this); + + this.on('add', function(s, r, i) { + this.setLastIndex(i); + }); + + }, +// +// onBatchComplete: function(batch, operation) { +// this.callParent(arguments); +// this.fireEvent('aftersync'); +// }, + + hasDummyRecord: function() { + return (this.dummyRecord != undefined && this.dummyRecord != null); + }, + + getDefaultModel: function() { + var cls = Ext.ModelManager.getModel(this.model.modelName); + var model = Ext.create(this.model.modelName, cls.instanceDefaults || {}) + return model; + }, + + loadWithDependencyFilter: function(id) { + //this.clearFilter(); + this.filter(Ext.apply(this.dependencyFilter, { + value: id + })); + //this.load(); + }, + + // as the default impl of filter method does not allow a callback, we assign + // it as a load listener callback (load() is called internally by filter) + // and remove it right afterwards + // cool, neh? + + filter: function(filters, value, cb, clear) { + var me = this, + clearFilters = (clear == null) ? true:clear, + wrapper = function() { + //always catch - otherwise the listener is not unregistered, and all hell breaks loose + try { + cb.callback.apply(cb.scope); + } catch(ex) { + console.error('Error occured in callback: ', cb, ex); + } finally { + me.un('load', arguments.callee); + } + }; + + if(me.filters && clearFilters) { + me.remoteFilter ? me.filters.clear():me.clearFilter(); + } + + if(me.remoteFilter && cb) { + me.on('load', wrapper); + } else if (cb) { + cb.callback.apply(cb.scope); + } + + me.callParent(arguments); + }, + + //and repeat + + sync: function(cb) { + var me = this; + if(cb) + me.on('aftersync', function() { + try { + cb.callback.apply(cb.scope); + } catch (ex) { + console.error('Error occured in callback: ', cb, ex); + } finally { + me.un('aftersync', arguments.callee); + } + }); + me.callParent(arguments); + }, + + // and repeat + + clearFilter: function(suppressEvent, cb) { + var me = this; + if(me.remoteFilter && cb) { + me.on('load', function() { + try { + cb.callback.apply(cb.scope); + } catch (ex) { + console.error('Error occured in callback: ', cb, ex); + } finally { + me.un('load', arguments.callee); + } + }); + } else if(cb) { + cb.callback.apply(cb.scope); + } + me.callParent(arguments); + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/model/Conf.js b/src/test/resources/app/lib/model/Conf.js new file mode 100644 index 0000000..8c03881 --- /dev/null +++ b/src/test/resources/app/lib/model/Conf.js @@ -0,0 +1,40 @@ +/** + * TitledKey + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 11, 2011 + */ + + +Ext.define('DanteFrontend.lib.model.Conf', { + singleton: true, + + enumKey: function(conf) { + return Ext.apply({ + fields: [ + {name: 'id', type: 'int'}, + {name: 'name', type: 'string'}, + {name: 'enumKey', type: 'string'} + ], + proxy: { + type: 'memory', + data: conf.proxyData + } + }, conf); + }, + + titledKey: function(conf) { + return Ext.apply({ + fields: [ + {name: 'id', type: 'int'}, + {name: 'name', type: 'string'} + ], + proxy: { + type: 'memory', + data: conf.proxyData + } + }, conf); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/resolver/Handler.js b/src/test/resources/app/lib/resolver/Handler.js new file mode 100644 index 0000000..3f4c843 --- /dev/null +++ b/src/test/resources/app/lib/resolver/Handler.js @@ -0,0 +1,17 @@ +/** + * Handler + * Resolves actions to handlers, ie. just runs the associated handler. + * + * @author fk + * @version 0.1 + * @date Jul 6, 2011 + */ + +Ext.define('DanteFrontend.lib.resolver.Handler', { + extend: 'DanteFrontend.lib.resolver.Resolver', + + resolve: function(regex, url) { + var parsedUrl = this.parseUrl(regex, url); + this.app.getHandler(parsedUrl.token).apply(this.app); + } +}); diff --git a/src/test/resources/app/lib/resolver/Modal.js b/src/test/resources/app/lib/resolver/Modal.js new file mode 100644 index 0000000..c6316a1 --- /dev/null +++ b/src/test/resources/app/lib/resolver/Modal.js @@ -0,0 +1,37 @@ +/** + * Modal + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 28, 2011 + */ + +Ext.define('DanteFrontend.lib.resolver.Modal', { + extend: 'DanteFrontend.lib.resolver.Resolver', + + getControllerName: function(token) { + return token; + }, + + load: function(controller, params) { +// var rootViewInstance = Ext.widget(controller.getRootView(),{ +// region: 'center' +// }); + controller.setUrlParams(params); + var rootViewInstance = controller.getRootViewInstance(); + + var modal = Ext.widget('df-window', { + title: rootViewInstance.windowTitle, + id: 'currentModalWindow', + items: rootViewInstance + }); + modal.show(); + modal.on('destroy', this.afterModalUnload, this); + }, + + afterModalUnload: function() { + //this.unload(); + Ext.util.History.back(); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/resolver/Resolver.js b/src/test/resources/app/lib/resolver/Resolver.js new file mode 100644 index 0000000..d627fbc --- /dev/null +++ b/src/test/resources/app/lib/resolver/Resolver.js @@ -0,0 +1,68 @@ +/** + * Resolver + * Abstract default class. + * + * @author fk + * @version 0.1 + * @date Jun 28, 2011 + */ + + +Ext.define('DanteFrontend.lib.resolver.Resolver', { + extend: 'Ext.util.Observable', + + constructor: function(app) { + this.app = app; + //this.setcurrentController = null; + this.callParent(); + }, + + resolve: function(regex, url, target) { + var parsedUrl = this.parseUrl(regex, url); + //console.log(parsedUrl.token); + var controllerName = (Ext.isEmpty(target)) ? + this.getControllerName(parsedUrl.token): + target; + var controller = this.app.getController(controllerName); + if(!Ext.isEmpty(controller)) { + if(this.isLoaded(controller)) { + return; + } + this.app.getViewport().setLoading(true); + controller.load(this.loadCallback, this, [controller, parsedUrl.params]); + } else { + console.error('No such controller exists.'); + } + }, + + loadCallback: function(controller, params) { + this.load(controller, params); + this.app.setCurrentController(controller); + this.app.getViewport().setLoading(false); + }, + + isLoaded: function(controller) { + return !Ext.isEmpty(this.app.getCurrentController()) && this.app.getCurrentController() == controller; + }, + + unload: function() { + this.app.setCurrentController(null); + }, + + parseUrl: function(regex, url) { + var arr = regex.exec(url); + var paramRegex = /\/?([a-zA-Z]+)\/([0-9]+)/g; + var obj = null; + + while((partialMatches = paramRegex.exec(arr[2])) != null) { + obj = obj || {}; + obj[partialMatches[1]] = partialMatches[2]; + } + + return { + token: arr[1], + params: obj + }; + } + +}); diff --git a/src/test/resources/app/lib/resolver/Viewport.js b/src/test/resources/app/lib/resolver/Viewport.js new file mode 100644 index 0000000..9073f01 --- /dev/null +++ b/src/test/resources/app/lib/resolver/Viewport.js @@ -0,0 +1,34 @@ +/** + * Viewport + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 28, 2011 + */ + +Ext.define('DanteFrontend.lib.resolver.Viewport', { + extend: 'DanteFrontend.lib.resolver.Resolver', + + + getControllerName: function(token) { + return token + ".Root"; + }, + + load: function(controller, params) { + this.flushViewport(); + this.fillViewport(controller, params); + }, + + flushViewport: function() { + if(!Ext.isEmpty(this.app.getCurrentRootView())) + this.app.getViewport().remove(this.app.getCurrentRootView(), true); + }, + + fillViewport: function(controller, params) { + controller.setUrlParams(params); + var rootViewInstance = controller.getRootViewInstance(); + this.app.setCurrentRootView(rootViewInstance); + this.app.getViewport().add(this.app.getCurrentRootView()); + } +}); diff --git a/src/test/resources/app/lib/ux/Growl.js b/src/test/resources/app/lib/ux/Growl.js new file mode 100644 index 0000000..3d0495e --- /dev/null +++ b/src/test/resources/app/lib/ux/Growl.js @@ -0,0 +1,83 @@ +/** + * Growl + * Growl-like notification support for ExtJS. + * Supports ExtJS4.x + * + * @author fk + * @author anon (tnx anyway ;) + * @version 0.2 + * @date Jul 12, 2011 + */ + +Ext.namespace("DanteFrontend.lib.ux"); + +DanteFrontend.lib.ux.Growl = (function() { + var _container, + _closerTpl = '
', + _basicTpl = '
{0}
', + _fullTpl = '
{0}
{1}
', + + _config = { + alignment: "t-t", + duration: 1500, + context: document, + offset: [0, 0], + + show: function(notification, options) { + if (!options.pin) { + notification.fadeIn({duration: 500}).pause((options.duration || _config.duration)).fadeOut({duration: 1000, remove: true}) + } else { + notification.fadeIn({duration: 500}); + } + }, + + close: function(notification, evt, elt, options) { + notification.fadeOut({remove: true}); + }, + + click: Ext.emptyFn + }, + + _getContainer = function() { + if (!_container) { + _container = Ext.core.DomHelper.insertFirst(document.body, {id:'x-growl-ct'}, true); + } + + return _container; + }; + + return { + notify: function(options) { + var closer, + notification, + container = _getContainer(), + hasIcon = options.iconCls ? "x-growl-msg-has-icon" : "", + hasTitle = options.title ? "x-growl-msg-has-title" : "", + content = options.content ? Ext.String.format(_basicTpl, options.content) : + Ext.String.format(_fullTpl, options.title || "", options.message || "", hasTitle + " " + hasIcon, options.iconCls || ""); + + notification = Ext.core.DomHelper[(options.alignment || _config.alignment).indexOf("b") === -1 ? "append" : "insertFirst"](container, content, true); + notification.on("click", function(evt, elt, op) { + if (Ext.fly(elt).hasClass("x-growl-msg-close")) { + (options.close || _config.close)(notification, evt, elt, options); + } else { + (options.click || _config.click)(notification, evt, elt, options); + } + }); + + if (options.closable !== false) { + closer = Ext.core.DomHelper.append(notification, _closerTpl, true); + + notification.hover(closer.fadeIn, closer.fadeOut, closer); + } + + container.alignTo((options.context || _config.context), (options.alignment || _config.alignment), (options.offset || _config.offset)); + + (options.show || _config.show)(notification, options); + }, + + init: function(config) { + Ext.apply(_config, config); + } + }; +})(); \ No newline at end of file diff --git a/src/test/resources/app/lib/ux/SchemaValidator.js b/src/test/resources/app/lib/ux/SchemaValidator.js new file mode 100644 index 0000000..3fed607 --- /dev/null +++ b/src/test/resources/app/lib/ux/SchemaValidator.js @@ -0,0 +1,241 @@ +/** + * SchemaValidator + * Adapted from dojo framework. + * + * Usage: http://dojotoolkit.org/reference-guide/dojox/json/schema.html + * + * @author fk + * @version 0.1 + * @date Aug 5, 2011 + * @copyright + */ + +/** + Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Available via Academic Free License >= 2.1 OR the modified BSD license. + see: http://dojotoolkit.org/license for details +*/ + +Ext.define('DanteFrontend.lib.ux.SchemaValidator', { + singleton: true, + + validate: function(/*Any*/instance,/*Object*/schema){ + // summary: + // To use the validator call this with an instance object and an optional schema object. + // If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating), + // that schema will be used to validate and the schema parameter is not necessary (if both exist, + // both validations will occur). + // instance: + // The instance value/object to validate + // schema: + // The schema to use to validate + // description: + // The validate method will return an object with two properties: + // valid: A boolean indicating if the instance is valid by the schema + // errors: An array of validation errors. If there are no errors, then an + // empty list will be returned. A validation error will have two properties: + // property: which indicates which property had the error + // message: which indicates what the error was + // + return this._validate(instance,schema,false); + }, + + checkPropertyChange: function(/*Any*/value,/*Object*/schema, /*String*/ property){ + // summary: + // The checkPropertyChange method will check to see if an value can legally be in property with the given schema + // This is slightly different than the validate method in that it will fail if the schema is readonly and it will + // not check for self-validation, it is assumed that the passed in value is already internally valid. + // The checkPropertyChange method will return the same object type as validate, see JSONSchema.validate for + // information. + // value: + // The new instance value/object to check + // schema: + // The schema to use to validate + // return: + // see dojox.validate.jsonSchema.validate + // + return this._validate(value,schema, property || "property"); + }, + + mustBeValid: function(result) { + // summary: + // This checks to ensure that the result is valid and will throw an appropriate error message if it is not + // result: the result returned from checkPropertyChange or validate + if(!result.valid) + throw new TypeError(Ext.Array.map(result.errors, function(error){return "for property " + error.property + ': ' + error.message;}).join(", ")); + }, + + _validate: function(/*Any*/instance,/*Object*/schema,/*Boolean*/ _changing) { + + var errors = []; + // validate a value against a property definition + function checkProp(value, schema, path,i){ + var l; + path += path ? typeof i == 'number' ? '[' + i + ']' : typeof i == 'undefined' ? '' : '.' + i : i; + function addError(message){ + errors.push({property:path,message:message}); + } + + if((typeof schema != 'object' || schema instanceof Array) && (path || typeof schema != 'function')){ + if(typeof schema == 'function'){ + if(!(Object(value) instanceof schema)){ + addError("is not an instance of the class/constructor " + schema.name); + } + }else if(schema){ + addError("Invalid schema/property definition " + schema); + } + return null; + } + if(_changing && schema.readonly){ + addError("is a readonly field, it can not be changed"); + } + if(schema['extends']){ // if it extends another schema, it must pass that schema as well + checkProp(value,schema['extends'],path,i); + } + // validate a value against a type definition + function checkType(type,value){ + if(type){ + if(typeof type == 'string' && type != 'any' && + (type == 'null' ? value !== null : typeof value != type) && + !(value instanceof Array && type == 'array') && + !(type == 'integer' && value%1===0)){ + return [{property:path,message:(typeof value) + " value found, but a " + type + " is required"}]; + } + if(type instanceof Array){ + var unionErrors=[]; + for(var j = 0; j < type.length; j++){ // a union type + if(!(unionErrors=checkType(type[j],value)).length){ + break; + } + } + if(unionErrors.length){ + return unionErrors; + } + }else if(typeof type == 'object'){ + var priorErrors = errors; + errors = []; + checkProp(value,type,path); + var theseErrors = errors; + errors = priorErrors; + return theseErrors; + } + } + return []; + } + if(value === undefined){ + if(!schema.optional){ + addError("is missing and it is not optional"); + } + }else{ + errors = errors.concat(checkType(schema.type,value)); + if(schema.disallow && !checkType(schema.disallow,value).length){ + addError(" disallowed value was matched"); + } + if(value !== null){ + if(value instanceof Array){ + if(schema.items){ + if(schema.items instanceof Array){ + for(i=0,l=value.length; i schema.maxItems){ + addError("There must be a maximum of " + schema.maxItems + " in the array"); + } + }else if(schema.properties){ + errors.concat(checkObj(value,schema.properties,path,schema.additionalProperties)); + } + if(schema.pattern && typeof value == 'string' && !value.match(schema.pattern)){ + addError("does not match the regex pattern " + schema.pattern); + } + if(schema.maxLength && typeof value == 'string' && value.length > schema.maxLength){ + addError("may only be " + schema.maxLength + " characters long"); + } + if(schema.minLength && typeof value == 'string' && value.length < schema.minLength){ + addError("must be at least " + schema.minLength + " characters long"); + } + if(typeof schema.minimum !== undefined && typeof value == typeof schema.minimum && + schema.minimum > value){ + addError("must have a minimum value of " + schema.minimum); + } + if(typeof schema.maximum !== undefined && typeof value == typeof schema.maximum && + schema.maximum < value){ + addError("must have a maximum value of " + schema.maximum); + } + if(schema['enum']){ + var enumer = schema['enum']; + l = enumer.length; + var found; + for(var j = 0; j < l; j++){ + if(enumer[j]===value){ + found=1; + break; + } + } + if(!found){ + addError("does not have a value in the enumeration " + enumer.join(", ")); + } + } + if(typeof schema.maxDecimal == 'number' && + (value.toString().match(new RegExp("\\.[0-9]{" + (schema.maxDecimal + 1) + ",}")))){ + addError("may only have " + schema.maxDecimal + " digits of decimal places"); + } + } + } + return null; + } + + // validate an object against a schema + function checkObj(instance,objTypeDef,path,additionalProp){ + + if(typeof objTypeDef =='object'){ + if(typeof instance != 'object' || instance instanceof Array){ + errors.push({property:path,message:"an object is required"}); + } + + for(var i in objTypeDef){ + if(objTypeDef.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_')){ + var value = instance[i]; + var propDef = objTypeDef[i]; + checkProp(value,propDef,path,i); + } + } + } + for(i in instance){ + if(instance.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_') && objTypeDef && !objTypeDef[i] && additionalProp===false){ + errors.push({property:path,message:(typeof value) + "The property " + i + + " is not defined in the schema and the schema does not allow additional properties"}); + } + var requires = objTypeDef && objTypeDef[i] && objTypeDef[i].requires; + if(requires && !(requires in instance)){ + errors.push({property:path,message:"the presence of the property " + i + " requires that " + requires + " also be present"}); + } + value = instance[i]; + if(objTypeDef && typeof objTypeDef == 'object' && !(i in objTypeDef)){ + checkProp(value,additionalProp,path,i); + } + if(!_changing && value && value.$schema){ + errors = errors.concat(checkProp(value,value.$schema,path,i)); + } + } + return errors; + } + if(schema){ + checkProp(instance,schema,'',_changing || ''); + } + if(!_changing && instance && instance.$schema){ + checkProp(instance,instance.$schema,'',''); + } + return {valid:!errors.length,errors:errors}; + } + + +}) diff --git a/src/test/resources/app/lib/view/AddingGrid.js b/src/test/resources/app/lib/view/AddingGrid.js new file mode 100644 index 0000000..c4aac86 --- /dev/null +++ b/src/test/resources/app/lib/view/AddingGrid.js @@ -0,0 +1,71 @@ +/** + * AddingGrid + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 4, 2011 + */ + +Ext.define('DanteFrontend.lib.view.AddingGrid', { + extend: 'DanteFrontend.lib.view.Grid', + alias: 'widget.df-grid-adding', + + fieldConfig: null, + + initComponent: function() { + var config = { + toolbar: true, + columns: this.getColumnConfig() + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + }, + + getColumnConfig: function() { + this.initialConfig.columns.push({ + xtype:'actioncolumn', + width:20, + items: [{ + icon: media('/img/delete.png'), + iconCls: 'action', + tooltip: g('Remove'), + handler: this.removeItem, + scope: this + }] + }); + return this.initialConfig.columns; + }, + + addItem: function() { + var f = this.down('form'), + rec = this.getStore().getDefaultModel(); + rec.set(f.getValues()); + if(rec.isValid()) + this.fireEvent('itemadded', this, rec); + else + DanteFrontend.lib.Notify.base.error(g('Validation error'), g('Please select a value ...') ); + + }, + + getDefaultToolbarConfig: function() { + var parentConfig = this.callParent(); + + var items = [{ + xtype: 'form', + api: this.initialConfig.api, + border: false, + bodyStyle: 'background-color: transparent;', + height: 23, + width: 155, + items: this.initialConfig.fieldConfig + }]; + + items.push(parentConfig.items[0]); + items = Ext.Array.flatten(items); + parentConfig.items = items; + return parentConfig; + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/view/Combobox.js b/src/test/resources/app/lib/view/Combobox.js new file mode 100644 index 0000000..59414d1 --- /dev/null +++ b/src/test/resources/app/lib/view/Combobox.js @@ -0,0 +1,46 @@ +/** + * Combo + * Custom combobox. + * + * @author fk + * @version 0.1 + * @date Jul 27, 2011 + */ + + +Ext.define('DanteFrontend.lib.view.Combobox', { + extend: "Ext.form.field.ComboBox", + alias:["widget.df-combo"], + + initComponent: function() { + var triggers = { + trigger1Cls: Ext.baseCSSPrefix + "form-clear-trigger", + trigger2Cls: Ext.baseCSSPrefix + "form-arrow-trigger" + }; + + this.addEvents('clear'); + if(this.clearable) Ext.apply(this.initialConfig, triggers); + Ext.apply(this, this.initialConfig); + + this.callParent(arguments); + }, + + onTrigger2Click:function(){ + var me = this; + if(!me.readOnly&&!me.disabled){ + if(me.isExpanded){me.collapse();} + else{ + me.onFocus({}); + if(me.triggerAction==="all"){me.doQuery(me.allQuery,true);} + else{me.doQuery(me.getRawValue());} + } + me.inputEl.focus(); + } + }, + + onTrigger1Click:function(){ + var me=this; + me.clearValue(); + this.fireEvent('clear', this); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/view/ComboboxWithAdd.js b/src/test/resources/app/lib/view/ComboboxWithAdd.js new file mode 100644 index 0000000..a86f830 --- /dev/null +++ b/src/test/resources/app/lib/view/ComboboxWithAdd.js @@ -0,0 +1,177 @@ +/** + * ComboWithAdd + * + * @author fk + * @version 0.1 + * @date Sep 8, 2011 + */ + +Ext.define('DanteFrontend.lib.view.ComboboxWithAdd', { + extend: 'Ext.form.field.ComboBox', + + alias: 'widget.df-combo-add', + + initComponent: function() { + Ext.apply(this, Ext.apply(this.initialConfig, { + clearable: false, + listConfig: { + pageSize: 2, + maxHeight: 200 + } + })); + this.callParent(arguments); + }, + + + createPicker: function() { + var me = this, + picker, + menuCls = Ext.baseCSSPrefix + 'menu', + opts = Ext.apply({ + selModel: { + mode: me.multiSelect ? 'SIMPLE' : 'SINGLE' + }, + floating: true, + hidden: true, + ownerCt: me.ownerCt, + cls: me.el.up('.' + menuCls) ? menuCls : '', + store: me.store, + displayField: me.displayField, + focusOnToFront: false, + pageSize: me.pageSize, + tpl: me.tpl, + addHandler: Ext.bind(me.onAdd, me) + }, me.listConfig, me.defaultListConfig); + + picker = me.picker = Ext.create('DanteFrontend.lib.view.BoundListWithAdd', opts); + //picker = me.picker = Ext.create('Ext.view.BoundList', opts); + + me.mon(picker, { + itemclick: me.onItemClick, + refresh: me.onListRefresh, + scope: me + }); + + me.mon(picker.getSelectionModel(), { + selectionChange: me.onListSelectionChange, + scope: me + }); + + return picker; + }, + + onAdd: function() { + var f = this.addForm; + if(Ext.isObject(f) || Ext.isString(f)) this.openAddWindow(); + }, + + openAddWindow: function() { + var f = this.addForm; + var formConfig = Ext.apply({ + listeners: { + render: this.hookButtonActions, + scope: this + } + }, Ext.isObject(f) ? f:{}) + + this.form = Ext.widget(Ext.isObject(f) ? f.xtype:f, formConfig); + + var rec = this.store.getDefaultModel(); + this.store.add(rec); + this.form.loadRecord(rec); + this.form.setRecordAccessor(function(){return rec;}, this); + + this.modal = Ext.widget('df-window', { + title: this.windowTitle || this.getDefaultWindowTitle(), + id: 'addItemModal', + layout: 'fit', + width: 250, + height: 150, + items: this.form + }); + this.modal.show(); + }, + + + getDefaultWindowTitle: function() { + return g("Add") + " " + + this.store.model.getName().split(".").pop().toLowerCase(); + }, + + hookButtonActions: function(f) { + f.down('toolbar button[action=save]').on('click', this.addItem, this); + f.down('toolbar button[action=cancel]').on('click', this.cancelItem, this); + }, + + addItem: function() { + var f = this.form; + this.latestRec = f.accessRecord(); + + f.validateAndUpdate(); + this.store.sync(); + this.modal.destroy(); + delete this.form; + }, + +// onListRefresh: function() { +// this.callParent(arguments); +// console.log(this.latestRec); +// if(!Ext.isEmpty(this.latestRec)) { +// this.select(this.findAddedItem(this.latestRec)); +// } +// }, + + + cancelItem: function() { + this.modal.destroy(); + }, + + findAddedItem: function(r) { + return this.store.getAt(this.store.find('name', r.get('name'))); + } + +}); + + +Ext.define('DanteFrontend.lib.view.BoundListWithAdd', { + extend: 'Ext.view.BoundList', + + + initComponent: function() { + this.callParent(); + + this.addEvents(['additem']); + + // a little hack which avoids the need to override the layout, + // and replaces the paging toolbar with an add-item toolbar + this.pagingToolbar = this.createAddingToolbar(); + + this.tpl = Ext.create('Ext.XTemplate', + '
    ', + '
  • ' + '{' + this.displayField + '}' + '
  • ', + '
' + ); + }, + + createAddingToolbar: function(w) { + return Ext.widget('toolbar', { + style: 'border-width: 1px 0 0 0', + items: { + text: g('Add new item ...'), + handler: this.addHandler + }, + layout: { + type: 'hbox', + pack: 'start' + }, + bindStore: Ext.emptyFn + }); + }, + + onDestroy: function() { + this.callParent(arguments); + Ext.destroyMembers(this, 'addingToolbar'); + } + + +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/view/Editor.js b/src/test/resources/app/lib/view/Editor.js new file mode 100644 index 0000000..ef7d8bd --- /dev/null +++ b/src/test/resources/app/lib/view/Editor.js @@ -0,0 +1,26 @@ +/** + * Editor + * Editor component base. + * + * TODO: + * - think about the layout (it should be possible to use this without the list, if necessary. + * + * + * @author fk + * @version 0.1 + * @date Jul 27, 2011 + */ + + +Ext.define('DanteFrontend.lib.view.Editor', { + extend: 'Ext.panel.Panel', + + initComponent: function() { + var config = {}; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + Ext.apply(this, config); //unnecessary + + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/view/ExportTools.js b/src/test/resources/app/lib/view/ExportTools.js new file mode 100644 index 0000000..08b1427 --- /dev/null +++ b/src/test/resources/app/lib/view/ExportTools.js @@ -0,0 +1,74 @@ +/** + * ExportTools + * A common export toolbar for usage in grids. + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ + +Ext.define('DanteFrontend.lib.view.ExportTools', { + extend: 'Ext.toolbar.Toolbar', + alias: 'widget.df-exporttools', + gridId: '', + + initComponent: function() { + Ext.apply(this, { + items: [{ + //id: 'btn-no-export', + xtype: 'button', + text: g('Exporting is possible only when Customer is specified by the filter.'), + hidden: true, + disabled: true, + ref: 'noexport' + },{ + //id: 'btn-export-pdf', + xtype: 'button', + text: g('Export as PDF'), + cls: 'x-btn-text-icon', + icon: res('icon.exporttools.pdf'), + action: 'pdf' + },{ + //id: 'btn-export-csv', + xtype: 'button', + text: g('Export as CSV'), + cls: 'x-btn-text-icon', + icon: res('icon.exporttools.csv'), + action: 'csv' + },{ + //id: 'btn-export-xml', + xtype: 'button', + text: g('Export as XML'), + cls: 'x-btn-text-icon', + icon: res('icon.exporttools.xml'), + action: 'xml' + }] + }); + + this.callParent(arguments); + + Ext.each(this.query('button'), function(e) { + e.on('click', function(btn) { + window.location.href = approot('/'+ + this.gridId + '/report/'+ btn.action); + }, this); + }, this); + + }, + + enableExport: function() { + Ext.each(this.query('button[action]'), function(c) { + c.show(); + }); + this.down('button[ref=noexport]').hide(); + + }, + + disableExport: function() { + Ext.each(this.query('button[action]'), function(c) { + c.hide(); + }); + this.down('button[ref=noexport]').show(); + } + +}); diff --git a/src/test/resources/app/lib/view/FileGrid.js b/src/test/resources/app/lib/view/FileGrid.js new file mode 100644 index 0000000..8abb8ef --- /dev/null +++ b/src/test/resources/app/lib/view/FileGrid.js @@ -0,0 +1,138 @@ +/** + * FileGrid + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 17, 2011 + */ + + +Ext.define('DanteFrontend.lib.view.FileGrid', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-grid-file', + + addItemText: g('Upload new'), + removeItemText: g('Remove'), + + /** + * Form api to handle the file uploads + */ + api: null, + + config: { + filter: null, + submitParam: null, + store: null + }, + + initComponent: function() { + var config = { + border: false, + layout: { + type: 'vbox', + align: 'stretch' + }, + items: [Ext.apply(this.initialConfig, { + xtype: 'grid', + columns: this.initialConfig.columns || [{ + header: g('Title'), + dataIndex: 'fileName', + flex: 1 + }], + listeners: { + selectionchange: this.toggleRemove, + itemdblclick: this.download, + scope: this + }, + bbar: { + buttonAlign: 'left', + items: [{ + xtype: 'form', + baseParams: { csrfTkn: csrfTkn }, + api: this.initialConfig.api, + border: false, + bodyStyle: 'background-color: transparent;', + height: 23, + width: 80, + items: { + xtype: 'filefield', + buttonOnly: true, + name: 'fileUpload', + buttonText: this.addItemText, + listeners: { + change: this.onFileSelected, + scope: this + } + } + },{ + text: this.removeItemText, + handler: this.onRemove, + ref: 'remove', + scope: this + }] + } + })] + }; + + Ext.apply(this, config); + this.callParent(arguments); + }, + + onRender: function() { + this.callParent(arguments); + this.setStore(this.down('*[xtype=grid]').getStore()); + this.removeButton = this.down('*[xtype=grid] toolbar button[ref=remove]'); + this.grid = this.down('*[xtype=grid]'); + this.toggleRemove(); + }, + + onFileSelected: function(f) { + this.setLoading(g('Uploading...')); + var loadConf = { + scope: this, + callback: function() { + this.setLoading(false); + } + }; + this.query('form').shift().submit({ + params: this.getSubmitParam(), + success: function() { + DanteFrontend.lib.Notify.base.success('Upload', g('File uploaded successfully.')); + this.store.load(loadConf); + }, + failure: function(f, a) { + DanteFrontend.lib.Notify.base.error('Upload error', a.result.errorMessage); + this.store.load(loadConf); + }, + scope: this + }); + }, + + onRemove: function() { + var record = this.grid.getSelectionModel().getLastSelected(); + this.getStore().remove(record); + this.getStore().sync(); + }, + + setFilter: function(filterObj) { + this.filter = filterObj; + this.setSubmitParam(filterObj); + this.getStore().filter([this.filter]); + }, + + setSubmitParam: function(obj) { + this.submitParam = {}; + this.submitParam[obj.property] = obj.value; + }, + + toggleRemove: function() { + this.removeButton.setDisabled( + Ext.isEmpty(this.grid.getSelectionModel().getLastSelected()) + ); + }, + + download: function(v, r) { + window.location = this.api.download + r.getId(); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/view/Filter.js b/src/test/resources/app/lib/view/Filter.js new file mode 100644 index 0000000..7f97cd4 --- /dev/null +++ b/src/test/resources/app/lib/view/Filter.js @@ -0,0 +1,241 @@ +/** + * Filter + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 16, 2011 + */ + +Ext.define('DanteFrontend.lib.view.Filter', { + extend: 'Ext.form.Panel', + alias: 'widget.df-filter', + + labelAlign: 'top', + store: null, + + initComponent: function() { + + this.types = ['date', 'numeric', 'boolean', 'list', 'string', 'month']; + + this.processItems(this.items); + this.parseFilters(this.items); + + if(Ext.isEmpty(this.store)) { + Ext.Error.raise('You need to define a store for a filter!'); + } + + this.items.push([{ + xtype: 'button', + ref: 'apply', + text: g('Apply'), + margin: '10 0 0 6', + iconCls: 'btn-applyfilter' + },{ + xtype: 'button', + ref: 'clear', + text: g('Clear'), + margin: '10 0 0 6', + iconCls: 'btn-clearfilter' + }]); + + var config = { + border: false, + padding: 6, + layout: { + type: 'hbox' + }, + defaults: { + labelAlign: 'top', + margin: '0 6 0 0' + }, + appliedFilters: [] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + + this.store = Ext.isString(this.store) ? + Ext.data.StoreManager.lookup(this.store):this.store; + + this.store.on('beforeload', this.onBeforeStoreLoad, this); + this.on('render', function() { + var nav = new Ext.util.KeyNav(this.getEl(), { + "enter" : this.applyFilters, + "esc": this.clearFilters, + scope : this + }); + + }, this); + + this.on('destroy', function() { + this.store.un('beforeload', this.onBeforeStoreLoad, this); + }, this); + + this.query('button[ref=apply]').shift().setHandler(this.applyFilters, this); + this.query('button[ref=clear]').shift().setHandler(this.clearFilters, this); + + }, + + + processItems: function(items) { + var processor = function(i) { + i.emptyText = '<' + g('not filtered') + '>'; + } + Ext.each(items, processor, this); + }, + + parseFilters: function(items) { + this.filters = []; + + var parser = function(item) { + var fType, confProcessor; + fType = Ext.isString(item.filter) ? + item.filter: Ext.isObject(item.filter) ? item.filter.type:null; + + confProcessor = (fType == 'month') ? 'monthConfProcessor':'defaultConfProcessor'; + if(Ext.Array.contains(this.types, fType)) { + this[confProcessor](this.filters, fType, item); + } + else Ext.Error.raise('Unknown filter type: ' + fType); + }; + + Ext.each(items, parser, this); + }, + + defaultConfProcessor: function(filters, fType, item) { + var incomingConf = Ext.isString(item.filter) ? {}:item.filter; + filters.push(Ext.apply( + {field: item.name, type: fType}, Ext.applyIf(incomingConf, { + comparison: 'eq' + }))); + }, + + monthConfProcessor: function(filters, fType, item) { + filters.push({field: item.name, type: 'date', month: true, comparison: 'gt'}); + filters.push({field: item.name, type: 'date', month: true, comparison: 'lt'}); + }, + + processDefaultValue: function(filter, value) { + return value; + }, + + processMonthValue: function(filter, value) { + var begin = Ext.Date.parse('1/'+ value, Ext.Date.patterns.MonthFilter, true); + if(filter.comparison === 'gt') { + return Ext.Date.format(begin, 'Y-m-d 00:00:00'); + } else { + return Ext.Date.format(begin, 'Y-m-t 23:59:59') + } + }, + + applyFilters: function() { + //prevent duplication + this.appliedFilters = []; + Ext.each(this.filters, function(f) { + var field = this.getForm().findField(f.field); + var valueProcessorName = (f.hasOwnProperty('month') && f.month) ? 'month':'default'; + if (!Ext.isEmpty(field.getValue())) this.appliedFilters.push( + Ext.apply(f, {value: this['process' + + Ext.String.capitalize(valueProcessorName) + 'Value'](f, field.getValue()) + }) + ); + }, this); + + + if(!Ext.isEmpty(this.appliedFilters)) { + this.store.load(); +//{ +// callback: function() { +// if(this.store.sorters.getCount()) { +// this.store.sort(); +// } +// }, +// scope: this +// } + //this.store.clearFilter(); + //this.store.filter(appliedFilters); + } else { + this.clearFilters(); + DanteFrontend.lib.Notify.base.success(g('Empty filters'), + g('Cleared all filters.')); + } + }, + + onBeforeStoreLoad: function(store, options) { + options.params = {} || options.params; + Ext.apply(options.params, {filter: this.appliedFilters}); + }, + + clearFilters: function() { + this.appliedFilters = []; + this.store.load(); + this.getForm().reset(); + } + +// buildQuery : function (filters) { +// var p = {}, i, f, root, dataPrefix, key, tmp, +// len = filters.length; +// +// if (!this.encode){ +// for (i = 0; i < len; i++) { +// f = filters[i]; +// root = [this.paramPrefix, '[', i, ']'].join(''); +// p[root + '[field]'] = f.field; +// +// dataPrefix = root + '[data]'; +// for (key in f.data) { +// p[[dataPrefix, '[', key, ']'].join('')] = f.data[key]; +// } +// } +// } else { +// tmp = []; +// for (i = 0; i < len; i++) { +// f = filters[i]; +// tmp.push(Ext.apply( +// {}, +// {field: f.field}, +// f.data +// )); +// } +// // only build if there is active filter +// if (tmp.length > 0){ +// p[this.paramPrefix] = Ext.JSON.encode(tmp); +// } +// } +// return p; +// }, +// +// cleanParams : function (p) { +// // if encoding just delete the property +// if (this.encode) { +// delete p[this.paramPrefix]; +// // otherwise scrub the object of filter data +// } else { +// var regex, key; +// regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]'); +// for (key in p) { +// if (regex.test(key)) { +// delete p[key]; +// } +// } +// } +// }, +// +// getFilterData : function (filters) { +// var out = [], i, len; +// +// filters.each(function (f) { +// if (f.active) { +// var d = [].concat(f.serialize()); +// for (i = 0, len = d.length; i < len; i++) { +// out.push({ +// field: f.dataIndex, +// data: d[i] +// }); +// } +// } +// }); +// return out; +// } +}); diff --git a/src/test/resources/app/lib/view/Form.js b/src/test/resources/app/lib/view/Form.js new file mode 100644 index 0000000..5336b41 --- /dev/null +++ b/src/test/resources/app/lib/view/Form.js @@ -0,0 +1,113 @@ +/** + * Form + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.lib.view.Form', { + extend: 'Ext.form.Panel', + + labelAlign: 'top', + + accessor: { + }, + + initComponent: function() { + var config = { + validationMessages: [], + bbar: { + items: ['->',{ + text: g('Save'), + action: 'save', + iconCls: 'icon-save', + cls: 'button-save', + handler: function() { + this.fireEvent('save', this.getRecord()); + }, + scope: this + }/*,{ + text: g('Cancel'), + action: 'cancel', + iconCls: 'icon-cancel', + cls: 'button-cancel', + handler: function() { + this.fireEvent('cancel', this.getRecord()); + }, + scope: this + }*/] + } + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + + this.callParent(arguments); + }, + + addValidationMessage: function(msg) { + this.validationMessages.push(msg); + }, + + validateAndUpdate: function() { + if(this.getForm().isValid()) { + this.getForm().updateRecord(this.accessRecord()); + return true; + } + this.alertValidation(); + return false; + }, + + alertValidation: function() { + var msgs = this.validationMessages; + if(Ext.isEmpty(msgs)) { + DanteFrontend.lib.Notify.error.formValidation(); + } else { + Ext.each(msgs, function(m) { + DanteFrontend.lib.Notify.base.error(g('Validation problem'), m); + }) + } + //flush + this.validationMessages = []; + }, + + + // toto by mohlo byt zastresene notifikacnym mechanizmom medzi storeom a + // formom, form musi vzdy reloadnut record, ktory updatol. store po syncu + // record destroyne a nanovo nacita - vo forme zostava referencia na stary + // neaktualny (dirty) record + + //inak - vezmi z recordu ref na store, reloadni podla id. ak si natiahol phantom record, + //skus podla poradia v store? + + accessRecord: function() { + return this.accessor.fn.apply(this.accessor.scope); + }, + + accessAndLoad: function() { + var rec = this.accessRecord(); + this.loadRecord(rec); + return rec; + }, + + setRecordAccessor: function(accessFn, scope) { + this.accessor = { + fn: accessFn, + scope: scope + }; + }, + +// onRender: function() { +// this.mask = Ext.createWidget() +// this.callParent(arguments); +// }, + + onBeforeSync: function() { + this.setLoading(true); + }, + + onAfterSync: function() { + this.setLoading(false); + } + +}); diff --git a/src/test/resources/app/lib/view/Grid.js b/src/test/resources/app/lib/view/Grid.js new file mode 100644 index 0000000..ae07188 --- /dev/null +++ b/src/test/resources/app/lib/view/Grid.js @@ -0,0 +1,110 @@ +/** + * Grid + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.lib.view.Grid', { + extend: 'Ext.grid.Panel', + alias: 'widget.df-grid', + + + addItemText: g('Add'), + removeItemText: g('Remove'), + emptyText: g('No items defined.'), + + initComponent: function() { + this.hasNewRecord = false; + var config = { + deferEmptyText: false, + bbar: this.toolbar ? (this.initialConfig.bbar !== undefined ? + this.initialConfig.bbar : this.getDefaultToolbarConfig()) : null + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + + this.addEvents('itemadded', 'itemremoved', 'itemselected'); + //override of the default selectionchange event + + this.callParent(arguments); + + this.getStore().on('aftersync', function(s, o) { + this.hasNewRecord = false; + }, this); + + this.on('selectionchange', this.onSelectionChange, this); + + var initGrid = function() { + this.hasNewRecord = false; + if (this.getStore().count() > 0) this.getSelectionModel().select(0); + else this.toggleRemoveButton(false); + }; + + this.getStore().on('load', initGrid, this); + this.on('render', initGrid, this); + + this.on('beforedestroy', function() { + this.getStore().un('load', initGrid, this); + }, this); + }, + + + getDefaultToolbarConfig: function() { + return { + buttonAlign: 'left', + items: [{ + action: 'addItem', + text: this.addItemText, + handler: this.addItem, + scope: this, + iconCls: 'icon-add' + },'|',{ + action: 'removeItem', + text: this.removeItemText, + handler: this.removeItem, + scope: this, + iconCls: 'icon-remove' + }] + }; + }, + + addItem: function() { + if(!this.hasNewRecord) { + this.hasNewRecord = true; ; + var rec = this.getStore().getDefaultModel(); + this.getStore().add(rec); + this.getSelectionModel().select(rec); + this.fireEvent('itemadded', this, rec); + } + }, + + removeItem: function() { + var store = this.getStore(); + var selModel = this.getSelectionModel(); + var record = selModel.getSelection().shift(); + if(!Ext.isEmpty(record)) { + if(record.phantom) { + this.hasNewRecord = false; + } + store.removeAt(store.indexOf(record)) + store.sync(); + this.fireEvent('itemremoved', record, this); + } + }, + + + //privee + toggleRemoveButton: function(bool) { + var btn = this.down('toolbar button[action=removeItem]'); + if (btn) + bool ? btn.enable():btn.disable(); + }, + + onSelectionChange: function(v, s) { + this.toggleRemoveButton(!Ext.isEmpty(s)); + this.fireEvent('itemselected', v, s); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/view/RowEditing.js b/src/test/resources/app/lib/view/RowEditing.js new file mode 100644 index 0000000..be7f01d --- /dev/null +++ b/src/test/resources/app/lib/view/RowEditing.js @@ -0,0 +1,40 @@ +/** + * RowEditing + * Override and extension of the slightly buggy RowEditing plugin. + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.lib.view.RowEditing', { + extend: 'Ext.grid.plugin.RowEditing', + + errorSummary: false, + + initEditor: function() { + var me = this, + grid = me.grid, + view = me.view, + headerCt = grid.headerCt; + + return Ext.create('Ext.grid.RowEditor', { + autoCancel: me.autoCancel, + errorSummary: false, + fields: headerCt.getGridColumns(), + hidden: true, + + + editingPlugin: me, + renderTo: view.el, + + + //tooltip buggy, disabling completely + showToolTip: Ext.emptyFn, + hideToolTip: Ext.emptyFn, + getToolTip: Ext.emptyFn, + repositionTip: Ext.emptyFn + }); + + } +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/view/StatePanel.js b/src/test/resources/app/lib/view/StatePanel.js new file mode 100644 index 0000000..cc6ad16 --- /dev/null +++ b/src/test/resources/app/lib/view/StatePanel.js @@ -0,0 +1,72 @@ +/** + * StatePanel + * Panel with switchable content, showing by default an empty panel with default text. + * + * @author fk + * @version 0.1 + * @date Oct 7, 2011 + */ + + +Ext.define('DanteFrontend.lib.view.StatePanel', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-statepanel', + + config: { currentWidget: null }, + defaultState: null, + + initComponent: function() { + var config = { + layout: 'fit', + border: false, + items: [], + defaultState: this.defaultState || 'empty' + }; + + this.initialConfig.content.push( + { ref: 'empty', border: false, xtype: 'panel', html: this.initialConfig.emptyText || g('Empty panel') } + ); + + Ext.each(this.initialConfig.content, function(conf) { + this[this._refFn(conf.ref)] = this[this._refFn(conf.ref)] || function() { + this._showWidgetByConf(conf); + } + }, this); + + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + + this.on('afterrender', function() { + if(!this.getCurrentWidget()) + this[this._refFn(this.defaultState)](); + }, this); + + this.callParent(arguments); + }, + + showWidget: function(ref) { + this[this._refFn(ref)](); + }, + + _createWidget: function(conf) { +// if(Ext.isEmpty(this.widgetCache[conf.ref])) { +// this.widgetCache[conf.ref] = Ext.widget(conf.xtype, conf); +// } +// return this.widgetCache[conf.ref]; + + return Ext.widget(conf.xtype, conf) + }, + + _refFn: function(ref) { + return 'show'+ Ext.String.capitalize(ref); + }, + + _showWidgetByConf: function(conf) { + var w = this._createWidget(conf); + if(this.getCurrentWidget()) + this.remove(this.getCurrentWidget(), true); + this.setCurrentWidget(w); + this.add(w); + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/view/TextField.js b/src/test/resources/app/lib/view/TextField.js new file mode 100644 index 0000000..69ccbfe --- /dev/null +++ b/src/test/resources/app/lib/view/TextField.js @@ -0,0 +1,28 @@ +/** + * ClearableText + * Custom textfield with clear button. + * + * @author fk + * @version 0.1 + * @date Jul 27, 2011 + */ + + +Ext.define('DanteFrontend.lib.view.TextField', { + extend: "Ext.form.field.Trigger", + alias:["widget.df-textfield"], + + initComponent: function() { + var triggers = { + triggerCls: Ext.baseCSSPrefix + "form-clear-trigger", + onTriggerClick:function(){ + this.setValue(null); + } + }; + + if(this.clearable) Ext.apply(this.initialConfig, triggers); + Ext.apply(this, this.initialConfig); + + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/view/Window.js b/src/test/resources/app/lib/view/Window.js new file mode 100644 index 0000000..df0a393 --- /dev/null +++ b/src/test/resources/app/lib/view/Window.js @@ -0,0 +1,27 @@ +/** + * Window + * A basic preconfigured window. + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.lib.view.Window', { + extend: 'Ext.Window', + alias: 'widget.df-window', + + initComponent: function() { + var config = { + //autoScroll: true, + border: false, + layout:'fit', + modal: true, + shadow: false, + stateful: false //always fall back to default size + }; + Ext.apply(this, Ext.apply(this.initialConfig, config)); + + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/lib/view/multistateeditor/MultiStateEditor.js b/src/test/resources/app/lib/view/multistateeditor/MultiStateEditor.js new file mode 100644 index 0000000..30ef99d --- /dev/null +++ b/src/test/resources/app/lib/view/multistateeditor/MultiStateEditor.js @@ -0,0 +1,396 @@ +/** + * MultiStateEditor + * Universal editor with multiple states: + * - full list + * - mini-list + editor + * + * TODO: - view caching with store reload? + * + * @author fk + * @version 0.1 + * @date Aug 4, 2011 + */ + + +Ext.define('DanteFrontend.lib.view.multistateeditor.MultiStateEditor', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-msedit', + + /** + * Configuration for the brief editor column. + */ + briefGridConfig: { + column: { + /** + * which column of 'fullColumns' config to use as base column of the brief editor + * @required + */ + dataIndex: null, + /** + * base column template + * @alternate('tpl', 'renderer') + */ + tpl: '', + /** + * base column renderer + * @alternate('tpl', 'renderer') + */ + renderer: null + } + }, + + /** + * + */ + + + config: { + currentState: null, + currentIndex: null, + previousIndex: null, + selectedRecord: null + }, + + + initComponent: function() { + + this.states = new Ext.util.MixedCollection(); + this.states.addAll({ + fullGrid: { + selector: '*[ref=grid-full]', + view: null + }, + editorWithBriefGrid: { + selector: '*[ref=editor-grid-brief]', + view: null + }, + editor: { + selector: '*[ref=editor]', + view: null + }, + briefGrid: { + selector: '*[ref=grid-brief]', + view: null + } + }); + + this.addEvents('selectitem', 'createitem'); + + var config = { + //autoScroll: true, + border: false, + layout: { + type: 'fit' + }, + startState: this.getStartState(), + items: [], + tools: [{ + type: 'prev', + renderTpl: DanteFrontend.view.Renderers.tool(g('back to full list')), + handler: function() { + this.loadFullGrid(); + }, + scope: this +// },{ +// type: 'toggle', +// renderTpl: DanteFrontend.view.Renderers.tool(g('hide filter')), +// handler: function() { +// this.toggleFilter(); +// }, +// scope: this + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + + this.store = Ext.data.StoreManager.lookup(this.initialConfig.store); + + this.store.on('aftersync', this.onStoreSync, this); + + this.on('render', this.onEditorReady, this); + this.callParent(); + }, + + + getStartState: function() { + if(!this.initialConfig.fullGridConfig) return 'editorWithBriefGrid'; + return this.initialConfig.startState || 'fullGrid'; + }, + + onEditorReady: function() { + this['load' + Ext.String.capitalize(this.startState)](); + }, + + onStoreSync: function() { + var v = this.getView('briefGrid'); + if(v) { + v.getView().select(this.getCurrentIndex()); + this.selectBriefGridItem(this.store.getAt(this.getCurrentIndex())); + } + }, + + + onNewItem: function(v, r) { + this.setSelectedRecord(r); + this.fireEvent('createitem', r); + }, + + onRemovedItem: function(v, r) { + var rec = this.store.getAt(0); + if(rec) + this.selectBriefGridItem(rec); + + }, + + onFullGridNewItem: function(v, r) { + this.confirmFullGridItem(r); + this.onNewItem(v, r); + }, + + + loadFullGrid: function() { + this.loadStateView('fullGrid'); + this.down('tool[type=prev]').hide(); + }, + + loadEditorWithBriefGrid: function(rec) { + var prevTool = this.down('tool[type=prev]'); + if(Ext.isEmpty(rec)) { + rec = this.store.first(); + } + if(this.initialConfig.fullGridConfig) prevTool.show(); + else prevTool.hide(); + this.loadStateView('editorWithBriefGrid'); + + if(rec) { + this.query('*[ref=grid-brief]').shift().getSelectionModel().select(rec); + this.selectBriefGridItem(rec); + } + }, + + getView: function(viewName) { + return this.query(this.states.get(viewName).selector).shift(); + }, + + getStateByName: function(stateName) { + return this.states.get(stateName); + }, + + loadStateView: function(stateName) { + this.resetState(); + this.add(this.instantiateView(stateName)); + this.setCurrentState(this.getStateByName(stateName)); + }, + + resetState: function() { + if(!Ext.isEmpty(this.getCurrentState())) { + this.remove(this.getCurrentState().view, false); + //this.getCurrentState().view = null; + } + + }, + + instantiateView: function(stateName) { + var state = this.states.get(stateName), instance, conf, + postProcessor = this['process' + Ext.String.capitalize(stateName)] || false; + if(Ext.isEmpty(state.view)) { + conf = this['get' + Ext.String.capitalize(stateName) + 'ViewConfig'](stateName); + state.view = postProcessor ? + postProcessor(Ext.widget(conf.xtype, conf)):Ext.widget(conf.xtype, conf); + } + return state.view; + }, + + // listener handlers + + confirmFullGridItem: function(rec) { + this.loadEditorWithBriefGrid(rec); + }, + + selectBriefGridItem: function(rec) { + this.setSelectedRecord(rec); + this.fireEvent('selectitem', rec); + }, + + setSelectedRecord: function(rec) { + this.setPreviousIndex(this.getCurrentIndex()); + this.setCurrentIndex((!Ext.isEmpty(rec.index)) ? + rec.index:this.store.lastIndex); + + if(rec.phantom) { + this.setCurrentIndex(this.store.getCount() - 1); + } + + this._selectedRecord = rec; + }, + + getSelectedRecord: function() { + return this._selectedRecord; + }, + + // configuration getters + + /** + * A base state view config: a full grid panel. Double clicking an item + * switches to brief grid with editor state. + */ + getFullGridViewConfig: function() { + var lst = this.fullGridConfig.listeners || {}; + lst = { + itemdblclick: function(v, r) { this.confirmFullGridItem(r); }, + itemadded: this.onFullGridNewItem, + scope: this + }; + + Ext.apply(this.fullGridConfig, { + store: this.store, + xtype: 'df-grid', + listeners: lst, + flex: 1, + border: false, + bodyStyle: 'border-width: 1px 0 1px 0', + toolbar: true + }); + + var items = []; + + if(!Ext.isEmpty(this.filterConfig)) { + Ext.apply(this.filterConfig, { + xtype: this.filterConfig.xtype || 'df-filter', + height: 62, + store: this.store + }); + items.push(Ext.clone(this.filterConfig)); + } + + items.push(Ext.clone(this.fullGridConfig)); + + return { + xtype: 'panel', + ref: 'fullGrid', + border: false, + layout: { + type: 'vbox', + align: 'stretch' + }, + items: items + }; + }, + + /** + * A base state view config: a border-layout panel with brief grid and editor. + */ + getEditorWithBriefGridViewConfig: function() { + return { + animate: true, + border: false, + layout: { + type: 'border' + }, + xtype: 'panel', + ref: 'editorWithBriefGrid', + items: [ + Ext.apply({ + region: 'west', + //flex: this.briefGridConfig.splitterFlex, + //width: 230, + split: true, + //frame: true, + margin: '2 0 2 2' + }, this.getBriefGridViewConfig()), + Ext.apply({ + //frame: true, + region: 'center', + margin: '2 2 2 0' + }, this.getEditorViewConfig()) + ] + }; + }, + + /** + * Get brief grid view config: a one-column aggregated list, allowing to + * quickly switch between edited items. + */ + getBriefGridViewConfig: function() { + var bgc = this.briefGridConfig, + fgc = this.fullGridConfig, + briefColumn = null, + config = null; + + + //select column from full grid config + if(!Ext.isEmpty(fgc) && !Ext.isEmpty(fgc.columns)) { + Ext.Array.each(fgc.columns, function(col) { + if(col.dataIndex == bgc.column.dataIndex) { + briefColumn = Ext.clone(col); + return false; + } + return true; + }); + Ext.apply(briefColumn, bgc.column); + Ext.apply({ flex: 1 }, briefColumn); + //apply column from brief grid config + } else { + briefColumn = bgc.column; + //or don't apply anything and rely on xtype + } + + var lst = bgc.listeners || {}; + + lst = { + scope: this, + itemclick: function(v, r) { + this.selectBriefGridItem(r); + }, + itemadded: this.onNewItem, + itemremoved: this.onRemovedItem + }; + + var gridConfig = { + toolbar: true, + xtype: bgc.xtype || 'df-grid', + store: this.store, + ref: 'grid-brief', + listeners: lst }; + + if (briefColumn) gridConfig.columns = [briefColumn]; + else if (!bgc.xtype) + throw new Ext.Error('You need to either specify the ' + + 'brief list xtype, or the brief column config.'); + + Ext.apply(gridConfig, bgc); + + if(gridConfig.showFilter) { + Ext.apply(this.filterConfig, { + xtype: this.filterConfig.xtype || 'df-filter', + height: 62, + store: this.store + }); + + config = { + layout: { type: 'vbox', align: 'stretch' }, + items: [this.filterConfig, gridConfig], + flex: gridConfig.flex, + border: false + } + } else { + config = gridConfig; + } + + + return config; + }, + + /** + * Get editor view config: a form based on 'df-form' widget. + */ + getEditorViewConfig: function() { + return Ext.apply({ + ref: 'editor', + accessor: { + scope: this, + fn: this.getSelectedRecord + } + }, this.initialConfig.editor); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/login.js b/src/test/resources/app/login.js new file mode 100644 index 0000000..1ca9607 --- /dev/null +++ b/src/test/resources/app/login.js @@ -0,0 +1,100 @@ +/** + * Login + * Login screen - (refactoring unnecessary, as this is a standalone screen). + * + * @author fk + * @author mx + * @version 0.1 + * @date Jul 11, 2011 + */ + +Ext.onReady(function() { + Ext.BLANK_IMAGE_URL = media('/img/s.gif'); + + var formSubmitHandler = function() { + var loginForm = Ext.getCmp('loginForm'); + loginForm.getForm().submit({ + waitTitle: g('Login in progress'), + waitMsg: g('Please wait, authenticating...'), + success: function(f, a) { + window.location.href = approot('/app/'); + }, + failure: function(f, a) { + switch (a.failureType) { + case Ext.form.Action.CLIENT_INVALID: + Ext.Msg.alert(g('Login error'), g('Invalid field values. Please check your input.')); + break; + + case Ext.form.Action.CONNECT_FAILURE: + Ext.Msg.alert(g('Login error'), g('Communication with server failed. Please try again.')); + break; + + case Ext.form.Action.SERVER_INVALID: + default: + Ext.Msg.alert(g('Login error'), a.result.loginError); + break; + } + } + }) + } + + var win = Ext.create('Ext.window.Window', { + border: false, + title: g('Dante Login'), + width: 300, + closable: false, + draggable: true, + resizable: false, + y: 300, + items: [ + { + id: 'loginForm', + xtype: 'form', + layout: 'anchor', + frame: true, + border: false, + buttonAlign: 'center', + url: approot('/j_spring_security_check'), + items: [ + { + xtype: 'textfield', + fieldLabel: g('Username'), + name: 'j_username', + width: '100%', + listeners: { + specialkey: function(f, e) { + if (e.getKey() == e.ENTER) { + formSubmitHandler(); + } + } + } + }, + { + xtype: 'textfield', + inputType: 'password', + fieldLabel: g('Password'), + name: 'j_password', + width: '100%', + listeners: { + specialkey: function(f, e) { + if (e.getKey() == e.ENTER) { + formSubmitHandler(); + } + } + } + } + ], + buttons: [ + { + text: g('Login to Dante'), + handler: function() { + formSubmitHandler(); + } + } + ] + } + ] + }); + + win.show(); +}); diff --git a/src/test/resources/app/model/Attachment.js b/src/test/resources/app/model/Attachment.js new file mode 100644 index 0000000..3b7a328 --- /dev/null +++ b/src/test/resources/app/model/Attachment.js @@ -0,0 +1,20 @@ +/** + * Attachment + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 9, 2011 + */ + +Ext.define('DanteFrontend.model.Attachment', { + extend: 'Ext.data.Model', + + //proxy: DanteFrontend.test.MemoryProxy.inst('attachment'), + + fields: [{ + name: 'id', type: 'int' + },{ + name: 'fileName', type: 'string' + }] +}); diff --git a/src/test/resources/app/model/Customer.js b/src/test/resources/app/model/Customer.js new file mode 100644 index 0000000..2cc853d --- /dev/null +++ b/src/test/resources/app/model/Customer.js @@ -0,0 +1,29 @@ +/** + * Customer model + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ +Ext.define('DanteFrontend.model.Customer', { + extend: 'Ext.data.Model', + + fields: [ + 'id', 'taxID', 'tradeID', 'web', 'email', 'emailFormat', + { + name: 'name', + convert: function(v) { + return v.replace("\n", " "); + } //ugly little workaround + //TODO: override the displayTpl of the cmbbox class - + //probably use an own class, since we will need it to support adding... + } + ], + + + statics: { + instanceDefaults: { + emailFormat: 1 + } + } +}); \ No newline at end of file diff --git a/src/test/resources/app/model/CustomerAddresses.js b/src/test/resources/app/model/CustomerAddresses.js new file mode 100644 index 0000000..4021e43 --- /dev/null +++ b/src/test/resources/app/model/CustomerAddresses.js @@ -0,0 +1,60 @@ +/** + * CustomerAddresses + * A model which unifies billing and postal customer address to use in a form. + * Since model relationships were introduced in ExtJS 4, + * this model will be refactored in the future to use them. + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.model.CustomerAddresses', { + extend: 'Ext.data.Model', + + fields: [{ + name: 'postalId', + mapping: 'postalAddress.id' + },{ + name: 'postalName', + mapping: 'postalAddress.name' + },{ + name: 'postalStreet1', + mapping: 'postalAddress.street1' + },{ + name: 'postalStreet2', + mapping: 'postalAddress.street2' + },{ + name: 'postalMunicipality', + mapping: 'postalAddress.municipality' + },{ + name: 'postalPerson', + mapping: 'postalAddress.person' + },{ + name: 'postalPostalCode', + mapping: 'postalAddress.postalCode' + },{ + name: 'billingId', + mapping: 'billingAddress.id' + },{ + name: 'billingName', + mapping: 'billingAddress.name' + },{ + name: 'billingStreet1', + mapping: 'billingAddress.street1' + },{ + name: 'billingStreet2', + mapping: 'billingAddress.street2' + },{ + name: 'billingMunicipality', + mapping: 'billingAddress.municipality' + },{ + name: 'billingPerson', + mapping: 'billingAddress.person' + },{ + name: 'billingPostalCode', + mapping: 'billingAddress.postalCode' + },{ + name: 'hasBillingAddress' + },'id'] +}); \ No newline at end of file diff --git a/src/test/resources/app/model/CustomerContact.js b/src/test/resources/app/model/CustomerContact.js new file mode 100644 index 0000000..41833b0 --- /dev/null +++ b/src/test/resources/app/model/CustomerContact.js @@ -0,0 +1,30 @@ +/** + * CustomerContact + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 30, 2011 + */ + +Ext.define('DanteFrontend.model.CustomerContact', { + extend: 'Ext.data.Model', + + fields: [{ + name: 'id' + },{ + name: 'name' + },{ + name: 'email' + },{ + name: 'phone' + },{ + name: 'description' + },{ + name: 'receiveInvoices' + },{ + name: 'receiveReports' + },{ + name: 'user ' + }] +}); \ No newline at end of file diff --git a/src/test/resources/app/model/CustomerContactAndUser.js b/src/test/resources/app/model/CustomerContactAndUser.js new file mode 100644 index 0000000..5ffb308 --- /dev/null +++ b/src/test/resources/app/model/CustomerContactAndUser.js @@ -0,0 +1,58 @@ +/** + * CustomerContactAndUser + * A model which unifies customer contact and user. Since model relationships were introduced in ExtJS 4, + * this model will be refactored in the future to use them. + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.model.CustomerContactAndUser', { + extend: 'Ext.data.Model', + + fields: [{ + name: 'id' + },{ + name: 'customerContactId', + mapping: 'customerContact.id' + },{ + name: 'name', + mapping: 'customerContact.name' + },{ + name: 'email', + mapping: 'customerContact.email' + },{ + name: 'phone', + mapping: 'customerContact.phone' + },{ + name: 'description', + mapping: 'customerContact.description' + },{ + name: 'receiveInvoices', + mapping: 'customerContact.receiveInvoices' + },{ + name: 'receiveReports', + mapping: 'customerContact.receiveReports' + },{ + name: 'user', + mapping: 'customerContact.user' + },{ + name: 'hasUser' + },{ + name: 'customerUserId', + mapping: 'customerUser.id' + },{ + name: 'login', + mapping: 'customerUser.login' + },{ + name: 'password', + mapping: 'customerUser.password' + }], + + statics: { + instanceDefaults: { + name: g('New contact') + } + } +}); diff --git a/src/test/resources/app/model/EmailQueue.js b/src/test/resources/app/model/EmailQueue.js new file mode 100644 index 0000000..d27537e --- /dev/null +++ b/src/test/resources/app/model/EmailQueue.js @@ -0,0 +1,14 @@ +/** + * EmailQueue + * + * @author fk + * @version 0.1 + * @date Jun 3, 2011 + */ + +Ext.define('DanteFrontend.model.EmailQueue', { + extend: 'Ext.data.Model', + + fields: ['queue_email', 'queue_name'], + idProperty: ['queue_email'] +}); \ No newline at end of file diff --git a/src/test/resources/app/model/Employee.js b/src/test/resources/app/model/Employee.js new file mode 100644 index 0000000..36e718f --- /dev/null +++ b/src/test/resources/app/model/Employee.js @@ -0,0 +1,13 @@ +/** + * Employee model + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ + +Ext.define('DanteFrontend.model.Employee', { + extend: 'Ext.data.Model', + + fields: ['id', 'name'] +}); \ No newline at end of file diff --git a/src/test/resources/app/model/Month.js b/src/test/resources/app/model/Month.js new file mode 100644 index 0000000..180e4bc --- /dev/null +++ b/src/test/resources/app/model/Month.js @@ -0,0 +1,13 @@ +/** + * Month model + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ + +Ext.define('DanteFrontend.model.Month', { + extend: 'Ext.data.Model', + + fields: ['id', 'name'] +}); \ No newline at end of file diff --git a/src/test/resources/app/model/Project.js b/src/test/resources/app/model/Project.js new file mode 100644 index 0000000..2e75d0b --- /dev/null +++ b/src/test/resources/app/model/Project.js @@ -0,0 +1,14 @@ +/** + * Project model + * + * @author fk + * @version 0.1 + * @date Jun 2, 2011 + */ + +Ext.define('DanteFrontend.model.Project', { + extend: 'Ext.data.Model', + + //TODO: push the other fields to ProjectStats model, probably? + fields: ['id', 'name', 'timePlanned', 'timeSpent', 'timeSpentGraph'] +}); \ No newline at end of file diff --git a/src/test/resources/app/model/Supplier.js b/src/test/resources/app/model/Supplier.js new file mode 100644 index 0000000..b47cb9d --- /dev/null +++ b/src/test/resources/app/model/Supplier.js @@ -0,0 +1,17 @@ +/** + * Customer model + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ +Ext.define('DanteFrontend.model.Supplier', { + extend: 'Ext.data.Model', + proxy: DanteFrontend.test.MemoryProxy.inst('supplier'), + fields: [{ + name: 'id', type: 'int' + },{ + name: 'name', type: 'string' + }] + +}); diff --git a/src/test/resources/app/model/Task.js b/src/test/resources/app/model/Task.js new file mode 100644 index 0000000..d412814 --- /dev/null +++ b/src/test/resources/app/model/Task.js @@ -0,0 +1,16 @@ +/** + * Task + * + * @author fk + * @version 0.1 + * @date Jun 2, 2011 + */ + +Ext.define('DanteFrontend.model.Task', { + extend: 'Ext.data.Model', + + fields: ['id', 'description', + {name: 'begin', type: 'date'}, + {name: 'end', type: 'date'}, + 'duration', 'ticket', 'projectName', 'userName'] +}); \ No newline at end of file diff --git a/src/test/resources/app/model/TimeLine.js b/src/test/resources/app/model/TimeLine.js new file mode 100644 index 0000000..64c30a4 --- /dev/null +++ b/src/test/resources/app/model/TimeLine.js @@ -0,0 +1,31 @@ +/** + * TimeLine + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ +Ext.define('DanteFrontend.model.TimeLine', { + extend: 'Ext.data.Model', + + fields: [ + 'id', + 'userName', + 'projectName', + 'customerName', + 'description', + { name: 'begin', + type: 'date', + dateFormat: DanteFrontend.common.jsonDateFormat, + sortType: Ext.data.SortTypes.asDate + },{ + name: 'end', + type: 'date', + dateFormat: DanteFrontend.common.jsonDateFormat, + sortType: Ext.data.SortTypes.asDate + }, + 'duration', + 'hidden' + ] +}); diff --git a/src/test/resources/app/model/User.js b/src/test/resources/app/model/User.js new file mode 100644 index 0000000..a446619 --- /dev/null +++ b/src/test/resources/app/model/User.js @@ -0,0 +1,22 @@ +/** + * User + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 30, 2011 + */ + +Ext.define('DanteFrontend.model.User', { + extend: 'Ext.data.Model', + + fields: [{ + name: 'id' + },{ + name: 'login' + },{ + name: 'name' + },{ + name: 'password' + }] +}); diff --git a/src/test/resources/app/model/budget/Balance.js b/src/test/resources/app/model/budget/Balance.js new file mode 100644 index 0000000..1911ac7 --- /dev/null +++ b/src/test/resources/app/model/budget/Balance.js @@ -0,0 +1,84 @@ +/** + * Balance + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 27, 2011 + */ + + +Ext.define('DanteFrontend.model.budget.Balance', { + extend: 'Ext.data.Model', + + fields: [{ + name:'id', + type: 'int' + },{ + name: 'generatedTitle', + type: 'string' + },{ + name: 'from', + type: 'date', + dateFormat: Ext.Date.patterns.ISO8601Long + },{ + name: 'to', + type: 'date', + dateFormat: Ext.Date.patterns.ISO8601Long + },{ + name: 'currentTotal', + type: 'float' + },{ + name: 'endingTotal', + type: 'float' + },{ + name: 'transferred', + type: 'float' + },{ + name: 'initialCredit', + type: 'float' + },{ + name: 'previousBalance_id', + mapping: 'previousBalance', + type: 'int' + },{ + name: 'budget_id', + mapping: 'budget', + type: 'int' + }], + + associations: [{ + type: 'hasMany', model: 'DanteFrontend.model.budget.BalanceItem', autoLoad: true, primaryKey: 'id', foreignKey: 'balance', name: 'balanceItems' + }], + + + getBudget: function() { + var id = this.get('budget_id'); + return Ext.data.StoreManager.lookup('budget.Budgets').getById(id); + }, + + getTotalCredit: function() { + var s = Ext.data.StoreManager.lookup('budget.CurrentBalanceItems'); + var sum = 0; + s.each(function(r) { + if(r.get('direction_id') == 0 && r.get('balance_id') == this.getId()) { + sum += r.get('grandTotal'); + } + }, this); + + return sum; + }, + + getTotalSpending: function() { + var s = Ext.data.StoreManager.lookup('budget.CurrentBalanceItems'); + var sum = 0; + s.each(function(r) { + if(r.get('direction_id') == 1 && r.get('balance_id') == this.getId()) { + sum += r.get('grandTotal'); + } + }, this); + + return sum; + } + +}); diff --git a/src/test/resources/app/model/budget/BalanceItem.js b/src/test/resources/app/model/budget/BalanceItem.js new file mode 100644 index 0000000..c02e9dd --- /dev/null +++ b/src/test/resources/app/model/budget/BalanceItem.js @@ -0,0 +1,96 @@ +/** + * BalanceItem + * + * @author fk + * @version 0.1 + * @date Sep 27, 2011 + */ + +Ext.define('DanteFrontend.model.budget.BalanceItem', { + extend: 'Ext.data.Model', + + fields: [{ + name:'id', + type: 'int' + },{ + name: 'type_id', + mapping: 'type', + type: 'int' + },{ + name: 'direction_id', + mapping: 'direction', + type: 'int' + },{ + name: 'description', + type: 'string' + },{ + name: 'receivedOn', + type: 'date', + dateFormat: Ext.Date.patterns.ISO8601Long + },{ + name: 'paidOn', + type: 'date', + dateFormat: Ext.Date.patterns.ISO8601Long + },{ + name: 'amount', + type: 'float' + },{ + name: 'base', + type: 'float' + },{ + name: 'total', + type: 'float' + },{ + name: 'grandTotal', + type: 'float' + },{ + name: 'runningTotal', + type: 'float' + },{ + name: 'vatRate', + type: 'int' + },{ + name: 'confirmationReason_id', + mapping: 'confirmationReason', + type: 'int' + },{ + name: 'confirmationStatus_id', + mapping: 'confirmationStatus', + type: 'int' + },{ + name: 'balance_id', + mapping: 'balance', + type: 'int' + },{ + name: 'budget_id', + mapping: 'budget', + type: 'int' + },{ + name: 'createdBy', + type: 'int' + },{ + name: 'createdOn', + type: 'date', + dateFormat: Ext.Date.patterns.ISO8601Long + }], + + associations: [{ + type: 'belongsTo', model: 'DanteFrontend.model.budget.Balance', autoLoad: false, associationKey: 'balance', getterName: 'getBalance', setterName: 'setBalance' + }], + + isExpense: function() { + return this.get('direction_id') == 1; + }, + + getBalance: function() { + var id = this.get('balance_id'); + return Ext.data.StoreManager.lookup('budget.Balances').getById(id); + }, + + statics: { + instanceDefaults: { + description: g('New item'), + vatRate: 20 + } + } +}); diff --git a/src/test/resources/app/model/budget/BalanceItemUser.js b/src/test/resources/app/model/budget/BalanceItemUser.js new file mode 100644 index 0000000..054b663 --- /dev/null +++ b/src/test/resources/app/model/budget/BalanceItemUser.js @@ -0,0 +1,28 @@ +/** + * BalanceItemUser + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 6, 2011 + */ + +Ext.define('DanteFrontend.model.budget.BalanceItemUser', { + extend: 'Ext.data.Model', + + fields: [{ + name: 'id' + },{ + name: 'user_id', + mapping: 'user', + type: 'int' + },{ + name: 'balanceItem_id', + mapping: 'balanceItem', + type: 'int' + },{ + name: 'relationshipRole_id', + mapping: 'relationshipRole', + type: 'int' + }] +}); \ No newline at end of file diff --git a/src/test/resources/app/model/budget/Budget.js b/src/test/resources/app/model/budget/Budget.js new file mode 100644 index 0000000..7dd0759 --- /dev/null +++ b/src/test/resources/app/model/budget/Budget.js @@ -0,0 +1,90 @@ +/** + * Budget + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 27, 2011 + */ + +Ext.define('DanteFrontend.model.budget.Budget', { + extend: 'Ext.data.Model', + + fields: [{ + name:'id', + type: 'int' + },{ + name: 'title', + type: 'string' + },{ + name: 'purpose_id', + mapping: 'purpose', + type: 'int' + },{ + name: 'description', + type: 'string' + },{ + name: 'currentBalance_id', + mapping: 'currentBalance', + type: 'int' + },{ + name: 'maxItemPrice', + type: 'float' + },{ + name: 'confirmationUsersAmount_id', + mapping: 'confirmationUsersAmount', + type: 'int' + },{ + name: 'confirmOnOverMaxItemPrice', + type: 'boolean' + },{ + name: 'confirmOnBalanceOverdraw', + type: 'boolean' + },{ + name: 'confirmAlways', + type: 'boolean' + },{ + name: 'renewalDayOfMonth', + type: 'int' + },{ + name: 'nextBalanceRenewal', + type: 'date', + dateFormat: Ext.Date.patterns.ISO8601Long + },{ + name: 'balanceRenewalFrequency_id', + mapping: 'balanceRenewalFrequency', + type: 'int' + },{ + name: 'balanceRenewalStrategy_id', + mapping: 'balanceRenewalStrategy', + type: 'int' + },{ + name: 'balanceInitialCredit', + type: 'float' + },{ + name: 'automaticBalanceTransfer', + type: 'boolean' + }], + + associations: [{ + type: 'hasMany', model: 'DanteFrontend.model.budget.Category', autoLoad: true, associationKey: 'categories', getterName: 'getCategories', setterName: 'setCategories' + }], + + statics: { + instanceDefaults: { + title: g('New budget'), + purpose_id: 1, + renewalDayOfMonth: 1, + maxItemPrice: 0, + balanceInitialCredit: 0, + currentBalance: 1 + } + }, + + getRemainingDays: function() { + var nextRenewal = this.get('nextBalanceRenewal'); + if(nextRenewal) { + return Math.round((nextRenewal.getTime() - new Date().getTime()) / (86.4e6)); + } + } +}); diff --git a/src/test/resources/app/model/budget/BudgetUser.js b/src/test/resources/app/model/budget/BudgetUser.js new file mode 100644 index 0000000..d3e7d51 --- /dev/null +++ b/src/test/resources/app/model/budget/BudgetUser.js @@ -0,0 +1,40 @@ +/** + * BudgetUser + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 12, 2011 + */ + +Ext.define('DanteFrontend.model.budget.BudgetUser', { + extend: 'Ext.data.Model', + + fields: [{ + name: 'id' + },{ + name: 'user_id', + mapping: 'user', + type: 'int' + },{ + name: 'budget_id', + mapping: 'budget', + type: 'int' + },{ + name: 'maxItemPrice', + type: 'int' + },{ + name: 'canConfirm', + type: 'boolean' + }], + + statics: { + instanceDefaults: { + canConfirm: 1 + } + }, + + validations: [{ + type: 'presence', field: 'user_id' + }] +}); diff --git a/src/test/resources/app/model/budget/ConfirmableBalanceItem.js b/src/test/resources/app/model/budget/ConfirmableBalanceItem.js new file mode 100644 index 0000000..cd679bb --- /dev/null +++ b/src/test/resources/app/model/budget/ConfirmableBalanceItem.js @@ -0,0 +1,95 @@ +/** + * BalanceItem + * + * @author fk + * @version 0.1 + * @date Sep 27, 2011 + */ + +Ext.define('DanteFrontend.model.budget.ConfirmableBalanceItem', { + extend: 'Ext.data.Model', + + fields: [{ + name:'id', + type: 'int' + },{ + name: 'type_id', + mapping: 'type', + type: 'int' + },{ + name: 'direction_id', + mapping: 'direction', + type: 'int' + },{ + name: 'description', + type: 'string' + },{ + name: 'receivedOn', + type: 'date', + dateFormat: Ext.Date.patterns.ISO8601Long + },{ + name: 'paidOn', + type: 'date', + dateFormat: Ext.Date.patterns.ISO8601Long + },{ + name: 'amount', + type: 'float' + },{ + name: 'base', + type: 'float' + },{ + name: 'total', + type: 'float' + },{ + name: 'grandTotal', + type: 'float' + },{ + name: 'runningTotal', + type: 'float' + },{ + name: 'confirmationReason_id', + mapping: 'confirmationReason', + type: 'int' + },{ + name: 'confirmationStatus_id', + mapping: 'confirmationStatus', + type: 'int' + },{ + name: 'balance_id', + mapping: 'balance', + type: 'int' + //confirmable mappings + },{ + name: 'action_id', + mapping: 'action', + type: 'int' + },{ + name: 'budget_id', + mapping: 'budget', + type: 'int' + },{ + name: 'createdBy', + type: 'int' + }], + + associations: [{ + type: 'belongsTo', model: 'DanteFrontend.model.budget.Balance', autoLoad: false, associationKey: 'balance', getterName: 'getBalance', setterName: 'setBalance' + }], + + isExpense: function() { + return this.get('direction_id') == 1; + }, + + isConfirmable: function() { + return this.get('action_id') == 1; + }, + + isRevokable: function() { + return this.get('action_id') == 2 ; + }, + + getBalance: function() { + var id = this.get('balance_id'); + return Ext.data.StoreManager.lookup('budget.Balances').getById(id); + } +}); diff --git a/src/test/resources/app/model/order/Category.js b/src/test/resources/app/model/order/Category.js new file mode 100644 index 0000000..7a97f3e --- /dev/null +++ b/src/test/resources/app/model/order/Category.js @@ -0,0 +1,19 @@ +/** + * Category + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 12, 2011 + */ +Ext.define('DanteFrontend.model.order.Category', + DanteFrontend.lib.model.Conf.enumKey({ + extend: 'Ext.data.Model', + proxyData: [{ + id: 1, + name: g('Category 1') + },{ + id: 2, + name: g('Category 2') + }] +})); \ No newline at end of file diff --git a/src/test/resources/app/model/order/DeliveryStatus.js b/src/test/resources/app/model/order/DeliveryStatus.js new file mode 100644 index 0000000..9d4ac64 --- /dev/null +++ b/src/test/resources/app/model/order/DeliveryStatus.js @@ -0,0 +1,30 @@ +/** + * DeliveryStatus + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 12, 2011 + */ + +Ext.define('DanteFrontend.model.order.DeliveryStatus', + DanteFrontend.lib.model.Conf.enumKey({ + extend: 'Ext.data.Model', + proxyData: [{ + id: 0, + name: g('In negotiation'), + enumKey: 'IN_NEGOTIATION' + },{ + id: 1, + name: g('Undelivered'), + enumKey: 'UNDELIVERED' + },{ + id: 2, + name: g('Partly delivered'), + enumKey: 'PARTLY_DELIVERED' + },{ + id: 3, + name: g('Fully delivered'), + enumKey: 'FULLY_DELIVERED' + }] +})); \ No newline at end of file diff --git a/src/test/resources/app/model/order/ItemType.js b/src/test/resources/app/model/order/ItemType.js new file mode 100644 index 0000000..16f54c5 --- /dev/null +++ b/src/test/resources/app/model/order/ItemType.js @@ -0,0 +1,18 @@ +/** + * ItemType + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 12, 2011 + */ + +Ext.define('DanteFrontend.model.order.ItemType', + DanteFrontend.lib.model.Conf.enumKey({ + extend: 'Ext.data.Model', + proxyData: [ + { id: 0, name: g('Hardware') }, + { id: 1, name: g('Service') }, + { id: 2, name: g('Software') } + ] +})); \ No newline at end of file diff --git a/src/test/resources/app/model/order/Order.js b/src/test/resources/app/model/order/Order.js new file mode 100644 index 0000000..a34204a --- /dev/null +++ b/src/test/resources/app/model/order/Order.js @@ -0,0 +1,59 @@ +/** + * Order + * + * @author fk + * @version 0.1 + * @date Aug 4, 2011 + */ + +Ext.define('DanteFrontend.model.order.Order', { + extend: 'Ext.data.Model', + + fields: [{ + name:'id', + type: 'int' + },{ + name: 'referenceId', + type: 'string' + },{ + name: 'summary', + type: 'string' + },{ + name: 'acceptedOn', + type: 'date' + },{ //assoc + name: 'customer_id', + mapping: 'customer', + type: 'int' + },{ + name: 'deliveryStatus_id', + mapping: 'deliveryStatus', + type: 'int' + },{ + name: 'supplierGrandTotal', + type: 'int' + },{ + name: 'sellingGrandTotal', + type: 'int' + }], + + +// hasMany: { +// model: 'DanteFrontend.model.order.OrderItem', name: 'orderitems' +// } + associations: [{ + type: 'belongsTo', model: 'DanteFrontend.model.Customer', autoLoad: true, associationKey: 'customer', getterName: 'getCustomer', setterName: 'setCustomer' + },{ + type: 'belongsTo', model: 'DanteFrontend.model.order.DeliveryStatus', autoLoad: true, associationKey: 'deliveryStatus', getterName: 'getDeliveryStatus', setterName: 'setDeliveryStatus' + },{ + type: 'hasMany', model: 'DanteFrontend.model.order.OrderItem', name: 'orderItems' + },{ + type: 'hasMany', model: 'DanteFrontend.model.order.Attachment', name: 'attachments' + }], + + statics: { + instanceDefaults: { + customer: 0 + } + } +}); diff --git a/src/test/resources/app/model/order/OrderItem.js b/src/test/resources/app/model/order/OrderItem.js new file mode 100644 index 0000000..3da3573 --- /dev/null +++ b/src/test/resources/app/model/order/OrderItem.js @@ -0,0 +1,74 @@ +/** + * OrderItem + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 4, 2011 + */ + +Ext.define('DanteFrontend.model.order.OrderItem', { + extend: 'Ext.data.Model', + + fields: [{ + name:'id', + type: 'int' + },{ + name: 'description', + type: 'string' + },{ + name: 'amount', + type: 'int' + },{ + name: 'unit', + type: 'string' + },{ + name: 'supplierUnitPrice', + type: 'float' + },{ + name: 'supplierBasePrice', + type: 'float' + },{ + name: 'sellingUnitPrice', + type: 'float' + },{ + name: 'sellingBasePrice', + type: 'float' + },{ + name: 'supplier_id', + mapping: 'supplier', + type: 'int' + },{ + name: 'unit_id', + mapping: 'unit', + type: 'int' + },{ + name: 'type_id', + mapping: 'type', + type: 'int' + },{ + name: 'order_id', + mapping: 'incomingOrder', + type: 'int' + },{ + name: 'vatRate', + type: 'int' + }], + + associations: [{ + type: 'belongsTo', model: 'DanteFrontend.model.order.Order' + },{ + type: 'belongsTo', model: 'DanteFrontend.model.Supplier', associationKey: 'supplier', getterName: 'getSupplier', setterName: 'setSupplier' + },{ + type: 'belongsTo', model: 'DanteFrontend.model.order.ItemType' + }], + + statics: { + instanceDefaults: { + supplier: 0, + type: 0, + unit: 0, + vatRate: 20 + } + } +}); diff --git a/src/test/resources/app/model/order/Unit.js b/src/test/resources/app/model/order/Unit.js new file mode 100644 index 0000000..09f0144 --- /dev/null +++ b/src/test/resources/app/model/order/Unit.js @@ -0,0 +1,18 @@ +/** + * Unit + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 12, 2011 + */ + +Ext.define('DanteFrontend.model.order.Unit', + DanteFrontend.lib.model.Conf.enumKey({ + extend: 'Ext.data.Model', + proxyData: [ + { id: 0, name: g('pc') }, + { id: 1, name: g('hr') } + ] + +})); \ No newline at end of file diff --git a/src/test/resources/app/preboot.js b/src/test/resources/app/preboot.js new file mode 100644 index 0000000..77c0b64 --- /dev/null +++ b/src/test/resources/app/preboot.js @@ -0,0 +1,41 @@ +/** + * preboot + * + * @author fk + * @version 0.1 + * @date Jul 25, 2011 + */ + + +Ext.Loader.setConfig({ + enabled: true +}); + +Ext.BLANK_IMAGE_URL = media('/img/s.gif'); +Ext.app.REMOTING_API.enableBuffer = 100; +Ext.Direct.addProvider(Ext.app.REMOTING_API); + +//differentiate between prod and dev-test +Ext.Direct.on('exception', function(ex) { + DanteFrontend.lib.Notify.base.error(g("Remote error"), ex.message); +}); + +Ext.Ajax.defaultHeaders = { + 'X-Supplied-Token': csrfTkn +}; + +Ext.Ajax.on('requestexception', function(conn, response) { + DanteFrontend.lib.Notify.base.error(g('XmlHttpRequest error'), + 'Status ' + response.status + ': ' + response.statusText); +}); + +Ext.Date.patterns = { + MonthFilter: 'j/n/Y', + ISO8601Long:"Y-m-d H:i:s", + ISO8601Short:"Y-m-d", + SK: "d. m. Y" +}; + +Ext.tip.QuickTipManager.init(); + +//overrides \ No newline at end of file diff --git a/src/test/resources/app/store/CurrentProjects.js b/src/test/resources/app/store/CurrentProjects.js new file mode 100644 index 0000000..53e7d5c --- /dev/null +++ b/src/test/resources/app/store/CurrentProjects.js @@ -0,0 +1,21 @@ +/** + * CurrentProjects + * Store supporting the current projects panel. + * + * @author fk + * @version 0.1 + * @date Jun 2, 2011 + */ + +Ext.define('DanteFrontend.store.CurrentProjects', { + extend: 'Ext.data.Store', + + model: 'DanteFrontend.model.Project', + proxy: { + type: 'direct', + api: { + read: dashboardController.listCurrentProjects + } + }, + autoLoad: false +}); \ No newline at end of file diff --git a/src/test/resources/app/store/Customers.js b/src/test/resources/app/store/Customers.js new file mode 100644 index 0000000..82257cf --- /dev/null +++ b/src/test/resources/app/store/Customers.js @@ -0,0 +1,17 @@ +/** + * Customer + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ +Ext.define('DanteFrontend.store.Customers', { + extend: 'DanteFrontend.lib.base.Store', + + model: 'DanteFrontend.model.Customer', + + api: { + read: userController.listCustomersByLoggedUser + } +}); \ No newline at end of file diff --git a/src/test/resources/app/store/Employees.js b/src/test/resources/app/store/Employees.js new file mode 100644 index 0000000..d1c0b8c --- /dev/null +++ b/src/test/resources/app/store/Employees.js @@ -0,0 +1,20 @@ +/** + * Employee + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ + +Ext.define('DanteFrontend.store.Employees', { + extend: 'Ext.data.Store', + + model: 'DanteFrontend.model.Month', + proxy: { + type: 'direct', + api: { + read: userController.listEmployeesByLoggedUser + } + }, + autoLoad: false +}); \ No newline at end of file diff --git a/src/test/resources/app/store/Months.js b/src/test/resources/app/store/Months.js new file mode 100644 index 0000000..5763e76 --- /dev/null +++ b/src/test/resources/app/store/Months.js @@ -0,0 +1,21 @@ +/** + * Months + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ +Ext.define('DanteFrontend.store.Months', { + extend: 'Ext.data.Store', + + model: 'DanteFrontend.model.Month', + proxy: { + type: 'ajax', + url: res('url.worklog.months'), + reader: { + type: 'json', + root: 'rows' + } + }, + autoLoad: false +}); diff --git a/src/test/resources/app/store/Projects.js b/src/test/resources/app/store/Projects.js new file mode 100644 index 0000000..20c8001 --- /dev/null +++ b/src/test/resources/app/store/Projects.js @@ -0,0 +1,20 @@ +/** + * Project + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ + +Ext.define('DanteFrontend.store.Projects', { + extend: 'Ext.data.Store', + + model: 'DanteFrontend.model.Project', + proxy: { + type: 'direct', + api: { + read: userController.listProjectsByLoggedUser + } + }, + autoLoad: false +}); \ No newline at end of file diff --git a/src/test/resources/app/store/Suppliers.js b/src/test/resources/app/store/Suppliers.js new file mode 100644 index 0000000..fd0a3ea --- /dev/null +++ b/src/test/resources/app/store/Suppliers.js @@ -0,0 +1,17 @@ +/** + * Customer + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ +Ext.define('DanteFrontend.store.Suppliers', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.Customer', + + api: { + read: userController.listSuppliers, + create: userController.addSuppliers + } +}); \ No newline at end of file diff --git a/src/test/resources/app/store/account/CustomerAdditionalInfo.js b/src/test/resources/app/store/account/CustomerAdditionalInfo.js new file mode 100644 index 0000000..ebada8c --- /dev/null +++ b/src/test/resources/app/store/account/CustomerAdditionalInfo.js @@ -0,0 +1,17 @@ +/** + * CustomerAdditionalInfos + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.store.account.CustomerAdditionalInfo', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.Customer', + + api: { + read: customerManagementController.readCustomerAdditionalInfo, + update: customerManagementController.saveCustomerAdditionalInfo + } +}); \ No newline at end of file diff --git a/src/test/resources/app/store/account/CustomerAddresses.js b/src/test/resources/app/store/account/CustomerAddresses.js new file mode 100644 index 0000000..48b9b9f --- /dev/null +++ b/src/test/resources/app/store/account/CustomerAddresses.js @@ -0,0 +1,17 @@ +/** + * CustomerAddresses + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.store.account.CustomerAddresses', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.CustomerAddresses', + + api: { + read : customerManagementController.readCustomerAddresses, + update : customerManagementController.saveCustomerAddresses + } +}); \ No newline at end of file diff --git a/src/test/resources/app/store/account/CustomerContactsAndUsers.js b/src/test/resources/app/store/account/CustomerContactsAndUsers.js new file mode 100644 index 0000000..dedac33 --- /dev/null +++ b/src/test/resources/app/store/account/CustomerContactsAndUsers.js @@ -0,0 +1,24 @@ +/** + * CustomerContactsAndUsers + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.store.account.CustomerContactsAndUsers', { + extend: 'DanteFrontend.lib.base.Store', + alias: 'store.df-account-ccu', + model: 'DanteFrontend.model.CustomerContactAndUser', + + /** + * if no proxy specified, a default proxy (@see DanteFrontend.lib.base.Store) + * is used, with this api def + */ + api: { + read : customerManagementController.readContacts, + create : customerManagementController.saveContacts, + update : customerManagementController.saveContacts, + destroy : customerManagementController.destroyContacts + } +}); \ No newline at end of file diff --git a/src/test/resources/app/store/account/LoggedCustomerContactAndUser.js b/src/test/resources/app/store/account/LoggedCustomerContactAndUser.js new file mode 100644 index 0000000..9c874ed --- /dev/null +++ b/src/test/resources/app/store/account/LoggedCustomerContactAndUser.js @@ -0,0 +1,19 @@ +/** + * CustomerUser + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jul 6, 2011 + */ + + +Ext.define('DanteFrontend.store.account.LoggedCustomerContactAndUser', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.CustomerContactAndUser', + + api: { + read: customerManagementController.readCustomerContactsOfLoggedUser, + update: customerManagementController.saveCustomerContactsOfLoggedUser + } +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/BalanceItemDirections.js b/src/test/resources/app/store/budget/BalanceItemDirections.js new file mode 100644 index 0000000..de46fa2 --- /dev/null +++ b/src/test/resources/app/store/budget/BalanceItemDirections.js @@ -0,0 +1,20 @@ +/** + * BalanceItemTypes + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 29, 2011 + */ + +Ext.define('DanteFrontend.store.budget.BalanceItemDirections', { + extend: 'Ext.data.Store', + fields: ['id', 'name'], + data: [{ + id: 0, + name: g('Credit') + },{ + id: 1, + name: g('Withdrawal') + }] +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/BalanceItemEmployees.js b/src/test/resources/app/store/budget/BalanceItemEmployees.js new file mode 100644 index 0000000..c290d9d --- /dev/null +++ b/src/test/resources/app/store/budget/BalanceItemEmployees.js @@ -0,0 +1,27 @@ +/** + * BalanceItemEmployees + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 6, 2011 + */ + + +Ext.define('DanteFrontend.store.budget.BalanceItemEmployees', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.budget.BalanceItemUser', + + api: { + read: budgetController.listBalanceItemAssociatedEmployees, + create: budgetController.saveBalanceItemAssociatedEmployees, + destroy: budgetController.destroyBalanceItemAssociatedEmployees + }, + + autoLoad: false, + remoteFilter: true, + + dependencyFilter: { + property: 'balanceItem_id' + } +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/BalanceItemTypes.js b/src/test/resources/app/store/budget/BalanceItemTypes.js new file mode 100644 index 0000000..c5b0d31 --- /dev/null +++ b/src/test/resources/app/store/budget/BalanceItemTypes.js @@ -0,0 +1,33 @@ +/** + * BalanceItemTypes + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 29, 2011 + */ + +Ext.define('DanteFrontend.store.budget.BalanceItemTypes', { + extend: 'Ext.data.Store', + fields: ['id', 'name'], + data: [{ + id: 0, + name: g('Invoice') + },{ + id: 1, + name: g('Bill') + },{ + id: 2, + name: g('Budget transfer') + },{ + id: 3, + name: g('Balance transfer') + },{ + id: 4, + name: g('External refill') + },{ + id: 5, + name: g('Other') + }] + +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/BalanceItems.js b/src/test/resources/app/store/budget/BalanceItems.js new file mode 100644 index 0000000..ee4b28b --- /dev/null +++ b/src/test/resources/app/store/budget/BalanceItems.js @@ -0,0 +1,31 @@ +/** + * BalanceItems + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.store.budget.BalanceItems', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.budget.BalanceItem', + + api: { + read: budgetController.listBalanceItems, + create: budgetController.saveBalanceItem, + update: budgetController.saveBalanceItem, + destroy: budgetController.destroyBalanceItem + }, + + autoLoad: false, + remoteFilter: true, + + sorters: [{ + property : 'receivedOn', + direction: 'DESC' + }], + + sortOnLoad: true + +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/BalanceRenewalFrequencies.js b/src/test/resources/app/store/budget/BalanceRenewalFrequencies.js new file mode 100644 index 0000000..26636a2 --- /dev/null +++ b/src/test/resources/app/store/budget/BalanceRenewalFrequencies.js @@ -0,0 +1,27 @@ +/** + * BalanceRenewalFrequencies + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.store.budget.BalanceRenewalFrequencies', { + extend: 'Ext.data.Store', + fields: ['id', 'name'], + data: [{ + id: 0, + name: g('Monthly') + },{ + id: 1, + name: g('Quarterly') + },{ + id: 2, + name: g('Biannually') + },{ + id: 3, + name: g('Annually') + }] + +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/BalanceRenewalStrategies.js b/src/test/resources/app/store/budget/BalanceRenewalStrategies.js new file mode 100644 index 0000000..3d7a704 --- /dev/null +++ b/src/test/resources/app/store/budget/BalanceRenewalStrategies.js @@ -0,0 +1,20 @@ +/** + * BalanceRenewalStrategies + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.store.budget.BalanceRenewalStrategies', { + extend: 'Ext.data.Store', + fields: ['id', 'name'], + data: [{ + id: 0, + name: g('Manual') + },{ + id: 1, + name: g('Automatic') + }] +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/Balances.js b/src/test/resources/app/store/budget/Balances.js new file mode 100644 index 0000000..5abc367 --- /dev/null +++ b/src/test/resources/app/store/budget/Balances.js @@ -0,0 +1,21 @@ +/** + * Balances + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.store.budget.Balances', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.budget.Balance', + + api: { + read: budgetController.listBalances, + create: budgetController.renewBalance + }, + + autoLoad: false, + remoteFilter: false +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/BudgetUsers.js b/src/test/resources/app/store/budget/BudgetUsers.js new file mode 100644 index 0000000..d608e37 --- /dev/null +++ b/src/test/resources/app/store/budget/BudgetUsers.js @@ -0,0 +1,25 @@ +/** + * BudgetUsers + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 12, 2011 + */ + +Ext.define('DanteFrontend.store.budget.BudgetUsers', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.budget.BudgetUser', + + api: { + read: budgetController.listBudgetUsers, + create: budgetController.saveBudgetUser, + destroy: budgetController.destroyBudgetUser + }, + + autoLoad: false, + remoteFilter: true, + dependencyFilter: { + property: 'budget_id' + } +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/Budgets.js b/src/test/resources/app/store/budget/Budgets.js new file mode 100644 index 0000000..5446620 --- /dev/null +++ b/src/test/resources/app/store/budget/Budgets.js @@ -0,0 +1,23 @@ +/** + * Budgets + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.store.budget.Budgets', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.budget.Budget', + + api: { + read: budgetController.list, + create: budgetController.save, + update: budgetController.save, + destroy: budgetController.destroy + }, + + autoLoad: false, + remoteFilter: true +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/ConfirmableBalanceItems.js b/src/test/resources/app/store/budget/ConfirmableBalanceItems.js new file mode 100644 index 0000000..dc01ed3 --- /dev/null +++ b/src/test/resources/app/store/budget/ConfirmableBalanceItems.js @@ -0,0 +1,22 @@ +/** + * ConfirmableExpenseItems + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 10, 2011 + */ + +Ext.define('DanteFrontend.store.budget.ConfirmableBalanceItems', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.budget.ConfirmableBalanceItem', + + api: { + read: budgetController.listConfirmableBalanceItems, + update: budgetController.confirmBalanceItems + }, + + autoLoad: false, + remoteFilter: true + +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/ConfirmationReasons.js b/src/test/resources/app/store/budget/ConfirmationReasons.js new file mode 100644 index 0000000..d701646 --- /dev/null +++ b/src/test/resources/app/store/budget/ConfirmationReasons.js @@ -0,0 +1,26 @@ +/** + * ConfirmationReasons + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 11, 2011 + */ + +Ext.define('DanteFrontend.store.budget.ConfirmationReasons', { + extend: 'Ext.data.Store', + fields: ['id', 'name'], + data: [{ + id: 0, + name: g('Confirmation not needed') + },{ + id: 1, + name: g('Always confirmable') + },{ + id: 2, + name: g('Balance is overdrawn') + },{ + id: 3, + name: g('Over maximum item price') + }] +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/ConfirmationStatuses.js b/src/test/resources/app/store/budget/ConfirmationStatuses.js new file mode 100644 index 0000000..e06a33e --- /dev/null +++ b/src/test/resources/app/store/budget/ConfirmationStatuses.js @@ -0,0 +1,26 @@ +/** + * BalanceRenewalStrategies + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.store.budget.ConfirmationStatuses', { + extend: 'Ext.data.Store', + fields: ['id', 'name'], + data: [{ + id: 0, + name: g('Not required') + },{ + id: 1, + name: g('Not confirmed') + },{ + id: 2, + name: g('Partly confirmed') + },{ + id: 3, + name: g('Fully confirmed') + }] +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/ConfirmationUsersAmounts.js b/src/test/resources/app/store/budget/ConfirmationUsersAmounts.js new file mode 100644 index 0000000..26fa7eb --- /dev/null +++ b/src/test/resources/app/store/budget/ConfirmationUsersAmounts.js @@ -0,0 +1,23 @@ +/** + * BalanceRenewalStrategies + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.store.budget.ConfirmationUsersAmounts', { + extend: 'Ext.data.Store', + fields: ['id', 'name'], + data: [{ + id: 0, + name: g('No one') + },{ + id: 1, + name: g('One user') + },{ + id: 2, + name: g('All users') + }] +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/ConfirmingUsers.js b/src/test/resources/app/store/budget/ConfirmingUsers.js new file mode 100644 index 0000000..21a5b3e --- /dev/null +++ b/src/test/resources/app/store/budget/ConfirmingUsers.js @@ -0,0 +1,25 @@ +/** + * ConfirmingUsers + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 6, 2011 + */ + + +Ext.define('DanteFrontend.store.budget.ConfirmingUsers', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.budget.BalanceItemUser', + + api: { + read: budgetController.listBalanceItemConfirmingUsers + }, + + autoLoad: false, + remoteFilter: true, + + dependencyFilter: { + property: 'balanceItem_id' + } +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/CurrentBalanceItems.js b/src/test/resources/app/store/budget/CurrentBalanceItems.js new file mode 100644 index 0000000..7656af8 --- /dev/null +++ b/src/test/resources/app/store/budget/CurrentBalanceItems.js @@ -0,0 +1,22 @@ +/** + * CurrentBalanceItems + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 10, 2011 + */ + +Ext.define('DanteFrontend.store.budget.CurrentBalanceItems', { + extend: 'DanteFrontend.store.budget.BalanceItems', + model: 'DanteFrontend.model.budget.BalanceItem', + + dependencyFilter: { + property: 'balance_id' + }, + + sorters: [{ + property : 'createdOn', + direction: 'ASC' + }] +}); \ No newline at end of file diff --git a/src/test/resources/app/store/budget/Purposes.js b/src/test/resources/app/store/budget/Purposes.js new file mode 100644 index 0000000..1a5f4d8 --- /dev/null +++ b/src/test/resources/app/store/budget/Purposes.js @@ -0,0 +1,33 @@ +/** + * Purposes + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.store.budget.Purposes', { + extend: 'Ext.data.Store', + + fields: ['id', 'name'], + data: [{ + id: 0, + name: g('Office supplies') + },{ + id: 1, + name: g('Education') + },{ + id: 2, + name: g('Client orders') + },{ + id: 3, + name: g('Hardware') + },{ + id: 4, + name: g('Drinks') + },{ + id: 5, + name: g('Rocket fuel') + }] +}); \ No newline at end of file diff --git a/src/test/resources/app/store/dashboard/CurrentProjects.js b/src/test/resources/app/store/dashboard/CurrentProjects.js new file mode 100644 index 0000000..865f61a --- /dev/null +++ b/src/test/resources/app/store/dashboard/CurrentProjects.js @@ -0,0 +1,21 @@ +/** + * CurrentProjects + * Store supporting the current projects panel. + * + * @author fk + * @version 0.1 + * @date Jun 2, 2011 + */ + +Ext.define('DanteFrontend.store.dashboard.CurrentProjects', { + extend: 'Ext.data.Store', + + model: 'DanteFrontend.model.Project', + proxy: { + type: 'direct', + api: { + read: dashboardController.listCurrentProjects + } + }, + autoLoad: false +}); \ No newline at end of file diff --git a/src/test/resources/app/store/dashboard/CurrentTasks.js b/src/test/resources/app/store/dashboard/CurrentTasks.js new file mode 100644 index 0000000..5b44e9c --- /dev/null +++ b/src/test/resources/app/store/dashboard/CurrentTasks.js @@ -0,0 +1,26 @@ +/** + * CurrentTasks + * Store supporting the current tasks panel. + * + * @author fk + * @version 0.1 + * @date Jun 2, 2011 + */ + +Ext.define('DanteFrontend.store.dashboard.CurrentTasks', { + extend: 'Ext.data.Store', + + model: 'DanteFrontend.model.Task', + proxy: { + type: 'direct', + api: { + read: dashboardController.listCurrentTasks + } + }, + groupField: 'userName', + sorters: [{ + property : 'begin', + direction: 'DESC' + }], + autoLoad: false +}); \ No newline at end of file diff --git a/src/test/resources/app/store/dashboard/HotlineEmailQueues.js b/src/test/resources/app/store/dashboard/HotlineEmailQueues.js new file mode 100644 index 0000000..273d42c --- /dev/null +++ b/src/test/resources/app/store/dashboard/HotlineEmailQueues.js @@ -0,0 +1,23 @@ +/** + * HotlineEmailQueues + * + * @author fk + * @version 0.1 + * @date Jun 3, 2011 + */ + +Ext.define('DanteFrontend.store.dashboard.HotlineEmailQueues', { + extend: 'Ext.data.Store', + + model: 'DanteFrontend.model.EmailQueue', + proxy: { + type: 'ajax', + url: res('url.hotlineEmail'), + reader: { + type: 'json', + root: 'rows' + } + + }, + autoLoad: false +}); \ No newline at end of file diff --git a/src/test/resources/app/store/order/Attachments.js b/src/test/resources/app/store/order/Attachments.js new file mode 100644 index 0000000..855c8d3 --- /dev/null +++ b/src/test/resources/app/store/order/Attachments.js @@ -0,0 +1,24 @@ +/** + * Attachments + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 9, 2011 + */ + +Ext.define('DanteFrontend.store.order.Attachments', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.Attachment', + + api: { + read: orderController.listOrderAttachments, + destroy: orderController.destroyOrderAttachments + }, + + remoteFilter: true, + autoLoad: false, + dependencyFilter: { + property: 'order_id' + } +}); \ No newline at end of file diff --git a/src/test/resources/app/store/order/Categories.js b/src/test/resources/app/store/order/Categories.js new file mode 100644 index 0000000..8f221d2 --- /dev/null +++ b/src/test/resources/app/store/order/Categories.js @@ -0,0 +1,13 @@ +/** + * Categories + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 11, 2011 + */ + +Ext.define('DanteFrontend.store.order.Categories', { + extend: 'Ext.data.Store', + model: 'DanteFrontend.model.order.Category' +}); \ No newline at end of file diff --git a/src/test/resources/app/store/order/DeliveryStatuses.js b/src/test/resources/app/store/order/DeliveryStatuses.js new file mode 100644 index 0000000..e96b8cc --- /dev/null +++ b/src/test/resources/app/store/order/DeliveryStatuses.js @@ -0,0 +1,13 @@ +/** + * DeliveryStatus + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 9, 2011 + */ + +Ext.define('DanteFrontend.store.order.DeliveryStatuses', { + extend: 'Ext.data.Store', + model: 'DanteFrontend.model.order.DeliveryStatus' +}); \ No newline at end of file diff --git a/src/test/resources/app/store/order/ItemTypes.js b/src/test/resources/app/store/order/ItemTypes.js new file mode 100644 index 0000000..0d9f2f7 --- /dev/null +++ b/src/test/resources/app/store/order/ItemTypes.js @@ -0,0 +1,14 @@ +/** + * ItemTypes + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 10, 2011 + */ + + +Ext.define('DanteFrontend.store.order.ItemTypes', { + extend: 'Ext.data.Store', + model: 'DanteFrontend.model.order.ItemType' +}); \ No newline at end of file diff --git a/src/test/resources/app/store/order/OrderItems.js b/src/test/resources/app/store/order/OrderItems.js new file mode 100644 index 0000000..070ecd1 --- /dev/null +++ b/src/test/resources/app/store/order/OrderItems.js @@ -0,0 +1,26 @@ +/** + * OrderItems + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 9, 2011 + */ + +Ext.define('DanteFrontend.store.order.OrderItems', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.order.OrderItem', + + api: { + read: orderController.listOrderItems, + create: orderController.saveOrderItems, + update: orderController.saveOrderItems, + destroy: orderController.destroyOrderItems + }, + + remoteFilter: true, + autoLoad: false, + dependencyFilter: { + property: 'order_id' + } +}); \ No newline at end of file diff --git a/src/test/resources/app/store/order/Orders.js b/src/test/resources/app/store/order/Orders.js new file mode 100644 index 0000000..ba85423 --- /dev/null +++ b/src/test/resources/app/store/order/Orders.js @@ -0,0 +1,23 @@ +/** + * Orders + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 4, 2011 + */ + +Ext.define('DanteFrontend.store.order.Orders', { + extend: 'DanteFrontend.lib.base.Store', + model: 'DanteFrontend.model.order.Order', + + api: { + read: orderController.list, + create: orderController.save, + update: orderController.save, + destroy: orderController.destroy + }, + + autoLoad: false, + remoteFilter: true +}); \ No newline at end of file diff --git a/src/test/resources/app/store/order/Units.js b/src/test/resources/app/store/order/Units.js new file mode 100644 index 0000000..e7f5412 --- /dev/null +++ b/src/test/resources/app/store/order/Units.js @@ -0,0 +1,13 @@ +/** + * Units + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 11, 2011 + */ + +Ext.define('DanteFrontend.store.order.Units', { + extend: 'Ext.data.Store', + model: 'DanteFrontend.model.order.Unit' +}); \ No newline at end of file diff --git a/src/test/resources/app/store/worklog/TimeLine.js b/src/test/resources/app/store/worklog/TimeLine.js new file mode 100644 index 0000000..fe9b8d1 --- /dev/null +++ b/src/test/resources/app/store/worklog/TimeLine.js @@ -0,0 +1,28 @@ +/** + * TimeLine + * Store for the timeline grid. + * + * @author fk + * @version 0.1 + * @date Jun 16, 2011 + */ + +Ext.define('DanteFrontend.store.worklog.TimeLine', { + extend: 'Ext.data.Store', + + model: 'DanteFrontend.model.TimeLine', + proxy: { + type: 'direct', + api: { + read: worklogController.listTimeLine + } + }, + + remoteFilter: true, + groupField: 'projectName', + sortInfo: { + field: 'projectName', + direction: 'ASC' + }, + autoload: false +}); \ No newline at end of file diff --git a/src/test/resources/app/test/FileProxy.js b/src/test/resources/app/test/FileProxy.js new file mode 100644 index 0000000..6c75ba3 --- /dev/null +++ b/src/test/resources/app/test/FileProxy.js @@ -0,0 +1,26 @@ +/** + * TestProxy + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 11, 2011 + */ + + + +Ext.define('DanteFrontend.test.FileProxy', { + singleton: true, + + inst: function(fixtureName) { + return Ext.create('Ext.data.proxy.Ajax', { + url: '/dante_frontend_spring/static/js/app/test/fixtures/'+ fixtureName +'.json', + reader: { + type: 'json', + root: 'result', + successProperty: 'success' + } + //data: DanteFrontend.test.Fixtures[fixtureName] + }); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/test/Fixtures.js b/src/test/resources/app/test/Fixtures.js new file mode 100644 index 0000000..86a1446 --- /dev/null +++ b/src/test/resources/app/test/Fixtures.js @@ -0,0 +1,122 @@ +/** + * OrderItems + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 9, 2011 + */ + +Ext.define('DanteFrontend.test.Fixtures', { + singleton: true, + + orderItem: { + result: [{ + id: 1, + summary: 'Order item 01', + amount: 4, + supplierPrice: 100, + deliveryPrice: 120, + margin: 20, + description: 'Some rather tedious and long description of the orderitem=1', + supplier_id: 1, + unit_id: 1, + itemType_id: 1, + order_id: 1 + },{ + id: 2, + summary: 'Order item 02', + amount: 2, + supplierPrice: 220, + deliveryPrice: 312, + margin: 20, + description: 'Some rather tedious and long description of the orderitem=2', + supplier_id: 1, + unit_id: 1, + itemType_id: 1, + order_id: 1 + },] + }, + + order: { + result: [{ + id: 1, + referenceId: '2011/055', + summary: 'Some rather tedious and long description of the order=1', + acceptedOn: '2010-01-01', + customer_id: 1, + deliverystatus_id: 1, + category_id: 1, + project_id: null +// orderItems: [{ +// id: 1, +// summary: 'Dell XH3049432 RAM 8MB, BLABLA', +// amount: 4, +// unit_id: 1, +// supplierPrice: 100, +// deliveryPrice: 120, +// margin: 20, +// description: 'Some rather tedious and long description of the orderitem=1' +// }], +// attachments: [{ +// id: 1, +// fileName: 'attachment.jpg', +// url: '/static/attachments/01.jpg' +// }], +// customer: { +// id: 1, +// name: 'Test, s.r.o.' +// }, +// category: { +// id: 1, +// name: 'Category 1' +// }, +// deliveryStatus: { +// id: 1, +// name: 'In negotiation' +// } + }] + }, + + attachment: { + result: [{ + id: 1, + fileName: 'attachment.jpg', + url: '/static/attachments/01.jpg', + order_id: 1 + },{ + id: 2, + fileName: 'zmluva.doc', + url: '/static/attachments/01.jpg', + order_id: 1 + },{ + id: 3, + fileName: 'dopyt.rtf', + url: '/static/attachments/01.jpg', + order_id: 2 + },{ + id: 4, + fileName: 'ine.csv', + url: '/static/attachments/01.jpg', + order_id: 2 + }] + }, + + supplier: { + result: [{ + id: 1, + name: 'Westech' + },{ + id: 2, + name: 'Dell' + },{ + id: 3, + name: 'Cisco systems' + }] +// }, +// +// getData: function(model) { +// return this.order; + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/test/MemoryProxy.js b/src/test/resources/app/test/MemoryProxy.js new file mode 100644 index 0000000..714234f --- /dev/null +++ b/src/test/resources/app/test/MemoryProxy.js @@ -0,0 +1,24 @@ +/** + * TestProxy + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 11, 2011 + */ + + + +Ext.define('DanteFrontend.test.MemoryProxy', { + singleton: true, + + inst: function(fixtureName) { + return Ext.create('Ext.data.proxy.Memory', { + reader: { + type: 'json', + root: 'result' + }, + data: DanteFrontend.test.Fixtures[fixtureName] + }); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/test/fixtures/balanceitems.json b/src/test/resources/app/test/fixtures/balanceitems.json new file mode 100644 index 0000000..dab7f9b --- /dev/null +++ b/src/test/resources/app/test/fixtures/balanceitems.json @@ -0,0 +1,60 @@ +{ + result: [{ + id: 1, + type: 0, + direction: 0, + description: 'Store balance item.', + receivedOn: '2011-01-12', + sum: 25, + runningTotal: 280, + paymentStatus: 1, + confirmationStatus: 0, + confirmationReason: 0, + confirmationComment: 'Just like that.', + balance: 1, + budget: 1 + },{ + id: 2, + type: 1, + direction: 0, + description: 'Item 02.', + receivedOn: '2011-01-13', + sum: 150, + runningTotal: 190, + paymentStatus: 1, + confirmationStatus: 1, + confirmationReason: 0, + confirmationComment: 'Just like that.', + balance: 1, + budget: 1 + },{ + id: 3, + type: 2, + direction: 1, + description: 'Item 03.', + receivedOn: '2011-01-14', + sum: 280, + runningTotal: 140, + paymentStatus: 1, + confirmationStatus: 2, + confirmationReason: 0, + confirmationComment: 'Revoked, because ...', + balance: 3, + budget: 2 + },{ + id: 4, + type: 3, + direction: 1, + description: 'Store balance item.', + receivedOn: '2011-01-13', + sum: 250, + runningTotal: 120, + paymentStatus: 1, + confirmationStatus: 0, + confirmationReason: 0, + confirmationComment: 'Just like that.', + balance: 3, + budget: 2 + }], + success: true +} \ No newline at end of file diff --git a/src/test/resources/app/test/fixtures/balanceitemusers.json b/src/test/resources/app/test/fixtures/balanceitemusers.json new file mode 100644 index 0000000..d2602df --- /dev/null +++ b/src/test/resources/app/test/fixtures/balanceitemusers.json @@ -0,0 +1,20 @@ +{ + result: [{ + id: 1, + user: 25516, + balanceItem: 1 + },{ + id: 2, + user: 44112, + balanceItem: 1 + },{ + id: 3, + user: 107231, + balanceItem: 2 + },{ + id: 4, + user: 204103, + balanceItem: 2 + }], + success: true +} \ No newline at end of file diff --git a/src/test/resources/app/test/fixtures/balances.json b/src/test/resources/app/test/fixtures/balances.json new file mode 100644 index 0000000..9e560e7 --- /dev/null +++ b/src/test/resources/app/test/fixtures/balances.json @@ -0,0 +1,110 @@ +{ + result: [{ + id: 1, + from: '2011-01-01', + generatedTitle: 'January 2011', + to: '2011-02-01', + startingTotal: 520.50, + currentTotal: 220.30, + endingTotal: 120.50, + transferred: 20.50, + previousBalance: null, + budget: 1, + balanceItems: [{ + id: 1, + type: 1, + description: 'A balance item', + receivedOn: '2011-01-12', + sum: 25, + executionStatus: 1, + confirmed: true, + confirmation: null, + balance: 1 + },{ + id: 2, + type: 0, + description: 'Another balance item', + receivedOn: '2011-01-13', + sum: 250, + executionStatus: 1, + confirmed: true, + confirmation: null, + balance: 1 + },{ + id: 3, + type: 1, + description: 'Another another balance item', + receivedOn: '2011-01-14', + sum: 56, + executionStatus: 1, + confirmed: true, + confirmation: null, + balance: 1 + }] + },{ + id: 2, + generatedTitle: 'February 2011', + from: '2011-02-01', + to: '2011-03-01', + startingTotal: 620.50, + currentTotal: 320.30, + endingTotal: null, + transferred: 120.50, + previousBalance: 1, + budget: 1, + balanceItems: [{ + id: 1, + type: 0, + description: 'A balance item', + receivedOn: '2011-01-12', + sum: 25, + executionStatus: 1, + confirmed: true, + confirmation: null, + balance: 2 + },{ + id: 2, + type: 1, + description: 'Another balance item', + receivedOn: '2011-01-13', + sum: 250, + executionStatus: 1, + confirmed: true, + confirmation: null, + balance: 2 + }] + },{ + id: 3, + generatedTitle: 'March 2011', + from: '2011-03-01', + to: '2011-04-01', + startingTotal: 620.50, + currentTotal: 320.30, + endingTotal: null, + transferred: 120.50, + previousBalance: null, + budget: 2, + balanceItems: [{ + id: 1, + type: 0, + description: 'A balance item', + receivedOn: '2011-01-12', + sum: 251, + executionStatus: 1, + confirmed: true, + confirmation: null, + balance: 3 + },{ + id: 2, + type: 1, + description: 'Another balance item', + receivedOn: '2011-01-13', + sum: 432, + executionStatus: 1, + confirmed: true, + confirmation: null, + balance: 3 + }] + }], + success: true +} \ No newline at end of file diff --git a/src/test/resources/app/test/fixtures/budgets.json b/src/test/resources/app/test/fixtures/budgets.json new file mode 100644 index 0000000..8db2858 --- /dev/null +++ b/src/test/resources/app/test/fixtures/budgets.json @@ -0,0 +1,61 @@ +{ + result: [{ + id: 1, + title: 'Budget 1', + purpose: 0, + description: 'A sample budget.', + currentBalance: { + id: 2, + generatedTitle: 'February 2011', + from: '2011-02-01', + to: '2011-03-01', + startingTotal: 620.50, + currentTotal: 320.30, + endingTotal: null, + transferred: 120.50, + previousBalance: 1, + budget: 1 + }, + maximumItemPrice: 300, + confirmAlways: true, + confirmOnOverdraw: false, + confirmOnOverMaximumItemPrice: false, + confirmationUsersAmount: 1, + renewalDayOfMonth: 1, + nextBalanceRenewal: '2010-01-01', + balanceRenewalFrequency: 0, + balanceRenewalStrategy: 0, + balanceInitialCredit: 100.05, + automaticBalanceTransfer: true + },{ + id: 2, + title: 'Budget 2', + purpose: 1, + description: 'Another sample budget.', + currentBalance: { + id: 3, + from: '2011-02-01', + to: '2011-03-01', + startingTotal: 620.50, + currentTotal: 320.30, + endingTotal: null, + incomingTotal: 250, + outgoingTotal: 200, + transferred: 120.50, + previousBalance: null, + budget: 2 + }, + maximumItemPrice: 400, + confirmAlways: true, + confirmOnOverdraw: false, + confirmOnOverMaximumItemPrice: false, + confirmationUsersAmount: 2, + renewalDayOfMonth: 28, + nextBalanceRenewal: '2011-01-28', + balanceRenewalFrequency: 1, + balanceRenewalStrategy: 1, + balanceInitialCredit: 120.05, + automaticBalanceTransfer: false + }], + success: true +} \ No newline at end of file diff --git a/src/test/resources/app/test/fixtures/budgetusers.json b/src/test/resources/app/test/fixtures/budgetusers.json new file mode 100644 index 0000000..04160b4 --- /dev/null +++ b/src/test/resources/app/test/fixtures/budgetusers.json @@ -0,0 +1,24 @@ +{ + result: [{ + id: 1, + user: 25516, + budget: 1, + maxItemPrice: 200 + },{ + id: 2, + user: 44112, + budget: 1, + maxItemPrice: 400 + },{ + id: 3, + user: 107231, + budget: 2, + maxItemPrice: 300 + },{ + id: 4, + user: 204103, + budget: 2, + maxItemPrice: 250 + }], + success: true +} \ No newline at end of file diff --git a/src/test/resources/app/test/fixtures/orderitems.json b/src/test/resources/app/test/fixtures/orderitems.json new file mode 100644 index 0000000..0da4efd --- /dev/null +++ b/src/test/resources/app/test/fixtures/orderitems.json @@ -0,0 +1,28 @@ +{ + result: [{ + id: 1, + summary: 'Order item 01', + amount: 4, + supplierPrice: 100, + deliveryPrice: 120, + margin: 20, + description: 'Some rather tedious and long description of the orderitem=1', + supplier_id: 1, + unit_id: 1, + itemType_id: 1, + order_id: 1 + },{ + id: 2, + summary: 'Order item 02', + amount: 2, + supplierPrice: 220, + deliveryPrice: 312, + margin: 20, + description: 'Some rather tedious and long description of the orderitem=2', + supplier_id: 1, + unit_id: 1, + itemType_id: 1, + order_id: 2 + }], + success: true +} \ No newline at end of file diff --git a/src/test/resources/app/test/fixtures/orders.json b/src/test/resources/app/test/fixtures/orders.json new file mode 100644 index 0000000..2848b11 --- /dev/null +++ b/src/test/resources/app/test/fixtures/orders.json @@ -0,0 +1,42 @@ +{ + result: [{ + id: 1, + referenceId: '2011/055', + summary: 'Some rather tedious and long description of the order=1', + acceptedDate: '2010-01-01', + customer: { + id: 98500, + name: 'František Karika' + }, + deliveryStatus: { + id: 1, + name: 'In negotiation' + }, + category: { + id: 1, + name: 'Category 1' + }, + project_id: null, + total: 1120 + },{ + id: 2, + referenceId: '2011/065', + summary: 'Some other tedious and long description of the order=2', + acceptedDate: '2011-01-01', + customer: { + id: 98500, + name: 'František Karika' + }, + deliveryStatus: { + id: 2, + name: 'Undelivered' + }, + category: { + id: 2, + name: 'Category 2' + }, + project_id: null, + total: 11430, + }], + success: true +} \ No newline at end of file diff --git a/src/test/resources/app/view/Renderers.js b/src/test/resources/app/view/Renderers.js new file mode 100644 index 0000000..f39bae6 --- /dev/null +++ b/src/test/resources/app/view/Renderers.js @@ -0,0 +1,136 @@ +/** + * Renderers + * A collection of general-purpose renderers. + * + * @author fk + * @version 0.1 + * @date Jun 3, 2011 + */ + +DanteFrontend.view.Renderers = { + + progressBar: function(min, max, current, gcolor, bcolor) { + var one = 100 / (max - min); + var wpbar = Math.floor(one * current); // width of progress bar + + // if wpbar is more than 100%, cap it to 100 + if (wpbar > 100) wpbar = 100; + // if there is some current progress, but progress bar displays nothing, show some progress + if (current > 0 && wpbar == 0) wpbar = 1; + + var wbbar = 100 - wpbar; // width of background bar + + var pbar = ''; + // show progress part only if some progress + if (wpbar > 0) { + pbar += '
 
'; + } + // show background part only if some background + if (wbbar > 0) { + pbar += '
 
'; + } + + return (pbar); + }, + + duration: function(val) { + var minutes = Math.ceil(val % 60); + var hours = Math.floor(val / 60); + return (Ext.String.format('{0}:{1} hrs', hours, ('00' + minutes).slice(-2))); + }, + + date: function(val) { + return Ext.Date.format(val, DanteFrontend.common.defaultDateFormat);; + }, + + worklog: { + hiddenRow: function(record) { + return record.json.hidden ? 'worklog-tasklist-row-hiddentask':''; + }, + + descriptionTooltip: function(value,metadata, record){ + if(record.data.hidden) value += " (hidden task)" + metadata.tdAttr = 'data-qtip="' + value + '"'; + return value; + }, + + taskSummary: function(v, params, data){ + return ((v === 0 || v > 1) ? '(' + v +' tasks)' : '(1 task)'); + } + }, + dashboard: { + projectName: function(val, md, rec) { + //console.log('' + val + ''); + return ('' + val + ''); + }, + plannedTimeRemaining: function(val, md, rec) { + var time_planned = rec.data['timePlanned']; + var time_remaining = time_planned - rec.data['timeSpent']; + var time_spent = time_planned - time_remaining; + if (time_spent > time_planned * 0.75) { + gauge_color = 'red'; + } else { + gauge_color = 'green'; + } + + return (DanteFrontend.view.Renderers.progressBar(0, time_planned, time_spent, gauge_color, '#eee')); + }, + timeRemaining: function(val, md, rec) { + var time_planned = rec.data['timePlanned']; + var time_remaining = time_planned - rec.data['timeSpent']; + + var is_overdue = false; + if (time_remaining < 0) { + time_remaining *= -1; + is_overdue = true; + } + + if (is_overdue) { + md.style = 'color: red; font-weight: bold'; + } + + var minutes = Math.floor(time_remaining % 60); + var hours = Math.floor(time_remaining / 60); + return ((is_overdue ? '+ ' : '') + Ext.String.format('{0}:{1} hrs', hours, ('00' + minutes).slice(-2))); + } + }, + + tool: function(text) { + return ['
' + + '' + + '' + text + '
']; + }, + + userNameRenderer: function(v, md, rec) { + // lookup store, return name + return 'Ferko Karika'; + }, + + sum: function(v) { + return v + ' €'; + }, + + + budget: { + reason: function(val, md, r) { + return Ext.data.StoreManager.lookup('budget.ConfirmationReasons').getById(r.get('confirmationReason_id')).get('name'); + }, + + status: function(val, md, r) { + return Ext.data.StoreManager.lookup('budget.ConfirmationStatuses').getById(r.get('confirmationStatus_id')).get('name'); + }, + + + direction: function(v, md, r) { + var dir = r.get('direction_id'); + md.tdCls = "direction-" + dir; + }, + + directionSum: function(v, md, r) { + v = v + ' €'; + var dir = r.get('direction_id'); + if(dir) return '- ' + v; + return v; + } + } +}; \ No newline at end of file diff --git a/src/test/resources/app/view/Root.js b/src/test/resources/app/view/Root.js new file mode 100644 index 0000000..734a59d --- /dev/null +++ b/src/test/resources/app/view/Root.js @@ -0,0 +1,32 @@ +/** + * Root + * The root view (panel widget) of the whole application. + * + * @author fk + * @version 0.1 + * @date May 27, 2011 + */ + +Ext.define('DanteFrontend.view.Root', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-root', + + initComponent: function() { + Ext.apply(this, { + layout: 'border', + items: [{ + xtype: 'df-main-toolbar', + region: 'north', + margins: '0 0 5 0' + },{ + xtype: 'df-main-panel', + region: 'center' + },{ + xtype: 'df-main-footer', + region: 'south', + margins: '10 10 10 5' + }] + }); + this.callParent(arguments); + } +}); diff --git a/src/test/resources/app/view/account/EditCompanyAdditionalInfo.js b/src/test/resources/app/view/account/EditCompanyAdditionalInfo.js new file mode 100644 index 0000000..c60be6f --- /dev/null +++ b/src/test/resources/app/view/account/EditCompanyAdditionalInfo.js @@ -0,0 +1,117 @@ +/** + * EditCompanyAddInfo + * + * @author fk + * @version 0.1 + * @date Jun 23, 2011 + */ + + +Ext.define('DanteFrontend.view.account.EditCompanyAdditionalInfo', { + extend: 'DanteFrontend.lib.view.Form', + alias: 'widget.df-account-editcompanyaddinfo', + + initComponent: function() { + var config = { + bodyCls: 'base', + paramsAsHash: true, + padding: '8', + border: false, + items: [{ + xtype:'fieldset', + title: g('Identifiers'), + autoHeight: true, + defaultType: 'textfield', + items :[{ + layout: 'column', + xtype: 'panel', + border: false, + items: [{ + columnWidth: .5, + layout: 'anchor', + defaultType: 'textfield', + border: false, + items: [{ + labelWidth: 60, + anchor: '90%', + fieldLabel: g('Trade ID'), + name: 'tradeID' + }]//eo panel + },{ + columnWidth: .5, + layout: 'anchor', + defaultType: 'textfield', + border: false, + items: [{ + labelWidth: 60, + anchor: '90%', + fieldLabel: g('Tax ID'), + name: 'taxID' + }]//eo panel + }] + }]//eo columnpanel + },{ + xtype:'fieldset', + layout: 'anchor', + title: g('Communication'), + autoHeight: true, + defaultType: 'textfield', + items :[{ + layout: 'column', + xtype: 'panel', + border: false, + items:[{ + columnWidth: .5, + layout: 'anchor', + defaultType: 'textfield', + //defaults: {anchor: '90%'}, + border: false, + items: [{ + anchor: '90%', + labelWidth: 60, + fieldLabel: g('Web'), + name: 'web' + }]//eo panel + },{ + columnWidth: .5, + layout: 'anchor', + defaultType: 'textfield', + //defaults: {anchor: '90%'}, + border: false, + items: [{ + anchor: '90%' , + labelWidth: 60, + fieldLabel: g('Email'), + name: 'email' + }]//eo panel + }]//eo panel + },{ + anchor: '45%', + padding: '8 0 0 0', + labelWidth: 100, + fieldLabel: g('Email att format'), + xtype: 'combo', + triggerAction: 'all', + lazyRender: true, + mode: 'local', + name: 'emailFormat', + store: Ext.create('Ext.data.ArrayStore', { + id: '0', + fields: [ + 'emailFormat', + 'description' + ], + data: [[0, 'XML'],[1, 'PDF'], [2, 'XLS'], [3, 'CSV']] + }), + valueField: 'emailFormat', + displayField: 'description' + }]//eo columnpanel + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + + } + +}); diff --git a/src/test/resources/app/view/account/EditCompanyAddresses.js b/src/test/resources/app/view/account/EditCompanyAddresses.js new file mode 100644 index 0000000..773c80c --- /dev/null +++ b/src/test/resources/app/view/account/EditCompanyAddresses.js @@ -0,0 +1,135 @@ +/** + * EditCompanyAddresses + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + + +Ext.define('DanteFrontend.view.account.EditCompanyAddresses', { + extend: 'DanteFrontend.lib.view.Form', + alias: 'widget.df-account-editcompanyaddr', + + initComponent: function() { + var config = { + bodyCls: 'base', + paramsAsHash: true, + padding: '8', + border: false, + items: [{ + xtype:'fieldset', + title: g('Postal address'), + autoHeight: true, + items :[{ + layout: 'column', + xtype: 'panel', + border: false, + items: [{ + columnWidth: .5, + defaultType: 'textfield', + defaults: {anchor: '90%'}, + border: false, + layout: 'anchor', + + items: [{ + name: 'postalId', + xtype: 'hidden' + },{ + fieldLabel: g('Name'), + name: 'postalName' + },{ + fieldLabel: g('Street'), + name: 'postalStreet1' + + },{ + fieldLabel: g('Municipality'), + name: 'postalMunicipality' + + }]//eo panel + },{ + columnWidth: .5, + defaultType: 'textfield', + defaults: {anchor: '90%'}, + layout: 'anchor', + border: false, + items: [{ + fieldLabel: g('Person'), + name: 'postalPerson' + },{ + fieldLabel: g('Street (add)'), + name: 'postalStreet2' + + },{ + fieldLabel: g('Postal code'), + name: 'postalPostalCode', + width: 120, + maxLength: 6 + + }]//eo panel + }] + }]//eo columnpanel + },{ + xtype:'fieldset', + checkboxToggle: true, + collapsed: true, + //collapsible: true, + ref: 'billingAddressPanel', + title: g('Billing address (different from postal address)'), + autoHeight: true, + items :[{ + layout: 'column', + xtype: 'panel', + border: false, + items: [{ + columnWidth: .5, + defaults: {anchor: '90%'}, + layout: 'anchor', + defaultType: 'textfield', + border: false, + items: [{ + name: 'billingId', + xtype: 'hidden' + },{ + fieldLabel: g('Name'), + name: 'billingName' + },{ + fieldLabel: g('Street'), + name: 'billingStreet1' + + },{ + fieldLabel: g('Municipality'), + name: 'billingMunicipality' + + }]//eo panel + },{ + columnWidth: .5, + defaults: {anchor: '90%'}, + layout: 'anchor', + defaultType: 'textfield', + border: false, + items: [{ + fieldLabel: g('Person'), + name: 'billingPerson' + },{ + fieldLabel: g('Street (add)'), + name: 'billingStreet2' + + },{ + fieldLabel: g('Postal code'), + name: 'billingPostalCode', + width: 120, + maxLength: 6 + }]//eo panel + }] + }]//eo columnpanel + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/view/account/EditCustomerContact.js b/src/test/resources/app/view/account/EditCustomerContact.js new file mode 100644 index 0000000..f3e1feb --- /dev/null +++ b/src/test/resources/app/view/account/EditCustomerContact.js @@ -0,0 +1,138 @@ +/** + * EditCustomerContact + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.view.account.EditCustomerContact', { + extend: 'DanteFrontend.lib.view.Form', + alias: 'widget.df-account-editcc', + + border: false, + + initComponent: function() { + var config = { + + validatedFields: ['name', 'email', 'login', 'password'], + items: [{ + xtype: 'tabpanel', + layout: 'fit', + width: '100%', + activeTab: 0, + border: false, + items: [{ + id: 'customerContactTab', + //ref: 'customerContactForm', + title: g('Basic information'), + xtype: 'df-account-editccdetails', + border: false, + padding: '8', + width: '100%' + },{ + id: 'attachedUserTab', + title: g('User details'), + xtype: 'panel', + padding: '8', + border: false, + items: [{ + xtype:'df-account-editccuser' + },{ + xtype: 'button', + style: 'margin: 10px', + text: g('Create user'), + action: 'createUser', + handler: this.enableUserEditing, + scope: this + }] + }] + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + + this.callParent(arguments); + }, + + loadRecord: function(record) { + this.callParent(arguments); + this.onLoadRecord(record); + }, + + onLoadRecord: function(record) { + if(!record.phantom && record.data.hasUser) { + this.enableUserEditing(); + } + + else this.disableUserEditing(); + }, + + showCustomerUserForm: function() { + this.down('button[action=createUser]').hide(); + this.down('df-account-editccuser').show(); + }, + + hideCustomerUserForm: function() { + this.down('button[action=createUser]').show(); + this.down('df-account-editccuser').hide(); + }, + + validate: function(record, msg) { + var pass = true; + if (!this.userEditing) { + Ext.Array.remove(this.validatedFields, 'login'); + Ext.Array.remove(this.validatedFields, 'password'); + Ext.Array.remove(this.validatedFields, 'passwordRetype'); + } + Ext.each(this.validatedFields, function(f) { + var fld = this.getForm().findField(f); + pass = fld.validate(); + if(!pass) fld.markInvalid(g('This field is invalid.')); + return pass; + }, this); + + if(!pass) return pass; + + if(this.userEditing) { + if(Ext.isEmpty(record.data.customerUserId)) { + pwMatch = this.down('df-account-editccuser').passwordMatch(true); + pass = pass && pwMatch; + if(!pwMatch) this.addValidationMessage(g('The passwords do not match, or are empty.')); + } else { + pwMatch = this.down('df-account-editccuser').passwordMatch(); + pass = pass && pwMatch; + if(!pwMatch) this.addValidationMessage(g('The passwords do not match.')); + } + } + + return pass; + }, + + updateAccessorRecord: function() { + //var editor = this.getEditor(); + var record = this.accessRecord(); + if(this.validate(record)) { + if(this.userEditing) { + record.set('hasUser', true); + } else { + record.set('hasUser', false) + } + this.getForm().updateRecord(record); + return true; + } + this.alertValidation(); + return false; + }, + + enableUserEditing: function() { + this.userEditing = true; + this.showCustomerUserForm(); + }, + + disableUserEditing: function() { + this.userEditing = false; + this.hideCustomerUserForm(); + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/view/account/EditCustomerContactDetails.js b/src/test/resources/app/view/account/EditCustomerContactDetails.js new file mode 100644 index 0000000..f4e522b --- /dev/null +++ b/src/test/resources/app/view/account/EditCustomerContactDetails.js @@ -0,0 +1,89 @@ +/** + * CustomerContact + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.view.account.EditCustomerContactDetails', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-account-editccdetails', + + bodyCssClass: 'base', + + initComponent: function() { + var config = { + layout: 'anchor', + defaults: { + labelWidth: 70, + anchor: '100%' + }, + items: [{ + layout: 'column', + border: false, + items: [{ + columnWidth: .6, + defaultType: 'textfield', + border: false, + defaults: { + labelWidth: 70, + anchor: '90%' + }, + layout: 'anchor', + items: [{ + name: 'id', + xtype: 'hidden' + },{ + fieldLabel: g('Name'), + xtype: 'textfield', + name: 'name', + allowBlank: false + },{ + fieldLabel: g('Phone'), + name: 'phone', + xtype: 'textfield' + + }] + },{ + columnWidth: .4, + border: false, + items: [/*{ + fieldLabel: g('Report format'), + name: 'reportFormat', + xtype: 'combo', + width: 140 + //default-type etc. + },*/{ + id:'receiveGroup', + xtype: 'checkboxgroup', + width: 110, + hideLabel: true, + columns: 1, + items: [ + {boxLabel: g('Receive invoices'), name: 'receiveInvoices'}, + {boxLabel: g('Receive reports'), name: 'receiveReports'} + ] + }] + }] + },{ + fieldLabel: g('Email'), + name: 'email', + xtype: 'textfield', + vtype: 'email', + allowBlank: false, + width: 280 + },{ + fieldLabel: g('Description'), + name: 'description', + xtype: 'textarea', + width: 280 + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/account/EditCustomerUser.js b/src/test/resources/app/view/account/EditCustomerUser.js new file mode 100644 index 0000000..133f606 --- /dev/null +++ b/src/test/resources/app/view/account/EditCustomerUser.js @@ -0,0 +1,56 @@ +/** + * CustomerUser + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.view.account.EditCustomerUser', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-account-editccuser', + border: false, + + initComponent: function() { + var config = { + layout: 'anchor', + defaults: { + anchor: '100%' + }, + items: [{ + fieldLabel: g('Login'), + name: 'login', + xtype: 'textfield', + allowBlank: false + },{ + xtype: 'fieldset', + title: g('Password change'), + //layout: 'form', + items: [{ + fieldLabel: g('Password'), + name: 'password', + xtype: 'textfield', + inputType: 'password'//, + //validator: this.jointValidator('passwordRetype') + },{ + fieldLabel: g('Retype password'), + name: 'passwordRetype', + xtype: 'textfield', + inputType: 'password'//, + //validator: this.jointValidator('password') + }] + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + }, + + passwordMatch: function(nonEmpty) { + var pwd = this.down('field[name=password]').getValue(); + var pwdRetype = this.down('field[name=passwordRetype]').getValue(); + if(nonEmpty) return (!Ext.isEmpty(pwd) && !Ext.isEmpty(pwdRetype) && (pwd == pwdRetype)); + else return (pwd == pwdRetype); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/account/ListCustomerContacts.js b/src/test/resources/app/view/account/ListCustomerContacts.js new file mode 100644 index 0000000..368a6af --- /dev/null +++ b/src/test/resources/app/view/account/ListCustomerContacts.js @@ -0,0 +1,39 @@ +/** + * ListCustomerContacts + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.view.account.ListCustomerContacts', { + extend: 'DanteFrontend.lib.view.Grid', + alias: 'widget.df-account-listcc', + + addItemText: g('Add'), + removeItemText: g('Remove'), + emptyText: g('No customer contacts defined.'), + + initComponent: function() { + var config = { + columns: [{ + id: 'name', //is name necessary? + header: g('Contacts'), + dataIndex: 'name', + renderer: this.contactRowRenderer, + width: 180 + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + }, + + + contactRowRenderer: function(value, metadata, record, rowIndex, colIndex, store) { + return Ext.String.format('
\n\ +
{0}
\n\ +
{1}
', value, record.data.email); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/account/ManageCompanyDetails.js b/src/test/resources/app/view/account/ManageCompanyDetails.js new file mode 100644 index 0000000..cc31e03 --- /dev/null +++ b/src/test/resources/app/view/account/ManageCompanyDetails.js @@ -0,0 +1,34 @@ +/** + * ManageCompanyDetails + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 23, 2011 + */ + +Ext.define('DanteFrontend.view.account.ManageCompanyDetails', { + extend: 'Ext.tab.Panel', + alias: 'widget.df-account-managecompanydetails', + + initComponent: function() { + var config = { + activeTab: 0, + border: true, + windowTitle: g('Company details'), + width: 550, + height: 350, + items: [{ + id: 'addressTab', + title: g('Address'), + xtype: 'df-account-editcompanyaddr' + },{ + id: 'otherTab', + title: g('Other'), + xtype: 'df-account-editcompanyaddinfo' + }] + }; + Ext.apply(this, config); + this.callParent(arguments); + } +}); diff --git a/src/test/resources/app/view/account/ManageCustomerContacts.js b/src/test/resources/app/view/account/ManageCustomerContacts.js new file mode 100644 index 0000000..3b42b37 --- /dev/null +++ b/src/test/resources/app/view/account/ManageCustomerContacts.js @@ -0,0 +1,63 @@ +/** + * ManageContacts + * + * @author fk + * @version 0.1 + * @date Jun 22, 2011 + */ + +Ext.define('DanteFrontend.view.account.ManageCustomerContacts', { + extend: 'DanteFrontend.lib.view.Editor', + alias: 'widget.df-account-managecc', + + border: false, + noContactText: g('No contact selected'), + + initComponent: function() { + + var config = { + layout: 'border', + windowTitle: g('Customer contacts'), + width: 550, + height: 300, + items: [{ + region: 'center', + layout: 'border', + border: true, + items: [{ + xtype: 'component', + region: 'north', + padding: '8', + //ref: 'customerContactTabTitle', + autoEl: { + tag: 'h3', + cls: 'contact-tabpanel-title', + html: '', + style: { height: '30px' } + } + },{ + xtype: 'df-account-editcc', + region: 'center', + layout: 'fit' + }] + },{ + xtype: 'df-account-listcc', + region: 'west', + width: 200, + split: true, + store: 'account.CustomerContactsAndUsers' + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + + }, + + setContactText: function(str) { + var cmp = this.down('component[region=north]'); + if(cmp.rendered) cmp.getEl().dom.innerHTML = + str ? str:this.noContactText; + } + +}); diff --git a/src/test/resources/app/view/account/ManageLoggedCustomerContactAndUser.js b/src/test/resources/app/view/account/ManageLoggedCustomerContactAndUser.js new file mode 100644 index 0000000..afc1536 --- /dev/null +++ b/src/test/resources/app/view/account/ManageLoggedCustomerContactAndUser.js @@ -0,0 +1,31 @@ +/** + * ManageUserPreferences + * ..description.. + * + * @author fk + * @version 0.1 + * @date Jun 23, 2011 + */ + +Ext.define('DanteFrontend.view.account.ManageLoggedCustomerContactAndUser', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-account-manageloggedccu', + + initComponent: function() { + var config = { + layout: 'border', + windowTitle: g('User preferences'), + width: 350, + height: 250, + padding: '8 0 8 0', + border: false, + items: { + region: 'center', + xtype: 'df-account-editcc' + } + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/budget/BalanceInfo.js b/src/test/resources/app/view/budget/BalanceInfo.js new file mode 100644 index 0000000..cdba122 --- /dev/null +++ b/src/test/resources/app/view/budget/BalanceInfo.js @@ -0,0 +1,61 @@ +/** + * BalanceInfo + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 29, 2011 + */ + +Ext.define('DanteFrontend.view.budget.BalanceInfo', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-budget-balanceinfo', + + config: { + record: null + }, + + initComponent: function() { + this.balanceRenewalStrategyStore = Ext.data.StoreManager.lookup('budget.BalanceRenewalStrategies'); + + var config = { + tpl: new Ext.XTemplate( + '
', + '
', + '
{value}
', + '
', + '
', + '
{value}
', + '
', + '
' + ) + } + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + }, + + loadRecord: function(rec, b) { + var p = Ext.Date.patterns.SK; + if(b) { + this.update(this.tpl.apply({ + primary: [{ + name: 'type', label: g('Balance renewal'), value: this.balanceRenewalStrategyStore.getById(rec.get('balanceRenewalStrategy_id')).get('name') + },{ + name: 'current', label: g('Current total'), value: b.get('currentTotal') + ' €' + },{ + name: 'initial', label: g('Transfers from previous balance'), value: b.get('transferred') ? b.get('transferred') + ' €':'-' + ' €' + }], + secondary: [{ + name: 'period', label: g('Valid between'), value: b.get('from') ? Ext.Date.format(b.get('from'), p):'?' + ' - ' + b.get('to') ? Ext.Date.format(b.get('to'), p):'?' + },{ + name: 'credit', label: g('Credit'), value: b.getTotalCredit() + ' €' + },{ + name: 'spent', label: g('Spent'), value: b.getTotalSpending() + ' €' + },{ + name: 'nextcredit', label: g('Next credit date'), value: rec.get('nextBalanceRenewal') ? Ext.Date.format(rec.get('nextBalanceRenewal'), p):'-' + }] + })); + } + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/budget/BriefList.js b/src/test/resources/app/view/budget/BriefList.js new file mode 100644 index 0000000..a95b929 --- /dev/null +++ b/src/test/resources/app/view/budget/BriefList.js @@ -0,0 +1,49 @@ +/** + * BriefList + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ +Ext.define('DanteFrontend.view.budget.BriefList', { + extend: 'DanteFrontend.lib.view.Grid', + alias: 'widget.df-budget-list', + + initComponent: function() { + this.purposeStore = Ext.data.StoreManager.lookup('budget.Purposes'); + this.balanceStore = Ext.data.StoreManager.lookup('budget.Balances'); + Ext.apply(this, { + hideHeaders: true, + xtype: 'df-grid', + region: 'center', + store: 'budget.Budgets', + columns: [{ + dataIndex: 'title', + renderer: this.summaryRenderer, + scope: this, + flex: 1 + }] + }); + + this.summaryTpl = new Ext.Template('
', + '
{title}', + '{purpose}
', + '
{currentTotal} €', + '{daysLeft}
', + '
', { compiled: true }); + + this.callParent(arguments); + }, + + summaryRenderer: function(value, md, record) { + var daysLeft = record.getRemainingDays(); + var cb = this.balanceStore.getById(record.get('currentBalance_id')); + return this.summaryTpl.apply({ + title: record.get('title'), + purpose: this.purposeStore.getById(record.get('purpose_id')).get('name'), + currentTotal: cb ? cb.get('currentTotal'):"-", + daysLeft: daysLeft ? daysLeft + " days left":"" + }); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/budget/balanceitems/ConfirmationGrid.js b/src/test/resources/app/view/budget/balanceitems/ConfirmationGrid.js new file mode 100644 index 0000000..1a43aa7 --- /dev/null +++ b/src/test/resources/app/view/budget/balanceitems/ConfirmationGrid.js @@ -0,0 +1,43 @@ +/** + * ConfirmationGrid + * ..description.. + * + * @author fk + * @version 0.1 + * @date Nov 7, 2011 + */ + +Ext.define('DanteFrontend.view.budget.balanceitems.ConfirmationGrid', { + extend: 'DanteFrontend.lib.view.Grid', + alias: 'widget.df-budget-confirmationgrid', + + getDefaultToolbarConfig: function() { + return { + buttonAlign: 'left', + items: [{ + action: 'confirm', + text: g('Add your confirmation'), + scope: this, + iconCls: 'icon-add' + },{ + action: 'unconfirm', + text: g('Remove your confirmation'), + scope: this, + iconCls: 'icon-remove' + }] + }; + }, + + switchButtons: function(confRec) { + var confirm = this.down('button[action=confirm]'), + unconfirm = this.down('button[action=unconfirm]'); + + confirm.hide(); unconfirm.hide(); + + if(confRec) { + if(confRec.isConfirmable()) confirm.show(); + if(confRec.isRevokable()) unconfirm.show(); + } + + } +}); diff --git a/src/test/resources/app/view/budget/balanceitems/DetailsForm.js b/src/test/resources/app/view/budget/balanceitems/DetailsForm.js new file mode 100644 index 0000000..b627dff --- /dev/null +++ b/src/test/resources/app/view/budget/balanceitems/DetailsForm.js @@ -0,0 +1,59 @@ +/** + * DetailsForm + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 17, 2011 + */ + + +Ext.define('DanteFrontend.view.budget.balanceitems.DetailsForm', { + extend: 'DanteFrontend.lib.view.StatePanel', + + alias: 'widget.df-budget-balanceitems-details', + + initComponent: function() { + var config = { + emptyText: g('No balance item selected'), + ref: 'details', + content: [{ + xtype: 'df-budget-balanceitems-expensedetails', + ref: 'expenseForm', + accessor: this.initialConfig.accessor + },{ + xtype: 'df-budget-balanceitems-incomedetails', + ref: 'incomeForm', + accessor: this.initialConfig.accessor + }] + }; + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.delegateFormMethods(); + this.callParent(arguments); + }, + + getExpenseForm: function() { + return this.down('*[ref=expenseForm]'); + }, + + getIncomeForm: function() { + return this.down('*[ref=incomeForm]'); + }, + + delegateFormMethods: function() { + var methods = ['addValidationMessage', 'validateAndUpdate', + 'alertValidation', 'accessRecord', 'accessAndLoad', 'onBeforeSync', + 'onAfterSync']; + + Ext.each(methods, function(m) { + var me = this, impl = {}; + impl[m] = function() { + var delegate = me.getCurrentWidget(); + return delegate[m].apply(delegate, arguments); + }; + + this.self.implement(impl); + }, this); + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/view/budget/balanceitems/ExpenseDetailsForm.js b/src/test/resources/app/view/budget/balanceitems/ExpenseDetailsForm.js new file mode 100644 index 0000000..b8eecfa --- /dev/null +++ b/src/test/resources/app/view/budget/balanceitems/ExpenseDetailsForm.js @@ -0,0 +1,215 @@ +/** + * Details + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 6, 2011 + */ + + +Ext.define('DanteFrontend.view.budget.balanceitems.ExpenseDetailsForm', { + extend: 'DanteFrontend.lib.view.Form', + alias: 'widget.df-budget-balanceitems-expensedetails', + + initComponent: function() { + + this.employeeStore = Ext.data.StoreManager.lookup('Employees'); + this.confirmationStatusStore = Ext.data.StoreManager.lookup('budget.ConfirmationStatuses'); + this.confirmationReasonStore = Ext.data.StoreManager.lookup('budget.ConfirmationReasons'); + var config = { + title: g('Expense details'), + layout: { + type: 'fit' + }, + items: [{ + xtype: 'tabpanel', + border: false, + margin: 4, + defaults: { + border: false + }, + items: [{ + title: g('Details'), + ref: 'details', + layout: 'anchor', + defaults: { + labelAlign: 'top', + anchor: '90%' + }, + padding: 8, + items: [{ + layout: { type: 'hbox', align: 'stretch'}, + border: false, + height: 60, + defaults: { labelAlign: 'top'}, + items:[{ + xtype: 'datefield', + name: 'receivedOn', + fieldLabel: g('Date received'), + flex: .5, + margin: '0 6 0 0' + },{ + xtype: 'combo', + store: 'budget.Budgets', + name: 'budget_id', + emptyText: g('Select budget ...'), + queryMode: 'local', + valueField: 'id', + displayField: 'title', + xtype: 'combo', + allowBlank: false, + fieldLabel: g('Budget'), + flex: .5 + }] + },{ + layout: { type: 'anchor'}, + defaults: { anchor: '100%' }, + xtype: 'fieldset', + padding: '6 0 6 12', + title: g('Price'), + items: [{ + layout: { type: 'hbox', align: 'stretch'}, + border: false, + height: 50, + defaults: { + labelAlign: 'top', margin: '0 12 0 0', + listeners: { + change: this.recalculate, + scope: this + } + }, + items:[{ + xtype: 'numberfield', + name: 'amount', + fieldLabel: g('Amount'), + flex: .3, + step: 1, + minValue: 1 + },{ + xtype: 'numberfield', + name: 'total', + decimalPrecision: 2, + fieldLabel: g('Total'), + flex: .3, + step: .10, + minValue: 0 + },{ + xtype: 'numberfield', + name: 'grandTotal', + decimalPrecision: 2, + fieldLabel: g('Grand total'), + flex: .3, + step: .10, + minValue: 0 + }] + }] + },{ + xtype: 'textareafield', + height: 120, + fieldLabel: g('Description'), + name: 'description' + }] + },{ + title: g('Associated employees'), + layout: 'fit', + ref: 'associatedEmployees', + padding: '8 12 22 8', + items: { + store: 'budget.BalanceItemEmployees', + xtype: 'df-grid-adding', + fieldConfig: { + name: 'id', + store: 'Employees', + emptyText: g('Find employee ...'), + queryMode: 'local', + valueField: 'id', + displayField: 'name', + xtype: 'combo' + }, + columns: [{ + flex: 1, + header: g('Employee'), + dataIndex: 'name', + renderer: this.userNameRenderer, + scope: this + }] + } + },{ + title: g('Confirmation'), + ref: 'confirmation', + layout: {type: 'vbox', align: 'stretch' }, + padding: '8 12 22 8', + defaults: { + labelAlign: 'top', + labelWidth: 150 + }, + items: [{ + xtype: 'displayfield', + fieldLabel: g('Status'), + labelAlign: 'left', + name: 'confirmationStatus_id' + },{ + xtype: 'displayfield', + fieldLabel: g('Reason for confirmation'), + labelAlign: 'left', + name: 'confirmationReason_id' + },{ + xtype: 'label', + text: g('Confirming users'), + margin: '5 0 0 0' + },{ + fieldLabel: g('Users'), + store: 'budget.ConfirmingUsers', + xtype: 'df-budget-confirmationgrid', + toolbar: true, + flex: 1, + columns: [{ + flex: 1, + header: g('User'), + dataIndex: 'user_id', + renderer: this.userNameRenderer, + scope: this + }], + margin: '0 0 10 0' + }] + }] + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + }, + + userNameRenderer: function(v, md, r) { + return this.employeeStore.getById(r.get('user_id')).get('name'); + }, + + + recalculate: function(field, newValue) { + var total = this.down('numberfield[name=total]'), + grandTotal = this.down('numberfield[name=grandTotal]'), + vat = (100.0 + this.getRecord().get('vatRate')) / 100.0; + + if(field.getName() == total.getName()) { + grandTotal.setValue(newValue * vat); + } + + if(field.getName() == grandTotal.getName()) { + total.setValue(newValue / vat); + } + }, + + loadRecord: function(rec) { + this.callParent(arguments); + var reason = this.confirmationReasonStore.getById(rec.get('confirmationReason_id')), + status = this.confirmationStatusStore.getById(rec.get('confirmationStatus_id')), + reasonFld = this.down('displayfield[name=confirmationReason_id]'), + statusFld = this.down('displayfield[name=confirmationStatus_id]'); + + reasonFld.setRawValue(reason.get('name')); + statusFld.setRawValue(status.get('name')); + } + + +}); diff --git a/src/test/resources/app/view/budget/balanceitems/Filter.js b/src/test/resources/app/view/budget/balanceitems/Filter.js new file mode 100644 index 0000000..3af5041 --- /dev/null +++ b/src/test/resources/app/view/budget/balanceitems/Filter.js @@ -0,0 +1,80 @@ +/** + * Filter + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 6, 2011 + */ + +Ext.define('DanteFrontend.view.budget.balanceitems.Filter', { + extend: 'DanteFrontend.lib.view.Filter', + alias: 'widget.df-budget-balanceitems-filter', + + initComponent: function() { + + var config = { + items: [{ + xtype: 'df-combo', + name: 'budget_id', + fieldLabel: g('Budget'), + padding: 0, + filter: 'numeric', + clearable: true, + queryMode: 'local', + store: 'budget.Budgets', + valueField: 'id', + displayField: 'title' + },{ + xtype: 'df-combo', + clearable: true, + name: 'balance_id', + fieldLabel: g('Balance'), + queryMode: 'local', + store: 'budget.Balances', + valueField: 'id', + displayField: 'generatedTitle', + filter: 'numeric', + width: 110, + forceSelection: true, + lastQuery: '', + triggerAction: 'all', + editable: false + },{ + xtype: 'df-combo', + clearable: true, + name: 'receivedOn', + fieldLabel: g('Month'), + queryMode: 'local', + store: 'Months', + valueField: 'id', + displayField: 'name', + filter: 'month', + width: 140 + },{ + xtype: 'df-combo', + clearable: true, + name: 'confirmationStatus_id', + fieldLabel: g('Confirmation'), + queryMode: 'local', + store: 'budget.ConfirmationStatuses', + valueField: 'id', + displayField: 'name', + filter: 'numeric' + },{ + xtype: 'df-combo', + clearable: true, + name: 'direction_id', + fieldLabel: g('Direction'), + queryMode: 'local', + store: 'budget.BalanceItemDirections', + valueField: 'id', + displayField: 'name', + filter: 'numeric' + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } +}); diff --git a/src/test/resources/app/view/budget/balanceitems/IncomeDetailsForm.js b/src/test/resources/app/view/budget/balanceitems/IncomeDetailsForm.js new file mode 100644 index 0000000..943b041 --- /dev/null +++ b/src/test/resources/app/view/budget/balanceitems/IncomeDetailsForm.js @@ -0,0 +1,57 @@ +/** + * Details + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 6, 2011 + */ + + +Ext.define('DanteFrontend.view.budget.balanceitems.IncomeDetailsForm', { + extend: 'DanteFrontend.lib.view.Form', + alias: 'widget.df-budget-balanceitems-incomedetails', + + initComponent: function() { + var config = { + title: g('Income details'), + layout: { + type: 'anchor' + }, + defaults: { + labelAlign: 'top', + anchor: '65%', + margin: '0 0 6 0' + }, + + bodyStyle: 'padding: 8px;', + items:[{ + xtype: 'datefield', + name: 'receivedOn', + fieldLabel: g('Date added') + },{ + store: 'budget.Budgets', + name: 'budget_id', + emptyText: g('Select budget ...'), + queryMode: 'local', + valueField: 'id', + displayField: 'title', + xtype: 'combo', + allowBlank: false, + fieldLabel: g('Budget') + },{ + xtype: 'numberfield', + name: 'grandTotal', + fieldLabel: g('Grand total') + },{ + xtype: 'textareafield', + height: 120, + fieldLabel: g('Description'), + name: 'description' + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } +}); diff --git a/src/test/resources/app/view/budget/balanceitems/List.js b/src/test/resources/app/view/budget/balanceitems/List.js new file mode 100644 index 0000000..775edb9 --- /dev/null +++ b/src/test/resources/app/view/budget/balanceitems/List.js @@ -0,0 +1,117 @@ +/** + * List + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 6, 2011 + */ + +Ext.define('DanteFrontend.view.budget.balanceitems.List', { + extend: 'DanteFrontend.lib.view.Grid', + alias: 'widget.df-budget-balanceitems-list', + + + initComponent: function() { + this.budgetStore = Ext.data.StoreManager.lookup('budget.Budgets'); + this.confirmationStatusStore = Ext.data.StoreManager.lookup('budget.ConfirmationStatuses'); + this.itemTypeStore = Ext.data.StoreManager.lookup('budget.BalanceItemTypes'); + + Ext.apply(this, { + toolbar: true, + store: 'budget.BalanceItems', + columns: [{ + header: g('Date'), + dataIndex: 'receivedOn', + width: 75, + format: Ext.Date.patterns.SK, + xtype: 'datecolumn' + },{ + header: g('Budget'), + dataIndex: 'budget_id', + width: 80, + renderer: function(v, md, r) { + return this.budgetStore.getById(r.get('budget_id')).get('title'); + } + },{ + header: ' ', + width: 15, + dataIndex: 'direction_id', + renderer: DanteFrontend.view.Renderers.budget.direction, + scope: this + },{ + header: g('Type'), + width: 65, + dataIndex: 'type_id', + renderer: this.typeRenderer + },{ + header: g('Description'), + dataIndex: 'description', + flex: 1 + },{ + header: g('Grand total'), + dataIndex: 'grandTotal', + width: 65, + renderer: DanteFrontend.view.Renderers.sum + },{ + header: g('Status'), + dataIndex: 'confirmationStatus_id', + renderer: this.statusRenderer, + width: 80 + },{ + header: g('Created by'), + dataIndex: 'createdBy', + renderer: DanteFrontend.view.Renderers.userNameRenderer, + width: 80 + }] + }); + this.callParent(arguments); + }, + + getDefaultToolbarConfig: function() { + return { + buttonAlign: 'left', + items: [{ + action: 'addExpense', + text: g('Add expense'), + scope: this, + iconCls: 'icon-add' + },{ + action: 'addIncome', + text: g('Add income'), + scope: this, + iconCls: 'icon-add' + },'|',{ + action: 'removeItem', + text: g('Remove'), + handler: this.removeItem, + scope: this, + iconCls: 'icon-remove' + }] + }; + }, + + addItem: function(rec) { + if(!this.hasNewRecord) { + this.hasNewRecord = true; ; + this.getStore().add(rec); + this.getSelectionModel().select(rec); + this.fireEvent('itemadded', this, rec); + } + }, + + typeRenderer: function(v, md, rec) { + var type = this.itemTypeStore.getById(rec.get('type_id')); + return type ? type.get('name'):'-'; + }, + + statusRenderer: function(v, md, rec) { + var status = this.confirmationStatusStore.getById(rec.get('confirmationStatus_id')); + return status ? status.get('name'):'-'; + }, + + directionRenderer: function(v, md, r) { + var dir = r.get('direction_id'); + md.tdCls = "direction-" + dir; + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/budget/balanceitems/Root.js b/src/test/resources/app/view/budget/balanceitems/Root.js new file mode 100644 index 0000000..f2afaad --- /dev/null +++ b/src/test/resources/app/view/budget/balanceitems/Root.js @@ -0,0 +1,81 @@ +/** + * Root + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 4, 2011 + */ + + +Ext.define('DanteFrontend.view.budget.balanceitems.Root', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-budget-balanceitems', + + + initComponent: function() { + var config = { + layout: 'border', + region: 'center', + border: false, + margins: '-5 0 0 0', + items: [{ + region: 'center', + title: g('Balance items'), + xtype: 'df-msedit', + store: 'budget.BalanceItems', + filterConfig: { + xtype: 'df-budget-balanceitems-filter' + }, + briefGridConfig: { + xtype: 'df-budget-balanceitems-list', + flex: .7, + showFilter: true + }, + editor: { + xtype: 'df-budget-balanceitems-details', + flex: .3 + } + }] +// items: [{ +// +// },{ +// flex: 1, +// layout: 'border', +// margin: 2, +// items: [{ +// region: 'west', +// title: g('Balance items list'), +// xtype: 'df-budget-balanceitems-list', +// border: false, +// flex: .7 +// },{ +// region: 'center', +// flex: .3, +// layout: 'fit', +// border: false, +// items: [{ +// xtype: 'df-statepanel', +// emptyText: g('No balance item selected'), +// ref: 'details', +// content: [{ +// xtype: 'df-budget-balanceitems-expensedetails', +// border: false, +// ref: 'expenseForm' +// },{ +// xtype: 'df-budget-balanceitems-incomedetails', +// ref: 'incomeForm', +// border: false +// }] +// }] +// }] +// }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + + this.callParent(arguments); + } + +}); + diff --git a/src/test/resources/app/view/budget/dashboard/CurrentBalanceItemsList.js b/src/test/resources/app/view/budget/dashboard/CurrentBalanceItemsList.js new file mode 100644 index 0000000..2a76ba3 --- /dev/null +++ b/src/test/resources/app/view/budget/dashboard/CurrentBalanceItemsList.js @@ -0,0 +1,52 @@ +/** + * CurrentBalanceItems + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 3, 2011 + */ + +Ext.define('DanteFrontend.view.budget.dashboard.CurrentBalanceItemsList', { + extend: 'DanteFrontend.lib.view.Grid', + alias: 'widget.df-budget-dashboard-currentbalanceitems', + + + initComponent: function() { + this.typeStore = Ext.data.StoreManager.lookup('budget.BalanceItemTypes') + Ext.apply(this, { + toolbar: false, + store: 'budget.CurrentBalanceItems', + columns: [{ + header: g('Created'), + dataIndex: 'createdOn', + width: 75, + xtype: 'datecolumn', + format: Ext.Date.patterns.SK + },{ + header: ' ', + width: 15, + dataIndex: 'direction_id', + renderer: DanteFrontend.view.Renderers.budget.direction, + scope: this + },{ + header: g('Description'), + dataIndex: 'description', + flex: 1 + },{ + header: g('Grand total'), + dataIndex: 'grandTotal', + renderer: DanteFrontend.view.Renderers.budget.directionSum, + width: 65, + scope: this + },{ + header: g('Running total'), + dataIndex: 'runningTotal', + width: 80, + renderer: DanteFrontend.view.Renderers.sum + }] + }); + + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/budget/dashboard/ExpenseConfirmation.js b/src/test/resources/app/view/budget/dashboard/ExpenseConfirmation.js new file mode 100644 index 0000000..965bd4c --- /dev/null +++ b/src/test/resources/app/view/budget/dashboard/ExpenseConfirmation.js @@ -0,0 +1,126 @@ +/** + * ExpenseConfirmationList + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 5, 2011 + */ + +Ext.define('DanteFrontend.view.budget.dashboard.ExpenseConfirmation', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-budget-expenseconfirmation', + + config: { + handlerScope: null, + confirmHandler: Ext.emptyFn, + unconfirmHandler: Ext.emptyFn + }, + + initComponent: function() { + this.budgetStore = Ext.data.StoreManager.lookup('budget.Budgets'); + //this.balanceStore = Ext.data.StoreManager.lookup('budget.Balances'); + + this.detailTpl = Ext.create('Ext.XTemplate', [ + '
', + '
Date
{receivedOn:date(Ext.Date.patterns.SK)}
', + '
Status
{confirmationStatus}
', + '
Grand total
{grandTotal} €
', + '
Reason
{confirmationReason}
', + '
Employees
{associatedEmployees}
', + '
Description
{description}
', + '
' + ]); + + if(!this.getHandlerScope()) this.setHandlerScope(this); + + Ext.apply(this, { + // + layout: {type: 'vbox', align: 'stretch'}, + items: [{ + xtype: 'df-budget-dashboard-expenseconfirmationfilter', + height: 60, + store: 'budget.ConfirmableBalanceItems' + },{ + toolbar: false, + xtype: 'df-grid', + flex: 1, + region: 'center', + store: 'budget.ConfirmableBalanceItems', + border: false, + columns: [{ + dataIndex: 'budget', + width: 65, + header: g('Budget'), + renderer: function(v, md, r) { + return this.budgetStore.getById(r.get('budget_id')).get('title'); + }, + scope: this + },{ + dataIndex: 'description', + flex: 1, + header: g('Description') + },{ + dataIndex: 'grandTotal', + width: 60, + header: g('Grand total'), + renderer: DanteFrontend.view.Renderers.sum + },{ + dataIndex: 'confirmationStatus_id', + width: 100, + header: g('Status'), + renderer: DanteFrontend.view.Renderers.budget.status + },{ + xtype:'actioncolumn', + width: 30, + items: [{ + icon: media('/img/add.png'), + tooltip: g('Confirm'), + ref: 'confirm', + iconCls: 'action action-confirm', + getClass: this.actionRenderer, + handler: function() { + this.getConfirmHandler().apply(this.getHandlerScope(), arguments); + }, + scope: this + },{ + icon: media('/img/delete.png'), + tooltip: g('Unconfirm'), + ref: 'unconfirm', + iconCls: 'action action-unconfirm', + getClass: this.actionRenderer, + handler: function() { + this.getUnconfirmHandler().apply(this.getHandlerScope(), arguments); + }, + scope: this + }] + }] + },{ + xtype: 'panel', + ref: 'detail', + region: 'south', + height: 130, + html: g('Please select an expense item.'), + bodyStyle: 'border-width: 1px 0 0 0;' + }] + }); + + this.callParent(arguments); + }, + + updateDetails: function(data) { + data.confirmationReason = + Ext.data.StoreManager.lookup('budget.ConfirmationReasons') + .getById(data.confirmationReason_id).get('name'); + data.confirmationStatus = + Ext.data.StoreManager.lookup('budget.ConfirmationStatuses') + .getById(data.confirmationStatus_id).get('name'); + if(this.down('panel[ref=detail]').rendered) { + this.detailTpl.overwrite(this.down('panel[ref=detail]').body, data); + } + }, + + actionRenderer: function(v, md, r) { + md.css = 'confirmationStatus-' + (r.isConfirmable() ? 0:1); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/budget/dashboard/ExpenseConfirmationFilter.js b/src/test/resources/app/view/budget/dashboard/ExpenseConfirmationFilter.js new file mode 100644 index 0000000..1e13ca2 --- /dev/null +++ b/src/test/resources/app/view/budget/dashboard/ExpenseConfirmationFilter.js @@ -0,0 +1,45 @@ +/** + * ExpenseConfirmationFilter + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 13, 2011 + */ + +Ext.define('DanteFrontend.view.budget.dashboard.ExpenseConfirmationFilter', { + extend: 'DanteFrontend.lib.view.Filter', + alias: 'widget.df-budget-dashboard-expenseconfirmationfilter', + + initComponent: function() { + var config = { + store: 'budget.ConfirmableBalanceItems', + items: [{ + xtype: 'df-combo', + clearable: true, + name: 'budget_id', + fieldLabel: g('Budget'), + queryMode: 'local', + store: 'budget.Budgets', + valueField: 'id', + displayField: 'title', + filter: 'numeric', + width: 140 + },{ + xtype: 'df-combo', + clearable: true, + name: 'confirmationStatus_id', + fieldLabel: g('Status'), + queryMode: 'local', + store: 'budget.ConfirmationStatuses', + valueField: 'id', + displayField: 'name', + filter: 'numeric', + width: 140 + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/budget/dashboard/Overview.js b/src/test/resources/app/view/budget/dashboard/Overview.js new file mode 100644 index 0000000..1226a24 --- /dev/null +++ b/src/test/resources/app/view/budget/dashboard/Overview.js @@ -0,0 +1,103 @@ +/** + * CurrentBalance + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 29, 2011 + */ + +Ext.define('DanteFrontend.view.budget.dashboard.Overview', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-budget-dashboard-overview', + + config: { + record: null + }, + + initComponent: function() { + var config = { + layout: { + type: 'hbox', + align: 'stretch' + }, + items: [{ + xtype: 'df-budget-list', + border: false, + width: 150, + toolbar: false, + title: g('Budgets overview') + },{ + layout: { type: 'hbox', align: 'stretch'}, + cls: 'keep-left-border', + flex: 1, + title: ' ', + ref: 'balancePanel', + dockedItems: [{ + xtype: 'toolbar', + style: 'border-width: 1px 0 1px 1px !important;', + dock: 'top', + items: [{ + store: 'budget.Balances', + name: 'balance_id', + queryMode: 'local', + valueField: 'id', + displayField: 'generatedTitle', + xtype: 'combo', + allowBlank: false, + width: 148, + forceSelection: true, + lastQuery: '', + triggerAction: 'all', + editable: false, + margin: '0 2 0 2' + },'|',{ + xtype: 'button', + ref: 'prevBalance', + text: g('Previous balance'), + iconCls: 'x-tbar-page-prev' + },{ + xtype: 'button', + ref: 'nextBalance', + text: g('Next balance'), + iconCls: 'x-tbar-page-next', + iconAlign: 'right' + }] + }], + items: [{ + width: 160, + layout: { + type: 'vbox', + align: 'stretch' + }, + border: false, + defaults: { + border: false + }, + items: [{ + xtype: 'df-budget-balanceinfo', + padding: '6' + },{ + xtype: 'df-budget-dashboard-refill', + padding: '6 0 0 0', + height: 70 + }] + },{ + xtype: 'df-budget-dashboard-currentbalanceitems', + flex: 1, + border: true, + cls: 'keep-left-border', + title: g('Current balance items') + }] + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + }, + + tipTextRenderer: function(t) { + return g('Balance') + t.value; + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/view/budget/dashboard/RefillForm.js b/src/test/resources/app/view/budget/dashboard/RefillForm.js new file mode 100644 index 0000000..fc2e622 --- /dev/null +++ b/src/test/resources/app/view/budget/dashboard/RefillForm.js @@ -0,0 +1,58 @@ +/** + * RefillForm + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 3, 2011 + */ + +Ext.define('DanteFrontend.view.budget.dashboard.RefillForm', { + extend: 'Ext.form.Panel', + alias: 'widget.df-budget-dashboard-refill', + + initComponent: function() { + var config = { + layout: { type: 'vbox', align: 'stretch' }, + items: [{ + layout: 'hbox', + flex: 1, + border: false, + defaults: { + margin: '0 0 0 2' + }, + margin: 2, + ref: 'refill', + items: [{ + flex: .3, + xtype: 'button', + text: g('Refill by') + },{ + flex: .4, + hideLabel: true, + xtype: 'numberfield', + name: 'sum' + },{ + flex: .1, + xtype: 'displayfield', + hideLabel: true, + value: '€' + }] + },{ + margin: '0 4 0 4', + border: true, + bodyStyle: 'border-width: 1px 0 0 0 !important', + ref: 'newBalance', + items: { + margin: '4 0 0 0', + xtype: 'button', + text: g('Renew balance'), + iconCls: 'btn-refresh' + } + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/budget/dashboard/Root.js b/src/test/resources/app/view/budget/dashboard/Root.js new file mode 100644 index 0000000..5ec655c --- /dev/null +++ b/src/test/resources/app/view/budget/dashboard/Root.js @@ -0,0 +1,39 @@ +/** + * Overview root + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.view.budget.dashboard.Root', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-budget-dashboard', + + + initComponent: function() { + Ext.apply(this, { + layout: 'border', + region: 'center', + border: false, + margins: '-5 0 0 0', + items: [{ + xtype: 'df-budget-dashboard-overview', + region: 'center', + flex: .6, + border: true, + margin: 2 + },{ + region: 'east', + title: g('Expense confirmation'), + flex: .4, + split: true, + xtype: 'df-budget-expenseconfirmation', + margin: '2 2 2 -2' + }] + }); + + this.callParent(arguments); + } + +}); diff --git a/src/test/resources/app/view/budget/manage/DetailsForm.js b/src/test/resources/app/view/budget/manage/DetailsForm.js new file mode 100644 index 0000000..05733f3 --- /dev/null +++ b/src/test/resources/app/view/budget/manage/DetailsForm.js @@ -0,0 +1,248 @@ +/** + * DetailsForm + * ..description.. + * + * @author fk + * @version 0.1 + * @date Oct 3, 2011 + */ + + +Ext.define('DanteFrontend.view.budget.manage.DetailsForm', { + extend: 'DanteFrontend.lib.view.Form', + alias: 'widget.df-budget-manage-details', + + initComponent: function() { + this.employeeStore = Ext.data.StoreManager.lookup('Employees'); + var config = { + title: g('Budget details'), + layout: { + type: 'vbox', + align: 'stretch' + }, + defaults: { + border: false + }, + items: [{ + defaults: { + border: false, + height: 270 + }, + layout: 'hbox', + defaultType: 'textfield', + items: [{ + layout: 'anchor', + title: g('Basic information'), + xtype: 'fieldset', + flex: .4, + width: 250, + margin: '0 0 4 4', + defaults: { + labelWidth: 120, + labelAlign: 'top', + anchor: '90%', + margin: '0 0 10 0' + }, + items: [{ + fieldLabel: g('Title'), + xtype: 'textfield', + name: 'title' + },{ + fieldLabel: g('Description'), + xtype: 'textareafield', + name: 'description', + height: 120 + },{ + layout: 'hbox', + defaults: { + labelWidth: 120, + labelAlign: 'top' + }, + border: false, + items: [{ + fieldLabel: g('Purpose'), + name: 'purpose_id', + store: 'budget.Purposes', + emptyText: g('Select purpose ...'), + queryMode: 'local', + valueField: 'id', + displayField: 'name', + xtype: 'combo', + allowBlank: false, + anchor: '50%', + flex: .5, + margin: '0 10 0 0' + }] + }] + },{ + title: g('Confirmation policy'), + xtype: 'fieldset', + flex: .6, + margin: '0 4 0 4', + layout: { + type: 'hbox', + align: 'stretch' + }, + items: [{ + defaults: { + labelWidth: 120, + labelAlign: 'top', + width: 170, + margin: '0 0 10 0' + }, + margin: '0 10 0 0', + flex: .5, + border: false, + items: [{ + xtype: 'checkboxgroup', + width: 220, + fieldLabel: g('Confirm balance items'), + columns: 1, + margin: '0 0 10 0', + labelWidth: 80, + items: [{ + boxLabel: g('Always'), + name: 'confirmAlways' + },{ + boxLabel: g('When balance overdrawn'), + name: 'confirmOnBalanceOverdraw' + },{ + boxLabel: g('When item over max price'), + name: 'confirmOnOverMaxItemPrice' + }] + },{ + fieldLabel: g('Maximum item price'), + name: 'maxItemPrice', + xtype: 'numberfield', + allowBlank: false + },{ + fieldLabel: g('Confirmation required from'), + name: 'confirmationUsersAmount_id', + store: 'budget.ConfirmationUsersAmounts', + emptyText: g('Select number of users ...'), + queryMode: 'local', + valueField: 'id', + displayField: 'name', + xtype: 'combo', + allowBlank: false + }] + },{ + layout: { + type: 'vbox', + align: 'stretch' + }, + border: false, + flex: .5, + items: [{ + xtype: 'label', + text: g('Confirming users') + },{ + store: 'budget.BudgetUsers', + xtype: 'df-grid-adding', + flex: 1, + margin: '0 0 10 0', + fieldConfig: { + store: 'Employees', + emptyText: g('Select user ...'), + queryMode: 'local', + valueField: 'id', + displayField: 'name', + xtype: 'combo', + name: 'user_id' + }, + columns: [{ + flex: 1, + header: g('User name'), + dataIndex: 'user_id', + renderer: this.userNameRenderer, + scope: this + }] + }] + }] + }] + },{ + title: g('Balance renewal'), + xtype: 'fieldset', + margin: '0 4 4 4', + flex: 1, + layout: { + type: 'vbox', + align: 'stretch' + }, + defaults: { + border: false + }, + items: [{ + defaults: { + labelWidth: 120, + labelAlign: 'top', + flex: .25, + margin: '0 10 0 0' + }, + layout: { + type: 'hbox' + }, + items: [{ + fieldLabel: g('Balance renewal strategy'), + name: 'balanceRenewalStrategy_id', + store: 'budget.BalanceRenewalStrategies', + emptyText: g('Select strategy ...'), + queryMode: 'local', + valueField: 'id', + displayField: 'name', + xtype: 'combo', + allowBlank: false + },{ + fieldLabel: g('Balance renewal frequency'), + name: 'balanceRenewalFrequency_id', + store: 'budget.BalanceRenewalFrequencies', + emptyText: g('Select frequency ...'), + queryMode: 'local', + valueField: 'id', + displayField: 'name', + xtype: 'combo', + allowBlank: false + },{ + xtype: 'fieldcontainer', + layout: { type: 'hbox' }, + fieldLabel: g('Balance renewal on'), + combineErrors: false, + defaults: { hideLabel: true }, + items: [{ + name : 'renewalDayOfMonth', + xtype: 'numberfield', + width: 48, + allowBlank: false, + margin: { right: 5 } + },{ + xtype: 'displayfield', + value: g('day of month'), + width: 80 + }] + },{ + fieldLabel: g('Initial balance credit'), + name: 'balanceInitialCredit', + xtype: 'numberfield', + allowBlank: false + }] + },{ + xtype: 'checkboxgroup', + width: 400, + margin: '10 0 0 0', + hideLabel: true, + columns: 1, + items: [ + {boxLabel: g('Automatic transfer of balance totals between periods'), name: 'automaticBalanceTransfer'}, + ] + }] + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + }, + + userNameRenderer: function(v, md, r) { + return this.employeeStore.getById(r.get('user_id')).get('name'); + } +}); diff --git a/src/test/resources/app/view/budget/manage/Root.js b/src/test/resources/app/view/budget/manage/Root.js new file mode 100644 index 0000000..7926c2c --- /dev/null +++ b/src/test/resources/app/view/budget/manage/Root.js @@ -0,0 +1,54 @@ +/** + * Overview root + * + * @author fk + * @version 0.1 + * @date Sep 28, 2011 + */ + +Ext.define('DanteFrontend.view.budget.manage.Root', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-budget-manage', + + + initComponent: function() { + Ext.apply(this, { + layout: 'border', + region: 'center', + border: false, + //split: true, + margins: '-5 0 0 0', + items: [{ +// xtype: 'df-budget-list', +// region: 'west', +// flex: .3, +// margin: '2 0 2 2', +// split: true, +// toolbar: true +// },{ +// xtype: 'df-budget-manage-details', +// region: 'center', +// flex: .7, +// margin: '2 2 2 0', +// border: true +// },{ + title: g('Manage budgets'), + xtype: 'df-msedit', + region: 'center', + store: 'budget.Budgets', + briefGridConfig: { + xtype: 'df-budget-list', + width: 230 + }, + editor: { + xtype: 'df-budget-manage-details' + } + }] + }); + + + + this.callParent(arguments); + } + +}); diff --git a/src/test/resources/app/view/customer/SupplierDetails.js b/src/test/resources/app/view/customer/SupplierDetails.js new file mode 100644 index 0000000..5c40bb2 --- /dev/null +++ b/src/test/resources/app/view/customer/SupplierDetails.js @@ -0,0 +1,81 @@ +/** + * SupplierDetails + * ..description.. + * + * @author fk + * @version 0.1 + * @date Sep 8, 2011 + */ + +/** + * BasicForm + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 5, 2011 + */ + +Ext.define('DanteFrontend.view.customer.SupplierDetails', { + extend: 'DanteFrontend.lib.view.Form', + alias: 'widget.df-customer-supplier-details', + + + initComponent: function() { + var config = { + border: false, + layout: { + type: 'hbox', + align: 'stretch' + }, + defaults: { + border: false + }, + items: [{ + layout: 'anchor', + title: g('Basic information'), + xtype: 'fieldset', + defaultType: 'textfield', + flex: 1, + margin: 4, + defaults: { + labelWidth: 120, + labelAlign: 'top', + anchor: '100%', + maxWidth: 250 + }, + items: [{ + name: 'id', + xtype: 'hidden' + },{ + fieldLabel: g('Supplier name'), + name: 'name', + allowBlank: false + }] + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } + +}); + +// },{ +// fieldLabel: g('Category'), +// queryModel: 'local', +// name: 'category_id', +// store: 'order.Categories', +// emptyText: g('Select category ...'), +// valueField: 'id', +// displayField: 'name', +// xtype: 'combo', +// flex: 1 +// },{ +// fieldLabel: g('Associated project'), +// name: 'project_id', +// store: 'CurrentProjects', +// emptyText: g('Select project ...'), +// valueField: 'id', +// displayField: 'name', +// xtype: 'combo' diff --git a/src/test/resources/app/view/dashboard/AccountManager.js b/src/test/resources/app/view/dashboard/AccountManager.js new file mode 100644 index 0000000..608659c --- /dev/null +++ b/src/test/resources/app/view/dashboard/AccountManager.js @@ -0,0 +1,29 @@ +/** + * AccountManager + * + * @author fk + * @version 0.1 + * @date Jun 3, 2011 + */ + +Ext.define('DanteFrontend.view.dashboard.AccountManager', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-dashboard-accountmanager', + + frame: true, + split: true, + height: 100, + minHeight: 100, + + initComponent: function() { + Ext.apply(this, { + title: g('Account Manager'), + items: [{ + border: false, + html: res('tpl.accountManager') + }] + }); + this.callParent(arguments); + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/view/dashboard/CurrentProjects.js b/src/test/resources/app/view/dashboard/CurrentProjects.js new file mode 100644 index 0000000..df418c1 --- /dev/null +++ b/src/test/resources/app/view/dashboard/CurrentProjects.js @@ -0,0 +1,61 @@ +/** + * CurrentProjects + * Current projects view. + * + * @author fk + * @version 0.1 + * @date Jun 2, 2011 + */ + +Ext.define('DanteFrontend.view.dashboard.CurrentProjects', { + extend: 'Ext.grid.Panel', + alias: 'widget.df-dashboard-currentprojects', + + frame: true, + split: true, + viewConfig: { + emptyText: g('No projects are currently in progress.'), + stripeRows: true + }, + + initComponent: function() { + Ext.apply(this, { + title: g('Current Projects'), + features: [{ftype: 'grouping'}], + + disableSelection: true, + store: 'dashboard.CurrentProjects', + columns: [{ + id: 'projectName', + header: g('Project Name'), + width: 160, + renderer: DanteFrontend.view.Renderers.dashboard.projectName, + dataIndex: 'name', + flex: 1 + },{ + align: 'right', + header: g('Planned'), + width: 65, + renderer: DanteFrontend.view.Renderers.duration, + dataIndex: 'timePlanned' + },{ + align: 'right', + header: g('Remaining'), + width: 75, + renderer: DanteFrontend.view.Renderers.dashboard.timeRemaining, + dataIndex: 'timeSpent' + },{ + header: '', + width: 250, + sortable: false, + menuDisabled: true, + renderer: DanteFrontend.view.Renderers.dashboard.plannedTimeRemaining, + dataIndex: 'timeSpentGraph' + }] + }); + this.callParent(arguments); + } + + + +}); \ No newline at end of file diff --git a/src/test/resources/app/view/dashboard/CurrentTasks.js b/src/test/resources/app/view/dashboard/CurrentTasks.js new file mode 100644 index 0000000..a6a650a --- /dev/null +++ b/src/test/resources/app/view/dashboard/CurrentTasks.js @@ -0,0 +1,75 @@ +/** + * CurrentTasks + * Current tasks view. + * + * @author fk + * @version 0.1 + * @date Jun 2, 2011 + */ + +Ext.define('DanteFrontend.view.dashboard.CurrentTasks', { + extend: 'Ext.grid.Panel', + alias: 'widget.df-dashboard-currenttasks', + + + frame: true, + split: true, + disableSelection: true, + + + initComponent: function() { + Ext.apply(this, { + title: g('Current Tasks'), + store: 'dashboard.CurrentTasks', + loadMask: { + msg: g('Please wait, loading...'), + store: 'dashboard.CurrentTasksStore' + }, + viewConfig: { stripeRows: true }, + columns: [{ + header: 'Project Name', + width: 125, + dataIndex: 'projectName' + },{ + id: 'description', + header: 'Task Description', + width: 160, + flex: 1, + dataIndex: 'description' + },{ + header: g('Duration'), + width: 65, + dataIndex: 'duration', + renderer: DanteFrontend.view.Renderers.duration + }, { + header: g('Begin Time'), + width: 125, + dataIndex: 'begin', + xtype: 'datecolumn', + format: 'd.m.Y H:i', + hidden: true + }, { + header: g('End Time'), + width: 125, + dataIndex: 'end', + xtype: 'datecolumn', + format: 'd.m.Y H:i', + hidden: true + }, { + header: g('OTRS Ticket'), + width: 115, + dataIndex: 'ticket' + }, { + header: g('Owner'), + width: 105, + dataIndex: 'userName', + hidden: true + }], + features: [{ + ftype: 'grouping', + groupHeaderTpl: '{name}' + }] + }); + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/dashboard/HotlineEmail.js b/src/test/resources/app/view/dashboard/HotlineEmail.js new file mode 100644 index 0000000..b5fee0a --- /dev/null +++ b/src/test/resources/app/view/dashboard/HotlineEmail.js @@ -0,0 +1,70 @@ +/** + * HotlineEmail + * + * @author fk + * @version 0.1 + * @date Jun 3, 2011 + */ + +Ext.define('DanteFrontend.view.dashboard.HotlineEmail', { + extend: 'Ext.form.Panel', + alias: 'widget.df-dashboard-hotlineemail', + + id: 'hotlineEmailForm', + border: true, + frame: true, + region: 'center', + split: true, + + fieldDefaults: { + labelAlign: 'top', + labelWidth: 110 + }, + bodyStyle: 'padding:5px 5px 0', + + initComponent: function() { + var config = { + title: g('E-mail Hotline Contact'), + api: { + submit: dashboardController.sendHotlineEmail + }, + defaultType: 'textfield', + layout: { + type: 'vbox', + align: 'stretch' + }, + items: [ + { + xtype: 'combo', + fieldLabel: g('Request Queue'), + queryMode: 'local', + store: 'dashboard.HotlineEmailQueues', + valueField: 'queue_email', + displayField: 'queue_name', + name: 'queue', + forceSelection: true, + editable: false, + triggerAction: 'all', + emptyText: g('Please select request queue...'), + typeAhead: false + },{ + fieldLabel: g('Subject'), + name: 'subject' + }, + new Ext.form.TextArea({ + fieldLabel: g('Text'), + name: 'message', + flex: 1 + })], + + buttons: [{ + text: g('Send Request'), + action: 'sendRequest' + }] + } + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/view/dashboard/HotlinePhone.js b/src/test/resources/app/view/dashboard/HotlinePhone.js new file mode 100644 index 0000000..b20eb01 --- /dev/null +++ b/src/test/resources/app/view/dashboard/HotlinePhone.js @@ -0,0 +1,34 @@ +/** + * HotlinePhone + * + * @author fk + * @version 0.1 + * @date Jun 3, 2011 + */ + + +Ext.define('DanteFrontend.view.dashboard.HotlinePhone', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-dashboard-hotlinephone', + + + frame: true, + split: true, + + initComponent: function() { + Ext.apply(this, { + title: g('Telephone Hotline Contact'), + items: [ + { + xtype: 'box', + autoEl: { + tag: 'div', + cls: 'dante-dashboard-hotline-phone', + html: res('text.hotlinePhoneNumber') + } + } + ] + }); + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/dashboard/Root.js b/src/test/resources/app/view/dashboard/Root.js new file mode 100644 index 0000000..2ec4c1f --- /dev/null +++ b/src/test/resources/app/view/dashboard/Root.js @@ -0,0 +1,63 @@ +/** + * Panel + * + * @author fk + * @version 0.1 + * @date Jun 2, 2011 + */ + + +Ext.define('DanteFrontend.view.dashboard.Root', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-dashboard-root', + + collapsible: false, + border: false, + split: true, + margins: '-5 0 0 0', + layout: 'border', + width: '100%', + height: '100%', + region: 'center', + + initComponent: function() { + Ext.apply(this, { + title: g('Dashboard'), + items: [{ + region: 'center', + xtype: 'panel', + collapsible: false, + border: false, + split: true, + minSize: 250, + maxSize: 400, + margins: '5 0 0 5', + layout: 'border', + items: [ + {xtype: 'df-dashboard-currentprojects', region: 'north', height: '50%'}, + {xtype: 'df-dashboard-currenttasks', region: 'center', autoHeight: true} + ] + },{ + region: 'east', + xtype: 'panel', + collapsible: true, + width: 400, + border: false, + split: true, + minSize: 350, + maxSize: 500, + margins: '5 5 0 0', + layout: { + type: 'vbox', + align: 'stretch' + }, + items: [ + {xtype: 'df-dashboard-accountmanager'}, + {xtype: 'df-dashboard-hotlinephone', flex: 1, maxHeight: 80}, + {xtype: 'df-dashboard-hotlineemail', flex: 2} + ] + }] + }); + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/main/Footer.js b/src/test/resources/app/view/main/Footer.js new file mode 100644 index 0000000..92ead1a --- /dev/null +++ b/src/test/resources/app/view/main/Footer.js @@ -0,0 +1,25 @@ +/** + * Footer + * The footer, showing basic info about the app. + * + * @author fk + * @version 0.1 + * @date May 27, 2011 + */ + +Ext.define('DanteFrontend.view.main.Footer', { + extend: 'Ext.Component', + alias: 'widget.df-main-footer', + + initComponent: function() { + Ext.apply(this, { + autoEl: { + tag: 'div', + cls: 'dante-copyright', + html: 'Copyright © 2009 - 2011 ' + + 'Digmia s.r.o. All rights reserved' + } + }); + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/main/Panel.js b/src/test/resources/app/view/main/Panel.js new file mode 100644 index 0000000..bddd576 --- /dev/null +++ b/src/test/resources/app/view/main/Panel.js @@ -0,0 +1,14 @@ +/** + * Panel + * The central panel widget. + * + * @author fk + * @version 0.1 + * @date May 27, 2011 + */ + +Ext.define('DanteFrontend.view.main.Panel', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-main-panel', + layout: 'fit' +}); \ No newline at end of file diff --git a/src/test/resources/app/view/main/Toolbar.js b/src/test/resources/app/view/main/Toolbar.js new file mode 100644 index 0000000..a37f7cd --- /dev/null +++ b/src/test/resources/app/view/main/Toolbar.js @@ -0,0 +1,43 @@ +/** + * View: MainToolbar + * Main application toolbar widget. + * + * @author fk + * @version 0.1 + * @date May 27, 2011 + */ +Ext.Img.override({ + initRenderTpl: Ext.emptyFn +}); + +Ext.define('DanteFrontend.view.main.Toolbar', { + extend: 'Ext.toolbar.Toolbar', + alias: 'widget.df-main-toolbar', + + initComponent: function() { + Ext.apply(this, { + height: 36, + layout: 'hbox', + items: [{ + xtype: 'box', + width: 100, + height: 30, + id: 'digmia-logo', + margins: '0 0 0 0' + },'-', + DanteFrontend.toolbar + ,'-',{ + xtype: 'box', + autoEl: { + html: res('text.loggedIn') + } + },' ',{ + xtype: 'button', + action: '/logout/', + text: 'Logout from Dante' + }] + }); + this.callParent(arguments); + } + +}); diff --git a/src/test/resources/app/view/order/Filter.js b/src/test/resources/app/view/order/Filter.js new file mode 100644 index 0000000..50415ad --- /dev/null +++ b/src/test/resources/app/view/order/Filter.js @@ -0,0 +1,60 @@ +/** + * Filter + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 17, 2011 + */ + +Ext.define('DanteFrontend.view.order.Filter', { + extend: 'DanteFrontend.lib.view.Filter', + alias: 'widget.df-order-filter', + + initComponent: function() { + + var config = { + items: [{ + xtype: 'df-textfield', + name: 'referenceId', + fieldLabel: g('Reference #'), + padding: 0, + filter: 'string', + clearable: true + },{ + xtype: 'df-combo', + clearable: true, + name: 'acceptedOn', + fieldLabel: g('Month'), + queryMode: 'local', + store: 'Months', + valueField: 'id', + displayField: 'name', + filter: 'month' + },{ + xtype: 'df-combo', + clearable: true, + name: 'customer', + fieldLabel: g('Customer'), + queryMode: 'local', + store: 'Customers', + valueField: 'id', + displayField: 'name', + filter: 'numeric' + },{ + xtype: 'df-combo', + clearable: true, + name: 'deliveryStatus', + fieldLabel: g('Delivery status'), + queryMode: 'local', + store: 'order.DeliveryStatuses', + valueField: 'id', + displayField: 'name', + filter: 'numeric' + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } +}); diff --git a/src/test/resources/app/view/order/Root.js b/src/test/resources/app/view/order/Root.js new file mode 100644 index 0000000..7ff9c41 --- /dev/null +++ b/src/test/resources/app/view/order/Root.js @@ -0,0 +1,113 @@ +/** + * Root + * + * @author fk + * @version 0.1 + * @date Aug 3, 2011 + */ + + +Ext.define('DanteFrontend.view.order.Root', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-order-root', + + + collapsible: false, + border: false, + margins: '-5 0 0 0', + + initComponent: function() { + this.deliveryStatusStore = Ext.data.StoreManager.lookup('order.DeliveryStatuses'); + this.customerStore = Ext.data.StoreManager.lookup('Customers'); + Ext.apply(this, { + layout: 'border', + region: 'center', + items: [{ + title: g('Orders'), + xtype: 'df-msedit', + region: 'center', + store: 'order.Orders', + dependentStores: ['order.Attachments'], + filterConfig: { + xtype: 'df-order-filter' + }, + fullGridConfig: { + columns: [{ + header: g('Reference #'), + dataIndex: 'referenceId', + width: 100 + },{ + header: g('Customer'), + dataIndex: 'customer_id', + width: 100, + renderer: Ext.bind(function(v) { + var rec = this.customerStore.getById(v); + if(!Ext.isEmpty(rec)) return rec.get('name'); + }, this) + },{ + header: g('Summary'), + dataIndex: 'summary', + flex: 1 + },{ + header: g('Status'), + dataIndex: 'deliveryStatus_id', + width: 100, + renderer: Ext.bind(function(v) { + return this.deliveryStatusStore.getById(v).get('name'); + }, this) + },{ + header: g('Selling grand total'), + dataIndex: 'sellingGrandTotal', + width: 100, + renderer: function(v) { + return v + ' €'; + } + },{ + header: g('Date accepted'), + dataIndex: 'acceptedOn', + width: 100, + xtype: 'datecolumn', + format: Ext.Date.patterns.SK + }] + }, + briefGridConfig: { + title: g('Brief list'), + column: { + dataIndex: 'referenceId', + renderer: this.descriptionRenderer, + scope: this, + flex: 1 + }, + flex: .3 + }, + editor: { + xtype: 'df-order-details' + } + }] + }); + + this.tpl = new Ext.Template('
', + '
{referenceId}', + '{customerName}
', + '
{total} €', + '{deliveryStatusName}
', + '
', { compiled: true }); + + this.callParent(arguments); + }, + + descriptionRenderer: function(value, md, record) { + var customer = this.customerStore.getById(record.get('customer_id')); + + return this.tpl.apply({ + referenceId: record.get('referenceId'), + customerName: Ext.isEmpty(customer) ? g('No customer selected'):customer.get('name'), + total: record.get('sellingGrandTotal'), + deliveryStatusName: this.deliveryStatusStore.getById(record.get('deliveryStatus_id')).get('name'), + deliveryStatus: record.get('deliveryStatus_id') + }); + } + + + +}); \ No newline at end of file diff --git a/src/test/resources/app/view/order/details/AttachmentsList.js b/src/test/resources/app/view/order/details/AttachmentsList.js new file mode 100644 index 0000000..0867c8c --- /dev/null +++ b/src/test/resources/app/view/order/details/AttachmentsList.js @@ -0,0 +1,24 @@ +/** + * Attachments + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 9, 2011 + */ + + +Ext.define('DanteFrontend.view.order.details.AttachmentsList', { + extend: 'DanteFrontend.lib.view.Grid', + alias: 'widget.df-order-details-attachments', + + initComponent: function() { + var config = { + + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } +}); + diff --git a/src/test/resources/app/view/order/details/BasicForm.js b/src/test/resources/app/view/order/details/BasicForm.js new file mode 100644 index 0000000..ecf39e7 --- /dev/null +++ b/src/test/resources/app/view/order/details/BasicForm.js @@ -0,0 +1,114 @@ +/** + * BasicForm + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 5, 2011 + */ + +Ext.define('DanteFrontend.view.order.details.BasicForm', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-order-details-basicform', + + initComponent: function() { + var config = { + defaultType: 'textfield', + border: false, + layout: { + type: 'hbox', + align: 'stretch' + }, + defaults: { + border: false + }, + items: [{ + layout: 'anchor', + title: g('Basic information'), + xtype: 'fieldset', + autoHeight: true, + flex: .3, + margin: '0 0 0 4', + defaults: { + labelWidth: 120, + labelAlign: 'top', + anchor: '100%', + maxWidth: 250 + }, + items: [{ + name: 'id', + xtype: 'hidden' + },{ + fieldLabel: g('Order reference #'), + xtype: 'displayfield', + name: 'referenceId' + },{ + fieldLabel: g('Customer'), + name: 'customer_id', + store: 'Customers', + emptyText: g('Select customer ...'), + queryMode: 'local', + valueField: 'id', + displayField: 'name', + xtype: 'combo', + allowBlank: false + },{ + fieldLabel: g('Delivery status'), + name: 'deliveryStatus_id', + emptyText: g('Select status ...'), + store: 'order.DeliveryStatuses', + queryMode: 'local', + valueField: 'id', + displayField: 'name', + xtype: 'combo', + allowBlank: false + },{ + fieldLabel: g('Date accepted'), + name: 'acceptedOn', + xtype: 'datefield', + allowBlank: false, + format: Ext.Date.patterns.SK + }] + },{ + layout: 'fit', + title: g('Additional information'), + xtype: 'fieldset', + autoHeight: true, + margin: '0 0 0 4', + padding: '8 8 24 8', + flex: .4, + defaults: { + labelWidth: 120, + labelAlign: 'top' + }, + items: { + fieldLabel: g('Description'), + xtype: 'textareafield', + name: 'summary' + } + },{ + layout: 'fit', + title: g('Attachments'), + xtype: 'fieldset', + autoHeight: true, + margin: '0 4 0 4', + flex: .3, + items: { + xtype: 'df-grid-file', + ref: 'attachments', + margin: '0 0 6 0', + store: 'order.Attachments', + flex: 1, + api: { + submit: orderController.attachToOrder, + download: approot('/attachment/order/') + } + } + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } + +}); \ No newline at end of file diff --git a/src/test/resources/app/view/order/details/ItemsEditor.js b/src/test/resources/app/view/order/details/ItemsEditor.js new file mode 100644 index 0000000..8fa6ebd --- /dev/null +++ b/src/test/resources/app/view/order/details/ItemsEditor.js @@ -0,0 +1,21 @@ +/** + * Items + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 5, 2011 + */ + +Ext.define('DanteFrontend.view.order.details.ItemsEditor', { + extend: 'Ext.panel.Panel', + alias: 'df-order-details-itemseditor', + + initComponent: function() { + var config = { + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/order/details/ItemsList.js b/src/test/resources/app/view/order/details/ItemsList.js new file mode 100644 index 0000000..c21d657 --- /dev/null +++ b/src/test/resources/app/view/order/details/ItemsList.js @@ -0,0 +1,144 @@ +/** + * ItemsList + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 9, 2011 + */ + +Ext.define('DanteFrontend.view.order.details.ItemsList', { + extend: 'DanteFrontend.lib.view.Grid', + alias: 'widget.df-order-details-items', + + initComponent: function() { + this.unitStore = Ext.data.StoreManager.lookup('order.Units'); + this.typeStore = Ext.data.StoreManager.lookup('order.ItemTypes'); + this.supplierStore = Ext.data.StoreManager.lookup('Suppliers'); + + this.rowEditing = Ext.create('DanteFrontend.lib.view.RowEditing', { + clicksToEdit: 2, + clicksToMoveEditor: 1 + }); + + var config = { + store: 'order.OrderItems', + plugins: [ + this.rowEditing + ], + //margin: 4, + selType: 'rowmodel', + toolbar: true, + columns: [{ + header: g('Description'), + dataIndex: 'description', + editor: { + xtype: 'textfield', + name: 'description', + allowBlank: false + }, + flex: 1 + },{ + header: g('Supplier'), + dataIndex: 'supplier_id', + width: 120, + editor: { + name: 'supplier_id', + store: this.supplierStore, + emptyText: g('Select supplier ...'), + valueField: 'id', + displayField: 'name', + xtype: 'df-combo-add', + windowTitle: g('Add supplier'), + addForm: { + xtype: 'df-customer-supplier-details' + }, + queryMode: 'local', + allowBlank: false + }, + + renderer: Ext.bind(function(v) { + var rec = this.supplierStore.getById(v); + return !Ext.isEmpty(rec) ? rec.get('name'):null; + }, this) + },{ + header: g('Type'), + dataIndex: 'type_id', + width: 120, + renderer: Ext.bind(function(v) { + return this.typeStore.getById(v).get('name'); + }, this), + editor: { + name: 'type_id', + store: this.typeStore, + emptyText: g('Select type ...'), + valueField: 'id', + displayField: 'name', + xtype: 'combo', + queryMode: 'local' + } + },{ + header: g('Amount'), + dataIndex: 'amount', + field: 'numberfield', + width: 50 + },{ + header: g('Unit'), + dataIndex: 'unit_id', + width: 32, + renderer: Ext.bind(function(v) { + return this.unitStore.getById(v).get('name'); + }, this), + editor: { + name: 'unit_id', + store: this.unitStore, + emptyText: g('Select type ...'), + valueField: 'id', + displayField: 'name', + xtype: 'combo', + queryMode: 'local' + } + },{ + header: g('Supplier unit'), + dataIndex: 'supplierUnitPrice', + field: 'numberfield', + width: 80, + renderer: DanteFrontend.view.Renderers.sum + },{ + header: g('Supplier base'), + dataIndex: 'supplierBasePrice', + field: 'numberfield', + width: 80, + renderer: function(v) { + return v + ' €'; + } + },{ + header: g('Selling unit'), + dataIndex: 'sellingUnitPrice', + field: 'numberfield', + width: 80, + renderer: DanteFrontend.view.Renderers.sum + },{ + header: g('Selling base'), + dataIndex: 'sellingBasePrice', + field: 'numberfield', + width: 80, + renderer: function(v) { + return v + ' €'; + } + }], + listeners: { + itemadded: function(v, r) { + this.rowEditing.startEdit(r, 0); + }, + scope: this + } + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + + this.callParent(arguments); + } + + +}); diff --git a/src/test/resources/app/view/order/details/Root.js b/src/test/resources/app/view/order/details/Root.js new file mode 100644 index 0000000..d16362a --- /dev/null +++ b/src/test/resources/app/view/order/details/Root.js @@ -0,0 +1,40 @@ +/** + * OrderDetails + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 5, 2011 + */ + + +Ext.define('DanteFrontend.view.order.details.Root', { + extend: 'DanteFrontend.lib.view.Form', + alias: 'widget.df-order-details', + + initComponent: function() { + var config = { + xtype: 'panel', + border: true, + layout: { + type: 'border' + }, + items: [{ + title: g('Order details'), + xtype: 'df-order-details-basicform', + border: false, + height: 250, + region: 'north', + split: true + },{ + xtype: 'df-order-details-items', + title: g('Order items'), + region: 'center', + border: false + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/worklog/Filter.js b/src/test/resources/app/view/worklog/Filter.js new file mode 100644 index 0000000..b896066 --- /dev/null +++ b/src/test/resources/app/view/worklog/Filter.js @@ -0,0 +1,92 @@ +/** + * Filter + * Filter form for the TimeLine grid. + * + * @author fk + * @version 0.1 + * @date Jun 17, 2011 + */ +Ext.define('DanteFrontend.view.worklog.Filter', { + extend: 'Ext.form.Panel', + alias: 'widget.df-worklog-filter', + + frame: true, + collapsible: true, + anchor: '100%', + buttonAlign: 'center', + + initComponent: function() { + var config = { + paramsAsHash: true, + title: g('Filters'), + + api: { + load: worklogController.filterLoad, + submit: worklogController.filterExecute + }, + items: [{ + xtype: 'combo', + name: 'month', + fieldLabel: g('Month'), + labelWidth: 60, + queryMode: 'local', + store: 'Months', + valueField: 'id', + displayField: 'name', + anchor: '100%', + minListWidth: 130, + forceSelection: true, + emptyText: g('All months') + },{ + xtype: 'df-combo', + name: 'employee', + fieldLabel: g('Employee'), + labelWidth: 60, + queryMode: 'local', + store: 'Employees', + valueField: 'id', + displayField: 'name', + anchor: '100%', + minListWidth: 250, + emptyText: g('All employees'), + clearable: true + },{ + id: 'customer-combo', + xtype: 'df-combo', + name: 'customer', + fieldLabel: g('Customer'), + labelWidth: 60, + queryMode: 'local', + store: 'Customers', + valueField: 'id', + displayField: 'name', + anchor: '100%', + minListWidth: 250, + emptyText: g('All customers'), + clearable: true + },{ + xtype: 'df-combo', + name: 'project', + fieldLabel: g('Project'), + labelWidth: 60, + queryMode: 'local', + store: 'Projects', + valueField: 'id', + displayField: 'name', + anchor: '100%', + minListWidth: 250, + emptyText: g('All projects'), + clearable: true + } + ], + buttons: [{ + text: g('Apply filters'), + action: 'applyFilters' + }] + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + + this.callParent(arguments); + } +}); diff --git a/src/test/resources/app/view/worklog/Root.js b/src/test/resources/app/view/worklog/Root.js new file mode 100644 index 0000000..ed4298d --- /dev/null +++ b/src/test/resources/app/view/worklog/Root.js @@ -0,0 +1,51 @@ +/** + * Root + * Root worklog panel + * + * @author fk + * @version 0.1 + * @date Jun 16, 2011 + */ + +Ext.define('DanteFrontend.view.worklog.Root', { + extend: 'Ext.panel.Panel', + alias: 'widget.df-worklog-root', + + + collapsible: false, + border: false, + margins: '-5 0 0 0', + width: '100%', + height: '100%', + + timeLineBottomToolbar: null, + + initComponent: function() { + Ext.apply(this, { + title: g('Worklog'), + layout: 'border', + region: 'center', + items: [{ + preventHeader: true, + region: 'west', + collapsible: true, + width: 250, + frame: true, + split: true, + minSize: 250, + maxSize: 400, + margins: '5 0 5 5', + layout: 'anchor', + items: [{ + xtype: 'df-worklog-filter', + height: 200 + }] + }, Ext.apply({ + xtype: 'df-worklog-timeline', + region: 'center', + margins: '5 5 5 0' + }, this.timeLineBottomToolbar)] + }); + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/worklog/RootWithExportTools.js b/src/test/resources/app/view/worklog/RootWithExportTools.js new file mode 100644 index 0000000..7c5f0c2 --- /dev/null +++ b/src/test/resources/app/view/worklog/RootWithExportTools.js @@ -0,0 +1,37 @@ +/** + * RootWithExportTools + * ..description.. + * + * @author fk + * @version 0.1 + * @date Aug 2, 2011 + */ + +/** + * Root + * Root worklog panel + * + * @author fk + * @version 0.1 + * @date Jun 16, 2011 + */ + +/** + * Root + * Root worklog panel + * + * @author fk + * @version 0.1 + * @date Jun 16, 2011 + */ + +Ext.define('DanteFrontend.view.worklog.RootWithExportTools', { + extend: 'DanteFrontend.view.worklog.Root', + alias: 'widget.df-worklog-root-et', + requires: 'DanteFrontend.lib.view.ExportTools', + + initComponent: function() { + this.timeLineBottomToolbar = { bbar: Ext.widget('df-exporttools', {gridId: 'worklog'}) }; + this.callParent(arguments); + } +}); \ No newline at end of file diff --git a/src/test/resources/app/view/worklog/TimeLine.js b/src/test/resources/app/view/worklog/TimeLine.js new file mode 100644 index 0000000..11ffc96 --- /dev/null +++ b/src/test/resources/app/view/worklog/TimeLine.js @@ -0,0 +1,93 @@ +/** + * TimeLine + * The default worklog timeline grid. + * + * @author fk + * @version 0.1 + * @date Jun 16, 2011 + */ + + +Ext.define('DanteFrontend.view.worklog.TimeLine', { + extend: 'Ext.grid.Panel', + alias: 'widget.df-worklog-timeline', + require: ['DanteFrontend.lib.view.ExportTools'], + + margins: '5 5 0 0', + stripeRows: true, + + + initComponent: function() { + Ext.apply(this, { + title: g('List of Tasks'), + store: 'worklog.TimeLine', + features: [{ + groupHeaderTpl: '{name}', + ftype: 'groupingsummary' + }], + loadMask: { + msg: g('Please wait, loading...'), + store: 'worklog.TimeLine' + }, + + columns: [{ + header: g('Employee Name'), + dataIndex: 'userName', + width: 100, + sortable: true + },{ + header: g('Customer Name'), + dataIndex: 'customerName', + width: 150, + sortable: true + },{ + header: g('Project Name'), + dataIndex: 'projectName', + width: 150, + sortable: true + },{ + id: 'task_description_column', + header: g('Task Description'), + dataIndex: 'description', + renderer: DanteFrontend.view.Renderers.worklog.descriptionTooltip, + width: 250, + sortable: true, + summaryType:'count', + summaryRenderer: DanteFrontend.view.Renderers.worklog.taskSummary + },{ + renderer: DanteFrontend.view.Renderers.duration, + header: g('Duration'), + dataIndex: 'duration', + width: 75, + sortable: true, + align: 'right', + summaryType:'sum', + summaryRenderer: DanteFrontend.view.Renderers.duration + },{ + header: g('Task Started'), + dataIndex: 'begin', + width: 125, + sortable: true, + align: 'right', + xtype: 'datecolumn', + format: DanteFrontend.common.defaultDateFormat, + summaryType: 'min', + summaryRenderer: DanteFrontend.view.Renderers.date + },{ + header: g('Task Ended'), + dataIndex: 'end', + width: 125, + sortable: true, + align: 'right', + xtype: 'datecolumn', + format: DanteFrontend.common.defaultDateFormat, + summaryType: 'max', + summaryRenderer: DanteFrontend.view.Renderers.date + }], + autoExpandColumn: 'task_description_column', + disableSelection: true + }); + + this.callParent(arguments); + } +});