1b5445a78a0c6aed8e5bb203083564d46d74eeac
[mkws-moved-to-github.git] / src / mkws-widget.js
1 // Factory function for widget objects.
2 mkws.makeWidget = function($, team, type, node) {
3   // Static register of attributes that do not contribute to config
4   var ignoreAttrs = {
5     id:1, 'class':1, style:1, name:1, action:1, type:1, size:1,
6     value:1, width:1, valign:1
7   };
8
9   var that = {
10     team: team,
11     type: type,
12     node: $(node),
13     config: mkws.objectInheritingFrom(team.config)
14   };
15
16   that.log = team.log;
17   that.trace = team.trace;
18   that.debug = team.debug;
19   that.info = team.info;
20   that.warn = team.warn;
21   that.error = team.error;
22   that.fatal = team.fatal;
23
24   that.toString = function() {
25     return '[Widget ' + team.name() + ':' + type + ']';
26   };
27
28   that.value = function() {
29     return node.value;
30   };
31
32   // Returns the HTML of a subwidget of the specified type. It gets
33   // the same attributes at the parent widget that invokes this
34   // function, except where overrides are passed in. If defaults are
35   // also provided, then these are used when the parent widget
36   // provides no values.
37   that.subwidget = function(type, overrides, defaults) {
38     var attrs = { _team: team.name() };
39     
40     // Copy locally-set properties from the parent widget
41     for (var name in this.config) {
42       if (this.config.hasOwnProperty(name)) {
43         attrs[name] = this.config[name];
44         this.log(this + " copied property " + name + "='" + attrs[name] + "' to " + type + " subwidget");
45       }
46     }
47     
48     for (var name in overrides) {
49       this.log(this + " overrode property " + name + "='" + overrides[name] + "' (was '" + attrs[name] + "') for " + type + " subwidget");
50       attrs[name] = overrides[name];
51     }
52
53     if (defaults) {
54       for (var name in defaults) {
55         if (!attrs[name]) {
56           attrs[name] = defaults[name];
57           this.log(this + " fell back to default property " + name + "='" + attrs[name] + "' for " + type + " subwidget");
58         }
59       }
60     }
61
62     var s = [];
63     s.push('<div class="mkws', type, ' mkws-team-', attrs._team, '"');
64     for (var name in attrs) {    
65       if (name !== '_team')
66         s.push(' ', name, '="', attrs[name], '"');
67     }
68     s.push('></div>');
69     return s.join('');
70   };
71
72   function expandValue(val) {
73     if (val.match(/^!param!/)) {
74       var param = val.replace(/^!param!/, '');
75       val = mkws.getParameterByName(param);
76       that.log("obtained val '" + val + "' from param '" + param + "'");
77       if (!val) {
78         alert("This page has a MasterKey widget that needs a val specified by the '" + param + "' parameter");
79       }
80     } else if (val.match(/^!path!/)) {
81       var index = val.replace(/^!path!/, '');
82       var path = window.location.pathname.split('/');
83       val = path[path.length - index];
84       that.log("obtained val '" + val + "' from path-component '" + index + "'");
85       if (!val) {
86         alert("This page has a MasterKey widget that needs a val specified by the path-component " + index);
87       }
88     } else if (val.match(/^!var!/)) {
89       var name = val.replace(/^!var!/, '');
90       val = window[name]; // It's ridiculous that this works
91       that.log("obtained val '" + val + "' from variable '" + name + "'");
92       if (!val) {
93         alert("This page has a MasterKey widget that needs a val specified by the '" + name + "' variable");
94       }
95     }
96     return val;
97   };
98
99   // Utility function for use by all widgets that can invoke autosearch.
100   that.autosearch = function() {
101     var that = this;
102     var query = this.config.autosearch;
103     if (query) {
104       var old = this.team.config.query;
105       if (!old) {
106         // Stash this for subsequent inspection
107         this.team.config.query = query;
108       } else if (old === query) {
109         this.log("duplicate autosearch: '" + query + "': ignoring");
110         return;
111       } else {
112         this.log("conflicting autosearch: '" + query + "' vs '" + old + "': ignoring");
113         return;
114       }
115
116       this.team.queue("ready").subscribe(function() {
117         // Postpone testing for the configuration items: these are not
118         // yet set for Record subclass widgets that fill them in in the
119         // subclass, as widget.autosearch is called in the superclass,
120         // before the subclass fiddles with the configuration.
121         var sortOrder = that.config.sort;
122         var maxrecs = that.config.maxrecs;
123         var perpage = that.config.perpage;
124         var limit = that.config.limit;
125         var targets = that.config.targets;
126         var targetfilter = that.config.targetfilter;
127         var target = that.config.target;
128         if (target) targetfilter = 'udb=="' + target + '"';
129
130         var s = "running auto search: '" + query + "'";
131         if (sortOrder) s += " sorted by '" + sortOrder + "'";
132         if (maxrecs) s += " restricted to " + maxrecs + " records";
133         if (perpage) s += " with " + perpage + " per page";
134         if (limit) s += " limited by '" + limit + "'";
135         if (targets) s += " in targets '" + targets + "'";
136         if (targetfilter) s += " constrained by targetfilter '" + targetfilter + "'";
137         that.log(s);
138
139         that.team.newSearch(query, sortOrder, maxrecs, perpage, limit, targets, targetfilter);
140       });
141     }
142   };
143
144   // Utility function for all widgets that want to hide in narrow windows
145   that.hideWhenNarrow = function() {
146     var that = this;
147     this.team.queue("resize-narrow").subscribe(function(n) {
148       that.node.hide();
149     });
150     this.team.queue("resize-wide").subscribe(function(n) {
151       that.node.show();
152     });
153   };
154
155
156   for (var i = 0; i < node.attributes.length; i++) {
157     var a = node.attributes[i];
158     var val = expandValue(a.value);
159     if (a.name === 'data-mkws-config') {
160       // Treat as a JSON fragment configuring just this widget
161       this.log(node + ": parsing config fragment '" + val + "'");
162       var data;
163       try {
164         data = $.parseJSON(val);
165         for (var key in data) {
166           this.log(node + ": adding config element " + key + "='" + data[key] + "'");
167           that.config[key] = data[key];
168         }
169       } catch (err) {
170         alert("Can't parse " + node + " data-mkws-config as JSON: " + val);
171       }
172     } else if (a.name.match (/^data-mkws-/)) {
173       var name = a.name.replace(/^data-mkws-/, '')
174       that.config[name] = val;
175       this.log(that + ": set data-mkws attribute " + name + "='" + val + "'");
176     } else if (!ignoreAttrs[a.name]) {
177       that.config[a.name] = val;
178       this.log(that + ": set regular attribute " + a.name + "='" + val + "'");
179     }
180   }
181
182   var fn = mkws.promotionFunction(type);
183   if (fn) {
184     fn.call(that);
185     this.log("made " + type + " widget(node=" + node + ")");
186   } else if (type.match(/-Container-(narrow|wide)$/)) {
187     // Not really a widget: no need to log its lack of promotion
188   } else {
189     this.log("made UNPROMOTED widget(type=" + type + ", node=" + node + ")");
190   }
191
192   return that;
193 };